For subscription orders, when using test cards which require authentication (like x-3184), a link is sent per email to the customer, to provide for it.
If authentication fails (clicking "Fail Authentication, in a test environment) or by using an invalid card, the UI displays the order to be complete and in a paid state; this info is showed both on the admin and customer accounts. The payment attempt appears as failed on the Stripe dashboard.
It also appears as paid in the UI but also on the DB:
irb(main):007:0> Spree::Order.where(number: 'R462515802')
=> #<ActiveRecord::Relation [#<Spree::Order id: 445537, number: "R462515802", item_total: 0.24e2, total: 0.64e2, state: "complete", adjustment_total: 0.4e2, user_id: 6927, created_at: "2021-04-29 15:52:38", updated_at: "2021-04-29 15:55:08", completed_at: "2021-04-29 15:52:40", bill_address_id: 102331, ship_address_id: 102332, payment_total: 0.64e2, shipment_state: "ready", payment_state: "paid", email: "[email protected]", special_instructions: nil, distributor_id: 1785, order_cycle_id: 3431, currency: "CAD", last_ip_address: nil, customer_id: 8152, created_by_id: nil, included_tax_total: 0.0, additional_tax_total: 0.0>]>
irb(main):010:0> Spree::Payment.where(order_id: 445537)
=> #<ActiveRecord::Relation [#<Spree::Payment id: 33640, amount: 0.64e2, order_id: 445537, created_at: "2021-04-29 15:52:39", updated_at: "2021-04-29 15:55:08", source_id: 4036, source_type: "Spree::CreditCard", payment_method_id: 751, state: "completed", response_code: "pi_1IlcB8KuuB1fWySnByQmNVb4", avs_response: nil, identifier: "KN7ZU2SA", cvv_response_code: nil, cvv_response_message: nil>]>
It appears as failed in Stripe:
Payment state reflects the success/failure of the authentication step.
Payment state is displayed as "Paid", despite failed authentication.
None, this appears as paid an is not.
S2 for investigation, not sure something similar is happening in production...
We had a fruitful call with @filipefurtad0 and it's now clear what we're looking at:
SubscriptionConfirmJob regardless of the state of the payment. That maps to https://github.com/openfoodfoundation/openfoodnetwork/issues/7510 and should be considered a separate thing (and not so urgent).payment: paid and it is not. It feels like we don't check Stripe's information about the payment, or if we do, it's broken.I dug through the code and the issue seems to be here:
we don't check the actual status of the payment intent before marking the payment as complete on our side. In the case that @filipefurtad0 managed to reproduce Stripe's dashboard shows the intent is in requires_payment_method state along with detailed error information.
Unless I'm missing something very obvious, we should call Stripe::PaymentIntent.retrieve to also validate the intent and show an error to the user if invalid. We'll need to investigate what's the course of action to retry the payment. I don't know if visiting again the link to authorize the payment would work.
Thoughts @andrewpbrett ?
That sounds exactly right, thanks @sauloperez. I tried reproducing this locally and when I checked out and clicked Fail Authentication I got the correct error message on the checkout screen, so I'm not sure what's different about the scenario that @filipefurtad0 found. But in any case, yes, we should make that call when processing the payment intent.
this is with a subscription, that might be why @andrewpbrett . However, do you recall where we show or validate that in the case of a regular checkout? Stripe::PaymentIntentValidator seems to be the one. We need to reuse it in the context of subscriptions.
In any case, I just found out that @filipefurtad0 and I were checking against his own Stripe account in test mode but the API keys configured at the instance level are others. So we need to rule out the possibility of this being an environment-specific issue only before we move any further. I found about it because of the failed attempt on staging's console:
irb(main):015:0> Stripe::PaymentIntent.retrieve('pi_1InQEsKuuB1fWySnPtFs7kW2')
Traceback (most recent call last):
15: from script/rails:6:in `<main>'
14: from script/rails:6:in `require'
13: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/railties-5.0.7.2/lib/rails/commands.rb:18:in `<top (required)>'
12: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/railties-5.0.7.2/lib/rails/commands/commands_tasks.rb:49:in `run_command!'
11: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/railties-5.0.7.2/lib/rails/commands/commands_tasks.rb:78:in `console'
10: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/railties-5.0.7.2/lib/rails/commands/console_helper.rb:9:in `start'
9: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/railties-5.0.7.2/lib/rails/commands/console.rb:65:in `start'
8: from (irb):15
7: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/api_resource.rb:104:in `retrieve'
6: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/api_resource.rb:96:in `refresh'
5: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/api_operations/request.rb:89:in `execute_resource_request'
4: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/api_operations/request.rb:25:in `execute_resource_request'
3: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/stripe_client.rb:233:in `execute_request'
2: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/stripe_client.rb:468:in `execute_request_with_rescues'
1: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/stripe_client.rb:592:in `handle_error_response'
Stripe::InvalidRequestError ((Status 404) (Request req_Za0xl96MxXS2HH) No such payment_intent: 'pi_1InQEsKuuB1fWySnPtFs7kW2')
I got a bit further and it seems that indeed the API keys should be the testing ones
irb(main):006:0> stripe_account = StripeAccount.find_by(enterprise_id: order.distributor.id)
=> #<StripeAccount id: 106, stripe_user_id: "acct_1FiqEsKuuB1fWySn", stripe_publishable_key: "pk_test_51FiqEsKuuB1fWySnpdmYuymNt1yDpZhELaUFuHemI...", created_at: "2021-04-29 09:35:27", updated_at: "2021-04-29 09:35:27", enterprise_id: 1785>
irb(main):008:0> Stripe::PaymentIntentValidator.new.call('pi_1InQEsKuuB1fWySnPtFs7kW2', stripe_account.id.to_s)
Traceback (most recent call last):
16: from script/rails:6:in `<main>'
15: from script/rails:6:in `require'
14: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/railties-5.0.7.2/lib/rails/commands.rb:18:in `<top (required)>'
13: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/railties-5.0.7.2/lib/rails/commands/commands_tasks.rb:49:in `run_command!'
12: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/railties-5.0.7.2/lib/rails/commands/commands_tasks.rb:78:in `console'
11: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/railties-5.0.7.2/lib/rails/commands/console_helper.rb:9:in `start'
10: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/railties-5.0.7.2/lib/rails/commands/console.rb:65:in `start'
9: from (irb):8
8: from /home/openfoodnetwork/apps/openfoodnetwork/releases-old/2021-05-04-151104/lib/stripe/payment_intent_validator.rb:7:in `call'
7: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/api_resource.rb:104:in `retrieve'
6: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/api_resource.rb:96:in `refresh'
5: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/api_operations/request.rb:89:in `execute_resource_request'
4: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/api_operations/request.rb:25:in `execute_resource_request'
3: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/stripe_client.rb:233:in `execute_request'
2: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/stripe_client.rb:468:in `execute_request_with_rescues'
1: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/stripe_client.rb:592:in `handle_error_response'
Stripe::PermissionError ((Status 403) The provided key 'sk_test_Ra******************34gD' does not have access to account '106' (or that account does not exist). Application access may have been revoked.)
but changing the publishable_key and secret_key yields:
irb(main):012:0> Stripe::PaymentIntentValidator.new.call('pi_1InQEsKuuB1fWySnPtFs7kW2', stripe_account.id.to_s)
Traceback (most recent call last):
16: from script/rails:6:in `<main>'
15: from script/rails:6:in `require'
14: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/railties-5.0.7.2/lib/rails/commands.rb:18:in `<top (required)>'
13: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/railties-5.0.7.2/lib/rails/commands/commands_tasks.rb:49:in `run_command!'
12: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/railties-5.0.7.2/lib/rails/commands/commands_tasks.rb:78:in `console'
11: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/railties-5.0.7.2/lib/rails/commands/console_helper.rb:9:in `start'
10: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/railties-5.0.7.2/lib/rails/commands/console.rb:65:in `start'
9: from (irb):12
8: from /home/openfoodnetwork/apps/openfoodnetwork/releases-old/2021-05-04-151104/lib/stripe/payment_intent_validator.rb:7:in `call'
7: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/api_resource.rb:104:in `retrieve'
6: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/api_resource.rb:96:in `refresh'
5: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/api_operations/request.rb:89:in `execute_resource_request'
4: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/api_operations/request.rb:25:in `execute_resource_request'
3: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/stripe_client.rb:233:in `execute_request'
2: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/stripe_client.rb:468:in `execute_request_with_rescues'
1: from /home/openfoodnetwork/.gem/ruby/2.5.0/gems/stripe-5.30.0/lib/stripe/stripe_client.rb:592:in `handle_error_response'
Stripe::PermissionError ((Status 403) Only Stripe Connect platforms can work with other accounts. If you specified a client_id parameter, make sure it's correct. If you need to setup a Stripe Connect platform, you can do so at https://dashboard.stripe.com/account/applications/settings.)
Does it ring a bell @andrewpbrett ?
Good news! @filipefurtad0 and I came up with https://github.com/openfoodfoundation/openfoodnetwork/pull/7562 which solves the issue :tada: teamwork! :hugs:
However, we both agree that this requires a follow-up. We should provide the user with a new authorization link when the admin re-sends the email from the backoffice (from the order payments page). And the same should happen for regular backoffice orders, not only subscriptions.
We also thought that the order confirmation page is still too "green". If the user misses the error message or comes back to that page, it's almost impossible to guess that the payment requires further action.
Most helpful comment
Good news! @filipefurtad0 and I came up with https://github.com/openfoodfoundation/openfoodnetwork/pull/7562 which solves the issue :tada: teamwork! :hugs:
However, we both agree that this requires a follow-up. We should provide the user with a new authorization link when the admin re-sends the email from the backoffice (from the order payments page). And the same should happen for regular backoffice orders, not only subscriptions.
We also thought that the order confirmation page is still too "green". If the user misses the error message or comes back to that page, it's almost impossible to guess that the payment requires further action.