JavaScript SDK

Waffy JavaScript SDK Guide

Complete JavaScript SDK for integrating Waffy payments into your web applications. Supports payment links, custom UI, user tokens, and comprehensive event handling.

Key Features

Dual Payment Views

Custom popup UI or external Waffy iframe views

User Token Support

Automatic customer authentication and token management

Web Components

Native custom elements for easy integration

Installation & Setup

Option 1: CDN (Recommended)

<!DOCTYPE html>
<html>
<head>
  <title>Waffy Payment Integration</title>
  <!-- Include Waffy SDK -->
  <script src="https://cdn.waffy.com/sdk/v2/waffy.min.js"></script>
</head>
<body>
  <!-- Your content -->
</body>
</html>
Advantages: Always latest version, global CDN, automatic updates, no build process

Option 2: NPM Package

# Install via npm
npm install @waffy/payment-sdk

# Or with yarn
yarn add @waffy/payment-sdk
// ES6 Modules
import WaffyPayment from '@waffy/payment-sdk';

// CommonJS
const WaffyPayment = require('@waffy/payment-sdk');
Advantages: Version control, bundle optimization, TypeScript support, offline development

Quick Start - 3 Lines Integration

Get payments working in under 5 minutes

<!DOCTYPE html>
<html>
<head>
  <script src="https://cdn.waffy.com/sdk/v2/waffy.min.js"></script>
</head>
<body>
  <h1>Buy Premium Plan - 99 SAR</h1>
  
  <!-- Step 1: Add button container -->
  <div id="waffy-payment-button"></div>
  
  <script>
    // Step 2: Initialize with minimal config
    WaffyPayment.init({
      merchantId: 'YOUR_MERCHANT_ID',
      amount: 99,
      currency: 'SAR'
    });
  </script>
</body>
</html>
Automatic Features
  • • Creates a payment button automatically
  • • Detects available payment methods
  • • Handles the entire payment flow
  • • Shows success/error messages

SDK Components

Three main components for different integration approaches

1. WaffyLinkGenerator

Creates payment links through backend API integration.

const linkGenerator = new WaffyLinkGenerator({
  backendUrl: 'http://localhost:5001/api/waffy',
  debug: true,
  timeout: 30000
});

// Create simple payment
const result = await linkGenerator.createSimplePayment({
  amount: 1000,
  customerPhone: '+966563335555',
  providerPhone: '+966557174411',
  title: 'Service Payment',
  description: 'Website development project',
  redirectUrl: 'https://yoursite.com/success'
});

console.log('Payment URL:', result.payment_url);

2. WaffyPaymentPopup

Displays payments in a custom popup interface.

const popup = new WaffyPaymentPopup({
  theme: 'light',
  debug: true,
  onSuccess: (result) => {
    console.log('Payment successful:', result);
    window.location.href = '/success';
  },
  onCancel: () => {
    console.log('Payment cancelled');
  }
});

popup.open(paymentUrl, {
  amount: '1000',
  title: 'Payment Title',
  description: 'Payment description'
});

3. WaffyPaymentButton (Web Component)

Native web component for external payment views.

<waffy-payment-button 
    payment-url="https://waffyapp.com/payment/abc123"
    text="Pay Now"
    size="large"
    theme="light"
    debug="true">
</waffy-payment-button>

<script>
// Handle events
document.querySelector('waffy-payment-button')
  .addEventListener('payment-success', (e) => {
    console.log('Payment completed!', e.detail);
  });
</script>

Payment Link Generation

Different types of payment links for various use cases

Simple Payment

const result = await linkGenerator.createSimplePayment({
  amount: 2500,                           // Amount in SAR
  customerPhone: '+966563335555',         // Customer phone
  providerPhone: '+966557174411',         // Provider phone
  title: 'Website Development',           // Payment title
  description: 'Full-stack development',  // Description
  redirectUrl: 'https://mysite.com/success'
});

// Result contains:
// - contract_id: Contract ID
// - milestone_id: Milestone ID  
// - payment_url: Payment URL with user token
// - success: true/false

Payment with Platform Fee

