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 WooPay (WooCommerce Payments) with CoCart Preview API. Requires CoCart v4.6+ and WooCommerce Payments configured with WooPay enabled.

Overview

WooPay is the express checkout feature of WooCommerce Payments, which is built on Stripe’s infrastructure. This means you can use the Stripe SDK directly with your WooCommerce Payments account for headless checkout implementations.
This tutorial is specifically for WooCommerce Payments users. If you’re using the standalone WooCommerce Stripe Gateway plugin, see the Stripe tutorial instead. Both use Stripe’s infrastructure, but require different payment method IDs.Key Differences:
  • WooCommerce Payments: Available in 39 countries, managed by WooCommerce/Automattic, includes WooPay express checkout. (payment_method: 'woocommerce_payments')
  • Stripe Gateway: Available in 45+ countries, direct Stripe integration, full control over Stripe account. (payment_method: 'stripe')
View supported countries

Why This Works

WooCommerce Payments operates on Stripe’s payment processing infrastructure:
  • Your WooCommerce Payments account is a Stripe Connect account
  • You can use Stripe.js v3 for client-side payment processing
  • All Stripe SDK features are available to you
  • Your publishable key starts with pk_test_ or pk_live_

Prerequisites

Before implementing WooPay checkout, ensure you have:
  1. Supported country - Your business must be registered in one of the supported countries
  2. WooCommerce Payments plugin installed and configured
  3. WooPay enabled in WooCommerce Payments settings
  4. A valid cart with items added via CoCart API
  5. Customer billing address information
  6. Stripe.js v3 loaded in your frontend

Integration Flow

1

Get Your Stripe Credentials

Extract your Stripe publishable key from WooCommerce Payments
2

Initialize Stripe SDK

Set up Stripe.js with your WooCommerce Payments credentials
3

Create Payment Elements

Build the payment form using Stripe Elements
4

Collect Payment Details

Let customers securely enter payment information
5

Complete Checkout

Submit checkout with payment data to CoCart

Step 1: Get Your Stripe Credentials

Since WooCommerce Payments runs on Stripe, you need to extract your Stripe publishable key. Choose one of these methods: Add this temporary code to your functions.php or custom plugin:
/**
 * Display WooCommerce Payments Stripe Keys
 * For development purposes only - remove after use
 */
add_action('admin_notices', function() {
    if (!current_user_can('manage_woocommerce')) {
        return;
    }

    if (!class_exists('WC_Payments')) {
        return;
    }

    $account_service = WC_Payments::get_account_service();
    if (!$account_service) {
        return;
    }

    $is_test_mode = WC_Payments::mode()->is_test();
    $publishable_key = $account_service->get_publishable_key($is_test_mode);
    $account_id = $account_service->get_stripe_account_id();

    if ($publishable_key) {
        echo '<div class="notice notice-info is-dismissible">';
        echo '<h3>WooCommerce Payments - Stripe Credentials</h3>';
        echo '<p><strong>Mode:</strong> ' . ($is_test_mode ? 'Test Mode' : 'Live Mode') . '</p>';
        echo '<p><strong>Publishable Key:</strong> <code style="user-select: all;">' . esc_html($publishable_key) . '</code></p>';
        echo '<p><strong>Stripe Account ID:</strong> <code style="user-select: all;">' . esc_html($account_id) . '</code></p>';
        echo '<p><em>⚠️ Remove this code snippet after copying your keys!</em></p>';
        echo '</div>';
    }
});
Steps:
  1. Add the code above to your site temporarily
  2. Navigate to WordPress Admin Dashboard
  3. Look for the “WooCommerce Payments - Stripe Credentials” notice
  4. Copy your Publishable Key (starts with pk_test_ or pk_live_)
  5. Important: Remove the helper code after copying

Method B: Browser Console (Simpler)

  1. Go to your WooCommerce checkout page
  2. Press F12 to open Developer Tools
  3. Go to the Console tab
  4. Run this command:
// Copy publishable key to clipboard
copy(wcpay_upe_config.publishableKey);
console.log('✓ Publishable key copied to clipboard!');
console.log('Account ID:', wcpay_upe_config.accountId);
console.log('Test Mode:', wcpay_upe_config.testMode);
Security Reminders:
  • ✅ Publishable keys are safe to expose client-side (that’s their purpose)
  • ⚠️ NEVER expose secret/private keys
  • 🔒 Always use test mode keys during development (pk_test_)
  • 🗑️ Remove helper code after obtaining keys

Step 2: Load Stripe SDK

Add Stripe.js to your frontend application:
<!-- Load Stripe.js v3 -->
<script src="https://js.stripe.com/v3/"></script>
Or via npm/yarn if using a build system:
npm install @stripe/stripe-js
# or
yarn add @stripe/stripe-js
import { loadStripe } from '@stripe/stripe-js';

Step 3: Initialize Stripe Elements

Set up Stripe with your WooCommerce Payments publishable key:
  • Vanilla JavaScript
  • React
  • HTML Form
// Initialize Stripe with WooCommerce Payments key
const stripe = Stripe('pk_test_XXXXXXXXXXXXX'); // Your WCPay publishable key
const elements = stripe.elements();

// Create card element with custom styling
const cardElement = elements.create('card', {
    style: {
        base: {
            fontSize: '16px',
            color: '#32325d',
            fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
            '::placeholder': {
                color: '#aab7c4',
            },
        },
        invalid: {
            color: '#fa755a',
            iconColor: '#fa755a'
        },
    },
    hidePostalCode: false,
});

// Mount the card element
cardElement.mount('#card-element');

// Handle real-time validation
cardElement.on('change', ({error}) => {
    const displayError = document.getElementById('card-errors');
    if (error) {
        displayError.textContent = error.message;
        displayError.style.display = 'block';
    } else {
        displayError.textContent = '';
        displayError.style.display = 'none';
    }
});

Step 4: 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 {
        // Collect billing data 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
        };

        // Create payment method with Stripe
        const {paymentMethod, error} = await stripe.createPaymentMethod({
            type: 'card',
            card: cardElement,
            billing_details: {
                name: `${billingData.first_name} ${billingData.last_name}`,
                email: billingData.email,
                phone: billingData.phone,
                address: {
                    line1: billingData.address_1,
                    city: billingData.city,
                    state: billingData.state,
                    postal_code: billingData.postcode,
                    country: billingData.country,
                }
            },
        });

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

        console.log('Payment method created:', paymentMethod.id);

        // Proceed to checkout
        await processCheckout(billingData, paymentMethod);

    } catch (error) {
        console.error('Payment failed:', error);
        document.getElementById('card-errors').textContent = error.message;

        // Re-enable submit button
        submitButton.disabled = false;
        submitButton.textContent = 'Complete Order';
    }
}

// Attach to form
document.getElementById('payment-form').addEventListener('submit', handlePaymentSubmission);

Step 5: Complete Checkout

Process the checkout with CoCart using the payment method:
async function processCheckout(billingAddress, paymentMethod) {
    const cartKey = localStorage.getItem('cart_key');

    try {
        const response = await fetch('https://your-store.com/wp-json/cocart/preview/checkout', {
            method: 'PUT',
            headers: {
                'Cart-Key': cartKey,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                billing_address: billingAddress,
                payment_method: 'woocommerce_payments', // Important: use WooCommerce Payments ID
                payment_method_data: {
                    payment_method: paymentMethod.id // Stripe payment method ID
                }
            })
        });

        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 successfully:', result.order_id);

            // Clear cart
            localStorage.removeItem('cart_key');

            // Show success message
            showSuccessMessage(`Order #${result.order_number} completed!`);

            // Redirect to thank you page
            if (result.payment_result?.redirect_url) {
                setTimeout(() => {
                    window.location.href = result.payment_result.redirect_url;
                }, 2000);
            }
        }

    } catch (error) {
        console.error('Checkout error:', error);
        throw error;
    }
}

function showSuccessMessage(message) {
    const errorElement = document.getElementById('card-errors');
    errorElement.textContent = message;
    errorElement.style.color = '#28a745';
    errorElement.style.display = 'block';
}

Complete Integration Example

Here’s a complete, production-ready implementation:
class WooPayCheckout {
    constructor(publishableKey) {
        this.stripe = null;
        this.cardElement = null;
        this.publishableKey = publishableKey;
    }

    async initialize() {
        try {
            // Initialize Stripe
            this.stripe = Stripe(this.publishableKey);

            // Create Elements
            const elements = this.stripe.elements();

            // Create and mount card element
            this.cardElement = elements.create('card', {
                style: {
                    base: {
                        fontSize: '16px',
                        color: '#32325d',
                        fontFamily: 'system-ui, sans-serif',
                        '::placeholder': {
                            color: '#aab7c4',
                        },
                    },
                    invalid: {
                        color: '#fa755a',
                        iconColor: '#fa755a',
                    },
                },
                hidePostalCode: false,
            });

            this.cardElement.mount('#card-element');

            // Setup validation
            this.setupValidation();

            // Setup form submission
            this.setupFormSubmission();

            console.log('WooPay checkout initialized');
        } catch (error) {
            console.error('WooPay initialization error:', error);
            this.showError('Payment system unavailable. Please refresh and try again.');
        }
    }

    setupValidation() {
        this.cardElement.on('change', ({error}) => {
            const errorElement = document.getElementById('card-errors');
            if (error) {
                errorElement.textContent = error.message;
                errorElement.style.display = 'block';
            } else {
                errorElement.textContent = '';
                errorElement.style.display = 'none';
            }
        });
    }

    setupFormSubmission() {
        const form = document.getElementById('payment-form');

        form.addEventListener('submit', async (event) => {
            event.preventDefault();
            await this.handleSubmit(form);
        });
    }

