Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.cocartapi.com/llms.txt

Use this file to discover all available pages before exploring further.

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:
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

Troubleshooting

Common Issues

Problem: PayPal buttons don’t appear on the page.Solution: Check these common issues:
// Verify PayPal SDK is loaded
if (typeof paypal === 'undefined') {
    console.error('PayPal SDK not loaded');
    // Check the script tag is present and loads successfully
}

// Ensure container exists before rendering
const container = document.getElementById('paypal-button-container');
if (!container) {
    console.error('PayPal button container not found');
}

// Check for initialization errors
paypal.Buttons({
    // ... your configuration
}).render('#paypal-button-container').catch(error => {
    console.error('PayPal button render error:', error);
});
  • Verify the PayPal SDK script tag is present and loading
  • Check client ID is correct and matches WooCommerce settings
  • Ensure container element exists in DOM
  • Look for JavaScript errors in browser console
  • Verify no CSS is hiding the container
Problem: “Invalid client ID” or currency mismatch errors.Solution: Ensure SDK configuration matches your store:
<!-- Client ID must match WooCommerce PayPal settings -->
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID&currency=USD"></script>
  • Get client ID from WooCommerce → Settings → Payments → PayPal
  • Verify currency matches your store’s currency
  • Use sandbox client ID for testing, live client ID for production
  • Check that your PayPal account supports the currency
Problem: Errors when creating PayPal order.Solution: Verify order data structure:
createOrder: function(data, actions) {
    // Validate cart total exists
    if (!cartInfo.total || cartInfo.total <= 0) {
        console.error('Invalid cart total:', cartInfo.total);
        return Promise.reject(new Error('Invalid cart total'));
    }

    return actions.order.create({
        purchase_units: [{
            amount: {
                value: cartInfo.total, // Must be string or number
                currency_code: cartInfo.currency // Must match SDK currency
            }
        }]
    });
}
  • Ensure amount.value is a valid number or string
  • Verify currency_code matches SDK initialization
  • Check that cart total is greater than zero
  • Validate all required billing/shipping fields are provided
Problem: PayPal payment succeeds but WooCommerce order fails to create.Solution: Debug the onApprove callback:
onApprove: async function(data, actions) {
    try {
        console.log('PayPal approval data:', data);

        const order = await actions.order.capture();
        console.log('PayPal order captured:', order);

        // Verify order structure
        if (!order.purchase_units || !order.purchase_units[0].payments.captures[0]) {
            throw new Error('Invalid PayPal order structure');
        }

        // Submit to CoCart
        const checkoutResponse = await processPayPalCheckout(
            billingAddress,
            shippingAddress,
            order,
            data
        );

        console.log('Checkout response:', checkoutResponse);

    } catch (error) {
        console.error('onApprove error:', error);
        // Check if error is from PayPal capture or CoCart checkout
    }
}
  • Check browser console for error details
  • Verify billing address is complete and valid
  • Check PHP error logs for server-side issues
  • Ensure payment_method is set to ‘paypal’
  • Verify WooCommerce PayPal gateway is active
Problem: PayPal buttons appear but are disabled.Solution: Common causes:
// 1. Form validation preventing button enable
createOrder: function(data, actions) {
    // Check if form is valid
    if (!validateForm()) {
        // Show error message to user
        showError('Please fill in all required fields');
        return Promise.reject(new Error('Form validation failed'));
    }

    return actions.order.create({...});
}

// 2. CSS preventing interaction
#paypal-button-container {
    pointer-events: auto; /* Ensure not disabled */
    opacity: 1;
}
  • Verify form validation isn’t blocking buttons
  • Check for pointer-events: none CSS
  • Ensure buttons finished rendering completely
  • Look for overlay elements covering buttons
Problem: Customer’s payment method is declined by PayPal.Solution: Handle gracefully:
onError: function(error) {
    console.error('PayPal error:', error);

    let message = 'Payment failed. Please try again.';

    if (error.message && error.message.includes('INSTRUMENT_DECLINED')) {
        message = 'Your payment method was declined. Please try a different payment method or contact PayPal support.';
    }

    showError(message);
}
Common reasons:
  • Insufficient funds in PayPal account
  • Payment method (bank/card) linked to PayPal is invalid
  • PayPal account restrictions or limitations
  • International transaction not supported
Problem: onCancel not triggered or not handled properly.Solution: Implement comprehensive cancellation handling:
onCancel: function(data) {
    console.log('PayPal payment cancelled:', data);

    // Clear any loading states
    hideLoading();

    // Show user-friendly message
    showMessage('Payment was cancelled. You can try again anytime.');

    // Re-enable form or payment buttons
    enablePaymentForm();

    // Optional: Track cancellation for analytics
    trackEvent('paypal_payment_cancelled', data);
}
  • Test cancellation flow thoroughly
  • Ensure UI resets to allow retry
  • Don’t treat cancellation as an error
  • Provide clear next steps for customer
Problem: Works in sandbox but fails in production.Solution: Verify production configuration:
// Ensure using correct credentials for environment
const clientId = isProduction
    ? 'YOUR_LIVE_CLIENT_ID'   // Live from PayPal dashboard
    : 'YOUR_SANDBOX_CLIENT_ID'; // Sandbox from developer dashboard

// SDK URL for production (no -sandbox)
const sdkUrl = `https://www.paypal.com/sdk/js?client-id=${clientId}&currency=USD`;
Checklist for going live:
  • Switch to live client ID in WooCommerce settings
  • Update SDK script to use live client ID
  • Verify PayPal account is verified and approved for production
  • Test with real PayPal account (small amount)
  • Configure production webhooks
  • Enable live payment processing in WooCommerce
Problem: Not receiving PayPal webhook notifications.Solution: Configure webhooks properly:
  1. Go to PayPal Developer Dashboard → My Apps & Credentials
  2. Select your app
  3. Scroll to Webhooks section
  4. Add webhook URL: https://yoursite.com/wc-api/wc_gateway_paypal_express_helper/
  5. Subscribe to events:
    • Payment sale completed
    • Payment sale refunded
    • Payment capture completed
  6. Test webhook delivery
  7. Check webhook signature verification in WooCommerce logs
Problem: “Amount mismatch” errors during checkout.Solution: Ensure consistency:
// PayPal amount format
const paypalAmount = parseFloat(cartTotal).toFixed(2); // "10.00"

// In createOrder
amount: {
    value: paypalAmount, // String with 2 decimals
    currency_code: 'USD'
}

// In CoCart checkout
payment_data: {
    gross_amount: paypalAmount, // Must match PayPal order
    currency: 'USD'
}
  • Use consistent decimal formatting (2 decimal places)
  • Match currency codes everywhere
  • Don’t modify cart during PayPal flow
  • Verify cart hasn’t changed between order creation and checkout

Debug Mode

Enable detailed logging for troubleshooting:
// Add detailed PayPal logging
const DEBUG = true; // Set to false in production

if (DEBUG) {
    paypal.Buttons({
        onInit: function(data, actions) {
            console.log('[PayPal] Buttons initialized', data);
        },
        onClick: function(data, actions) {
            console.log('[PayPal] Button clicked', data);
        },
        createOrder: function(data, actions) {
            console.log('[PayPal] Creating order');
            return actions.order.create({...}).then(orderId => {
                console.log('[PayPal] Order created:', orderId);
                return orderId;
            });
        },
        onApprove: function(data, actions) {
            console.log('[PayPal] Payment approved:', data);
            return actions.order.capture().then(details => {
                console.log('[PayPal] Payment captured:', details);
                return details;
            });
        },
        onCancel: function(data) {
            console.log('[PayPal] Payment cancelled:', data);
        },
        onError: function(error) {
            console.error('[PayPal] Error:', error);
        }
    }).render('#paypal-button-container');
}

Getting Help

If issues persist:
  1. PayPal Developer Forums: Visit PayPal Developer Community
  2. PayPal Documentation: Check PayPal JavaScript SDK documentation
  3. WooCommerce Logs: Review logs at WooCommerce → Status → Logs
  4. Browser Console: Always check for JavaScript errors
  5. Network Tab: Monitor API calls in browser DevTools

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.