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:- PayPal payment gateway configured in WooCommerce
- PayPal JavaScript SDK loaded in your frontend
- A valid cart with items added
- 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:Copy
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_PAYPAL_CLIENT_ID¤cy=USD&components=buttons"></script>
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
Copy
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:Copy
<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:Copy
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:Copy
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:Copy
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:Copy
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:- Use PayPal Sandbox accounts
- Test with different PayPal account types (personal, business)
- Test payment cancellations
- 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.