From dc0a75e52d1c38f92240c86e7b6df2e24c6239ca Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 3 Jun 2021 12:22:37 +0200 Subject: [PATCH] verify each deadlines on payzen for payment schedules --- .../src/javascript/api/clients/api-client.ts | 2 +- .../templates/admin/invoices/settings.html | 2 +- app/models/coupon.rb | 4 +-- app/services/payment_gateway_service.rb | 16 +++++++++ app/services/payment_schedule_service.rb | 4 +-- app/workers/payment_schedule_item_worker.rb | 31 ++-------------- config/locales/app.admin.de.yml | 2 +- config/locales/app.admin.en.yml | 2 +- config/locales/app.admin.es.yml | 2 +- config/locales/app.admin.fr.yml | 2 +- config/locales/app.admin.pt.yml | 2 +- config/locales/app.admin.zu.yml | 2 +- config/locales/app.shared.en.yml | 4 +-- config/locales/app.shared.fr.yml | 4 +-- lib/pay_zen/service.rb | 32 +++++++++++++++++ lib/payment/service.rb | 2 ++ lib/stripe/service.rb | 36 +++++++++++++++++++ 17 files changed, 105 insertions(+), 44 deletions(-) diff --git a/app/frontend/src/javascript/api/clients/api-client.ts b/app/frontend/src/javascript/api/clients/api-client.ts index ab6ef7833..8deb6d7b4 100644 --- a/app/frontend/src/javascript/api/clients/api-client.ts +++ b/app/frontend/src/javascript/api/clients/api-client.ts @@ -1,5 +1,5 @@ import axios, { AxiosInstance } from 'axios' - +// compile const token: HTMLMetaElement = document.querySelector('[name="csrf-token"]'); const client: AxiosInstance = axios.create({ headers: { diff --git a/app/frontend/templates/admin/invoices/settings.html b/app/frontend/templates/admin/invoices/settings.html index a862f4b63..53c8b5d47 100644 --- a/app/frontend/templates/admin/invoices/settings.html +++ b/app/frontend/templates/admin/invoices/settings.html @@ -137,7 +137,7 @@ diff --git a/app/models/coupon.rb b/app/models/coupon.rb index b5dd71401..235997423 100644 --- a/app/models/coupon.rb +++ b/app/models/coupon.rb @@ -98,11 +98,11 @@ class Coupon < ApplicationRecord private def create_gateway_coupon - PaymentGatewayService.create_coupon(id) + PaymentGatewayService.new.create_coupon(id) end def delete_gateway_coupon - PaymentGatewayService.delete_coupon(id) + PaymentGatewayService.new.delete_coupon(id) end end diff --git a/app/services/payment_gateway_service.rb b/app/services/payment_gateway_service.rb index 4484db361..a1f0f6883 100644 --- a/app/services/payment_gateway_service.rb +++ b/app/services/payment_gateway_service.rb @@ -34,4 +34,20 @@ class PaymentGatewayService def create_or_update_product(klass, id) @gateway.create_or_update_product(klass, id) end + + def process_payment_schedule_item(payment_schedule_item) + service = case payment_schedule_item.payment_schedule.gateway_subscription.klass + when /^PayZen::/ + require 'pay_zen/service' + PayZen::Service + when /^Stripe::/ + require 'stripe/service' + Stripe::Service + else + require 'payment/service' + Payment::Service + end + gateway = service.new + gateway.process_payment_schedule_item(payment_schedule_item) + end end diff --git a/app/services/payment_schedule_service.rb b/app/services/payment_schedule_service.rb index 41003293c..379c0e651 100644 --- a/app/services/payment_schedule_service.rb +++ b/app/services/payment_schedule_service.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# create PaymentSchedules for various items +# perform various operations on PaymentSchedules class PaymentScheduleService ## # Compute a payment schedule for a new subscription to the provided plan @@ -184,7 +184,7 @@ class PaymentScheduleService subscription = payment_schedule_item.payment_schedule.payment_schedule_objects.find(&:subscription).subscription if payment_schedule_item.payment_schedule.main_object.object_type == Reservation.name details[:reservation] = payment_schedule_item.details['other_items'] - reservation = payment_schedule_item.payment_schedule.main_object + reservation = payment_schedule_item.payment_schedule.main_object.reservation end # the wallet transaction diff --git a/app/workers/payment_schedule_item_worker.rb b/app/workers/payment_schedule_item_worker.rb index e0a42c844..9f9b6ec0b 100644 --- a/app/workers/payment_schedule_item_worker.rb +++ b/app/workers/payment_schedule_item_worker.rb @@ -17,35 +17,10 @@ class PaymentScheduleItemWorker end def check_item(psi) - # the following depends on the payment method (stripe/check) - # FIXME + # the following depends on the payment method (card/check) if psi.payment_schedule.payment_method == 'card' - ### Stripe - stripe_key = Setting.get('stripe_secret_key') - stp_subscription = psi.payment_schedule.gateway_subscription.retrieve - stp_invoice = Stripe::Invoice.retrieve(stp_subscription.latest_invoice, api_key: stripe_key) - if stp_invoice.status == 'paid' - ##### Stripe / Successfully paid - PaymentScheduleService.new.generate_invoice(psi, payment_method: 'card', payment_id: stp_invoice.payment_intent, payment_type: 'Stripe::PaymentIntent') # FIXME - psi.update_attributes(state: 'paid', payment_method: 'card', stp_invoice_id: stp_invoice.id) - elsif stp_subscription.status == 'past_due' || stp_invoice.status == 'open' - ##### Stripe / Payment error - if psi.state == 'new' - # notify only for new deadlines, to prevent spamming - NotificationCenter.call type: 'notify_admin_payment_schedule_failed', - receiver: User.admins_and_managers, - attached_object: psi - NotificationCenter.call type: 'notify_member_payment_schedule_failed', - receiver: psi.payment_schedule.user, - attached_object: psi - end - stp_payment_intent = Stripe::PaymentIntent.retrieve(stp_invoice.payment_intent, api_key: stripe_key) - psi.update_attributes(state: stp_payment_intent.status, - stp_invoice_id: stp_invoice.id, - client_secret: stp_payment_intent.client_secret) - else - psi.update_attributes(state: 'error') - end + ### Cards + PaymentGatewayService.new.process_payment_schedule_item(psi) elsif psi.state == 'new' ### Check (only new deadlines, to prevent spamming) NotificationCenter.call type: 'notify_admin_payment_schedule_check_deadline', diff --git a/config/locales/app.admin.de.yml b/config/locales/app.admin.de.yml index 12e4adcaf..1d6bb1064 100644 --- a/config/locales/app.admin.de.yml +++ b/config/locales/app.admin.de.yml @@ -472,7 +472,7 @@ de: n_digits_annual_amount_of_orders: "(n) Ziffern, jährliche Anzahl von Bestellungen (z.B. yyyyy => 00012 : 12. Bestellung des Jahres" add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "Fügen Sie nur dann eine Mitteilung zu den Online-Verkäufen hinzu, wenn die Rechnung betroffen ist." this_will_never_be_added_when_a_refund_notice_is_present: "Dies wird nie hinzugefügt, wenn eine Rückerstattung vorhanden ist." - eg_XVL_will_add_VL_to_the_invoices_settled_with_stripe: '(z.B. fügt W[/VL] jenen Rechnungen "/VL" hinzu, die mit Stripe bezahlt werden)' + eg_XVL_will_add_VL_to_the_invoices_settled_by_card: '(z.B. fügt W[/VL] jenen Rechnungen "/VL" hinzu, die mit karte bezahlt werden)' add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Fügen Sie nur dann eine Mitteilung zu Erstattungen hinzu, wenn die Rechnung betroffen ist." this_will_never_be_added_when_an_online_sales_notice_is_present: "Dies wird nie hinzugefügt, wenn eine Online-Verkaufsmitteilung vorhanden ist." eg_RA_will_add_A_to_the_refund_invoices: '(z.B. fügt R[/A] den Rückerstattungsrechnungen "/A" hinzu)' diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 07fe8310f..818dde7de 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -472,7 +472,7 @@ en: n_digits_annual_amount_of_orders: "(n) digits, annual count of orders (ex. yyyyyy => 000008 : 8th order of this year)" add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "Add a notice regarding the online sales, only if the invoice is concerned." this_will_never_be_added_when_a_refund_notice_is_present: "This will never be added when a refund notice is present." - eg_XVL_will_add_VL_to_the_invoices_settled_with_stripe: '(eg. X[/VL] will add "/VL" to the invoices settled with stripe)' + eg_XVL_will_add_VL_to_the_invoices_settled_by_card: '(eg. X[/VL] will add "/VL" to the invoices settled by online card)' add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Add a notice regarding refunds, only if the invoice is concerned." this_will_never_be_added_when_an_online_sales_notice_is_present: "This will never be added when an online sales notice is present." eg_RA_will_add_A_to_the_refund_invoices: '(eg. R[/A] will add "/A" to the refund invoices)' diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index be60a2742..fc82c6bf7 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -472,7 +472,7 @@ es: n_digits_annual_amount_of_orders: "(n) dígitos, recuento anual de órdenes (ej: aaaaa => 000008: octava orden de este año)" add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "Añadir un aviso con respecto a las ventas en línea, sólo si la factura es de interés." this_will_never_be_added_when_a_refund_notice_is_present: "Esto nunca se agregará cuando se presente un aviso de reembolso." - eg_XVL_will_add_VL_to_the_invoices_settled_with_stripe: '(por ejemplo, X [/ VL] agregará "/ VL" a las facturas liquidadas con la raya)' + eg_XVL_will_add_VL_to_the_invoices_settled_by_card: '(por ejemplo, X [/ VL] agregará "/ VL" a las facturas liquidadas con tarjeta de crédito)' add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Añada un aviso con respecto a los reembolsos, sólo si la factura es de interés." this_will_never_be_added_when_an_online_sales_notice_is_present: "Esto nunca se agregará cuando un aviso de venta en línea está presente." eg_RA_will_add_A_to_the_refund_invoices: '(ed. R[/A] añadirá "/A" a las facturas de reembolso)' diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index eaba68970..a7256d413 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -472,7 +472,7 @@ fr: n_digits_annual_amount_of_orders: "Nombre de commandes dans l'année, sur (n) chiffres (ex. yyyyyy => 000008 : 8ème commande cette année)" add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "Ajoute une information relative à la vente en ligne, uniquement si cela concerne la facture." this_will_never_be_added_when_a_refund_notice_is_present: "Ceci ne sera jamais cumulé avec une information de remboursement." - eg_XVL_will_add_VL_to_the_invoices_settled_with_stripe: '(ex. X[/VL] ajoutera "/VL" aux factures réglées avec stripe)' + eg_XVL_will_add_VL_to_the_invoices_settled_by_card: '(ex. X[/VL] ajoutera "/VL" aux factures réglées en ligne par carte bancaire)' add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Ajoute une information relative aux remboursements, uniquement si cela concerne la facture." this_will_never_be_added_when_an_online_sales_notice_is_present: "Ceci ne sera jamais cumulé avec une information de vente en ligne." eg_RA_will_add_A_to_the_refund_invoices: '(ex. R[/A] ajoutera "/A" aux factures de remboursement)' diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index a183ed5dc..2cac8c28f 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -472,7 +472,7 @@ pt: n_digits_annual_amount_of_orders: "(n) dígitos, contagem anual de pedidos (ex. aaaaaa => 000008: oitava ordem deste ano)" add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "Adicionar um aviso sobre as vendas on-line, somente se a fatura estiver envolvida." this_will_never_be_added_when_a_refund_notice_is_present: "Isso nunca será adicionado quando uma notificação de reembolso estiver presente." - eg_XVL_will_add_VL_to_the_invoices_settled_with_stripe: '(ex. X[/VL] irá adicionar "/VL" às faturas liquidadas com lista)' + eg_XVL_will_add_VL_to_the_invoices_settled_by_card: '(ex. X[/VL] irá adicionar "/VL" às faturas liquidadas com cartão de crédito)' add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Adicionar um aviso sobre reembolsos, apenas se a fatura estiver envolvida." this_will_never_be_added_when_an_online_sales_notice_is_present: "Isto nunca será adicionado quando uma notificação de vendas online estiver presente." eg_RA_will_add_A_to_the_refund_invoices: '(ed. R[/A] irá adicionar "/A" nas faturas reembolsadas)' diff --git a/config/locales/app.admin.zu.yml b/config/locales/app.admin.zu.yml index 15549b683..d932891ca 100644 --- a/config/locales/app.admin.zu.yml +++ b/config/locales/app.admin.zu.yml @@ -472,7 +472,7 @@ zu: n_digits_annual_amount_of_orders: "crwdns7437:0crwdne7437:0" add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "crwdns7439:0crwdne7439:0" this_will_never_be_added_when_a_refund_notice_is_present: "crwdns7441:0crwdne7441:0" - eg_XVL_will_add_VL_to_the_invoices_settled_with_stripe: 'crwdns7443:0[/VL]crwdne7443:0' + eg_XVL_will_add_VL_to_the_invoices_settled_by_card: 'crwdns7443:0[/VL]crwdne7443:0' add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "crwdns7445:0crwdne7445:0" this_will_never_be_added_when_an_online_sales_notice_is_present: "crwdns7447:0crwdne7447:0" eg_RA_will_add_A_to_the_refund_invoices: 'crwdns21064:0[/A]crwdne21064:0' diff --git a/config/locales/app.shared.en.yml b/config/locales/app.shared.en.yml index caa6061ed..6f5391714 100644 --- a/config/locales/app.shared.en.yml +++ b/config/locales/app.shared.en.yml @@ -446,7 +446,7 @@ en: do_you_really_want_to_cancel_this_reservation_html: "

