Skip to main content

Implement Card Payments

This guide explains how to implement card payments using Elastic Path Payments powered by Stripe and the Elastic Path Shopper SDK. We'll cover both recommended implementation approaches:

  1. Using Stripe Payment Element - The recommended approach providing a complete, customizable UI component
  2. Using Direct Tokenization - An alternative approach for more custom implementations

Prerequisites

  • Elastic Path Payments enabled in your store
  • API client credentials with appropriate permissions
  • A cart that has been converted to an order
  • @epcc-sdk/sdks-shopper package installed in your project

Understanding the Payment Flow

Processing card payments with Elastic Path Payments follows a specific sequence of steps. Based on real implementations, the complete flow is:

  1. Add shipping costs: Add shipping as a custom item to the cart (if applicable)
  2. Submit payment form: Call elements?.submit() to validate the Stripe form
  3. Convert cart to order: Create an order from the shopper's cart (guest or account checkout)
  4. Initiate payment: Start payment against the order with Elastic Path Payments
  5. Confirm with Stripe: Use stripe.confirmPayment() with the client secret and payment elements
  6. Confirm with Elastic Path: Complete the payment confirmation with the order confirmation endpoint

This flow ensures proper validation, 3D Secure handling, and payment confirmation across both Stripe and Elastic Path systems.

Adding Shipping Costs

Before converting a cart to an order, you may need to add shipping costs as a custom item:

import { addCustomItemToCart } from "@epcc-sdk/sdks-shopper";

async function addShippingToCart(cartId, shippingMethod, shippingAmount) {
return await addCustomItemToCart({
path: {
cartID: cartId,
},
body: {
data: {
type: "custom_item",
name: "Shipping",
sku: shippingMethod,
quantity: 1,
price: {
amount: shippingAmount,
includes_tax: true,
},
},
},
});
}

Converting Cart to Order

You'll need to convert the cart to an order before processing payment. This can be done for both guest and account checkouts:

import { checkoutApi, checkoutWithAccountApi } from "@epcc-sdk/sdks-shopper";

// For guest checkout
async function checkoutAsGuest(cartId, customerData) {
return await checkoutApi({
path: {
cartID: cartId,
},
body: {
data: {
customer: {
email: customerData.email,
name: customerData.name,
},
billing_address: customerData.billingAddress,
shipping_address: customerData.shippingAddress,
},
},
});
}

// For account checkout
async function checkoutWithAccount(cartId, accountData, accountToken) {
return await checkoutWithAccountApi({
path: {
cartID: cartId,
},
body: {
data: {
contact: {
name: accountData.name,
email: accountData.email,
},
token: accountToken,
billing_address: accountData.billingAddress,
shipping_address: accountData.shippingAddress,
},
},
});
}

Stripe Payment Element is a pre-built, customizable UI component that handles collecting and validating payment details securely.

Install Dependencies

npm install @stripe/stripe-js

Complete Implementation Example

Here's a complete example showing the integration of cart checkout and payment processing using the Payment Element approach:

import {
addCustomItemToCart,
checkoutApi,
paymentSetup,
confirmPayment,
} from "@epcc-sdk/sdks-shopper";
import { loadStripe } from "@stripe/stripe-js";
import type { CheckoutForm } from "./types";

export type PaymentCompleteProps = {
cartId: string;
};

export type PaymentCompleteReq = {
data: CheckoutForm;
};

export async function completePayment(
{ cartId }: PaymentCompleteProps,
data: CheckoutForm,
stripe: any,
elements: any,
) {
const { shippingAddress, billingAddress, sameAsShipping, shippingMethod } =
data;

const customerName = `${shippingAddress.first_name} ${shippingAddress.last_name}`;

const checkoutProps = {
billing_address:
billingAddress && !sameAsShipping ? billingAddress : shippingAddress,
shipping_address: shippingAddress,
};

/**
* The handling of shipping options is not production ready.
* You must implement your own based on your business needs.
*/
const shippingAmount =
staticDeliveryMethods.find((method) => method.value === shippingMethod)
?.amount ?? 0;

/**
* Using a cart custom_item to represent shipping for demo purposes.
*/
const cartInclShipping = await addCustomItemToCart({
path: {
cartID: cartId,
},
body: {
data: {
type: "custom_item",
name: "Shipping",
sku: shippingMethod,
quantity: 1,
price: {
amount: shippingAmount,
includes_tax: true,
},
},
},
});

await elements?.submit();

/**
* 1. Convert our cart to an order we can pay
*/
const createdOrder = await checkoutApi({
path: {
cartID: cartId,
},
body: {
data: {
customer: {
email: data.guest.email,
name: customerName,
},
...checkoutProps,
},
},
});

/**
* 2. Start payment against the order
*/
const confirmedPayment = await paymentSetup({
path: {
orderID: createdOrder.data.id,
},
body: {
data: {
gateway: "elastic_path_payments_stripe",
method: "purchase",
payment_method_types: ["card"],
},
},
});

/**
* 3. Confirm the payment with Stripe
*/
const stripeConfirmResponse = await stripe?.confirmPayment({
elements: elements!,
clientSecret: confirmedPayment.data.payment_intent.client_secret,
redirect: "if_required",
confirmParams: {
return_url: `${window.location.href}/thank-you`,
payment_method_data: {
billing_details: {
address: {
country: billingAddress?.country,
postal_code: billingAddress?.postcode,
state: billingAddress?.region,
city: billingAddress?.city,
line1: billingAddress?.line_1,
line2: billingAddress?.line_2,
},
},
},
},
});

if (stripeConfirmResponse?.error) {
throw new Error(stripeConfirmResponse.error.message);
}

/**
* 4. Confirm the payment with Elastic Path
*/
const resultingConfirmation = await confirmPayment({
path: {
orderID: createdOrder.data.id,
transactionID: confirmedPayment.data.id,
},
body: {
data: {
options: {},
},
},
});

return {
order: createdOrder,
payment: confirmedPayment,
cart: cartInclShipping,
};
}

Testing Your Implementation

When testing payments, use these Stripe test cards:

Card NumberScenario
4242 4242 4242 4242Successful payment
4000 0000 0000 32203D Secure authentication required
4000 0000 0000 9995Payment declined

Use any future expiration date, any three-digit CVC, and any postal code.

Error Handling

Implement robust error handling to provide a good user experience:

try {
// Process payment
} catch (error) {
if (error.response?.data?.errors) {
const errorData = error.response.data.errors[0];

if (errorData.status === "402") {
// Payment declined
showError("Your card was declined. Please try another card.");
} else if (errorData.status === "400") {
// Validation error
showError("Invalid payment information: " + errorData.detail);
} else {
// Other API error
showError("An error occurred: " + errorData.detail);
}
} else if (error.message.includes("network")) {
// Network error
showError("Network error. Please check your connection and try again.");
} else {
// Generic error
showError("An error occurred. Please try again.");
}
}

Security Considerations

When implementing card payments, follow these security best practices:

  • Never store actual card details in your application
  • Always use HTTPS for all payment-related communications
  • Implement proper validation for all user inputs
  • Use Content Security Policy (CSP) to prevent XSS attacks
  • Keep your Stripe.js and SDK packages updated