When using the Direct Order Submission flow for Checkout, a different payment flow is required. You must generate Payment Methods before creating an Order. For more details, check out the Direct Order Submission guide here.

Overview

Stripe.js V3 libraries allow you to accept payments from shoppers through 25+ different payment methods with a single, embeddable UI component. Stripe powers this through their Payment Element, which requires both front-end and server-side work in order to accept a payment. This is different to the current Violet Checkout flow, which takes in credit card or Stripe token information in order to process a payment.

Violet checkout has been enhanced to enable you to use Stripe Payment Elements to create easier, more secure payment flows for your shoppers. This document walks you through how you can integrate with these changes. For more information, such as details on how Violet is powering this or how to build multi-step confirmations, please refer to Stripe’s Guides, available here.

This document assumes that you have already set up Violet within your software ecosystem and integrated with our APIs. If this is not the case, please follow and read through Quick Start guide.

Changes to the Existing Checkout Flow

To integrate with Stripe V3 you will need to make some specific changes to the existing Violet checkout flow which consists of:

  1. Create a Cart
  2. Apply Customer to Cart
  3. Get Available Shipping Methods
  4. Apply Shipping Methods
  5. Apply Payment Info
  6. Submit Cart

We will reference the above steps in the following docs to clarify at what points the adjustments need to be made.

Creating a Cart

The general process to 1. Create a Cart remains the same, however, with the following new flag added to the request body:

{
"wallet_based_checkout": true
}

Carts with wallet_based_checkout set to true cannot be completed without completing the front end client work listed below, specifically the Stripe Element integration and the client side Payment Confirmation. There is no API-only path complete such an order and attempting to do so will return errors.

This will initialize the cart as a wallet based cart allowing you to utilize a payment intent rather than raw credit card details. When wallet_based_checkout is true the response will include two new properties:

{
  "stripe_key": "pk_test_UHg8oLvg4rrDCbvtqfwTE8qd",
  "payment_intent_client_secret": "pi_3MbFHUK29KDiBVld0N8b8EDd_secret_cb5AOMAxXyZeJfy4wylgnVd3C"
}

Stripe requires both a Stripe Key and a Payment Intent Client Secret to create a Stripe PaymentElement.

The stripe_key only changes at the environment level and is used to load the Stripe libraries. We recommend saving it as an environment variable in your client-side code so that you don’t need to re-render Stripe elements each time.

The payment_intent_client_secret changes per order — this is what is used to tie the order to the payment. It does not change during the order though. Violet automatically updates this payment intent with correct totals as you make changes to the cart (adding/removing skus, shipping, discounts).

Apply Payment Info

The next change comes at 5. Apply Payment Info. This step is no longer needed at all since the payment intent was created with the cart and Violet keeps the total up to date automatically.

Submit Cart

The majority of changes take place in this step. The crucial part is the Client Side Payment Confirmation (detailed below) through Stripe the must be done first and only after a successful response from Stripe should you call Violet to submit the cart. Violet checks the payment intent during submit and if the payment has not confirmed on the client, it will return an error.

Below are the specific instructions to complete your integration with Stripe Payment Elements

Integrating with Stripe Payment Elements

The Stripe Payment Element is a single, embeddable UI component that accepts payment information from your shoppers and attaches it to the Violet order. Now that you have a Stripe Key and Payment Intent Client Secret, you’re ready to render the Stripe Payment Element form and collect payment details from your shopper.

You can find a fully integrated example of using stripe payment elements in our open source demo app on Github

Set up Stripe.js

Install the Stripe.js React libraries and the Stripe JS loader from the npm public registry:

npm install --save @stripe/react-stripe-js @stripe/stripe-js

Add and configure the Payment Element to your payment page

To use the Stripe Payment element, wrap your checkout page in a Stripe Elements provider. This is where you will also use the Stripe Key and Payment Intent Client Secret provided in the Order response.

import React from 'react';
import ReactDOM from 'react-dom';
import { Elements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';

import CheckoutForm from './CheckoutForm';

// Make sure to call `loadStripe` outside of a component’s render to avoid
// recreating the `Stripe` object on every render.
const stripePromise = loadStripe('{{STRIPE_KEY_FROM_ORDER_RESPONSE}}', {});

function App() {
  const options = {
    clientSecret: '{{PAYMENT_INTENT_CLIENT_SECRET_FROM_ORDER_RESPONSE}}',

    // Fully customizable with appearance API.
    appearance: {
      /*...*/
    },
  };

  return (
    <Elements stripe={stripePromise} options={options}>
      <CheckoutForm />
    </Elements>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));

Add the Payment Element to your Checkout Form

In your checkout form, render the Stripe PaymentElement component:

import React from 'react';
import { PaymentElement } from '@stripe/react-stripe-js';

const CheckoutForm = () => {
  return (
    <form>
      <PaymentElement />
      <button>Submit</button>
    </form>
  );
};

export default CheckoutForm;

The Payment Element renders a dynamic form that allows your customer to pick a payment method. Depending on their payment method, the form automatically requests that the customer fills in all necessary payment details.

You can customize the Payment Element to match the design of your site by passing the appearance object into options when creating the Elements provider.

By default, the Payment Element only collects necessary billing address details. In cases where you need to collect full billing address, like for calculating tax for digital goods and services, you can use the Address Element in Billing mode.

Confirm the Payment

When your shopper is ready to complete their cart and submit their order, you will need to confirm the payment. Note that you should only confirm a payment as the final step before calling /submit on a cart to Violet since no other action can be taken on the cart that alters the cart total (like setting shipping methods or adding skus) after confirming the payment.

Add a listener to your Checkout form and then call Stripe.confirmPayment to attach the payment method your shopper provided to the payment intent sent by Violet and authorize the payment. Stripe will automatically request all the information that is needed for that payment method.

The Stripe.confirmPayment call does not charge the credit card. Instead, it only authorizes the amount that needs to be paid and ensures there are sufficient funds. Charges are only captured once an order has successfully been placed in the external merchant store.

If there are issues during submission, the shopper’s card is never charged. The authorization will fall off their card within a few business days.

import React from 'react';
import {
  useStripe,
  useElements,
  PaymentElement,
} from '@stripe/react-stripe-js';

export default function CheckoutForm() {
  const stripe = useStripe();
  const elements = useElements();

  const handleSubmit = async (event) => {
    // We don't want to let default form submission happen here,
    // which would refresh the page.
    event.preventDefault();

    if (!stripe || !elements) {
      // Stripe.js has not yet loaded.
      // Make sure to disable form submission until Stripe.js has loaded.
      return;
    }

    const result = await stripe.confirmPayment({
      elements,
      confirmParams: {
        // Make sure to change this to your payment completion page
        return_url: '<http://localhost:3000>',
      },
    });

    stripePaymentMethodHandler(result);
  };

  return (
    <form onSubmit={handleSubmit}>
      <PaymentElement />
      <button type="submit" disabled={!stripe}>
        Submit Payment
      </button>
    </form>
  );
}

If a successful result is returned from the call to Stripe, a payment method has successfully been attached to the payment intent that Violet provided.

Submit the order and render post-processing information to your Shopper

You can submit the order in the stripePaymentMethodHandler method called above. This is where you will call Violet Checkout submit:

const stripePaymentMethodHandler = async (result) => {
  if (result.error) {
    // Show error in payment form
  } else {
    // Otherwise submit order to Violet
    const res = await fetch('{{baseUrl}}/checkout/cart/:cart_id/submit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        app_order_id: //orderId for this order in your system,
      })
    });

    const orderResponse = await res.json();

    handleServerResponse(orderResponse);
  }
}

Note that the request body is empty, in contrast with the request body of the similar Apple Pay flow. The difference here is that you should have already collected and sent to Violet a complete set of customer info before confirming the payment. Therefore it is not necessary to send any of that data here.

Optional: If there are further actions required for your customer during the payment process, such as 3D secure authentication, Violet will respond with the following status on the order:

{
    "payment_status": "REQUIRES_ACTION",
}

If this is the case, you can use the same payment_intent_client_secret and the Stripe.handleNextAction method to request the additional information from the shopper.

const handleServerResponse = async (orderResponse) => {
  if (response.error) {
    // Show error from server on payment form
  } else if (orderResponse.payment_status.requires_action) {
    // Use Stripe.js to handle the required next action
    const { error: errorAction, paymentIntent } = await stripe.handleNextAction(
      {
        clientSecret: orderResponse.payment_intent_client_secret,
      }
    );

    if (errorAction) {
      // Show error from Stripe.js in payment form
    } else {
      // Actions handled, submit order to Violet once again
      const res = await fetch('{{baseUrl}}/checkout/cart/:cart_id/submit', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          app_order_id: //orderId for this order in your system,
        })
      });
    }
  } else {
    // No actions needed, show success message
  }
};