Skip to content

Create a single charge when using Stripe Payment Intents#45

Merged
spaghetticode merged 13 commits into
solidusio:masterfrom
spaghetticode:payment-intents-single-payment
Apr 10, 2020
Merged

Create a single charge when using Stripe Payment Intents#45
spaghetticode merged 13 commits into
solidusio:masterfrom
spaghetticode:payment-intents-single-payment

Conversation

@spaghetticode

@spaghetticode spaghetticode commented Apr 9, 2020

Copy link
Copy Markdown
Member

Fixes #44, plus other minor issues found while working on that main problem.

When using the Payment Intents API, we need to create a single charge. In order to achieve the goal, we now create a Solidus payment as soon as the Payment Intent is authorized from Stripe (while the customer is still on the payment page). Later, we skip the creation of the second payment during Solidus default flow after the order has been confirmed.

The first payment will be created uncaptured instead of authorized (already captured) as the customer may decide to not complete their purchase.

When the customer enters multiple credit cards during the same checkout process or changes the payment method from a freshly entered credit card to something else (ie. check) we also need to properly invalidate (void) stale Payment Intents charges and their payment records in Solidus.

This PR also fixes an issue with 3D-Secure card reusability. before this fix, orders finalized by reusing an already authorized 3DS card would end up in creating payments that cannot be properly captured.

Some integration tests were added in order to improve edge cases coverage, including checks on the backend that verify payments are created with the proper state and their capturability, when appropriate.

This allows the controller to be aware of the data included in
the payment form.
When the customer pays with Payment Intents we need to explicitly
require manual capture, or the Payment Intents charge will be
marked as authorized as soon as the customer authorizes the use
of their card.

Also, in order to allow the reusability of the payment method that
is created when the customer adds a new card, we need to explicitly
mark the payment intents valid for off-session usage.
We need to make sure that when the customer is checking out by
adding a new Payment Intents card, we don't create a second
payment by following the standard Solidus payment flow.

Also, when the customer does completes the payment by changing
the payment method after having added a new Payment Intents card,
we need to make sure that the pending charge for that intent is
voided.
This change is required in order to allow customers to successfully
checkout by reusing an existing card that was added via the Payment
Intents API.
The card ending with 3184 requires always 3D Secure authentication,
whether the payment method was created with the option to be used
off-session.

It is replaced by the one ending with 3155, which represents a more
realistic scenario, requiring authentication only when actually
necessary.
This will allow to edit the Payment Intent description on Stripe
dashboard.
@spaghetticode spaghetticode added the bug Something isn't working label Apr 9, 2020
@spaghetticode spaghetticode self-assigned this Apr 9, 2020
Spree::Payment#gateway_order_id is defined as private in Solidus,
so the public method `gateway_order_identifier` is introduced
in order to make it accessible outside the context of the Payment
model.

app/decorators/models/spree/payment_decorator.rb

decoerator
This service encapsulates all the logic for creating a Solidus
payment starting from a Stripe Payment Intent object.

It also updates the Payment Intent description with Solidus
payment number, so it's much easier to match them on Stripe
dashboard.
When reusing a 3D-Secure card it's important to verify
that the payment stored in Solidus can be properly captured.

If the original payment method or subsequent payments made with
it are not properly configured, Stripe will create charges that
cannot be actually be captured, without showing any error.
By extracting the method `#authenticate_3d_secure_card` we can avoid
much code repetition among specs using the 3D Secure card.
We need to ensure that, when the customer pays first with a
Payment Intents and then changes the payment to something
else (ie. check) the first payment gets voided.
We need to invalidate existing pending Payment Intents payments
when we're adding a new credit card with Payment Intents.
@spaghetticode
spaghetticode force-pushed the payment-intents-single-payment branch from 3336912 to 891e31a Compare April 9, 2020 15:50

@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 @spaghetticode, commits are super-readable and they made it easier to review.

@spaghetticode
spaghetticode merged commit e68d896 into solidusio:master Apr 10, 2020
@spaghetticode
spaghetticode deleted the payment-intents-single-payment branch November 20, 2020 16:33
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.

Duplicates charges with Payment Intents

2 participants