Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Split Checkout] insufficient_funds decline code breaks checkout #9038

Closed
filipefurtad0 opened this issue Mar 25, 2022 · 12 comments · Fixed by #9057
Closed

[Split Checkout] insufficient_funds decline code breaks checkout #9038

filipefurtad0 opened this issue Mar 25, 2022 · 12 comments · Fixed by #9057
Assignees

Comments

@filipefurtad0
Copy link
Contributor

Description

Looks related to #8952.

The insufficient_funds decline code - say from introducing the card 4000000000009995 on split ckeckout - breaks the checkout flow.

Expected Behavior

I think the expected behavior would be to be redirected to /payment step.

Actual Behaviour

Snail.

Steps to Reproduce

  1. Visit /payment and introduced the card * 9995
  2. Proceed to /summary
  3. Complete the order
  4. See the snail

Animated Gif/Screenshot

Peek 2022-03-25 11-23

@filipefurtad0
Copy link
Contributor Author

filipefurtad0 commented Mar 28, 2022

Server log:


I, [2022-03-28 15:44:29#26144]  INFO -- : Started PUT "/checkout/summary" for 185.101.176.172 at 2022-03-28 15:44:29 +0000
I, [2022-03-28 15:44:29#26144]  INFO -- : Processing by SplitCheckoutController#update as CABLE_READY
I, [2022-03-28 15:44:29#26144]  INFO -- :   Parameters: {"authenticity_token"=>"alQKQlP-utfvEfjO1_CUaKvAd4R32ky_VnuawHRh30HtKznMTWPtODbn9aGUm9cuCPJB2LmidKp52WeLQqZjrA", "accept_terms"=>"1", "confirm_order"=>"Complete order", "step"=>"summary"}
E, [2022-03-28 15:44:33#26144] ERROR -- : Payment failed
E, [2022-03-28 15:44:33#26144] ERROR -- :   --- !ruby/object:ActiveMerchant::Billing::Response
params:
  error:
    charge: ch_3KiKk8KuuB1fWySn0bsVnAV3
    code: card_declined
    decline_code: insufficient_funds
    doc_url: https://stripe.com/docs/error-codes/card-declined
    message: Your card has insufficient funds.
    payment_intent:
      id: pi_3KiKk8KuuB1fWySn0oTJJX7G
      object: payment_intent
      amount: 200
      amount_capturable: 0
      amount_received: 0
      application: ca_9ByaSyyyXj5O73DWisU0KLluf0870Vro
      application_fee_amount:
      automatic_payment_methods:
      canceled_at:
      cancellation_reason:
      capture_method: manual
      charges:
        object: list
        data:
        - id: ch_3KiKk8KuuB1fWySn0bsVnAV3
          object: charge
          amount: 200
          amount_captured: 0
          amount_refunded: 0
          application: ca_9ByaSyyyXj5O73DWisU0KLluf0870Vro
          application_fee:
          application_fee_amount:
          balance_transaction:
          billing_details:
            address:
              city:
              country:
              line1:
              line2:
              postal_code:
              state:
            email:
            name:
            phone:
          calculated_statement_descriptor: OFNOFNOFN
          captured: false
          created: 1648482272
          currency: cad
          customer:
          description: 'Spree Order ID: R534222338-9DQX8QY2'
          destination:
          dispute:
          disputed: false
          failure_code: card_declined
          failure_message: Your card has insufficient funds.
          fraud_details: {}
          invoice:
          livemode: false
          metadata: {}
          on_behalf_of:
          order:
          outcome:
            network_status: declined_by_network
            reason: insufficient_funds
            risk_level: normal
            risk_score: 27
            seller_message: The bank returned the decline code `insufficient_funds`.
            type: issuer_declined
          paid: false
          payment_intent: pi_3KiKk8KuuB1fWySn0oTJJX7G
          payment_method: pm_1KiKk7KuuB1fWySn10VtcG0g
          payment_method_details:
            card:
              brand: visa
              checks:
                address_line1_check:
                address_postal_code_check:
                cvc_check: pass
              country: US
              exp_month: 2
              exp_year: 2033
              fingerprint: C0RIWvvyZY8XL1D9
              funding: credit
              installments:
              last4: '9995'
              mandate:
              network: visa
              three_d_secure:
              wallet:
            type: card
          receipt_email:
          receipt_number:
          receipt_url:
          refunded: false
          refunds:
            object: list
            data: []
            has_more: false
            total_count: 0
            url: "/v1/charges/ch_3KiKk8KuuB1fWySn0bsVnAV3/refunds"
          review:
          shipping:
          source:
          source_transfer:
          statement_descriptor:
          statement_descriptor_suffix:
          status: failed
          transfer_data:
          transfer_group:
        has_more: false
        total_count: 1
        url: "/v1/charges?payment_intent=pi_3KiKk8KuuB1fWySn0oTJJX7G"
      client_secret: pi_3KiKk8KuuB1fWySn0oTJJX7G_secret_04AGKlaHazH5KUtOOVnoU9lac
      confirmation_method: automatic
      created: 1648482272
      currency: cad
      customer:
      description: 'Spree Order ID: R534222338-9DQX8QY2'
      invoice:
      last_payment_error:
        charge: ch_3KiKk8KuuB1fWySn0bsVnAV3
        code: card_declined
        decline_code: insufficient_funds
        doc_url: https://stripe.com/docs/error-codes/card-declined
        message: Your card has insufficient funds.
        payment_method:
          id: pm_1KiKk7KuuB1fWySn10VtcG0g
          object: payment_method
          billing_details:
            address:
              city:
              country:
              line1:
              line2:
              postal_code:
              state:
            email:
            name:
            phone:
          card:
            brand: visa
            checks:
              address_line1_check:
              address_postal_code_check:
              cvc_check: pass
            country: US
            exp_month: 2
            exp_year: 2033
            fingerprint: C0RIWvvyZY8XL1D9
            funding: credit
            generated_from:
            last4: '9995'
            networks:
              available:
              - visa
              preferred:
            three_d_secure_usage:
              supported: true
            wallet:
          created: 1648482271
          customer:
          livemode: false
          metadata: {}
          type: card
        type: card_error
      livemode: false
      metadata: {}
      next_action:
      on_behalf_of:
      payment_method:
      payment_method_options:
        card:
          installments:
          mandate_options:
          network:
          request_three_d_secure: automatic
      payment_method_types:
      - card
      processing:
      receipt_email:
      review:
      setup_future_usage:
      shipping:
      source:
      statement_descriptor:
      statement_descriptor_suffix:
      status: requires_payment_method
      transfer_data:
      transfer_group:
    payment_method:
      id: pm_1KiKk7KuuB1fWySn10VtcG0g
      object: payment_method
      billing_details:
        address:
          city:
          country:
          line1:
          line2:
          postal_code:
          state:
        email:
        name:
        phone:
      card:
        brand: visa
        checks:
          address_line1_check:
          address_postal_code_check:
          cvc_check: pass
        country: US
        exp_month: 2
        exp_year: 2033
        fingerprint: C0RIWvvyZY8XL1D9
        funding: credit
        generated_from:
        last4: '9995'
        networks:
          available:
          - visa
          preferred:
        three_d_secure_usage:
          supported: true
        wallet:
      created: 1648482271
      customer:
      livemode: false
      metadata: {}
      type: card
    type: card_error
message: Your card has insufficient funds.
success: false
test: false
authorization: ch_3KiKk8KuuB1fWySn0bsVnAV3
fraud_review:
error_code: card_declined
emv_authorization:
network_transaction_id:
avs_result:
  code:
  message:
  street_match:
  postal_match:
cvv_result:
  code:
  message:

I, [2022-03-28 15:44:33#26144]  INFO -- : Completed 500 Internal Server Error in 4690ms (ActiveRecord: 51.6ms | Allocations: 53684)
I, [2022-03-28 15:44:33#26144]  INFO -- [Bugsnag]: Notifying https://notify.bugsnag.com of Spree::Core::GatewayError
F, [2022-03-28 15:44:33#26144] FATAL -- :   
Spree::Core::GatewayError (The card was declined.):
  

Bugsnag:

Spree::Core::GatewayErrorsplit_checkout#update

https://app.bugsnag.com/yaycode/openfoodnetwork-uk/errors/60db6e8f3ca5f50007ee621a?filters[event.since]=30d&filters[error.status]=open&event_id=6241d7e10092e088fe1a0000

@jibees
Copy link
Contributor

jibees commented Mar 30, 2022

It's the same for
4000000000000127: Incorrect CVC decline and probably all the cards for declined payment https://stripe.com/docs/testing#declined-payments

@jibees
Copy link
Contributor

jibees commented Mar 30, 2022

It also happens on step 2 (the payment one), if you check the Save card for future use checkbox.

@jibees
Copy link
Contributor

jibees commented Mar 30, 2022

@filipefurtad0
I think we should improve the tests by testing at least one invalid card number with stripe on both step 2 with the checkbox Save card for future use checked and on step 3.
What to you think?

@filipefurtad0
Copy link
Contributor Author

filipefurtad0 commented Mar 30, 2022

Yes, I agree @jibees .

On Step 2: currently the Stripe form is mocked in test environment, so maybe we can make it to work with puffing-billy. Since there are some issues in integrating it with system tests (cuprite driver), maybe we should go for feature spec, instead?

Update/related: see this PR.

On Step 3: I'm currently looking at this controller spec here here and checking whether these tests pass, when the split-checkout is toggled.

Overall: We also have this request spec here, which I think we should/could (?) use as well, in the context of split checkout.

I'm kind of exploring (i.e. rambling) 😄 about these possibilities on this slack thread.

@jibees
Copy link
Contributor

jibees commented Mar 31, 2022

Notes to myself, cases which failed

  • Trying to save a card that trigger any stripe errors on step 2. Status: ✔️, commit 7596f9d
  • Trying to pay with a card that trigger any stripe errors on step 3. Status: ✔️, commit 4f10b92
  • Trying to re-submit the payment on step 3 if a previously submit has already been done and failed. Status: ✔️, commit 8bbb80e

@jibees
Copy link
Contributor

jibees commented Mar 31, 2022

Case 1, aka. Trying to save a card that trigger any stripe errors on step 2

The issue seems to be related to CableReady/cable_car but i failed to figured out why.
If I change the method #update of the controller to this:

flash.now[:error] ||= I18n.t('split_checkout.errors.global')
render status: :ok, operations: cable_car.
  replace("#checkout", partial("split_checkout/checkout")).
  replace("#flashes", partial("shared/flashes", locals: { flashes: flash }))

the HTML sent by cable_car is download as a file by GoogleChrome

[{"html":"\u003ccheckout class='row' id='checkout'\u003e\n\u003cdiv class='small-12 medium-12 columns'\u003e\n\u003cdiv class='flex'\u003e\n\u003cdiv class='columns three text-center checkout-tab success'\u003e\n\u003cdiv\u003e\n\u003cspan class='checkout-tab-number'\u003e\n1 -\n\u003c/span\u003e\n\u003cspan class='checkout-tab-label'\u003e\n\u003ca href=\"/checkout/details\"\u003eVos informations\u003c/a\u003e\u003c/span\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003cdiv class='columns three text-center checkout-tab selected'\u003e\n\u003cdiv\u003e\n\u003cspan class='checkout-tab-number'\u003e\n2 -\n\u003c/span\u003e\n\u003cspan class='checkout-tab-label'\u003e\nMoyen de paiement\n\u003c/span\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003cdiv class='columns three text-center checkout-tab'\u003e\n\u003cdiv\u003e\n\u003cspan class='checkout-tab-number'\u003e\n3 -\n\u003c/span\u003e\n\u003cspan class='checkout-tab-label'\u003e\nRécapitulatif de la commande\n\u003c/span\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv class='checkout-step'\u003e\n\u003cform data-remote=\"true\" action=\"/checkout/payment\" accept-charset=\"UTF-8\" method=\"post\"\u003e\u003cinput type=\"hidden\" name=\"_method\" value=\"put\" /\u003e\u003cinput type=\"hidden\" name=\"authenticity_token\" value=\"x2pjDVBwZ32rhfyCTdnCEbg8jxdw8UrnH8sLWvYtEXUzrgUyK4D76FXjR6RyEG9cwcUJ8YOa5BlbiHJq8-Flvw\" /\u003e\u003cdiv class='medium-6'\u003e\n\u003cdiv class='checkout-substep' data-controller='paymentmethod'\u003e\n\u003cdiv class='checkout-title'\u003e\nMoyen de paiement\n\u003c/div\u003e\n\u003cdiv class='checkout-input checkout-input-radio'\u003e\n\u003cinput id=\"payment_method_1\" name=\"order[payments_attributes][][payment_method_id]\" data-action=\"paymentmethod#selectPaymentMethod\" data-paymentmethod-id=\"paymentmethod1\" data-paymentmethod-target=\"input\" type=\"radio\" value=\"1\" /\u003e\n\u003clabel for=\"payment_method_1\"\u003eCash on collection (Pas de frais supplémentaires)\u003c/label\u003e\n\u003c/div\u003e\n\u003cdiv class='checkout-input checkout-input-radio'\u003e\n\u003cinput id=\"payment_method_12\" name=\"order[payments_attributes][][payment_method_id]\" data-action=\"paymentmethod#selectPaymentMethod\" data-paymentmethod-id=\"paymentmethod12\" data-paymentmethod-target=\"input\" type=\"radio\" value=\"12\" checked=\"checked\" /\u003e\n\u003clabel for=\"payment_method_12\"\u003eStripe (Pas de frais supplémentaires)\u003c/label\u003e\n\u003c/div\u003e\n\n\u003cdiv class='paymentmethod-container' id='paymentmethod1' style='display: none'\u003e\n\u003cdiv class='paymentmethod-description panel'\u003e\nPay on collection!\n\u003c/div\u003e\n\u003cdiv class='paymentmethod-form'\u003e\n\n\u003c/div\u003e\n\u003c/div\u003e\n\u003cdiv class='paymentmethod-container' id='paymentmethod12' style='display: block'\u003e\n\u003cdiv class='paymentmethod-form'\u003e\n\u003cdiv data-controller='stripe-cards'\u003e\n\u003cdiv class='checkout-input'\u003e\n\u003clabel\u003e\nUtilisez une carte enregistrée\n\u003c/label\u003e\n\u003cselect name=\"existing_card_id\" id=\"existing_card_id\" data-action=\"change-\u0026gt;stripe-cards#onSelectCard\" data-stripe-cards-target=\"select\"\u003e\u003coption value=\"18\"\u003evisa 4242 Exp:10/2022\u003c/option\u003e\n\u003coption value=\"20\"\u003evisa 4242 Exp:02/2024\u003c/option\u003e\n\u003coption value=\"22\"\u003evisa 4242 Exp:12/2044\u003c/option\u003e\n\u003coption value=\"\"\u003eou entrez les détails de votre nouvelle carte ci-dessous\u003c/option\u003e\u003c/select\u003e\n\u003c/div\u003e\n\u003cdiv data-stripe-cards-target='stripeelements'\u003e\n\u003cdiv class='checkout-input'\u003e\n\u003cdiv data-controller='stripe' data-stripe-key='pk_test_yFdIepNxviZOdmGWmhtKR8h5'\u003e\n\u003cdiv class='stripe-card'\u003e\n\u003cinput type=\"hidden\" name=\"order[payments_attributes][][source_attributes][first_name]\" id=\"order_payments_attributes__source_attributes_first_name\" value=\"Jean-Baptiste\" /\u003e\n\u003cinput type=\"hidden\" name=\"order[payments_attributes][][source_attributes][last_name]\" id=\"order_payments_attributes__source_attributes_last_name\" value=\"Bellet2\" /\u003e\n\u003cinput type=\"hidden\" name=\"order[payments_attributes][][source_attributes][month]\" id=\"order_payments_attributes__source_attributes_month\" data-stripe-target=\"expMonth\" /\u003e\n\u003cinput type=\"hidden\" name=\"order[payments_attributes][][source_attributes][year]\" id=\"order_payments_attributes__source_attributes_year\" data-stripe-target=\"expYear\" /\u003e\n\u003cinput type=\"hidden\" name=\"order[payments_attributes][][source_attributes][cc_type]\" id=\"order_payments_attributes__source_attributes_cc_type\" data-stripe-target=\"brand\" /\u003e\n\u003cinput type=\"hidden\" name=\"order[payments_attributes][][source_attributes][last_digits]\" id=\"order_payments_attributes__source_attributes_last_digits\" data-stripe-target=\"last4\" /\u003e\n\u003cinput type=\"hidden\" name=\"order[payments_attributes][][source_attributes][gateway_payment_profile_id]\" id=\"order_payments_attributes__source_attributes_gateway_payment_profile_id\" data-stripe-target=\"pmId\" /\u003e\n\u003cdiv class='card-element' data-stripe-target='cardElement'\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cdiv class='error card-errors' data-stripe-target='cardErrors'\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003cdiv class='checkout-input'\u003e\n\u003cinput type=\"checkbox\" name=\"order[payments_attributes][][source_attributes][save_requested_by_customer]\" id=\"order_payments_attributes__source_attributes_save_requested_by_customer\" value=\"1\" /\u003e\n\u003clabel for=\"save_requested_by_customer\"\u003eEnregistrez votre carte pour vos prochains achats\u003c/label\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003cdiv class='checkout-substep'\u003e\nVous pourrez vérifier et confirmer votre commande à l\u0026#39;étape suivante.\n\u003c/div\u003e\n\u003cdiv class='checkout-submit'\u003e\n\u003cinput type=\"submit\" name=\"commit\" value=\"Etape suivante - Récapitulatif de commande\" class=\"button primary\" data-disable-with=\"Etape suivante - Récapitulatif de commande\" /\u003e\n\u003ca class='button cancel' href='/checkout/details'\u003e\nRetour à vos coordonnées\n\u003c/a\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003c/form\u003e\u003c/div\u003e\n\n\u003c/div\u003e\n\u003c/checkout\u003e\n","selector":"#checkout","operation":"replace"},{"html":"\u003cdiv id='flashes'\u003e\n\u003calert class='animate-show' data-controller='flash'\u003e\n\u003cdiv class='alert-box alert' type='error'\u003e\n\u003cspan\u003eLa sauvegarde a échoué, merci de mettre à jour les champs mis en évidence.\u003c/span\u003e\n\u003ca class='small close' data-action='click-\u0026gt;flash#close'\u003e×\u003c/a\u003e\n\u003c/div\u003e\n\u003c/alert\u003e\n\u003c/div\u003e\n","selector":"#flashes","operation":"replace"}]

This is maybe related to the fact that before submitting the request to the server, we previously call for https://api.stripe.com/v1/payment_methods and, only after POST request is sent to the server. Maybe StripeElements cannot handle CableReady/cable_car.

I'd invoke for @Matt-Yorkley help for this one. 🙏

Re. after more investigations
I think the major issue is around app/webpacker/controllers/stripe_controller.js and how the form is finally submitted:

        this.parentForm.submit();

_After more investigations, and asking users on Discord server, we've found the solution! 👏 🎉 _
Use requestSubmit() instead of submit()
The main difference is that requestSubmit() triggers an event that can be actually catch by turbo/cable_ready (submit() doesn't trigger any event)
7596f9d

@jibees
Copy link
Contributor

jibees commented Apr 11, 2022

Ok, seems that I failed to resolve:

Trying to re-submit the payment on step 3 if a previously submit has already been done and failed. Status: ❌

I'd be happy if @Matt-Yorkley or @mkllnk could have a look at it! 🙏

@mkllnk
Copy link
Member

mkllnk commented Apr 12, 2022

What is happening? Is a new payment method created or is that failing the second time?

I would need a bit more time to debug this properly. Maybe later in the week? I'm in the middle of Active Storage.

@jibees
Copy link
Contributor

jibees commented Apr 14, 2022

What is happening? Is a new payment method created or is that failing the second time?

If we submit (on step3) a payment with a card error, then we probably should go back on step2 with the flash error, in order to make another round with stripe. I can't make this redirection functional.

@mkllnk
Copy link
Member

mkllnk commented Apr 26, 2022

Just as a proof of concept, this works for me:

--- a/app/controllers/split_checkout_controller.rb
+++ b/app/controllers/split_checkout_controller.rb
@@ -63,6 +63,9 @@ class SplitCheckoutController < ::BaseController
 
     render operations: cable_car.redirect_to(url: redirect_url)
     true
+  rescue
+    render operations: cable_car.redirect_to(url: checkout_step_path(:payment))
+    true
   end
 
   def selected_payment_method

The replacement with flash would be nicer though.

I found another scenario where this fails:

  1. Visit /payment and introduced the card * 9995
  2. Check "Save card for future use"
  3. Click Next and it fails

@jibees
Copy link
Contributor

jibees commented Apr 26, 2022

I found another scenario where this fails:

Did you test it with the branch modifications? It should not fail:

Trying to save a card that trigger any stripe errors on step 2. Status: ✔️, commit 7596f9d

But I'll have a look.

Thanks @mkllnk !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants