Skip to content

Fix for 3D-Secure payments on cart page checkout#49

Merged
spaghetticode merged 21 commits into
solidusio:masterfrom
spaghetticode:cart-checkout-3ds-fix
Apr 29, 2020
Merged

Fix for 3D-Secure payments on cart page checkout#49
spaghetticode merged 21 commits into
solidusio:masterfrom
spaghetticode:cart-checkout-3ds-fix

Conversation

@spaghetticode

Copy link
Copy Markdown
Member

The cart page checkout did not work properly when paying with a 3D-Secure credit card.

This PR fixes the cart checkout payment flow by making sure that a proper payment intent is created and authorized. This requires also to manually create the corresponding Solidus payment
record, as it happens already in the regular Payment Intents flow.

The payment intent is now confirmed automatically (ie. in the browser) so there's no more need to confirm it via Ruby in the Intents controller. According to Stripe customer support, this change is required in order to be able to properly manage the 3D-Secure iframe popup after the payment request data gathering process is completed in the browser.

All these changes resulted in a significant rework of the JS code for the cart checkout flow and the shared code between the cart checkout and the regular Payment Intent flow.

When the 3DSecure authorization fails the received error
is a complex object, not a simple string. We need to access
the message property in order to show a meaningful text to
the customer.
Customers needs to choose the shipping address again, once the payment
request process has failed for any reason.
Failing this, issues in the backend when calculating shipping rates may
arise.
The service object was renamed as its main responsibility is to
create the Solidus payment starting from a Stripe payment intent.
@spaghetticode spaghetticode added the bug Something isn't working label Apr 20, 2020
@spaghetticode spaghetticode self-assigned this Apr 20, 2020

@kennyadsl kennyadsl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Have you already thought about which version release we need to do after merging this?

@spaghetticode

spaghetticode commented Apr 21, 2020

Copy link
Copy Markdown
Member Author

@kennyadsl I think this needs a major release, as there are many breaking changes, both in the FE and the BE. The payment flow for the cart checkout was radically changed, these changes leaked on the Payment Intents flow as well (ie. we're now using "automatic" authorization in the FE instead of "manual" auth in the BE) and some routes changed as well.

@aldesantis aldesantis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing work @spaghetticode, thanks so much.

I have left a few comments on the implementation details, especially for the Ruby part.

I was wondering if it would also be possible to add a live integration test to confirm everything is working properly, given how complex the flow is? I don't know if it's doable, but it would be awesome.

To be honest, I don't feel like I have properly understood the entire data flow, so I'll hold off on an explicit approval until someone else can also chime in on this. I don't know if @kennyadsl has a better understanding of Payment Intents? If not, we'll just have to trust you. 🙂

Comment thread spec/models/solidus_stripe/create_intents_payment_service_spec.rb
Comment thread spec/models/solidus_stripe/create_intents_payment_service_spec.rb Outdated
Comment thread app/models/solidus_stripe/create_intents_payment_service.rb
Comment thread app/models/solidus_stripe/address_from_params_service.rb Outdated
@spaghetticode

Copy link
Copy Markdown
Member Author

@aldesantis testing the payment request button flow would add much value, but this is not testable unless going through much setup pain (this API works only on https and the server must be accessible externally from Stripe) and I'm not even sure that capybara is able to interact with the browser payment request popup (for lack of a better name).

Also, an important part of the test would be to verify the outcomes on the stripe backend account, and that again would add much more complexity and brittleness to the tests. Luckily the Payment Intents process on the payment page is already well tested, considering also some edge cases, so the weakness is confined to the payment request button only.

So we don't need to explicitly pass `nil` when there's no
user (guest checkout, cart page checkout).
The service needs to accept a payment intent id and load the
corresponding payment intent via ActiveMerchant.
We need to add this method in order to retrieve a payment intent
starting from an ID.
This action needs to be extracted in order to be called when creating
a Solidus Payment by the cart page checkout process.
According to Stripe support this is the only correct way to authorize
3D-Secure cards when using the payment request API.
The `confirm` action has been simplified and it now only creates a new
payment intent.
This is only the backend part of the change, JS code will need a more
invasive rework.
We need to create the Solidus payment later in the flow, so the JSON
returned by IntentsController#generate_payment_response needs to be
changed and include the payment intent ID.
`payment` seems to be more descriptive than a generic `result`.
This is needed in order to properly manage possible 3-DSecure payments
This will be used to create a payment on Solidus backend.
This change ensures the payment intent is confirmed via JS instead
of using a Solidus action. This allows to properly manage 3D-Secure
card payments in the cart page checkout.
When this method is used, `this` is now the window object.
This is the iframe name when confirming 3DS cards via JS.
A few changes are required in order to properly manage the
cart checkout flow in this service object.

Cart checkout page sends the address attributes as a JSON
that we need to manipulate before Solidus can use it to
create an address record. Also, the customer full name
needs to be extracted differently when checking out from
the cart page.
When for some reason the Solidus payment cannot be successfully created,
the service object returns `false`. This return value will be used by the
controller in order to properly respond to the JS request.
When for any reason the Solidus payment cannot be created, we need
to notify the JS code that something went wrong, so the error message
can be shown to the customer.

When the payment cannot be created, we also need to cancel the payment
intent on Stripe, or it will remain pending and this will result in
blocking some funds on the customer's card.
The renaming makes sense since this action now only creates a payment
intents. Also, routes were updated both in the Rails router and in
the JS code.
@spaghetticode
spaghetticode force-pushed the cart-checkout-3ds-fix branch from 7e487c1 to bc1d775 Compare April 21, 2020 13:34
@aldesantis

Copy link
Copy Markdown
Member

@spaghetticode understood. I think we can merge this one, it looks like all the tradeoffs that have been made were more than justified. Again, great work! 🎉

@spaghetticode
spaghetticode merged commit 3c8dbe4 into solidusio:master Apr 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants