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 PayPal with CoCart Preview API. Requires CoCart v4.6+ and a configured PayPal payment gateway.

Overview

PayPal integration with CoCart uses PayPal’s JavaScript SDK to create a seamless checkout experience. Customers can pay with their PayPal account, credit cards, or other payment methods supported by PayPal.

Prerequisites

Before implementing PayPal checkout, ensure you have:
  1. PayPal payment gateway configured in WooCommerce
  2. PayPal JavaScript SDK loaded in your frontend
  3. A valid cart with items added
  4. Customer billing address information

Integration Flow

1

Initialize PayPal Buttons

Set up PayPal SDK with your store’s client ID and cart information
2

Handle Payment Creation

Create PayPal order when customer clicks Pay with PayPal
3

Handle Payment Approval

Process the PayPal payment approval and capture transaction details
4

Complete Checkout

Submit checkout with PayPal order ID to CoCart for order creation

Step 1: Load PayPal SDK

Include the PayPal JavaScript SDK in your page:
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_PAYPAL_CLIENT_ID&currency=USD&components=buttons"></script>
Replace YOUR_PAYPAL_CLIENT_ID with your actual PayPal client ID from your WooCommerce PayPal settings.

Step 2: Get Cart Information

Get the cart total and currency for PayPal order creation:
  • JavaScript
  • PHP
async function getCartTotal() {
    const cartKey = localStorage.getItem('cart_key');

    const response = await fetch('https://yoursite.com/wp-json/cocart/preview/checkout', {
        method: 'GET',
        headers: {
            'Cart-Key': cartKey,
            'Content-Type': 'application/json',
        }
    });

    const cartData = await response.json();

    if (!response.ok) {
        throw new Error(cartData.message || 'Failed to get cart data');
    }

    return {
        total: cartData.cart_totals.total,
        currency: cartData.currency || 'USD'
    };
}
Important: Your PayPal client ID in the JavaScript SDK must exactly match the one configured in your WooCommerce PayPal settings.

Step 3: HTML Structure

Your checkout form should include containers for PayPal buttons and customer information:
<form id="checkout-form">
    <!-- Customer Information -->
    <div class="billing-section">
        <h3>Billing Information</h3>
        <input type="text" name="billing_first_name" placeholder="First Name" required>
        <input type="text" name="billing_last_name" placeholder="Last Name" required>
        <input type="email" name="billing_email" placeholder="Email" required>
        <input type="tel" name="billing_phone" placeholder="Phone">
        <input type="text" name="billing_address_1" placeholder="Address" required>
        <input type="text" name="billing_city" placeholder="City" required>
        <input type="text" name="billing_state" placeholder="State" required>
        <input type="text" name="billing_postcode" placeholder="ZIP Code" required>
        <select name="billing_country" required>
            <option value="US">United States</option>
            <!-- Add other countries -->
        </select>
    </div>

    <!-- Shipping Information (if different) -->
    <div class="shipping-section" id="shipping-section" style="display: none;">
        <h3>Shipping Information</h3>
        <input type="text" name="shipping_first_name" placeholder="First Name">
        <input type="text" name="shipping_last_name" placeholder="Last Name">
        <!-- Add other shipping fields -->
    </div>

    <label>
        <input type="checkbox" id="ship-to-different-address"> Ship to a different address
    </label>

    <!-- PayPal Button Container -->
    <div class="payment-section">
        <h3>Payment</h3>
        <div id="paypal-button-container">
            <!-- PayPal buttons will be rendered here -->
        </div>
        <div id="paypal-error-message" class="error-message" style="display: none;"></div>
    </div>
</form>

Step 4: Initialize PayPal Buttons

Set up PayPal buttons with your cart information:
async function setupPayPalButtons() {
    try {
        // Get cart total for PayPal order
        const cartInfo = await getCartTotal();

        // Get billing address from form
        const getBillingAddress = () => {
            const form = document.getElementById('checkout-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')
            };
        };

        // Get shipping address if different
        const getShippingAddress = () => {
            const shipToDifferent = document.getElementById('ship-to-different-address').checked;
            if (!shipToDifferent) {
                return getBillingAddress();
            }

            const form = document.getElementById('checkout-form');
            const formData = new FormData(form);

            return {
                first_name: formData.get('shipping_first_name'),
                last_name: formData.get('shipping_last_name'),
                address_1: formData.get('shipping_address_1'),
                city: formData.get('shipping_city'),
                state: formData.get('shipping_state'),
                postcode: formData.get('shipping_postcode'),
                country: formData.get('shipping_country')
            };
        };

        // Initialize PayPal buttons
        paypal.Buttons({
            // Style the buttons
            style: {
                shape: 'rect',
                color: 'gold',
                layout: 'vertical',
                label: 'paypal',
                height: 50
            },

            // Create the PayPal order
            createOrder: function(data, actions) {
                const billingAddress = getBillingAddress();

                // Validate required fields
                if (!billingAddress.first_name || !billingAddress.email) {
                    showError('Please fill in all required billing information.');
                    return Promise.reject(new Error('Missing required fields'));
                }

                return actions.order.create({
                    purchase_units: [{
                        amount: {
                            value: cartInfo.total,
                            currency_code: cartInfo.currency
                        },
                        shipping: {
                            name: {
                                full_name: `${billingAddress.first_name} ${billingAddress.last_name}`
                            },
                            address: {
                                address_line_1: billingAddress.address_1,
                                admin_area_2: billingAddress.city,
                                admin_area_1: billingAddress.state,
                                postal_code: billingAddress.postcode,
                                country_code: billingAddress.country
                            }
                        }
                    }],
                    payer: {
                        name: {
                            given_name: billingAddress.first_name,
                            surname: billingAddress.last_name
                        },
                        email_address: billingAddress.email,
                        phone: {
                            phone_number: {
                                national_number: billingAddress.phone
                            }
                        },
                        address: {
                            address_line_1: billingAddress.address_1,
                            admin_area_2: billingAddress.city,
                            admin_area_1: billingAddress.state,
                            postal_code: billingAddress.postcode,
                            country_code: billingAddress.country
                        }
                    }
                });
            },

            // Handle successful payment
            onApprove: async function(data, actions) {
                try {
                    // Show loading state
                    showLoading('Processing payment...');

                    // Capture the PayPal payment
                    const order = await actions.order.capture();

                    // Process checkout with PayPal data
                    await processPayPalCheckout(
                        getBillingAddress(),
                        getShippingAddress(),
                        order,
                        data
                    );

                } catch (error) {
                    console.error('PayPal approval error:', error);
                    showError('Payment processing failed. Please try again.');
                }
            },

            // Handle payment cancellation
            onCancel: function(data) {
                console.log('PayPal payment cancelled:', data);
                showError('Payment was cancelled. Please try again.');
            },

            // Handle payment errors
            onError: function(error) {
                console.error('PayPal error:', error);
                showError('Payment failed. Please try again or use a different payment method.');
            }

        }).render('#paypal-button-container');

        console.log('PayPal buttons initialized successfully');

    } catch (error) {
        console.error('PayPal setup error:', error);
        showError('Payment system unavailable. Please try again later.');
    }
}

Step 5: Process Checkout with PayPal Data

Submit the checkout with PayPal transaction information:
async function processPayPalCheckout(billingAddress, shippingAddress, paypalOrder, paypalData) {
    const cartKey = localStorage.getItem('cart_key');

    // Extract transaction details
    const capture = paypalOrder.purchase_units[0].payments.captures[0];

    const checkoutData = {
        billing_address: billingAddress,
        shipping_address: shippingAddress,
        payment_method: 'paypal',
        payment_method_data: {
            paypal_order_id: paypalOrder.id,
            paypal_payer_id: paypalData.payerID,
            transaction_id: capture.id,
            payer_email: paypalOrder.payer.email_address,
            payment_status: paypalOrder.status,
            gross_amount: capture.amount.value,
            currency: capture.amount.currency_code,
            payment_fee: capture.seller_receivable_breakdown?.paypal_fee?.value || '0.00'
        }
    };

    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) {
        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);
        }
    }

    return result;
}

Complete Integration Example

Here’s a complete working example with form validation:
class PayPalCheckout {
    constructor() {
        this.context = null;
        this.formValid = false;
    }

    async initialize() {
        try {
            // Setup form validation
            this.setupFormValidation();

            // Create payment context
            this.context = await this.createPaymentContext();

            // Initialize PayPal buttons
            await this.setupPayPalButtons();

            // Setup shipping toggle
            this.setupShippingToggle();

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

    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: 'paypal' })
        });

        const context = await response.json();

        if (!response.ok) {
            throw new Error(context.message || 'Failed to create payment context');
        }

        return context;
    }

    setupFormValidation() {
        const form = document.getElementById('checkout-form');
        const requiredFields = form.querySelectorAll('input[required]');

        const validateForm = () => {
            let isValid = true;

            requiredFields.forEach(field => {
                if (!field.value.trim()) {
                    isValid = false;
                    field.classList.add('error');
                } else {
                    field.classList.remove('error');
                }
            });

            // Email validation
            const emailField = form.querySelector('input[type="email"]');
            if (emailField.value && !this.isValidEmail(emailField.value)) {
                isValid = false;
                emailField.classList.add('error');
            }

            this.formValid = isValid;
            this.updatePayPalButtonsState();

            return isValid;
        };

        // Add event listeners
        requiredFields.forEach(field => {
            field.addEventListener('blur', validateForm);
            field.addEventListener('input', validateForm);
        });

        // Initial validation
        validateForm();
    }

    updatePayPalButtonsState() {
        const container = document.getElementById('paypal-button-container');
        if (this.formValid) {
            container.style.opacity = '1';
            container.style.pointerEvents = 'auto';
        } else {
            container.style.opacity = '0.5';
            container.style.pointerEvents = 'none';
        }
    }

    setupShippingToggle() {
        const checkbox = document.getElementById('ship-to-different-address');
        const shippingSection = document.getElementById('shipping-section');

        checkbox.addEventListener('change', () => {
            if (checkbox.checked) {
                shippingSection.style.display = 'block';
                // Make shipping fields required
                shippingSection.querySelectorAll('input').forEach(input => {
                    input.required = true;
                });
            } else {
                shippingSection.style.display = 'none';
                // Remove required attribute
                shippingSection.querySelectorAll('input').forEach(input => {
                    input.required = false;
                });
            }
        });
    }

    async setupPayPalButtons() {
        return new Promise((resolve, reject) => {
            paypal.Buttons({
                style: {
                    shape: 'rect',
                    color: 'gold',
                    layout: 'vertical',
                    label: 'paypal',
                    height: 50
                },

                createOrder: (data, actions) => {
                    if (!this.formValid) {
                        this.showError('Please fill in all required fields before proceeding.');
                        return Promise.reject(new Error('Form validation failed'));
                    }

                    const billingAddress = this.getBillingAddress();

                    return actions.order.create({
                        purchase_units: [{
                            amount: {
                                value: this.context.cart_total,
                                currency_code: this.context.currency
                            },
                            shipping: this.createShippingInfo(billingAddress)
                        }],
                        payer: this.createPayerInfo(billingAddress)
                    });
                },

                onApprove: async (data, actions) => {
                    try {
                        this.showLoading('Processing payment...');

                        const order = await actions.order.capture();

                        await this.processCheckout(
                            this.getBillingAddress(),
                            this.getShippingAddress(),
                            order,
                            data
                        );

                    } catch (error) {
                        console.error('PayPal approval error:', error);
                        this.showError('Payment processing failed. Please try again.');
                    }
                },

                onCancel: (data) => {
                    console.log('PayPal payment cancelled:', data);
                    this.showError('Payment was cancelled. You can try again anytime.');
                },

                onError: (error) => {
                    console.error('PayPal error:', error);
                    this.showError('Payment failed. Please try again or contact support.');
                }

            }).render('#paypal-button-container').then(() => {
                resolve();
            }).catch(reject);
        });
    }

    getBillingAddress() {
        const form = document.getElementById('checkout-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')
        };
    }

    getShippingAddress() {
        const shipToDifferent = document.getElementById('ship-to-different-address').checked;
        if (!shipToDifferent) {
            return this.getBillingAddress();
        }

        const form = document.getElementById('checkout-form');
        const formData = new FormData(form);

        return {
            first_name: formData.get('shipping_first_name'),
            last_name: formData.get('shipping_last_name'),
            address_1: formData.get('shipping_address_1'),
            city: formData.get('shipping_city'),
            state: formData.get('shipping_state'),
            postcode: formData.get('shipping_postcode'),
            country: formData.get('shipping_country')
        };
    }

    createShippingInfo(address) {
        return {
            name: {
                full_name: `${address.first_name} ${address.last_name}`
            },
            address: {
                address_line_1: address.address_1,
                admin_area_2: address.city,
                admin_area_1: address.state,
                postal_code: address.postcode,
                country_code: address.country
            }
        };
    }

    createPayerInfo(address) {
        return {
            name: {
                given_name: address.first_name,
                surname: address.last_name
            },
            email_address: address.email,
            phone: address.phone ? {
                phone_number: {
                    national_number: address.phone
                }
            } : undefined,
            address: {
                address_line_1: address.address_1,
                admin_area_2: address.city,
                admin_area_1: address.state,
                postal_code: address.postcode,
                country_code: address.country
            }
        };
    }

    async processCheckout(billingAddress, shippingAddress, paypalOrder, paypalData) {
        const cartKey = localStorage.getItem('cart_key');
        const capture = paypalOrder.purchase_units[0].payments.captures[0];

        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,
                shipping_address: shippingAddress,
                payment_method: 'paypal',
                payment_data: {
                    paypal_order_id: paypalOrder.id,
                    paypal_payer_id: paypalData.payerID,
                    transaction_id: capture.id,
                    payer_email: paypalOrder.payer.email_address,
                    payment_status: paypalOrder.status,
                    gross_amount: capture.amount.value,
                    currency: capture.amount.currency_code
                }
            })
        });

        const result = await response.json();

        if (!response.ok) {
            throw new Error(result.message || `HTTP ${response.status}`);
        }

        this.handleCheckoutSuccess(result);
        return result;
    }

    handleCheckoutSuccess(result) {
        this.showSuccess(`Order #${result.order_number} completed successfully!`);
        localStorage.removeItem('cart_key');

        if (result.payment_result?.redirect_url) {
            setTimeout(() => {
                window.location.href = result.payment_result.redirect_url;
            }, 2000);
        }
    }

    isValidEmail(email) {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(email);
    }

    showError(message) {
        const errorElement = document.getElementById('paypal-error-message');
        errorElement.textContent = message;
        errorElement.style.display = 'block';
        errorElement.className = 'error-message';
    }

    showSuccess(message) {
        const errorElement = document.getElementById('paypal-error-message');
        errorElement.textContent = message;
        errorElement.style.display = 'block';
        errorElement.className = 'success-message';
    }

    showLoading(message) {
        const errorElement = document.getElementById('paypal-error-message');
        errorElement.textContent = message;
        errorElement.style.display = 'block';
        errorElement.className = 'loading-message';
    }
}

// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', async () => {
    const checkout = new PayPalCheckout();
    await checkout.initialize();
});

Error Handling

Handle common PayPal errors:
function handlePayPalError(error) {
    let message = 'Payment failed. Please try again.';

    // PayPal specific error codes
    if (error.details) {
        const detail = error.details[0];

        switch (detail.issue) {
            case 'INSTRUMENT_DECLINED':
                message = 'Your payment method was declined. Please try a different payment method.';
                break;
            case 'PAYER_ACCOUNT_RESTRICTED':
                message = 'Your PayPal account has restrictions. Please contact PayPal support.';
                break;
            case 'PAYER_CANNOT_PAY':
                message = 'Unable to process payment with this PayPal account. Please try a different payment method.';
                break;
            case 'PAYEE_ACCOUNT_RESTRICTED':
                message = 'Unable to process payment at this time. Please try again later.';
                break;
            default:
                message = detail.description || message;
        }
    }

    document.getElementById('paypal-error-message').textContent = message;
    document.getElementById('paypal-error-message').style.display = 'block';
}

Testing

For development and testing:
  1. Use PayPal Sandbox accounts
  2. Test with different PayPal account types (personal, business)
  3. Test payment cancellations
  4. Test network failures during payment

Best Practices

User Experience

  • Validate forms before enabling PayPal buttons
  • Show clear loading states during processing
  • Handle payment cancellations gracefully
  • Provide clear error messages

Security

  • Use HTTPS for all requests
  • Validate data server-side
  • Never expose sensitive keys client-side
  • Handle webhook validations properly

Performance

  • Load PayPal SDK asynchronously
  • Cache payment contexts when possible
  • Minimize API calls
  • Handle timeouts appropriately

Accessibility

  • Ensure buttons are keyboard accessible
  • Provide alternative payment methods
  • Use proper ARIA labels
  • Test with screen readers
Always test your PayPal integration thoroughly using PayPal’s sandbox environment before going live. Configure webhook endpoints to handle payment notifications and updates.
I