Do you really want to cancel this reservation?

Warning: if this reservation was made free of charge, as part of a subscription, the credits used will not be re-credited.

" reservation_was_cancelled_successfully: "Reservation was cancelled successfully." cancellation_failed: "Cancellation failed." - confirm_payment_of_html: "{METHOD, select, stripe{Pay by card} other{Pay on site}}: {AMOUNT}" + confirm_payment_of_html: "{METHOD, select, card{Pay by card} other{Pay on site}}: {AMOUNT}" a_problem_occurred_during_the_payment_process_please_try_again_later: "A problem occurred during the payment process. Please try again later." none: "None" online_payment_disabled: "Online payment is not available. Please contact the FabLab's reception directly." @@ -497,7 +497,7 @@ en: state_paid: "Paid" state_error: "Error" state_canceled: "Canceled" - method_stripe: "by card" + method_card: "by card" method_check: "by check" confirm_payment: "Confirm payment" solve: "Solve" diff --git a/config/locales/app.shared.fr.yml b/config/locales/app.shared.fr.yml index 14a82903b..68864cccd 100644 --- a/config/locales/app.shared.fr.yml +++ b/config/locales/app.shared.fr.yml @@ -446,7 +446,7 @@ fr: do_you_really_want_to_cancel_this_reservation_html: "

