Broadleaf Microservices
  • v1.0.0-latest-prod

Stripe Payment Element Integration

Frontend Project Setup

To integrate the frontend with the Stripe Elements load the Stripe.js as an ECMAScript module. Add the provider to pass along the stripe promise within the context so that it can be accessed on the page when rendering stripe elements.

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

// Use the public key to load the Stripe.js
const stripePromise = await loadStripe('pk_...');

function MyApp(...) {
    // ...
    return (
        <StripeProvider stripePromise={stripePromise}>
          ...
        </StripeProvider>
    )
}

NOTE: When specifying paymentMethodTypes, the "card" type should be included to enable support for credit card forms, Apple Pay, and Google Pay. This is currently the only required payment method type. The list of paymentMethodTypes should correlate with PaymentIntentCreateParams.Builder.addPaymentMethodType("card") on the server side in com.broadleafcommerce.payment.service.gateway.DefaultStripeExternalCallService#populatePaymentIntentCreateParams. Alternatively, you can omit this client-side property and configure payment methods dynamically by setting the server-side property broadleaf.stripe.payment-methods-from-stripe-dashboard to true, allowing Stripe to determine available methods via the Dashboard.

Example Integration

import { useState } from 'react';
import { useStripeContext } from '@app/checkout/components/stripe-payment/context/stripe-context';
import {
  CardNumberElement,
  CardCvcElement,
  CardExpiryElement,
  useElements,
  useStripe,
  Elements,
  StripeElementsOptions
} from '@stripe/react-stripe-js';
import {
  useCallbackFormatAmountInMinorUnits,
  useCallbackGetPaymentAmount,
} from '@app/checkout/hooks';

export const StripePaymentForm = () => {
    ...
  type StripePaymentFormType = FC<Props> & {
  TYPE: 'STRIPE';
   };
    ...
  const { stripePromise } = useStripeContext();
  const getPaymentAmount = useCallbackGetPaymentAmount({
    gatewayType: StripePaymentForm.TYPE,
  });
  const paymentAmount = getPaymentAmount();
  const formatAmountInMinorUnits = useCallbackFormatAmountInMinorUnits();
  const options = {
    mode: 'payment',
    amount: formatAmountInMinorUnits(paymentAmount),
    currency: paymentAmount.currency.toLowerCase(),
    paymentMethodCreation: 'manual',
    //if don't set this prop or value doesn't correlate with the one on server
    //com/broadleafcommerce/payment/service/gateway/DefaultStripeExternalCallService.java:348
    //PaymentIntent.create(createParams, requestOptions); specifically in createParams
    //.setCaptureMethod(captureMethod)
    captureMethod: 'manual'/'automatic',
    //card type automatically includes apple_pay and google_pay, this is the only type we need to include so far
    //this should correlate with PaymentIntentCreateParams.Builder.addPaymentMethodType("card")
    //on server side, or remove this property so that methods will be read from the dashboard
    paymentMethodTypes: ['card'],
  } as StripeElementsOptions;

    const { error, onSubmit } = useHandleSubmit();

    return (
      <div>
        <h1>Payment Form</h1>
         <Elements stripe={stripePromise} options={options}>
             <Formik
               initialValues={...}
               onSubmit={onSubmit}
               validateOnBlur
               validateOnChange={false}
             >
               {props => (
                 <FormikForm>
                   <CheckboxField name="savePaymentForFutureUse" label="Save new payment?" />
                   <CardNumberElement ... />
                   <InputField name="nameOnAccount" autoComplete="cc-name" />
                   <CardCvcElement ... />
                   <CardExpiryElement ... />
                    ...
                   <button type="submit">Submit</button>
                 </FormikForm>
               )}
             </Formik>
         </Elements>
      </div>
    )
}


const useHandleSubmit = () => {
  const stripe = useStripe();
  const elements = useElements();
  const [error, setError] = useState();
  const [stripeError, setStripeError] = useState();

  const onSubmit = async (data, actions): Promise<void> => {
    const { savePaymentForFutureUse, ...billingAddress } = data;

    const billing_details = {
      ...billingAddress,
      email: emailAddress,
    };

    let paymentMethod;
    let displayAttributes= {};
    let paymentName;

    // selectedSavedPaymentMethod - one of the created saved payment method from PaymentTransactionServices
    if (!isEmpty(selectedSavedPaymentMethod)) {
      paymentMethod = {
        id: selectedSavedPaymentMethod.id,
      };

      displayAttributes = selectedSavedPaymentMethod.displayAttributes;

      paymentName = selectedSavedPaymentMethod.name;
    } else {
      const stripeResponse = await stripe.createPaymentMethod({
        type: 'card',
        card: elements.getElement(CardNumberElement),
        billing_details,
      });

      if (!isEmpty(stripeResponse.error)) {
        setStripeError(stripeResponse.error);
        return;
      }

      paymentMethod = stripeResponse.paymentMethod;

      // these are optional and only support displaying info about the card on the frontend
      displayAttributes = {
        creditCardType: paymentMethod.card.brand.toUpperCase(),
        creditCardNumber: maskCardNumber(paymentMethod.card.last4),
        creditCardExpDateMonth: padStart(
          `${paymentMethod.card.exp_month}`,
          2,
          '0'
        ),
        creditCardExpDateYear: `${paymentMethod.card.exp_year}`,
      };

      paymentName = `${displayAttributes.creditCardType} | ${displayAttributes.creditCardNumber} ${displayAttributes.creditCardExpDateMonth}/${displayAttributes.creditCardExpDateYear}`;
    }

    // these are required to pass to the backend as part of the payment
    const paymentMethodProperties = {
      PAYMENT_METHOD_ID: paymentMethod.id,
      CUSTOMER_ID: paymentMethod.customer
    };

    const paymentRequest = {
      name: paymentName,
      savedPaymentMethodId: selectedSavedPaymentMethod.id,
      type: 'CREDIT_CARD',
      gatewayType: 'STRIPE',
      amount: { amount: 11, currency: 'USD' },
      subtotal: cart.cartPricing.subtotal,
      adjustmentsTotal: cart.cartPricing.adjustmentsTotal,
      fulfillmentTotal: cart.cartPricing.fulfillmentTotal,
      taxTotal: cart.cartPricing.totalTax,
      billingAddress,
      shouldSavePaymentForFutureUse: savePaymentForFutureUse,
      paymentMethodProperties,
      displayAttributes,
    };

    try {
        // submit the payment request
        // "handleSubmitPaymentInfo" - can be obtained from our payment SDK - useSubmitPaymentRequest(). It creates a Payment in PaymentTransactionServices
        const paymentSummary = await handleSubmitPaymentInfo(paymentRequest);
        ...
    } catch (err) {
      setError(err);
    } finally {
      ...
    }
  };

  return { error, onSubmit, stripeError };
};