const result = await linkGenerator.createPaymentWithBroker({
  amount: 2500,
  customerPhone: '+966563335555',
  providerPhone: '+966557174411',
  brokerPhone: '+966507274790',           // Platform phone
  brokerFee: 125,                         // Platform fee (5%)
  title: 'Service with Platform Fee',
  description: 'Payment with 5% platform fee',
  redirectUrl: 'https://mysite.com/success'
});

Service Contract Payment

const result = await linkGenerator.createServicePayment({
  amount: 5000,
  customerPhone: '+966563335555',
  providerPhone: '+966557174411',
  projectTitle: 'Mobile App Development',        // Project name
  milestoneTitle: 'Initial Development Phase',   // Milestone name
  projectDescription: 'iOS and Android app',     // Project details
  milestoneDescription: 'UI/UX and backend',     // Milestone details
  redirectUrl: 'https://mysite.com/success'
});

Event Handling

Comprehensive event system for all payment states

Payment Button Events

const button = document.querySelector('waffy-payment-button');

// Payment successful
button.addEventListener('payment-success', (e) => {
  console.log('Payment completed:', e.detail);
  // Redirect or update UI
  window.location.href = '/success?id=' + e.detail.transactionId;
});

// Payment cancelled
button.addEventListener('payment-cancelled', (e) => {
  console.log('Payment cancelled:', e.detail);
  // Handle cancellation
});

// Modal opened
button.addEventListener('modal-open', (e) => {
  console.log('Payment modal opened');
  // Track analytics event
  gtag('event', 'payment_modal_open');
});

// Payment error
button.addEventListener('payment-error', (e) => {
  console.error('Payment error:', e.detail);
  // Show user-friendly error message
  showErrorMessage(e.detail.message);
});

Payment Popup Events

const popup = new WaffyPaymentPopup({
  onSuccess: (result) => {
    console.log('Payment successful:', result);
    // result.method - payment method used
    // result.transactionId - transaction ID
    // result.status - payment status
    
    // Process successful payment
    processSuccessfulPayment(result);
  },
  
  onError: (error) => {
    console.error('Payment error:', error);
    // error.message - error description
    // error.code - error code (if available)
    
    // Handle payment error
    handlePaymentError(error);
  },
  
  onCancel: () => {
    console.log('Payment cancelled by user');
    // Handle cancellation
  },
  
  onClose: () => {
    console.log('Popup closed');
    // Cleanup logic here
    cleanupPaymentState();
  }
});

Framework Integration

React Integration

import React, { useEffect, useState } from 'react';

const PaymentComponent = ({ amount, customer }) => {
  const [paymentUrl, setPaymentUrl] = useState('');
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const linkGenerator = new window.WaffyLinkGenerator({
      backendUrl: process.env.REACT_APP_BACKEND_URL,
      debug: process.env.NODE_ENV === 'development'
    });

    const createPayment = async () => {
      setLoading(true);
      try {
        const result = await linkGenerator.createSimplePayment({
          amount,
          customerPhone: customer.phone,
          providerPhone: process.env.REACT_APP_PROVIDER_PHONE,
          title: 'React Payment',
          redirectUrl: window.location.origin + '/success'
        });
        setPaymentUrl(result.payment_url);
      } catch (error) {
        console.error('Payment link creation failed:', error);
      } finally {
        setLoading(false);
      }
    };

    createPayment();
  }, [amount, customer]);

  const handlePaymentSuccess = (event) => {
    console.log('Payment completed:', event.detail);
    // Handle success
  };

  if (loading) {
    return <div>Creating payment...</div>;
  }

  return (
    <div>
      {paymentUrl && (
        <waffy-payment-button
          payment-url={paymentUrl}
          text={`Pay ${amount} SAR`}
          size="large"
          onPaymentSuccess={handlePaymentSuccess}
        />
      )}
    </div>
  );
};

Vue.js Integration

