Skip to main content
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:
  1. Stripe account - Create a direct account at stripe.com (must be in a supported country)
  2. WooCommerce Stripe Gateway plugin installed and configured
  3. Stripe JavaScript SDK loaded in your frontend
  4. A valid cart with items added via CoCart API
  5. Customer billing address information

Integration Flow

1

Initialize Stripe Elements

Set up Stripe SDK with your store’s publishable key and create payment form
2

Collect Payment Details

Let customers securely enter their payment information using Stripe Elements
3

Create Payment Method

Generate a payment method using Stripe’s client-side API
4

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:
  • JavaScript
  • HTML
// 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

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');
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
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
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
}
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
Problem: Not receiving payment confirmation webhooks.Solution: Configure webhooks properly:
  1. Go to Stripe Dashboard → Developers → Webhooks
  2. Add endpoint: https://yoursite.com/wc-api/wc_stripe/
  3. Select events: payment_intent.succeeded, payment_intent.payment_failed
  4. Test webhook delivery in Stripe Dashboard
  5. Check webhook signing secret matches WooCommerce settings
  6. Verify your server can receive HTTPS requests from Stripe IPs
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
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:
  1. Go to WooCommerce → Settings → Payments → Stripe
  2. Enable “Log debug messages”
  3. Check logs at WooCommerce → Status → Logs

Getting Help

If issues persist:
  1. Check Stripe Dashboard: View recent payment attempts and error details
  2. Review Logs: Check both browser console and server logs
  3. Test Mode: Always test thoroughly in Stripe test mode first
  4. Stripe Documentation: Visit Stripe’s troubleshooting guide
  5. 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.