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