<template>
  <div>
    <waffy-payment-button 
      v-if="paymentUrl"
      :payment-url="paymentUrl"
      :text="`Pay ${amount} SAR`"
      size="large"
      @payment-success="handlePaymentSuccess"
      @payment-error="handlePaymentError"
    />
    <div v-else>Creating payment...</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      paymentUrl: '',
      linkGenerator: null
    };
  },
  async mounted() {
    this.linkGenerator = new window.WaffyLinkGenerator({
      backendUrl: process.env.VUE_APP_BACKEND_URL,
      debug: process.env.NODE_ENV === 'development'
    });

    await this.createPaymentLink();
  },
  methods: {
    async createPaymentLink() {
      try {
        const result = await this.linkGenerator.createSimplePayment({
          amount: this.amount,
          customerPhone: this.customer.phone,
          providerPhone: process.env.VUE_APP_PROVIDER_PHONE,
          title: 'Vue Payment',
          redirectUrl: window.location.origin + '/success'
        });
        this.paymentUrl = result.payment_url;
      } catch (error) {
        console.error('Payment creation failed:', error);
      }
    },
    handlePaymentSuccess(event) {
      console.log('Payment successful:', event.detail);
      this.$emit('payment-completed', event.detail);
    }
  }
};
</script>

Security & Best Practices

Environment Configuration

// Never expose secret keys in frontend code
const config = {
  development: {
    merchantId: process.env.WAFFY_TEST_MERCHANT_ID,
    testMode: true,
    backendUrl: 'http://localhost:5001/api/waffy'
  },
  production: {
    merchantId: process.env.WAFFY_LIVE_MERCHANT_ID,
    testMode: false,
    backendUrl: 'https://api.yoursite.com/waffy'
  }
};

// Validate configuration
function validateConfig(env) {
  const cfg = config[env];
  if (!cfg.merchantId || !cfg.backendUrl) {
    throw new Error(`Missing Waffy configuration for ${env}`);
  }
  return cfg;
}

const waffyConfig = validateConfig(process.env.NODE_ENV);

Input Validation

class PaymentValidator {
  static validateAmount(amount) {
    const numAmount = parseFloat(amount);
    if (isNaN(numAmount) || numAmount <= 0) {
      throw new Error('Invalid amount');
    }
    return Math.round(numAmount * 100) / 100; // Round to 2 decimals
  }

  static validatePhone(phone) {
    const cleaned = phone.replace(/\D/g, '');
    if (!/^(966|0)?5[0-9]{8}$/.test(cleaned)) {
      throw new Error('Invalid Saudi phone number');
    }
    return '+966' + cleaned.slice(-9);
  }

  static sanitizeString(input) {
    return input.replace(/[<>"'&]/g, '').trim().slice(0, 255);
  }
}

// Usage before creating payment
const validatedData = {
  amount: PaymentValidator.validateAmount(userAmount),
  customerPhone: PaymentValidator.validatePhone(userPhone),
  title: PaymentValidator.sanitizeString(userTitle)
};

Error Handling

// Comprehensive error handling
async function createPaymentWithRetry(paymentData, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await linkGenerator.createSimplePayment(paymentData);
    } catch (error) {
      console.error(`Payment attempt ${attempt} failed:`, error);
      
      if (attempt === maxRetries) {
        // All retries failed
        throw new Error('Payment creation failed after multiple attempts');
      }
      
      // Wait before retry (exponential backoff)
      await new Promise(resolve => 
        setTimeout(resolve, 1000 * attempt)
      );
    }
  }
}

// Usage with user feedback
try {
  showLoadingState('Creating payment...');
  const result = await createPaymentWithRetry(paymentData);
  hideLoadingState();
  displayPaymentButton(result.payment_url);
} catch (error) {
  hideLoadingState();
  showErrorMessage('Unable to create payment. Please try again.');
  console.error('Payment creation failed:', error);
}

Troubleshooting Common Issues

SDK not loading

Check:

  • Script src URL is correct
  • Network connectivity
  • Content Security Policy allows cdn.waffy.com
  • Browser console for loading errors
Payment button not appearing

Solutions:

  • Verify container element exists
  • Check merchant ID is valid
  • Ensure payment URL is properly formatted
  • Enable debug mode for detailed logs
Backend connection failed

Check:

  • Backend server is running
  • Backend URL is correct
  • CORS is properly configured
  • API endpoints are accessible

Complete Integration Guide

Step-by-step payment integration tutorial

API Reference

Detailed API documentation and parameters