UNPKG

@moneygraph/sdk

Version:

AI-native SDK for global payouts powered by StratosPay

621 lines (513 loc) 14.6 kB
# MoneyGraph SDK - Accept Payments (Merchants) This recipe shows how merchants can accept payments via cards, embedded widgets, or popup checkout. ## Overview Three integration options: 1. **Widget Embed** - Payment form embedded directly on page 2. **Popup Checkout** - Modal overlay triggered by button 3. **Direct Card Charge** - Server-side API call (requires PCI compliance) --- ## Quick Start ```typescript import { MoneyGraph } from '@moneygraph/sdk'; const mg = new MoneyGraph({ apiKey: 'sk_test_xxx', // Secret key (server-side) publicKey: 'pk_test_xxx', // Public key (client-side) }); ``` --- ## Option 1: Widget Embed (Recommended) Best for: Seamless checkout experience embedded on your page. ### HTML Implementation ```html <!DOCTYPE html> <html> <head> <title>Checkout</title> </head> <body> <h1>Complete Your Purchase</h1> <!-- Widget renders here --> <div class="stratos-embed"></div> <script src="https://stratospay.com/embed.js"></script> <script> const PAYMENT_CONFIG = { // Required: Your public key public_key: "pk_test_ESaKJlvX4uhaZurmd4AuZDhvK1ppi4", // Required: Unique reference for this transaction external_reference: "order_" + Date.now(), // Required: Payment details title: "Order Payment", description: "Payment for Order #12345", amount: 10000, // Amount in cents ($100.00) currency: "USD", // Optional: Business logo image: "https://yoursite.com/logo.png", // Optional: Redirect after success callback_url: "https://yoursite.com/thank-you", // Required: Customer info customer: { first_name: "John", last_name: "Doe", email: "john@example.com", phone: "+12025551234", // E.164 format phone_iso2: "US", ip_address: "127.0.0.1" // User's IP }, // Required: Billing address billing_address: { country: "US", state: "CA", // Optional city: "Los Angeles", address: "123 Main Street", postal_code: "90001" }, // Optional: Extra data for your records metadata: { order_id: "12345", customer_id: "cust_abc", items: ["Widget Pro", "Widget Lite"] }, // Callbacks onsuccess: function(data) { console.log("Payment successful!", data); // data.id - Transaction ID // data.status - 'success' // data.external_reference - Your reference }, onerror: function(data) { console.error("Payment failed:", data); // data.code - Error code // data.message - Error message } }; // Initialize the widget checkout.init(PAYMENT_CONFIG); </script> </body> </html> ``` ### React Implementation ```tsx import { useEffect, useRef } from 'react'; interface PaymentWidgetProps { amount: number; orderId: string; customer: { firstName: string; lastName: string; email: string; }; onSuccess: (data: any) => void; onError: (error: any) => void; } export function PaymentWidget({ amount, orderId, customer, onSuccess, onError }: PaymentWidgetProps) { const containerRef = useRef<HTMLDivElement>(null); const scriptLoaded = useRef(false); useEffect(() => { // Load script once if (!scriptLoaded.current) { const script = document.createElement('script'); script.src = 'https://stratospay.com/embed.js'; script.async = true; script.onload = initWidget; document.body.appendChild(script); scriptLoaded.current = true; } else { initWidget(); } function initWidget() { if (typeof window.checkout !== 'undefined') { window.checkout.init({ public_key: process.env.NEXT_PUBLIC_STRATOSPAY_KEY, external_reference: `order_${orderId}_${Date.now()}`, title: 'Order Payment', description: `Payment for Order #${orderId}`, amount: amount * 100, // Convert to cents currency: 'USD', customer: { first_name: customer.firstName, last_name: customer.lastName, email: customer.email, ip_address: '127.0.0.1', // Get from server }, billing_address: { country: 'US', city: 'New York', address: '123 Main St', postal_code: '10001', }, onsuccess: onSuccess, onerror: onError, }); } } }, [amount, orderId, customer, onSuccess, onError]); return <div ref={containerRef} className="stratos-embed" />; } // Usage <PaymentWidget amount={99.99} orderId="12345" customer={{ firstName: 'John', lastName: 'Doe', email: 'john@example.com' }} onSuccess={(data) => router.push('/thank-you')} onError={(err) => toast.error(err.message)} /> ``` --- ## Option 2: Popup Checkout Best for: Pay buttons that open a modal overlay. ### HTML Implementation ```html <script src="https://stratospay.com/popup.js"></script> <button type="button" onclick="openPayment()"> Pay $99.99 </button> <script> function openPayment() { checkout.init({ public_key: "pk_test_xxx", external_reference: "order_" + Date.now(), title: "Premium Subscription", description: "Monthly subscription", amount: 9999, currency: "USD", customer: { first_name: "Jane", last_name: "Smith", email: "jane@example.com", ip_address: "127.0.0.1" }, billing_address: { country: "US", city: "San Francisco", address: "456 Market St", postal_code: "94102" }, onsuccess: function(data) { alert("Payment successful!"); window.location.href = "/dashboard"; }, onerror: function(data) { alert("Payment failed: " + data.message); }, onclose: function() { console.log("User closed the popup"); // Optional: Track abandonment } }); } </script> ``` ### React Implementation ```tsx import { useCallback } from 'react'; export function PayButton({ amount, productName }: { amount: number; productName: string }) { const handlePayment = useCallback(() => { // Ensure script is loaded if (typeof window.checkout === 'undefined') { const script = document.createElement('script'); script.src = 'https://stratospay.com/popup.js'; script.onload = () => initPayment(); document.body.appendChild(script); } else { initPayment(); } function initPayment() { window.checkout.init({ public_key: process.env.NEXT_PUBLIC_STRATOSPAY_KEY!, external_reference: `pay_${Date.now()}`, title: productName, description: `Purchase: ${productName}`, amount: amount * 100, currency: 'USD', customer: { first_name: 'Guest', last_name: 'User', email: 'guest@example.com', ip_address: '127.0.0.1', }, billing_address: { country: 'US', city: 'New York', address: '123 Main St', postal_code: '10001', }, onsuccess: (data: any) => { console.log('Success:', data); // Verify payment on server, then redirect }, onerror: (err: any) => { console.error('Error:', err); }, onclose: () => { console.log('Closed'); }, }); } }, [amount, productName]); return ( <button onClick={handlePayment} className="btn btn-primary"> Pay ${amount.toFixed(2)} </button> ); } ``` --- ## Option 3: Direct Card Charge (Server-Side) Best for: Custom checkout flows with full control. **Requires PCI DSS compliance!** ```typescript // ⚠️ SERVER-SIDE ONLY - Never expose card details to client! // ⚠️ Requires PCI DSS compliance! const result = await mg.payments.chargeCard({ title: "Order Payment", description: "Order #12345", external_reference: `order_${orderId}_${Date.now()}`, amount: 9999, // $99.99 in cents currency: "USD", customer: { first_name: "John", last_name: "Doe", email: "john@example.com", phone: "+12025551234", phone_iso2: "US", ip_address: req.ip, // From request }, billing_address: { country: "US", state: "CA", city: "Los Angeles", address: "123 Main St", postal_code: "90001", }, card: { card_number: "4917484589897107", cvv: "123", expire: "12/30", // MM/YY format }, }); // Handle response switch (result.status) { case 'success': // Payment complete! await markOrderPaid(orderId, result.id); break; case 'pending': // Payment processing await markOrderPending(orderId, result.id); // Poll for status or wait for webhook break; case 'requires_action': // 3DS authentication required // Redirect user to result.action_url return res.redirect(result.action_url); case 'declined': case 'blocked': // Payment failed throw new Error(result.message); } ``` --- ## Verify Payment (Server-Side) Always verify payments server-side before fulfilling orders: ```typescript // In your webhook handler or callback app.post('/api/payment/verify', async (req, res) => { const { external_reference } = req.body; const payment = await mg.payments.verifyPayment(external_reference); if (payment.status === 'success') { // Payment verified - fulfill order await fulfillOrder(external_reference); res.json({ success: true }); } else { res.json({ success: false, status: payment.status }); } }); ``` --- ## Test Cards (Sandbox) Use these card numbers for testing: | Card Number | Result | Use Case | |-------------|--------|----------| | `4917484589897107` | Success | Happy path | | `5555555555554444` | Success | Mastercard | | `6011000990139424` | Success | Discover | | `6011000991300009` | 🚫 Card Blocked | Test blocked card | | `6011111111111117` | 💳 Insufficient Funds | Test decline | | `378282246310005` | 🚫 Do Not Honour | Test decline | | `4263982640269299` | 🔐 Requires 3DS | Test 3DS flow | | `5425233430109903` | 🔐 Requires 3DS | Test 3DS (alt) | | `374245455400126` | Pending | Test async | | `4000000000001976` | Needs Confirmation | Test verification | **Test Blocked Countries:** Afghanistan (AF), North Korea (KP) **Test Amount Limits:** - Below $0.10 Declined (amount too low) - Above $1.00 Declined (amount too high) *[sandbox only]* --- ## Subscription Payments For recurring payments, include `plan_id`: ```javascript checkout.init({ public_key: "pk_test_xxx", external_reference: "sub_" + Date.now(), title: "Pro Plan", description: "Monthly subscription", amount: 2999, currency: "USD", plan_id: "plan_monthly_pro", // Your subscription plan ID // ... rest of config }); ``` --- ## Crediting Customer Wallets For marketplace/platform payments where funds go to a customer's wallet: ```javascript checkout.init({ // ... payment config account_id: "cust_abc123", // Onboarded customer ID // Funds credit to this customer's wallet instead of your main account }); ``` --- ## Best Practices ### 1. Unique External References ```typescript // Good: Unique per transaction external_reference: `order_${orderId}_${Date.now()}` // Bad: Can cause duplicates external_reference: orderId ``` ### 2. Server-Side Verification Always verify payments server-side: ```typescript // Client receives success callback onsuccess: async (data) => { // Verify on your server before showing success const verified = await fetch('/api/verify-payment', { method: 'POST', body: JSON.stringify({ reference: data.external_reference }) }); if (verified.ok) { router.push('/thank-you'); } } ``` ### 3. Error Handling ```typescript onerror: (error) => { switch (error.code) { case 'card_declined': showError('Your card was declined. Please try another card.'); break; case 'insufficient_funds': showError('Insufficient funds. Please try another payment method.'); break; case 'expired_card': showError('Your card has expired.'); break; default: showError('Payment failed. Please try again.'); } } ``` ### 4. Loading States ```tsx function PayButton() { const [loading, setLoading] = useState(false); const handlePayment = () => { setLoading(true); checkout.init({ // ... config onsuccess: () => setLoading(false), onerror: () => setLoading(false), onclose: () => setLoading(false), }); }; return ( <button onClick={handlePayment} disabled={loading}> {loading ? 'Processing...' : 'Pay Now'} </button> ); } ``` --- ## Webhooks Set up webhooks in your StratosPay dashboard for reliable payment notifications: ```typescript app.post('/webhooks/payments', (req, res) => { const { event, data } = req.body; switch (event) { case 'payment.success': fulfillOrder(data.external_reference); break; case 'payment.failed': notifyCustomer(data.external_reference, data.message); break; case 'payment.refunded': processRefund(data.external_reference); break; } res.status(200).send('OK'); }); ``` --- ## TypeScript Declarations Add to your project for type support: ```typescript // types/stratospay.d.ts declare global { interface Window { checkout: { init: (config: StratosPayConfig) => void; }; } } interface StratosPayConfig { public_key: string; external_reference: string; title: string; description: string; amount: number; currency: string; image?: string; callback_url?: string; customer: { first_name: string; last_name: string; email: string; phone?: string; phone_iso2?: string; ip_address: string; }; billing_address: { country: string; state?: string; city: string; address: string; postal_code: string; }; metadata?: Record<string, any>; plan_id?: string; account_id?: string; onsuccess?: (data: PaymentResult) => void; onerror?: (error: PaymentError) => void; onclose?: () => void; } interface PaymentResult { id: string; status: 'success' | 'pending'; external_reference: string; amount: number; currency: string; } interface PaymentError { code: string; message: string; } export {}; ```