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 NumberBrandResult
4242424242424242Visa✅ Success
5555555555554444Mastercard✅ Success
4000000000000002Visa❌ Declined
4000000000009995Visa⚠️ 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