This tutorial was written by Claude Code (an AI) and has not yet been reviewed. Follow along with caution. If the tutorial was helpful or a specific part was not clear/correct, please provide feedback at the bottom of the page. Thank you.
This guide covers integrating Stripe with CoCart Preview API. Requires CoCart v4.6+ and a configured Stripe payment gateway.
Plugin Compatibility : This tutorial covers the WooCommerce Stripe Payment Gateway plugin, not WooCommerce Payments (WCPay/WooPay).
WooCommerce Stripe Gateway (payment_method: 'stripe') - Available in 45+ countries , full API access, direct Stripe account ✅
WooCommerce Payments / WooPay (payment_method: 'woocommerce_payments') - Available in 39 countries , managed solution with WooPay features
Using WooCommerce Payments? If you want to use WooCommerce Payments, see the WooPay tutorial instead.Why use Stripe Gateway?
More countries supported (45+ vs 39)
Full control over your Stripe account
Direct access to Stripe dashboard and features
Better for businesses needing advanced Stripe features
Available in countries not supported by WooCommerce Payments (e.g., Brazil, India, Indonesia, Malaysia, Mexico, Thailand)
Overview
Stripe integration with CoCart follows a secure client-side payment flow using Stripe Elements and Payment Intents. This ensures sensitive payment data never touches your server while providing a seamless checkout experience.
Geographic Availability : The WooCommerce Stripe Gateway plugin works in all countries where Stripe is available (45+ countries including U.S., Canada, UK, EU, Australia, Japan, Singapore, and many more). View all supported countries .
Prerequisites
Before implementing Stripe checkout, ensure you have:
Stripe account - Create a direct account at stripe.com (must be in a supported country)
WooCommerce Stripe Gateway plugin installed and configured
Stripe JavaScript SDK loaded in your frontend
A valid cart with items added via CoCart API
Customer billing address information
Integration Flow
Initialize Stripe Elements
Set up Stripe SDK with your store’s publishable key and create payment form
Collect Payment Details
Let customers securely enter their payment information using Stripe Elements
Create Payment Method
Generate a payment method using Stripe’s client-side API
Complete Checkout
Submit the checkout with payment method reference to CoCart
Step 1: Initialize Stripe Elements
First, set up Stripe with your store’s publishable key and create the payment form:
// Initialize Stripe with your store's publishable key
const stripe = Stripe ( 'pk_live_your_stripe_publishable_key' ); // Must match WooCommerce settings
const elements = stripe . elements ();
// Create payment element with styling
const cardElement = elements . create ( 'card' , {
style: {
base: {
fontSize: '16px' ,
color: '#424770' ,
'::placeholder' : {
color: '#aab7c4' ,
},
},
invalid: {
color: '#9e2146' ,
},
},
});
// Mount the card element to your form
cardElement . mount ( '#card-element' );
// Handle real-time validation errors from the card Element
cardElement . on ( 'change' , ({ error }) => {
const displayError = document . getElementById ( 'card-errors' );
if ( error ) {
displayError . textContent = error . message ;
} else {
displayError . textContent = '' ;
}
});
Important : The publishable key in your JavaScript code must exactly match the one configured in your WooCommerce Stripe settings.
Step 2: Create Payment Method
When the customer submits the form, create a payment method using Stripe:
async function handlePaymentSubmission ( event ) {
event . preventDefault ();
const form = event . target ;
const submitButton = form . querySelector ( '#submit-button' );
// Disable submit button
submitButton . disabled = true ;
submitButton . textContent = 'Processing...' ;
try {
// Create payment method
const { paymentMethod , error } = await stripe . createPaymentMethod ({
type: 'card' ,
card: cardElement ,
billing_details: {
name: form . billing_first_name . value + ' ' + form . billing_last_name . value ,
email: form . billing_email . value ,
address: {
line1: form . billing_address_1 . value ,
city: form . billing_city . value ,
state: form . billing_state . value ,
postal_code: form . billing_postcode . value ,
country: form . billing_country . value ,
}
},
});
if ( error ) {
throw new Error ( error . message );
}
// Payment method created successfully
console . log ( 'Payment method created:' , paymentMethod . id );
// Proceed to checkout
await processCheckoutWithStripe ( paymentMethod );
} catch ( error ) {
console . error ( 'Payment method creation failed:' , error );
document . getElementById ( 'card-errors' ). textContent = error . message ;
// Re-enable submit button
submitButton . disabled = false ;
submitButton . textContent = 'Pay Now' ;
}
}
// Attach event listener to form
document . getElementById ( 'payment-form' ). addEventListener ( 'submit' , handlePaymentSubmission );
Step 3: Complete Checkout
Process the checkout with the payment method reference:
async function processCheckoutWithStripe ( paymentMethod ) {
const cartKey = localStorage . getItem ( 'cart_key' );
const form = document . getElementById ( 'payment-form' );
try {
// Collect billing information from form
const billingData = {
first_name: form . billing_first_name . value ,
last_name: form . billing_last_name . value ,
email: form . billing_email . value ,
phone: form . billing_phone ?. value || '' ,
address_1: form . billing_address_1 . value ,
city: form . billing_city . value ,
state: form . billing_state . value ,
postcode: form . billing_postcode . value ,
country: form . billing_country . value
};
// Submit checkout to CoCart
const response = await fetch ( 'https://yoursite.com/wp-json/cocart/preview/checkout' , {
method: 'PUT' ,
headers: {
'Cart-Key' : cartKey ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
billing_address: billingData ,
payment_method: 'stripe' ,
payment_method_data: {
payment_method: paymentMethod . id // Pass payment method reference
}
})
});
const result = await response . json ();
if ( ! response . ok ) {
throw new Error ( result . message || 'Checkout failed' );
}
// Handle successful order
if ( result . order_id ) {
console . log ( 'Order created:' , result . order_id );
// Clear cart
localStorage . removeItem ( 'cart_key' );
// Redirect to success page or show confirmation
if ( result . payment_result ?. redirect_url ) {
window . location . href = result . payment_result . redirect_url ;
} else {
showSuccessMessage ( `Order # ${ result . order_number } completed successfully!` );
}
}
} catch ( error ) {
console . error ( 'Checkout error:' , error );
document . getElementById ( 'card-errors' ). textContent = error . message ;
// Re-enable form
const submitButton = document . getElementById ( 'submit-button' );
submitButton . disabled = false ;
submitButton . textContent = 'Pay Now' ;
}
}
Complete Example
Here’s a complete working example that puts it all together:
async function handleCheckoutSubmission () {
const form = document . getElementById ( 'checkout-form' );
const submitButton = document . getElementById ( 'submit-button' );
const buttonText = document . getElementById ( 'button-text' );
const buttonSpinner = document . getElementById ( 'button-spinner' );
form . addEventListener ( 'submit' , async ( event ) => {
event . preventDefault ();
// Disable submit button
submitButton . disabled = true ;
buttonText . textContent = 'Processing...' ;
buttonSpinner . style . display = 'inline-block' ;
try {
// Get form data
const formData = new FormData ( form );
const billingAddress = {
first_name: formData . get ( 'billing_first_name' ),
last_name: formData . get ( 'billing_last_name' ),
email: formData . get ( 'billing_email' ),
phone: formData . get ( 'billing_phone' ),
address_1: formData . get ( 'billing_address_1' ),
city: formData . get ( 'billing_city' ),
state: formData . get ( 'billing_state' ),
postcode: formData . get ( 'billing_postcode' ),
country: formData . get ( 'billing_country' )
};
// Confirm payment with Stripe
const { error , paymentIntent } = await window . stripeInstance . confirmPayment ({
elements: window . stripeElements ,
redirect: 'if_required' ,
confirmParams: {
return_url: window . location . href ,
payment_method_data: {
billing_details: {
name: ` ${ billingAddress . first_name } ${ billingAddress . last_name } ` ,
email: billingAddress . email ,
phone: billingAddress . phone ,
address: {
line1: billingAddress . address_1 ,
city: billingAddress . city ,
state: billingAddress . state ,
postal_code: billingAddress . postcode ,
country: billingAddress . country
}
}
}
}
});
if ( error ) {
showStripeError ( error );
return ;
}
// Payment successful, process checkout
if ( paymentIntent . status === 'succeeded' ) {
await processCheckoutWithStripe ( billingAddress , paymentIntent );
} else {
throw new Error ( `Unexpected payment status: ${ paymentIntent . status } ` );
}
} catch ( error ) {
console . error ( 'Checkout error:' , error );
showError ( error . message || 'Checkout failed. Please try again.' );
} finally {
// Re-enable submit button
submitButton . disabled = false ;
buttonText . textContent = 'Complete Order' ;
buttonSpinner . style . display = 'none' ;
}
});
}
Step 5: Process Checkout with Payment Data
Submit the checkout with Stripe payment information:
async function processCheckoutWithStripe ( billingAddress , paymentIntent ) {
const cartKey = localStorage . getItem ( 'cart_key' );
const checkoutData = {
billing_address: billingAddress ,
payment_method: 'stripe' ,
payment_data: {
payment_intent_id: paymentIntent . id ,
payment_method_id: paymentIntent . payment_method ,
client_secret: window . paymentContext . client_secret ,
receipt_email: billingAddress . email
}
};
const response = await fetch ( 'https://yoursite.com/wp-json/cocart/preview/checkout' , {
method: 'PUT' ,
headers: {
'Cart-Key' : cartKey ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ( checkoutData )
});
const result = await response . json ();
if ( ! response . ok ) {
throw new Error ( result . message || `HTTP ${ response . status } ` );
}
// Handle successful checkout
if ( result . order_id ) {
showSuccessMessage ( `Order # ${ result . order_number } completed successfully!` );
// Clear cart
localStorage . removeItem ( 'cart_key' );
// Redirect to thank you page
if ( result . payment_result ?. redirect_url ) {
setTimeout (() => {
window . location . href = result . payment_result . redirect_url ;
}, 2000 );
}
}
return result ;
}
Complete Integration Example
Here’s a complete working example:
class StripeCheckout {
constructor () {
this . stripe = null ;
this . elements = null ;
this . context = null ;
}
async initialize () {
try {
// Setup Stripe Elements
await this . setupElements ();
// Handle form submission
this . handleFormSubmission ();
console . log ( 'Stripe checkout initialized successfully' );
} catch ( error ) {
console . error ( 'Stripe initialization error:' , error );
this . showError ( 'Payment system unavailable. Please try again later.' );
}
}
async setupElements () {
// Create payment context
this . context = await this . createPaymentContext ();
// Initialize Stripe
this . stripe = Stripe ( this . context . public_key );
// Create Elements
this . elements = this . stripe . elements ({
clientSecret: this . context . client_secret ,
appearance: {
theme: 'stripe' ,
variables: {
colorPrimary: '#0570de' ,
borderRadius: '8px'
}
}
});
// Create and mount payment element
const paymentElement = this . elements . create ( 'payment' );
paymentElement . mount ( '#stripe-payment-element' );
// Handle real-time validation errors
paymentElement . on ( 'change' , ({ error }) => {
const errorElement = document . getElementById ( 'stripe-error-message' );
if ( error ) {
errorElement . textContent = error . message ;
errorElement . style . display = 'block' ;
} else {
errorElement . style . display = 'none' ;
}
});
}
async createPaymentContext () {
const cartKey = localStorage . getItem ( 'cart_key' );
const response = await fetch ( '/wp-json/cocart/preview/checkout/payment-context' , {
method: 'POST' ,
headers: {
'Cart-Key' : cartKey ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({ payment_method: 'stripe' })
});
const context = await response . json ();
if ( ! response . ok ) {
throw new Error ( context . message || 'Failed to create payment context' );
}
return context ;
}
handleFormSubmission () {
const form = document . getElementById ( 'checkout-form' );
form . addEventListener ( 'submit' , async ( event ) => {
event . preventDefault ();
const submitButton = form . querySelector ( '[type="submit"]' );
const originalText = submitButton . textContent ;
try {
submitButton . disabled = true ;
submitButton . textContent = 'Processing...' ;
// Get billing address
const billingAddress = this . getBillingAddressFromForm ( form );
// Confirm payment
const { error , paymentIntent } = await this . stripe . confirmPayment ({
elements: this . elements ,
redirect: 'if_required' ,
confirmParams: {
return_url: window . location . href ,
payment_method_data: {
billing_details: this . formatBillingDetailsForStripe ( billingAddress )
}
}
});
if ( error ) {
this . showError ( `Payment failed: ${ error . message } ` );
return ;
}
if ( paymentIntent . status === 'succeeded' ) {
await this . processCheckout ( billingAddress , paymentIntent );
}
} catch ( error ) {
console . error ( 'Checkout error:' , error );
this . showError ( 'Checkout failed. Please try again.' );
} finally {
submitButton . disabled = false ;
submitButton . textContent = originalText ;
}
});
}
getBillingAddressFromForm ( form ) {
const formData = new FormData ( form );
return {
first_name: formData . get ( 'billing_first_name' ),
last_name: formData . get ( 'billing_last_name' ),
email: formData . get ( 'billing_email' ),
phone: formData . get ( 'billing_phone' ),
address_1: formData . get ( 'billing_address_1' ),
city: formData . get ( 'billing_city' ),
state: formData . get ( 'billing_state' ),
postcode: formData . get ( 'billing_postcode' ),
country: formData . get ( 'billing_country' )
};
}
formatBillingDetailsForStripe ( billingAddress ) {
return {
name: ` ${ billingAddress . first_name } ${ billingAddress . last_name } ` ,
email: billingAddress . email ,
phone: billingAddress . phone ,
address: {
line1: billingAddress . address_1 ,
city: billingAddress . city ,
state: billingAddress . state ,
postal_code: billingAddress . postcode ,
country: billingAddress . country
}
};
}
async processCheckout ( billingAddress , paymentIntent ) {
const cartKey = localStorage . getItem ( 'cart_key' );
const response = await fetch ( '/wp-json/cocart/preview/checkout' , {
method: 'PUT' ,
headers: {
'Cart-Key' : cartKey ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
billing_address: billingAddress ,
payment_method: 'stripe' ,
payment_data: {
payment_intent_id: paymentIntent . id ,
payment_method_id: paymentIntent . payment_method ,
client_secret: this . context . client_secret
}
})
});
const result = await response . json ();
if ( ! response . ok ) {
throw new Error ( result . message || `HTTP ${ response . status } ` );
}
// Handle success
this . handleCheckoutSuccess ( result );
return result ;
}
handleCheckoutSuccess ( result ) {
this . showSuccess ( `Order # ${ result . order_number } completed successfully!` );
// Clear cart
localStorage . removeItem ( 'cart_key' );
// Redirect after delay
if ( result . payment_result ?. redirect_url ) {
setTimeout (() => {
window . location . href = result . payment_result . redirect_url ;
}, 2000 );
}
}
showError ( message ) {
const errorElement = document . getElementById ( 'stripe-error-message' );
errorElement . textContent = message ;
errorElement . style . display = 'block' ;
errorElement . style . color = '#df1b41' ;
}
showSuccess ( message ) {
const errorElement = document . getElementById ( 'stripe-error-message' );
errorElement . textContent = message ;
errorElement . style . display = 'block' ;
errorElement . style . color = '#28a745' ;
}
}
// Initialize when DOM is loaded
document . addEventListener ( 'DOMContentLoaded' , async () => {
const checkout = new StripeCheckout ();
await checkout . initialize ();
});
Error Handling
Handle common Stripe and checkout errors:
function handleStripeError ( error ) {
let message = 'Payment failed. Please try again.' ;
switch ( error . code ) {
case 'card_declined' :
message = 'Your card was declined. Please try a different payment method.' ;
break ;
case 'insufficient_funds' :
message = 'Insufficient funds. Please try a different card.' ;
break ;
case 'expired_card' :
message = 'Your card has expired. Please try a different card.' ;
break ;
case 'incorrect_cvc' :
message = 'Your card \' s security code is incorrect.' ;
break ;
case 'processing_error' :
message = 'An error occurred while processing your card. Please try again.' ;
break ;
case 'rate_limit' :
message = 'Too many requests. Please wait a moment and try again.' ;
break ;
default :
message = error . message || message ;
}
document . getElementById ( 'stripe-error-message' ). textContent = message ;
document . getElementById ( 'stripe-error-message' ). style . display = 'block' ;
}
Testing
Use Stripe’s test card numbers for development:
Successful payment : 4242424242424242
Declined card : 4000000000000002
Insufficient funds : 4000000000009995
Expired card : 4000000000000069
Troubleshooting
Common Issues
'Stripe is not defined' Error
Problem : Stripe.js hasn’t loaded before your code executes.Solution : Ensure Stripe.js loads before initialization:function waitForStripe () {
return new Promise (( resolve ) => {
if ( typeof Stripe !== 'undefined' ) {
resolve ( Stripe );
return ;
}
const script = document . createElement ( 'script' );
script . src = 'https://js.stripe.com/v3/' ;
script . onload = () => resolve ( Stripe );
document . head . appendChild ( script );
});
}
// Usage
const Stripe = await waitForStripe ();
const stripe = Stripe ( 'pk_test_xxx' );
Payment Method Not Accepted
Problem : Backend doesn’t recognize the payment method.Solution : Verify your configuration:
Use 'stripe' as payment_method (not 'woocommerce_payments')
Ensure WooCommerce Stripe Gateway plugin is active
Check that Stripe is enabled in WooCommerce → Settings → Payments
Verify your Stripe account supports your currency
Problem : Stripe Elements don’t appear on the page.Solution : Check these common issues:
Verify mount selector matches HTML: cardElement.mount('#card-element')
Ensure the container exists in DOM before mounting
Check browser console for JavaScript errors
Verify no CSS is hiding the element (check display, visibility, height)
Try mounting with explicit styling:
const style = {
base: {
fontSize: '16px' ,
color: '#32325d'
}
};
const cardElement = elements . create ( 'card' , { style });
cardElement . mount ( '#card-element' );
Problem : “This API key cannot be used” or authentication errors.Solution :
Ensure frontend publishable key matches WooCommerce Stripe settings exactly
Check if you’re using test key (pk_test_) in test mode or live key (pk_live_) in live mode
Verify the key belongs to the correct Stripe account
Go to WooCommerce → Settings → Payments → Stripe to confirm your keys
Payment Intent Creation Fails
Problem : Error when creating payment intent via CoCart.Solution : Check your setup:// Ensure you're calling the correct endpoint
const response = await fetch ( '/wp-json/cocart/preview/checkout/payment-context' , {
method: 'POST' ,
headers: {
'Cart-Key' : cartKey ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
payment_method: 'stripe'
})
});
if ( ! response . ok ) {
const error = await response . json ();
console . error ( 'Payment context error:' , error );
// Check error.message for details
}
Verify your cart has items and a valid total
Ensure Stripe secret key is configured correctly in WooCommerce
Check PHP error logs for server-side issues
'Your card was declined' Errors
Problem : Legitimate cards are being declined.Solution : Check multiple factors:
Test Mode : Use official Stripe test cards (not real cards)
Live Mode : Common causes:
Insufficient funds
Card issuer blocking the transaction
International cards blocked by Stripe Radar rules
Incorrect CVV or expiration date
Address verification (AVS) failure
Enable detailed decline codes in your error handling: if ( error . code === 'card_declined' ) {
console . log ( 'Decline code:' , error . decline_code );
// Show appropriate message based on decline_code
}
3D Secure Authentication Not Working
Problem : 3DS authentication modal doesn’t appear or fails.Solution : Ensure proper 3DS implementation:const { error , paymentIntent } = await stripe . confirmCardPayment (
clientSecret ,
{
payment_method: {
card: cardElement ,
billing_details: {
name: billingName ,
email: billingEmail ,
address: billingAddress
}
}
}
);
if ( error ) {
// Handle 3DS authentication failure
if ( error . type === 'card_error' && error . code === 'authentication_required' ) {
showError ( 'Card authentication failed. Please try a different card.' );
}
}
Ensure popups are not blocked by browser
Test with 3DS test cards: 4000002500003155
Verify your return_url is configured correctly
Webhook Events Not Received
Problem : Not receiving payment confirmation webhooks.Solution : Configure webhooks properly:
Go to Stripe Dashboard → Developers → Webhooks
Add endpoint: https://yoursite.com/wc-api/wc_stripe/
Select events: payment_intent.succeeded, payment_intent.payment_failed
Test webhook delivery in Stripe Dashboard
Check webhook signing secret matches WooCommerce settings
Verify your server can receive HTTPS requests from Stripe IPs
Currency or Amount Errors
Problem : “Invalid currency” or “Invalid amount” errors.Solution : Check amount formatting:// Stripe expects amounts in cents (smallest currency unit)
// $10.00 USD = 1000 cents
const amount = Math . round ( cartTotal * 100 );
// Zero-decimal currencies (JPY, KRW) don't need multiplication
const isZeroDecimal = [ 'jpy' , 'krw' ]. includes ( currency . toLowerCase ());
const stripeAmount = isZeroDecimal ? Math . round ( cartTotal ) : Math . round ( cartTotal * 100 );
Verify your Stripe account supports the currency
Check minimum amount requirements per currency
Ensure no decimal values for zero-decimal currencies
Checkout Completes But Order Not Created
Problem : Payment succeeds but WooCommerce order is not created.Solution : Debug the checkout flow:try {
const response = await fetch ( '/wp-json/cocart/preview/checkout' , {
method: 'PUT' ,
headers: {
'Cart-Key' : cartKey ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ( checkoutData )
});
const result = await response . json ();
console . log ( 'Checkout response:' , result );
if ( ! response . ok ) {
console . error ( 'Checkout failed:' , result );
throw new Error ( result . message );
}
if ( ! result . order_id ) {
console . error ( 'No order ID in response:' , result );
throw new Error ( 'Order creation failed' );
}
} catch ( error ) {
console . error ( 'Checkout error:' , error );
}
Check PHP error logs on server
Verify WooCommerce stock levels
Check for plugin conflicts
Ensure customer data is valid
Debug Mode
Enable Stripe debug logging for detailed troubleshooting:
// Add to your initialization code
if ( process . env . NODE_ENV === 'development' ) {
// Enable detailed logging
console . log ( '[Stripe] Initializing with key:' , publishableKey );
// Log all Stripe events
cardElement . on ( 'change' , ( event ) => {
console . log ( '[Stripe] Card element changed:' , event );
});
// Log tokenization attempts
console . log ( '[Stripe] Creating payment method...' );
const { paymentMethod , error } = await stripe . createPaymentMethod ( ... );
console . log ( '[Stripe] Payment method result:' , { paymentMethod , error });
}
In WooCommerce:
Go to WooCommerce → Settings → Payments → Stripe
Enable “Log debug messages”
Check logs at WooCommerce → Status → Logs
Getting Help
If issues persist:
Check Stripe Dashboard : View recent payment attempts and error details
Review Logs : Check both browser console and server logs
Test Mode : Always test thoroughly in Stripe test mode first
Stripe Documentation : Visit Stripe’s troubleshooting guide
WooCommerce Support : Check WooCommerce Stripe Gateway plugin documentation
Best Practices
Security
Never expose secret keys client-side
Use HTTPS for all requests
Validate data server-side
Handle PCI compliance properly
User Experience
Show loading states during processing
Provide clear error messages
Enable real-time form validation
Support mobile-friendly interfaces
Error Handling
Handle network failures gracefully
Implement retry mechanisms
Log errors for debugging
Provide fallback options
Performance
Load Stripe.js asynchronously
Cache payment contexts when possible
Minimize API calls
Use request timeouts
Always test your Stripe integration thoroughly using Stripe’s test mode before going live. Ensure your webhook endpoints are properly configured to handle payment updates.