Warning
You are viewing the technical documentation for the Sharetribe Developer Platform. If you are looking for our no-code documentation, see our help center.

Last updated

How saving a payment card works in the Sharetribe Web Template

An overview of the Sharetribe Web Template functionality for storing payment cards.

Table of Contents

When a customer first comes to the marketplace and books a listing, there are several form fields the user needs to fill: expiration month, card verification code (CVC), card holder's name and possibly other billing details. To improve the user experience for returning customers, it is good to have an option to save payment card details for future bookings. If there is an existing payment card available, the user can just click the "Send request" button to complete checkout page.

From that point forward, the typical case is that customer pays upfront (aka makes an on-session payment). In practice, a preauthorization is created: the money is reserved, but not yet moved to Stripe. When a provider accepts the request, the preauthorization is captured (i.e. the payment is charged from the card and money moved to Stripe and held on the connected account of the provider until payout). The actual payout happens when the transaction completes.

Payment card needs also be saved for off-session payments if those are in use. They are automatic one-time payments that happen when the user is not interacting with your application. You can read more about off-session payments from a separate article.

Saving card details

There are 2 ways to save payment card details:

  • saving cards details without making an initial payment
  • saving cards details after a payment

The former of those can be done in payment methods page (profile menu -> account settings -> payment methods), but you can also save payment card details on the checkout page when making a one-time payment.

Saving cards without making an initial payment

If the user has no existing default payment card, PaymentMethodsPage component will show just a form to save payment card details:

Payment methods page

