diff --git a/app/models/notification_type.rb b/app/models/notification_type.rb index 394a12424..975bd3bc5 100644 --- a/app/models/notification_type.rb +++ b/app/models/notification_type.rb @@ -59,6 +59,10 @@ class NotificationType notify_member_payment_schedule_failed notify_admin_payment_schedule_check_deadline notify_admin_payment_schedule_transfer_deadline + notify_admin_payment_schedule_error + notify_member_payment_schedule_error + notify_admin_payment_schedule_gateway_canceled + notify_member_payment_schedule_gateway_canceled ] # deprecated: # - notify_member_subscribed_plan_is_changed diff --git a/app/views/api/notifications/_notify_admin_payment_schedule_error.json.jbuilder b/app/views/api/notifications/_notify_admin_payment_schedule_error.json.jbuilder new file mode 100644 index 000000000..3a646aa21 --- /dev/null +++ b/app/views/api/notifications/_notify_admin_payment_schedule_error.json.jbuilder @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +json.title notification.notification_type +json.description t('.schedule_error', DATE: I18n.l(notification.attached_object.due_date.to_date), + REFERENCE: notification.attached_object.payment_schedule.reference) diff --git a/app/views/api/notifications/_notify_admin_payment_schedule_gateway_canceled.json.jbuilder b/app/views/api/notifications/_notify_admin_payment_schedule_gateway_canceled.json.jbuilder new file mode 100644 index 000000000..c64579e9e --- /dev/null +++ b/app/views/api/notifications/_notify_admin_payment_schedule_gateway_canceled.json.jbuilder @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +json.title notification.notification_type +json.description t('.schedule_canceled', REFERENCE: notification.attached_object.payment_schedule.reference) diff --git a/app/views/api/notifications/_notify_member_payment_schedule_error.json.jbuilder b/app/views/api/notifications/_notify_member_payment_schedule_error.json.jbuilder new file mode 100644 index 000000000..3a646aa21 --- /dev/null +++ b/app/views/api/notifications/_notify_member_payment_schedule_error.json.jbuilder @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +json.title notification.notification_type +json.description t('.schedule_error', DATE: I18n.l(notification.attached_object.due_date.to_date), + REFERENCE: notification.attached_object.payment_schedule.reference) diff --git a/app/views/api/notifications/_notify_member_payment_schedule_gateway_canceled.json.jbuilder b/app/views/api/notifications/_notify_member_payment_schedule_gateway_canceled.json.jbuilder new file mode 100644 index 000000000..c64579e9e --- /dev/null +++ b/app/views/api/notifications/_notify_member_payment_schedule_gateway_canceled.json.jbuilder @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +json.title notification.notification_type +json.description t('.schedule_canceled', REFERENCE: notification.attached_object.payment_schedule.reference) diff --git a/app/views/notifications_mailer/notify_admin_payment_schedule_error.html.erb b/app/views/notifications_mailer/notify_admin_payment_schedule_error.html.erb new file mode 100644 index 000000000..3566d4983 --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_payment_schedule_error.html.erb @@ -0,0 +1,10 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +
+ <%= t('.body.remember', + REFERENCE: @attached_object.payment_schedule.reference, + AMOUNT: number_to_currency(@attached_object.amount / 100.00), + DATE: I18n.l(@attached_object.due_date, format: :long)) %> + <%= t('.body.error', GATEWAY: @attached_object.payment_gateway_object.gateway_object.gateway) %> +
+<%= t('.body.action') %>
diff --git a/app/views/notifications_mailer/notify_admin_payment_schedule_gateway_canceled.html.erb b/app/views/notifications_mailer/notify_admin_payment_schedule_gateway_canceled.html.erb new file mode 100644 index 000000000..e21c68f81 --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_payment_schedule_gateway_canceled.html.erb @@ -0,0 +1,8 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + ++ <%= t('.body.error', + REFERENCE: @attached_object.payment_schedule.reference, + GATEWAY: @attached_object.payment_gateway_object.gateway_object.gateway) %> +
+<%= t('.body.action') %>
diff --git a/app/views/notifications_mailer/notify_member_payment_schedule_error.html.erb b/app/views/notifications_mailer/notify_member_payment_schedule_error.html.erb new file mode 100644 index 000000000..843eac7f8 --- /dev/null +++ b/app/views/notifications_mailer/notify_member_payment_schedule_error.html.erb @@ -0,0 +1,7 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + ++ <%= t('.body.error', + REFERENCE: @attached_object.payment_schedule.reference) %> +
+<%= t('.body.action') %>
diff --git a/app/views/notifications_mailer/notify_member_payment_schedule_gateway_canceled.html.erb b/app/views/notifications_mailer/notify_member_payment_schedule_gateway_canceled.html.erb new file mode 100644 index 000000000..57eb813ba --- /dev/null +++ b/app/views/notifications_mailer/notify_member_payment_schedule_gateway_canceled.html.erb @@ -0,0 +1,10 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + ++ <%= t('.body.remember', + REFERENCE: @attached_object.payment_schedule.reference, + AMOUNT: number_to_currency(@attached_object.amount / 100.00), + DATE: I18n.l(@attached_object.due_date, format: :long)) %> + <%= t('.body.error') %> +
+<%= t('.body.action') %>
diff --git a/config/locales/en.yml b/config/locales/en.yml index 35929bd27..0fabfee8b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -367,10 +367,18 @@ en: all_objects_sync: "All data were successfully synchronized on Stripe." notify_user_when_payment_schedule_ready: your_schedule_is_ready_html: "Your payment schedule #%{REFERENCE}, of %{AMOUNT}, is ready. Click here to download." + notify_admin_payment_schedule_error: + schedule_error: "An error occurred for the card debit of the %{DATE} deadline, for schedule %{REFERENCE}" + notify_member_payment_schedule_error: + schedule_error: "An error occurred for the card debit of the %{DATE} deadline, for your schedule %{REFERENCE}" notify_admin_payment_schedule_failed: schedule_failed: "Failed card debit for the %{DATE} deadline, for schedule %{REFERENCE}" notify_member_payment_schedule_failed: schedule_failed: "Failed card debit for the %{DATE} deadline, for your schedule %{REFERENCE}" + notify_admin_payment_schedule_gateway_canceled: + schedule_error: "The payment schedule %{REFERENCE} was canceled by the gateway. An action is required." + notify_member_payment_schedule_gateway_canceled: + schedule_error: "Your payment schedule %{REFERENCE} was canceled by the gateway." notify_admin_payment_schedule_check_deadline: schedule_deadline: "You must cash the check for the %{DATE} deadline, for schedule %{REFERENCE}" notify_admin_payment_schedule_transfer_deadline: diff --git a/config/locales/mails.en.yml b/config/locales/mails.en.yml index 15ee9d53f..149f681d5 100644 --- a/config/locales/mails.en.yml +++ b/config/locales/mails.en.yml @@ -296,6 +296,18 @@ en: please_find_attached_html: "Please find attached your payment schedule, issued on {DATE}, with an amount of {AMOUNT} concerning your {TYPE, select, Reservation{reservation} other{subscription}}." #messageFormat interpolation schedule_in_your_dashboard_html: "You can find this payment schedule at any time from %{DASHBOARD} on the Fab Lab's website." your_dashboard: "your dashboard" + notify_admin_payment_schedule_error: + subject: "[URGENT] Card debit error" + body: + remember: "In accordance with the %{REFERENCE} payment schedule, a debit by card of %{AMOUNT} was scheduled on %{DATE}." + error: "Unfortunately, an error occurred and this card debit was unable to complete successfully." + action: "Please then consult the %{GATEWAY} dashboard and contact the member as soon as possible to resolve the problem." + notify_member_payment_schedule_error: + subject: "[URGENT] Card debit error" + body: + remember: "In accordance with your %{REFERENCE} payment schedule, a debit by card of %{AMOUNT} was scheduled on %{DATE}." + error: "Unfortunately, an error occurred and this card debit was unable to complete successfully." + action: "Please contact a manager as soon as possible to resolve the problem." notify_admin_payment_schedule_failed: subject: "[URGENT] Card debit failure" body: @@ -309,6 +321,16 @@ en: error: "Unfortunately, this card debit was unable to complete successfully." action_html: "Please check %{DASHBOARD} or contact a manager before 24 hours, otherwise your subscription may be interrupted." your_dashboard: "your dashboard" + notify_admin_payment_schedule_gateway_canceled: + subject: "[URGENT] Payment schedule canceled by the payment gateway" + body: + error: "The payment schedule %{REFERENCE} was canceled by the payment gateway (%{GATEWAY}). No further debits will be made on this payment mean." + action: "Please consult the payment schedule management interface and contact the member as soon as possible to resolve the problem." + notify_member_payment_schedule_gateway_canceled: + subject: "[URGENT] Payment schedule canceled by the payment gateway" + body: + error: "Your payment schedule %{REFERENCE} was canceled by the payment gateway. No further debits will be made on this payment mean." + action: "Please contact a manager as soon as possible to resolve the problem." notify_admin_payment_schedule_check_deadline: subject: "Payment deadline" body: diff --git a/lib/pay_zen/service.rb b/lib/pay_zen/service.rb index a3f1c1cc3..eabe89d9e 100644 --- a/lib/pay_zen/service.rb +++ b/lib/pay_zen/service.rb @@ -80,7 +80,8 @@ class PayZen::Service < Payment::Service def process_payment_schedule_item(payment_schedule_item) pz_subscription = payment_schedule_item.gateway_subscription.retrieve if DateTime.parse(pz_subscription['answer']['cancelDate']) < DateTime.current - # the subscription was canceled by the gateway => update the status + # the subscription was canceled by the gateway => notify & update the status + notify_payment_schedule_gateway_canceled(payment_schedule_item) payment_schedule_item.update_attributes(state: 'gateway_canceled') return end @@ -98,20 +99,13 @@ class PayZen::Service < Payment::Service 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 + notify_payment_schedule_item_failed(payment_schedule_item) 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 + notify_payment_schedule_item_error(payment_schedule_item) payment_schedule_item.update_attributes(state: 'error') end end diff --git a/lib/payment/service.rb b/lib/payment/service.rb index b61c6bab8..6b8f0e97b 100644 --- a/lib/payment/service.rb +++ b/lib/payment/service.rb @@ -21,4 +21,46 @@ class Payment::Service def process_payment_schedule_item(_payment_schedule_item); end def pay_payment_schedule_item(_payment_schedule_item); end + + protected + + # payment has failed but a recovery is still possible + def notify_payment_schedule_item_failed(payment_schedule_item) + # notify only for new deadlines, to prevent spamming + return unless payment_schedule_item.state == 'new' + + 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 has failed and recovery is not possible + def notify_payment_schedule_item_error(payment_schedule_item) + # notify only for new deadlines, to prevent spamming + return unless payment_schedule_item.state == 'new' + + NotificationCenter.call type: 'notify_admin_payment_schedule_error', + receiver: User.admins_and_managers, + attached_object: payment_schedule_item + NotificationCenter.call type: 'notify_member_payment_schedule_error', + receiver: payment_schedule_item.payment_schedule.user, + attached_object: payment_schedule_item + end + + # payment schedule was cancelled by the gateway + def notify_payment_schedule_gateway_canceled(payment_schedule_item) + # notify only for new deadlines, to prevent spamming + return unless payment_schedule_item.state == 'new' + + NotificationCenter.call type: 'notify_admin_payment_schedule_gateway_canceled', + receiver: User.admins_and_managers, + attached_object: payment_schedule_item + NotificationCenter.call type: 'notify_member_payment_schedule_gateway_canceled', + receiver: payment_schedule_item.payment_schedule.user, + attached_object: payment_schedule_item + end + end diff --git a/lib/stripe/service.rb b/lib/stripe/service.rb index 15544828c..3da2035be 100644 --- a/lib/stripe/service.rb +++ b/lib/stripe/service.rb @@ -95,7 +95,8 @@ class Stripe::Service < Payment::Service stripe_key = Setting.get('stripe_secret_key') stp_subscription = payment_schedule_item.payment_schedule.gateway_subscription.retrieve if stp_subscription.status == 'canceled' - # the subscription was canceled by the gateway => update the status + # the subscription was canceled by the gateway => notify & update the status + notify_payment_schedule_gateway_canceled(payment_schedule_item) payment_schedule_item.update_attributes(state: 'gateway_canceled') return end @@ -112,15 +113,7 @@ class Stripe::Service < Payment::Service 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 + notify_payment_schedule_item_failed(payment_schedule_item) 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) @@ -128,6 +121,7 @@ class Stripe::Service < Payment::Service pgo.gateway_object = stp_invoice pgo.save! else + notify_payment_schedule_item_error(payment_schedule_item) payment_schedule_item.update_attributes(state: 'error') end end