Êtes-vous sur de vouloir annuler cette réservation ?

Attention : si cette réservation a été effectuée gratuitement, dans le cadre d'un abonnement, les crédits utilisés ne seront pas re-crédités.

" reservation_was_cancelled_successfully: "La réservation a bien été annulée." cancellation_failed: "L'annulation a échouée." - confirm_payment_of_html: "{METHOD, select, stripe{Payer par carte} other{Paiement sur place}} : {AMOUNT}" + confirm_payment_of_html: "{METHOD, select, card{Payer par carte} other{Paiement sur place}} : {AMOUNT}" a_problem_occurred_during_the_payment_process_please_try_again_later: "Il y a eu un problème lors de la procédure de paiement. Veuillez réessayer plus tard." none: "Aucune" online_payment_disabled: "Le paiement par carte bancaire n'est pas disponible. Merci de contacter directement l'accueil du FabLab." @@ -497,7 +497,7 @@ fr: state_paid: "Payée" state_error: "Erreur" state_canceled: "Annulée" - method_stripe: "par carte" + method_card: "par carte" method_check: "par chèque" confirm_payment: "Confirmer l'encaissement" solve: "Résoudre" diff --git a/lib/pay_zen/service.rb b/lib/pay_zen/service.rb index 95466a1d2..f851e05fc 100644 --- a/lib/pay_zen/service.rb +++ b/lib/pay_zen/service.rb @@ -3,6 +3,7 @@ require 'payment/service' require 'pay_zen/charge' require 'pay_zen/order' +require 'pay_zen/item' # PayZen payement gateway module PayZen; end @@ -47,6 +48,37 @@ class PayZen::Service < Payment::Service pgo_sub.save! end + def process_payment_schedule_item(payment_schedule_item) + pz_order = payment_schedule_item.payment_schedule.payment_gateway_objects.find { |pgo| pgo.gateway_object_type == 'PayZen::Order' }.gateway_object.retrieve + transaction = pz_order['answer']['transactions'].last + if transaction['status'] == 'PAID' + PaymentScheduleService.new.generate_invoice(payment_schedule_item, + payment_method: 'card', + payment_id: transaction['uuid'], + payment_type: 'PayZen::Transaction') + payment_schedule_item.update_attributes(state: 'paid', payment_method: 'card') + pgo = PaymentGatewayObject.find_or_initialize_by(item: payment_schedule_item) + pgo.gateway_object = PayZen::Item.new('PayZen::Transaction', transaction['uuid']) + pgo.save! + elsif transaction['status'] == 'RUNNING' + if payment_schedule_item.state == 'new' + # notify only for new deadlines, to prevent spamming + NotificationCenter.call type: 'notify_admin_payment_schedule_failed', + receiver: User.admins_and_managers, + attached_object: payment_schedule_item + NotificationCenter.call type: 'notify_member_payment_schedule_failed', + receiver: payment_schedule_item.payment_schedule.user, + attached_object: payment_schedule_item + end + payment_schedule_item.update_attributes(state: transaction['detailedStatus']) + pgo = PaymentGatewayObject.find_or_initialize_by(item: payment_schedule_item) + pgo.gateway_object = PayZen::Item.new('PayZen::Transaction', transaction['uuid']) + pgo.save! + else + payment_schedule_item.update_attributes(state: 'error') + end + end + private def rrule(payment_schedule) diff --git a/lib/payment/service.rb b/lib/payment/service.rb index 941f54163..fcb00ce4e 100644 --- a/lib/payment/service.rb +++ b/lib/payment/service.rb @@ -13,4 +13,6 @@ class Payment::Service def delete_coupon(_coupon_id); end def create_or_update_product(_klass, _id); end + + def process_payment_schedule_item(_payment_schedule_item); end end diff --git a/lib/stripe/service.rb b/lib/stripe/service.rb index 42c17e299..e4768e62a 100644 --- a/lib/stripe/service.rb +++ b/lib/stripe/service.rb @@ -84,6 +84,42 @@ class Stripe::Service < Payment::Service amount end + def process_payment_schedule_item(payment_schedule_item) + stripe_key = Setting.get('stripe_secret_key') + stp_subscription = payment_schedule_item.payment_schedule.gateway_subscription.retrieve + stp_invoice = Stripe::Invoice.retrieve(stp_subscription.latest_invoice, api_key: stripe_key) + if stp_invoice.status == 'paid' + ##### Successfully paid + PaymentScheduleService.new.generate_invoice(payment_schedule_item, + payment_method: 'card', + payment_id: stp_invoice.payment_intent, + payment_type: 'Stripe::PaymentIntent') + payment_schedule_item.update_attributes(state: 'paid', payment_method: 'card') + pgo = PaymentGatewayObject.find_or_initialize_by(item: payment_schedule_item) + pgo.gateway_object = stp_invoice + pgo.save! + elsif stp_subscription.status == 'past_due' || stp_invoice.status == 'open' + ##### Payment error + if payment_schedule_item.state == 'new' + # notify only for new deadlines, to prevent spamming + NotificationCenter.call type: 'notify_admin_payment_schedule_failed', + receiver: User.admins_and_managers, + attached_object: payment_schedule_item + NotificationCenter.call type: 'notify_member_payment_schedule_failed', + receiver: payment_schedule_item.payment_schedule.user, + attached_object: payment_schedule_item + end + stp_payment_intent = Stripe::PaymentIntent.retrieve(stp_invoice.payment_intent, api_key: stripe_key) + payment_schedule_item.update_attributes(state: stp_payment_intent.status, + client_secret: stp_payment_intent.client_secret) + pgo = PaymentGatewayObject.find_or_initialize_by(item: payment_schedule_item) + pgo.gateway_object = stp_invoice + pgo.save! + else + payment_schedule_item.update_attributes(state: 'error') + end + end + private def subscription_invoice_items(payment_schedule, subscription, first_item, reservable_stp_id)