If there is a default payment methods saved, user will see a dropdown instead, where the current default method is selected. There are two options the user can do: replace the current payment card (it's an option inside the dropdown) or delete the current payment card.

Payment methods page saved card

Under the hood, saving a new payment method needs to complete following steps:

  • Create Stripe Setup Intent through Sharetribe API to obtain the client secret.
  • Call stripe.handleCardSetup with the client secret
    • stripe.handleCardSetup will handle user actions like 3D Secure authentication
  • Save payment method:
    • Create a new Stripe Customer if needed and attach the new payment method to stripeCustomer entity.
    • There's no replace or update option for defaultPaymentMethod entity. To replace a payment card, one needs to remove previous card first and then add a new payment method.

Saving cards after a payment

If the user doesn't have a default payment card saved, or if she is making a one-time payment with a different payment card, there's an option (checkbox) to save the new payment card as the default payment method.

One-time payment and saving card details for future use

If the default payment method exists, user needs to select one-time payment first and then she is able to replace the default card.

Pay with one-time payment card

Under the hood, there are few changes made to the Payment Intents checkout flow:

  • We need to know if the user has already saved a default payment method:
    • Fetch currentUser entity with stripeCustomer.defaultPaymentMethod relationship
  • Show correct form fields:
    • If there's a default payment method saved: show SavedCardDetails component. That component allows you to also select one-time payment instead (and optionally replace the current default payment method with this new payment card). If there's no default payment method saved, one-time payment form is shown on its own.
    • One-time payment needs card details, card holder's name etc. and optional permission to save card details for future bookings.
  • When requesting payment, a parameter needs to be passed if card details are saved at the same time: setupPaymentMethodForSaving. In the template, by default, this is handled in the first step of the processCheckoutWithPayment function.
  • stripe.confirmCardPayment call, confirm-payment transition and the call to send initial message are handled as before
  • Save the payment method if user has given permission to do that:
    • Create a stripeCustomer entity for the current user if needed.
    • Attach the created payment method to stripeCustomer entity.

Getting permission to save a card

Once you set up your payment flow to properly save a card with the Payment Intents or Setup Intents API, Stripe will mark any subsequent off-session payment as a merchant-initiated transaction to reduce the need to authenticate. Merchant-initiated transactions require an agreement (also known as a “mandate”) between you and your customer. Read more about needed permissions from Stripe's documentation.

Charging Saved Cards

If the user has a default payment method and she chooses to use it to book a listing, there are couple of changes needed:

  • The id of Stripe's payment method needs to be sent to Sharetribe API as paymentMethod, when requesting payment.
  • stripe.confirmCardPayment: Stripe Elements (card) is not needed if the default payment method is used.

Saving Payment Card with PaymentIntents payment flow

Here's the description of complete call sequence on CheckoutPage.

Initial data for Checkout:

Check if the user has already saved a default payment method. Fetch currentUser entity with stripeCustomer.defaultPaymentMethod relationship. In Sharetribe Web Template, we call a thunk function: fetchCurrentUser in CheckoutPage.duck.js.

Behind the scenes, this is essentially the following call:

sdk.currentUser.show({
  include: ['stripeCustomer.defaultPaymentMethod'],
});

StripePaymentForm changes - show correct form fields:

If there's a default payment method saved: show SavedCardDetails component. That component allows you to also select one-time payment instead (and optionally replace the current default payment method with this new payment card). If there's no default payment method saved, one-time payment form is shown on its own. One-time payment needs card details, card holder's name etc. and optional permission to save card details for future bookings.

Submit StripePaymentForm

After submitting StripePaymentForm, there are up to 5 calls in sequence (to Sharetribe and Stripe APIs):

Step 1.
sdk.transactions
  .initiate({ processAlias, transition: 'transition/request-payment', ...})

What happens behind the scene:

  • API creates PaymentIntent against Stripe API and returns stripePaymentIntentClientSecret inside the transaction's protectedData.
  • Booking is created at this step - so, availability management will block dates for conflicting bookings.
  • Automatic expiration happens in 15 minutes, if process is not transitioned with transition/confirm-payment before that.
  • After this call, the newly created transaction is saved to session storage in Sharetribe Web Template (or existing inquiry transaction is updated).

When you intend to save card details, a new parameter needs to be passed if card details are saved at the same time: setupPaymentMethodForSaving.

sdk.transactions.initiate({
  processAlias,
  transition: 'transition/request-payment',
  params: {
    listingId,
    bookingStart,
    bookingEnd,
    setupPaymentMethodForSaving: true,
  },
});

When you are using previously saved payment card, the id of Stripe's payment method needs to be sent to Sharetribe API as paymentMethod, when requesting payment.

sdk.transactions.initiate({
  processAlias,
  transition: 'transition/request-payment',
  params: {
    listingId,
    bookingStart,
    bookingEnd,
    paymentMethod: stripePaymentMethodId,
  },
});

In the default Sharetribe Web Template, both these cases are handled with optionalPaymentParams in CheckoutPageWithPayment.js.

const optionalPaymentParams =
  selectedPaymentFlow === USE_SAVED_CARD && hasDefaultPaymentMethodSaved
    ? { paymentMethod: stripePaymentMethodId }
    : selectedPaymentFlow === PAY_AND_SAVE_FOR_LATER_USE
    ? { setupPaymentMethodForSaving: true }
    : {};

Check initiateOrder thunk function and related orderParams from Sharetribe Web Template.

Step 2.
stripe.confirmCardPayment(
  stripePaymentIntentClientSecret,
  paymentParams
);

paymentParams should include card element and billing details. See stripe.confirmCardPayment documentation..

Stripe's frontent script checks if PaymentIntent needs extra actions from customer. Some payments might need Strong Customer Authentication (SCA). In practice, Stripe's script creates a popup (iframe) to card issuers site, where 3D secure v2 authentication flow can be completed.

Step 3.
sdk.transactions.transition({
  id: transactionId,
  transition: 'transition/confirm-payment',
  params,
});

Inform Marketplace API that PaymentIntent is ready to be captured after possible SCA authentication has been requested from user. Sharetribe Web Template does that in confirmPayment thunk call

Step 4.
sdk.messages.send({ transactionId: orderId, content: message });

The template sends an initial message to the transaction if customer has added a message.

Step 5.

As a final step, we need to save the payment method, if customer has selected the "Save for later use" checkbox. So, this is relevant if user has selected onetime payment - instead of making a charge from the previously saved credit card.

In the template, we call savePaymentMethod function that creates stripe customer and adds updates default payment method.

There are 3 different scenarios, which require different calls to Sharetribe API:

1. No StripeCustomer entity connected to Sharetribe API:

sdk.stripeCustomer.create(
  { stripePaymentMethodId },
  { expand: true, include: ['defaultPaymentMethod'] }
);

Template: dispatch(createStripeCustomer(stripePaymentMethodId))

2. Current user has already defaultPaymentMethod - 2 calls:

=> sdk.stripeCustomer.deletePaymentMethod({}, { expand: true })
=> sdk.stripeCustomer.addPaymentMethod({ stripePaymentMethodId }, { expand: true })

Template: dispatch(updatePaymentMethod(stripePaymentMethodId))

3. Current user has StripeCustomer entity connected, but no defaultPaymentMethod:

=> sdk.stripeCustomer.addPaymentMethod({ stripePaymentMethodId }, { expand: true })

Template: dispatch(addPaymentMethod(stripePaymentMethodId))

After these steps, the customer is redirected to their inbox. Depending on the transaction process, either the purchase is accepted directly, or it is up to the provider to accept or decline the booking.