    async handleSubmit(form) {
        const submitButton = form.querySelector('#submit-button');
        const originalText = submitButton.textContent;

        try {
            // Disable submit button
            submitButton.disabled = true;
            submitButton.textContent = 'Processing Payment...';

            // Get billing address
            const billingAddress = this.getBillingAddressFromForm(form);

            // Create payment method
            const {paymentMethod, error} = await this.stripe.createPaymentMethod({
                type: 'card',
                card: this.cardElement,
                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) {
                throw new Error(error.message);
            }

            // Process checkout
            await this.processCheckout(billingAddress, paymentMethod);

        } catch (error) {
            console.error('Payment error:', error);
            this.showError(error.message || 'Payment 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')
        };
    }

    async processCheckout(billingAddress, paymentMethod) {
        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: 'woocommerce_payments',
                payment_method_data: {
                    payment_method: paymentMethod.id
                }
            })
        });

        const result = await response.json();

        if (!response.ok) {
            throw new Error(result.message || `Checkout failed (${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 to thank you page
        if (result.payment_result?.redirect_url) {
            setTimeout(() => {
                window.location.href = result.payment_result.redirect_url;
            }, 2000);
        }
    }

    showError(message) {
        const errorElement = document.getElementById('card-errors');
        errorElement.textContent = message;
        errorElement.style.color = '#fa755a';
        errorElement.style.display = 'block';
    }

    showSuccess(message) {
        const errorElement = document.getElementById('card-errors');
        errorElement.textContent = message;
        errorElement.style.color = '#28a745';
        errorElement.style.display = 'block';
    }
}

// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', async () => {
    // Replace with your actual WooCommerce Payments publishable key
    const publishableKey = 'pk_test_XXXXXXXXXXXXX';

    const checkout = new WooPayCheckout(publishableKey);
    await checkout.initialize();
});

Error Handling

Handle common WooPay/Stripe errors gracefully:
function handlePaymentError(error) {
    let userMessage = 'Payment failed. Please try again.';

    // Map error codes to user-friendly messages
    const errorMessages = {
        'card_declined': 'Your card was declined. Please try a different payment method.',
        'insufficient_funds': 'Insufficient funds. Please use a different card.',
        'expired_card': 'Your card has expired. Please use a different card.',
        'incorrect_cvc': 'The security code is incorrect. Please check and try again.',
        'incorrect_number': 'The card number is incorrect. Please check and try again.',
        'processing_error': 'An error occurred while processing your card. Please try again.',
        'rate_limit': 'Too many requests. Please wait a moment and try again.',
    };

    if (error.code && errorMessages[error.code]) {
        userMessage = errorMessages[error.code];
    } else if (error.message) {
        userMessage = error.message;
    }

    // Display error to user
    const errorElement = document.getElementById('card-errors');
    errorElement.textContent = userMessage;
    errorElement.style.display = 'block';

    // Log for debugging
    console.error('Payment error details:', {
        code: error.code,
        message: error.message,
        type: error.type
    });
}

Testing

Test Card Numbers

Use Stripe’s test cards in test mode:
Card NumberScenario
4242424242424242Successful payment
4000000000000002Card declined
4000000000009995Insufficient funds
4000000000000069Expired card
4000000000000127Incorrect CVC

Test Details

  • CVC: Any 3-digit number (4 digits for Amex)
  • Expiry: Any future date
  • Postal Code: Any valid postal code

Enable Test Mode

Ensure WooCommerce Payments is in test mode:
  1. Go to WooCommerce → Settings → Payments
  2. Click WooCommerce Payments
  3. Check “Enable test mode”
  4. Save changes

WooPay Express Checkout

WooPay also offers express checkout features. To check if WooPay express is enabled:
// Available on checkout pages
if (wcpay_upe_config.isWooPayExpressCheckoutEnabled) {
    console.log('WooPay Express is enabled');
    console.log('WooPay Host:', wcpay_upe_config.woopayHost);
}
WooPay Express Checkout integration is beyond the scope of this tutorial. This guide focuses on standard credit card processing using WooCommerce Payments’ Stripe infrastructure.

Best Practices

Security

  • Never expose secret keys client-side
  • Always use HTTPS in production
  • Validate billing data before submission
  • Use test mode for development
  • Remove helper code from production

User Experience

  • Show loading states during processing
  • Provide clear, specific error messages
  • Enable real-time card validation
  • Support mobile-friendly inputs
  • Display accepted card types

Error Handling

  • Handle network failures gracefully
  • Log errors for debugging
  • Provide retry mechanisms
  • Never expose technical details to users
  • Test all error scenarios

Performance

  • Load Stripe.js asynchronously
  • Minimize unnecessary API calls
  • Implement request timeouts
  • Cache publishable keys appropriately
  • Monitor payment processing times

Troubleshooting

Common Issues

Problem: Cannot install or activate WooCommerce Payments in your country.Solution: WooCommerce Payments is available in 39 countries. If your country is not on this list, use WooCommerce Stripe Gateway instead.WooCommerce Payments Supported Countries (39): Australia, Austria, Belgium, Bulgaria, Canada, Croatia, Cyprus, Czech Republic, Denmark, Estonia, Finland, France, Germany, Greece, Hong Kong, Hungary, Ireland, Italy, Japan, Latvia, Lithuania, Luxembourg, Malta, Netherlands, New Zealand, Norway, Poland, Portugal, Romania, Singapore, Slovakia, Slovenia, Spain, Sweden, Switzerland, UAE, United Kingdom, United StatesIf your country is NOT listed above:Option 1: Use WooCommerce Stripe Gateway (Recommended)
  1. Uninstall WooCommerce Payments (if installed)
  2. Install WooCommerce Stripe Gateway plugin
  3. Create a direct Stripe account at stripe.com
  4. Follow the Stripe tutorial instead
  5. Use payment_method: 'stripe' in your checkout code
Why Stripe Gateway for unsupported countries?
  • Available in 45+ countries (more than WooCommerce Payments)
  • Includes additional countries like Brazil, India, Indonesia, Malaysia, Mexico, Thailand
  • Direct control over your Stripe account
  • Same Stripe infrastructure and security
  • Full API access for headless commerce
Important Notes:
Problem: Stripe.js hasn’t loaded before initialization.Solution: Wait for the script to load:
function loadStripe() {
    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 loadStripe();
const stripe = Stripe('pk_test_xxx');
Problem: Backend doesn’t recognize payment method.Solution: Ensure you’re using the correct payment method ID:
  • Use 'woocommerce_payments' (not 'stripe')
  • Verify WooCommerce Payments is enabled and active
  • Check that WooCommerce Payments is configured in test/live mode appropriately
  • Important: If your country is not in the 39 supported countries, WooCommerce Payments won’t work - use Stripe Gateway instead
Problem: Card input field doesn’t appear.Solution: Verify the following:
  • Element ID matches: cardElement.mount('#card-element')
  • Container exists in DOM before mount
  • No CSS conflicts hiding the element
  • Check browser console for errors
Problem: Can’t retrieve WooCommerce Payments credentials.Solution:
  • Ensure WooCommerce Payments is fully set up and connected
  • Verify you’re on a checkout page when using browser console method
  • Check that WooCommerce Payments is activated (not just installed)
  • Try the admin notice helper method instead

Additional Resources

Always thoroughly test your WooPay integration in test mode before enabling live payments. Verify that webhooks are configured and orders are being created correctly in WooCommerce.
Remember: WooCommerce Payments uses Stripe’s infrastructure, so all Stripe documentation and best practices apply to your implementation. The key difference is using payment_method: 'woocommerce_payments' when submitting to CoCart’s Checkout API.