Overview of how Stripe PaymentIntents work in Sharetribe Web Template, and how you can update legacy templates and custom apps to support Strong Customer Authentication (SCA).
Table of Contents
- 1. Process change
- 2. Add new thunk calls to stripe.duck.js
- 3. CheckoutPage: add new API calls and call them in sequence
- Step 1. onInitiateOrder
- Step 2. onConfirmCardPayment
- Step 3. onConfirmPayment
- Step 4. onSendMessage
- 4. CheckoutPage: save updated transaction
- 5. StripePaymentForm: adding billing details and showing errors
- 6. Test with live credit cards
This guide walks you through the process of taking PaymentIntents into use in your custom client application. These steps are already implemented in Sharetribe Web Template.
This article covers how PaymentIntents can be used with card payments. On general level, the steps are the same for other payment methods. See the concepts article on payment methods and payment intents for more information. Stripe's PaymentIntent is a new way to handle Strong Customer Authentication (SCA) by using frictionless 3D Secure 2 authentication.
Taking Stripe PaymentIntent flow into use is a big change for CheckoutPage and includes process change. You should carefully check what kind of changes are made in the legacy template release: v3.0.0. Taking an update from upstream or even cherry-picking commits might make the update easier, but you should first track your custom-code to affected components.
Stripe's Payment Intents API is a new way to build dynamic payment
automatic confirmation flow
helps a lot since all the authentication actions for a customer are
included in a single call:
stripe.confirmCardPayment. When called,
Stripe's SDK checks if there's a need for Strong Customer Authentication
(SCA) and creates a popup to card issuer's website.
However, to be able to call
confirmCardPayment, there needs to be a
new state to the transaction process. Because of this, we have split the
previous transition (Initial - request -> Preauthorized) into two:
So, after transitions (
request-payment-after-inquiry), API returns
stripePaymentIntentClientSecret among the protected data of the
current transaction. This client-secret is used for the call to
stripe.confirmCardPayment. Then there is another transition made
against Marketplace API, so that it can confirm the PaymentIntent and
preauthorize the order. Transaction process continues normally after
that - i.e. Provider has to accept or reject the order.
Concrete steps here are changing the transaction process, then updating
config.js and making necessary changes to
src/util/transaction.js file. Remember, when transaction process is
changed, you need to go through all the files that import transitions or
utility functions from
util/transaction. In practice, we made changes
to InboxPage, TransactionPage, TransactionPanel, ActivityFeed,
BookingBreakdown, BookingDatesForm, and CheckoutPage. The list
might be different if you have customized your components or process.
The default transaction process supports SCA, but if you have an older process version without PaymentIntents, you can see our new example processes here:
All the example processes support SCA. If you need help with the concrete steps to customize your process to support SCA, contact Sharetribe support from the support widget in Console and we'll guide you through the changes.
When using PaymentIntent flow, we don't need
anymore, but we need to add two new thunk calls:
stripe.confirmCardPayment is needed to provide SCA as mentioned
earlier. However, since customers are making several AJAX calls on
CheckoutPage, it is possible that there is a network error or
something else happening between those calls. Even the whole page, might
be reloaded at some point. We need to retrieve up-to-date PaymentIntent
from Stripe API and check its status to be able to continue the payment
process. This can be done with
Previously stripe.confirmCardPayment was called stripe.handleCardPayment, which is now deprecated. Basically, handleCardPayment has been renamed to confirmCardPayment. In addition to the rename, Stripe has slightly modified the arguments. These changes should not affect the behavior of the method.
The biggest change happens in CheckoutPage. When a user submits
handleSubmit is called from CheckoutPage, new
data needs to be prepared (billing details: name, email, and billing
address) and then 4 thunk-calls/Promises need to be made in sequence:
- This tells Marketplace API to create booking and PaymentIntent
- Booking is created, so availability management blocks dates for conflicting bookings
- API returns
stripePaymentIntentClientSecretinside transaction's protectedData
- This combines both transitions:
sdk.transitions.transitionaka continue inquiry with
- Automatic expiration happens in 15 minutes, if process is not
- Created transaction is saved to session storage or existing inquiry tx is updated. (There is more about this step later.)
- This is a call
- If the customer must perform additional steps to complete the payment, such as authentication, Stripe.js walks them through that process.
- This tells Marketplace API that customer has completed the payment requirements. API will validate and mark the payment confirmed in Sharetribe.
- If the customer has added an initial message to the provider, the app sends that message after the payment is confirmed.
The stripe.confirmCardPayment action needs an instance of Stripe to be passed from StripePaymentForm. stripe.confirmCardPayment will check card details from connected Stripe Elements input.
We use session storage to buffer checkout page against page reloads and errors - customer needs to be able to continue payment after accidental page refresh and network errors. This is a UX issue, but more importantly, it builds trust. Because of this need, we save booking dates and other data there. Previously inquiredTransaction was saved there too, but that concept is now expanded a bit: any transaction can now be saved to session storage under the key "transaction".
So, if there is an existing transaction in inquiry state and customer
books the listing, TransactionPage sends that
CheckoutPage. As a first step CheckoutPage saves received data to the
session store. This is pretty much the same functionality as with
previous card-token payment process - only the key is changed from
inquiryTransaction to transaction. However, after transition
request-payment-after-inquiry) the updated
transaction is saved again. (the relevant new data in transaction is
In addition, the handling of order breakdown with
speculatedTransaction needs to be changed, because saved transaction
already contains booking in some cases and a new call to
sdk.transactions.initiateSpeculative would just return a conflict
error telling about an already existing booking.
Most of the visual changes happen in StripePaymentForm. Billing details are added to the form and most of the errors of different thunk calls are shown inside it.
The default mode for Sharetribe Web Template is to show billing address
fields. Even though it is recommended by Stripe, you might want to
remove those fields due to UX reasons. That can be made just by not
If the page is reloaded after successful call to stripe.confirmCardPayment, billing details should not be shown to the user since credit card number and other billing details are already sent to Stripe.
Since 3D Secure authentication flow is different between different credit card issuers, you should test at least some credit cards how they work in a live environment.
This can be done by creating another live environment instance of your client app that uses
- your live Client Id for Sharetribe and
- live Stripe keys (both publishable and secret).
Then create a new Git branch that takes PaymentIntents flow into use and adds Basic Authentication configuration into environment variables. After that, you could deploy your payment-intent branch into your live environment. Then you can just book some existing listing and maybe reject it to get refund to your live card account.