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:
- Using Stripe Payment Element - The recommended approach providing a complete, customizable UI component
- 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:
- Add shipping costs: Add shipping as a custom item to the cart (if applicable)
- Submit payment form: Call
elements?.submit()
to validate the Stripe form - Convert cart to order: Create an order from the shopper's cart (guest or account checkout)
- Initiate payment: Start payment against the order with Elastic Path Payments
- Confirm with Stripe: Use
stripe.confirmPayment()
with the client secret and payment elements - 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,
},
},
});
}
Using Stripe Payment Element (Recommended)
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 Number | Scenario |
---|---|
4242 4242 4242 4242 | Successful payment |
4000 0000 0000 3220 | 3D Secure authentication required |
4000 0000 0000 9995 | Payment 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