Payment Integration Guide
Complete Payment Integration Guide
Step-by-step guide to integrating Waffy payment processing into your application. From basic setup to advanced features like Apple Pay and installment payments.
Quick Start - 5 Minutes Integration
Get payments working in your app with minimal setup
1. Include the SDK
<!-- Add to your HTML head --> <script src="https://cdn.waffy.com/sdk/v2/waffy.min.js"></script>
2. Initialize and Create Payment
// Initialize Waffy
const waffy = new WaffyPayment({
merchantId: 'YOUR_MERCHANT_ID',
testMode: true // Remove in production
});
// Create payment button
document.getElementById('pay-button').addEventListener('click', function() {
waffy.pay({
amount: 299,
currency: 'SAR',
description: 'Premium Subscription',
customer: {
email: 'customer@example.com',
phone: '+966501234567'
},
onSuccess: function(payment) {
console.log('Payment successful!', payment);
// Redirect to success page
window.location.href = '/success';
},
onError: function(error) {
console.error('Payment failed:', error);
alert('Payment failed. Please try again.');
}
});
});That's it!
Your payment integration is ready. The SDK automatically detects available payment methods and handles the entire payment flow securely.
Environment Setup
Development vs Production
// Environment-based configuration
const config = {
development: {
merchantId: 'test_merchant_123',
testMode: true,
apiBase: 'https://api-dev.waffy.com'
},
production: {
merchantId: 'live_merchant_789',
testMode: false,
apiBase: 'https://api.waffy.com'
}
};
const environment = process.env.NODE_ENV || 'development';
const waffyConfig = config[environment];
const waffy = new WaffyPayment(waffyConfig);Test Cards
| Card Number | Brand | Result |
|---|---|---|
| 4242424242424242 | Visa | ✅ Success |
| 5555555555554444 | Mastercard | ✅ Success |
| 4000000000000002 | Visa | ❌ Declined |
| 4000000000009995 | Visa | ⚠️ Insufficient funds |
Advanced Payment Integration
Handle complex scenarios with multiple items, subscriptions, and custom UI
Multi-Item E-commerce Checkout
function processCheckout(cartItems, customerInfo) {
// Calculate total
const total = cartItems.reduce((sum, item) => sum + (item.price * item.quantity), 0);
waffy.pay({
amount: total,
currency: 'SAR',
description: `Order with ${cartItems.length} items`,
// Required for Tabby installments
items: cartItems.map(item => ({
name: item.name,
description: item.description,
quantity: item.quantity,
price: item.price,
category: item.category
})),
customer: {
email: customerInfo.email,
phone: customerInfo.phone,
firstName: customerInfo.firstName,
lastName: customerInfo.lastName,
address: {
address: customerInfo.address,
city: customerInfo.city,
zip: customerInfo.zip,
country: 'SA'
}
},
// Custom metadata for order tracking
metadata: {
orderId: generateOrderId(),
source: 'website',
campaign: getCampaignCode(),
items: cartItems.length
},
onSuccess: function(payment) {
// Clear cart
clearCart();
// Track conversion in analytics
gtag('event', 'purchase', {
transaction_id: payment.transactionId,
value: payment.amount,
currency: payment.currency,
items: cartItems
});
// Redirect to success page
window.location.href = `/success?order=${payment.transactionId}`;
}
});
}Subscription Payments
function setupSubscription(planDetails) {
waffy.pay({
amount: planDetails.price,
currency: 'SAR',
description: `${planDetails.name} - Monthly Subscription`,
customer: {
email: user.email,
phone: user.phone
},
// Subscription metadata
metadata: {
type: 'subscription',
planId: planDetails.id,
frequency: 'monthly',
customerId: user.id
},
onSuccess: function(payment) {
// Create subscription record in your backend
fetch('/api/subscriptions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
customerId: user.id,
planId: planDetails.id,
transactionId: payment.transactionId,
status: 'active',
nextBilling: calculateNextBilling()
})
}).then(() => {
showSubscriptionWelcome(planDetails);
});
}
});
}Mobile Optimization
Mobile-First Configuration
const waffy = new WaffyPayment({
merchantId: 'YOUR_MERCHANT_ID',
// Mobile-optimized appearance
appearance: {
theme: 'light',
variables: {
fontSize: window.innerWidth < 768 ? '18px' : '16px',
borderRadius: '12px'
},
button: {
height: window.innerWidth < 768 ? '52px' : '48px'
}
}
});
// Handle viewport changes
window.addEventListener('resize', () => {
waffy.updateAppearance({
variables: {
fontSize: window.innerWidth < 768 ? '18px' : '16px'
}
});
});Apple Pay Integration
// Check if Apple Pay is available
if (waffy.isApplePayAvailable()) {
const applePayButton = waffy.createApplePayButton({
amount: 99,
currency: 'SAR',
countryCode: 'SA',
// Required merchant information
merchantInfo: {
displayName: 'Your Store Name',
domainName: 'yourstore.com'
},
// Payment request
paymentRequest: {
supportedNetworks: ['visa', 'masterCard', 'mada'],
merchantCapabilities: ['supports3DS'],
requiredBillingContactFields: ['email', 'phone'],
requiredShippingContactFields: ['name', 'phone', 'email']
},
onSuccess: (payment) => {
console.log('Apple Pay successful:', payment);
processOrder(payment);
},
onCancel: () => {
console.log('Apple Pay cancelled');
}
});
document.getElementById('apple-pay-container').appendChild(applePayButton);
}Framework Integration Examples
React Integration
import React, { useEffect, useState } from 'react';
import WaffyPayment from '@waffy/payment-sdk';
const CheckoutComponent = ({ orderTotal, customer }) => {
const [waffy, setWaffy] = useState(null);
const [isProcessing, setIsProcessing] = useState(false);
useEffect(() => {
const waffyInstance = new WaffyPayment({
merchantId: process.env.REACT_APP_WAFFY_MERCHANT_ID,
testMode: process.env.NODE_ENV === 'development'
});
waffyInstance.on('paymentProcessing', () => {
setIsProcessing(true);
});
waffyInstance.on('paymentSuccess', (payment) => {
setIsProcessing(false);
onPaymentSuccess(payment);
});
setWaffy(waffyInstance);
}, []);
const handlePayment = async () => {
if (!waffy) return;
try {
await waffy.pay({
amount: orderTotal,
currency: 'SAR',
customer: customer
});
} catch (error) {
console.error('Payment error:', error);
}
};
return (
<button
onClick={handlePayment}
disabled={isProcessing || !waffy}
>
{isProcessing ? 'Processing...' : `Pay ${orderTotal} SAR`}
</button>
);
};Vue.js Integration
<template>
<div class="payment-component">
<button
@click="processPayment"
:disabled="processing"
>
{{ processing ? 'Processing...' : `Pay ${amount} ${currency}` }}
</button>
</div>
</template>
<script>
import WaffyPayment from '@waffy/payment-sdk';
export default {
data() {
return {
waffy: null,
processing: false
};
},
async mounted() {
this.waffy = new WaffyPayment({
merchantId: process.env.VUE_APP_WAFFY_MERCHANT_ID
});
this.waffy.on('paymentSuccess', this.onPaymentSuccess);
},
methods: {
async processPayment() {
this.processing = true;
try {
await this.waffy.pay({
amount: this.amount,
currency: this.currency,
customer: this.customer
});
} catch (error) {
this.onPaymentError(error);
}
}
}
};
</script>Security Best Practices
Never Handle Raw Card Data
❌ Never Do This
// NEVER store or handle raw card data
const cardData = {
cardNumber: '4242424242424242',
cvv: '123',
expiryDate: '12/25'
};✅ Do This Instead
// Let Waffy handle card collection
waffy.pay({
amount: 100.00,
currency: 'SAR',
customer: {
email: 'customer@example.com'
}
});Input Validation & Sanitization
function validateAndSanitizeInput(input, type) {
switch (type) {
case 'email':
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input)) {
throw new Error('Invalid email format');
}
return input.toLowerCase().trim();
case 'phone':
const cleaned = input.replace(/\D/g, '');
if (!/^(966|0)?5[0-9]{8}$/.test(cleaned)) {
throw new Error('Invalid phone number');
}
return '+966' + cleaned.slice(-9);
case 'amount':
const amount = parseFloat(input);
if (isNaN(amount) || amount <= 0) {
throw new Error('Invalid amount');
}
return Math.round(amount * 100) / 100; // Round to 2 decimals
default:
return input.replace(/[<>"'&]/g, '').trim().slice(0, 255);
}
}
// Usage
const paymentData = {
amount: validateAndSanitizeInput(userAmount, 'amount'),
customer: {
email: validateAndSanitizeInput(userEmail, 'email'),
phone: validateAndSanitizeInput(userPhone, 'phone')
}
};HTTPS & CSP Requirements
<!-- Content Security Policy -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' https://cdn.waffy.com;
connect-src 'self' https://api.waffy.com;
frame-src https://payments.waffy.com;">
<script>
// Validate secure connection
if (location.protocol !== 'https:' &&
!location.hostname.includes('localhost')) {
throw new Error('HTTPS required for payment processing');
}
</script>Global Payment Considerations
Multi-Currency Support
// Detect user's preferred currency
function getPreferredCurrency(userCountry) {
const currencyMap = {
'SA': 'SAR',
'AE': 'AED',
'US': 'USD',
'GB': 'GBP',
'EU': 'EUR'
};
return currencyMap[userCountry] || 'SAR';
}
// Dynamic pricing based on location
const userCurrency = getPreferredCurrency(userLocation.country);
const convertedAmount = convertCurrency(baseAmount, 'SAR', userCurrency);
waffy.pay({
amount: convertedAmount,
currency: userCurrency,
description: 'Premium Service',
// Show pricing in user's preferred format
displayAmount: formatCurrency(convertedAmount, userCurrency, userLocation.locale)
});Localization
// Multi-language support
const waffy = new WaffyPayment({
merchantId: 'YOUR_MERCHANT_ID',
// Set interface language
locale: getUserLocale(), // 'en', 'ar', 'fr', etc.
appearance: {
// RTL support for Arabic
direction: getUserLocale() === 'ar' ? 'rtl' : 'ltr',
// Localized styling
variables: {
fontFamily: getUserLocale() === 'ar' ? 'Cairo, sans-serif' : 'Inter, sans-serif'
}
}
});
// Localized error messages
const errorMessages = {
'en': {
'card_declined': 'Your card was declined. Please try another card.',
'insufficient_funds': 'Insufficient funds. Please check your balance.'
},
'ar': {
'card_declined': 'تم رفض بطاقتك. يرجى تجربة بطاقة أخرى.',
'insufficient_funds': 'رصيد غير كافٍ. يرجى التحقق من رصيدك.'
}
};
function getLocalizedError(errorCode, locale = 'en') {
return errorMessages[locale][errorCode] || errorMessages['en'][errorCode];
}Common Issues & Troubleshooting
Issue: Payment button not appearing
Solution:
- Check if SDK script is loaded correctly
- Verify merchant ID is valid
- Ensure container element exists
- Check browser console for errors
Issue: Payments failing in production
Solution:
- Remove
testMode: truefrom configuration - Use production merchant credentials
- Ensure HTTPS is enabled
- Check webhook endpoints are accessible
Issue: Mobile payments not working
Solution:
- Ensure viewport meta tag is set correctly
- Check mobile-specific payment methods (Apple Pay, Google Pay)
- Test on actual devices, not just browser dev tools
- Verify touch events are handled properly
API Reference
Detailed API documentation and parameters
JavaScript SDK Guide
Complete SDK documentation and examples