UNPKG

@moneygraph/sdk

Version:

AI-native SDK for global payouts powered by StratosPay

367 lines (287 loc) 9.66 kB
# MoneyGraph SDK - International Remittance Flow This recipe shows the complete flow for international money transfers, based on the StratosPay remittance model. ## Overview International remittances follow a **Sender → Recipient → Transaction** model: 1. **Sender** - Your KYC'd customer initiating the transfer 2. **Recipient** - Person receiving funds in destination country 3. **Transaction** - The transfer tying sender, recipient, and payment details ## Key Concepts ### Currency Restrictions by Country Each currency under a country has specific capabilities: | Feature | Description | |---------|-------------| | **Local Swap** | Can swap to other currencies in same country (e.g., USD → EUR within US) | | **International Remittance** | List of destination countries/currencies available | | **Delivery Methods** | `bank`, `mobile_money`, `cash_pickup` - each has different fees | | **Delivery Windows** | Speed options (instant, same-day, 1-3 days) - affects fees | ### Fee Structure Fees include: - **Delivery window fee** - Based on speed selected - **Payout method fee** - Bank vs mobile money vs cash - **Swap fee** - If source and destination currencies differ Fee types: `no_fee`, `fiat` (flat), `percent`, `fiat_percent` (combined) --- ## Complete Example: US → Nigeria Transfer John (US) sends $100 USD to Sheila (Nigeria) who receives NGN in her bank. ### Step 1: Look Up Available Corridors ```typescript import { MoneyGraph } from '@moneygraph/sdk'; const mg = new MoneyGraph({ apiKey: process.env.MONEYGRAPH_API_KEY }); // Get currencies available for US senders const currencies = await mg.reference.getCurrencies('US'); // Find USD settings const usd = currencies.find(c => c.currency === 'USD'); console.log('Local swap enabled:', usd.settings.local_swap); console.log('Payout enabled:', usd.settings.payout); // Each currency lists available international remittance corridors // You need the international_remittance_id for the specific corridor ``` ### Step 2: Create/Register Sender (Your Customer) ```typescript // Create the sender (John) const sender = await mg.onboard.createCustomer({ account_type: 'personal', first_name: 'John', middle_name: 'Son', // Optional last_name: 'Doe', email: 'john.doe@remote.com', phone: '1234567789899', phone_iso2: 'US', country: 'US', state: 'DE', city: 'Wilmington', street: '123 Main Street', postal_code: '19801', }); // Complete KYC information await mg.onboard.updateCustomer(sender.id, { birthday: '29-06-1999', // DD-MM-YYYY format! id_type: 'SSN', // For US: SSN, others: PASSPORT, DRIVERS, NATIONAL_ID id_number: '123456789', id_url: 'https://storage.example.com/docs/john-id.pdf', ip_address: '98.97.79.160', // User's IP for compliance }); // Submit KYC for approval await mg.onboard.submitKyc(sender.id); // Store mapping: your system "Sender:US:123456" → sender.id ``` ### Step 3: Look Up Recipient Requirements ```typescript // Get available banks in Nigeria const banks = await mg.reference.getBanks('NG'); // Returns: [{ id, name, code, ... }] // Get account types for bank delivery const accountTypes = await mg.reference.getAccountTypes('NG'); // Returns: [{ id, name, ... }] // Get transfer purposes const transferPurposes = await mg.reference.getTransferPurposes(); // Returns: [{ id, name, ... }] // Get the NGN currency ID const ngCurrencies = await mg.reference.getCurrencies('NG'); const ngn = ngCurrencies.find(c => c.currency === 'NGN'); ``` ### Step 4: Create Recipient ```typescript // Create recipient (Sheila) for bank payout const recipient = await mg.remittance.createRecipient(sender.id, { // Identity first_name: 'Sheila', middle_name: 'Angel', last_name: 'Doe', email: 'sheila@example.com', phone: '09012345678', phone_iso2: 'NG', // Destination currency_id: ngn.id, // NGN currency ID transfer_purpose_id: transferPurposes[0].id, // Select appropriate purpose // Delivery method: Bank delivery_method: 'bank', bank_id: banks.find(b => b.code === '058').id, // GTBank acct_no: '0123456789', acct_name: 'Sheila Angel Doe', acct_type_id: accountTypes[0].id, }); ``` ### Step 5: Get Quote & Create Transaction ```typescript // First, ensure sender is KYC approved const { allowed } = await mg.onboard.canPayout(sender.id); if (!allowed) { throw new Error('Sender KYC not approved'); } // Get sender's USD wallet const wallets = await mg.onboard.getWallets(sender.id); const usdWallet = wallets.find(w => w.currency === 'USD'); // Get FX quote const quote = await mg.liquidity.getQuote({ from: 'USD', to: 'NGN', amount: 100, }); console.log(`Rate: 1 USD = ₦${quote.rate}`); console.log(`Sheila receives: ₦${quote.to_amount}`); // Confirm quote (locks the rate for 2 minutes) await mg.liquidity.confirmQuote(quote.id); // Create the transaction const transaction = await mg.remittance.createTransaction({ wallet_id: usdWallet.id, account_id: sender.id, recipient_id: recipient.id, international_remittance_id: quote.corridor_id, // From corridor lookup delivery_window_id: 'standard', // Or 'express', etc. external_id: 'Transaction:NGN:123456', // Your reference amount: 10000, // Amount in cents (100.00 USD) }); console.log('Transaction created:', transaction.id); ``` ### Step 6: Monitor Transaction Status ```typescript // Poll for status or set up webhooks const status = await mg.remittance.getTransactionStatus(transaction.id); // Statuses: pending → processing → completed (or failed) console.log('Status:', status.status); // Set up webhook for async notifications // POST to your webhook URL when status changes ``` --- ## Alternative Delivery Methods ### Mobile Money (e.g., M-Pesa in Kenya) ```typescript const recipient = await mg.remittance.createRecipient(sender.id, { first_name: 'Jane', last_name: 'Doe', phone: '254712345678', phone_iso2: 'KE', currency_id: kesCurrencyId, transfer_purpose_id: purposeId, delivery_method: 'mobile_money', mobile_network: 'MPESA', mobile_number: '254712345678', }); ``` ### Cash Pickup ```typescript const recipient = await mg.remittance.createRecipient(sender.id, { first_name: 'Jane', last_name: 'Doe', phone: '233201234567', phone_iso2: 'GH', currency_id: ghsCurrencyId, transfer_purpose_id: purposeId, delivery_method: 'cash_pickup', pickup_location_id: locationId, // From reference data }); ``` --- ## Simplified Flow with SDK Helper For common cases, use the convenience method: ```typescript // One-liner for complete payout flow const payout = await mg.sendPayout({ customerId: sender.id, from: 'USD', to: 'NGN', amount: 100, recipient: { name: 'Sheila Doe', bank_code: '058', account_number: '0123456789', }, reference: 'Transaction:NGN:123456', narration: 'Family support', }); ``` This handles: quote → confirm → KYC check → send in one call. --- ## Webhooks for Transaction Updates Register a webhook URL in your StratosPay dashboard to receive notifications: ```typescript // Webhook payload example { "event": "transaction.completed", "data": { "id": "tx_abc123", "external_id": "Transaction:NGN:123456", "status": "completed", "amount": 10000, "currency": "USD", "recipient_amount": 1550000, "recipient_currency": "NGN", "completed_at": "2024-01-15T10:30:00Z" } } ``` Handle webhook in your backend: ```typescript app.post('/webhooks/stratospay', (req, res) => { const { event, data } = req.body; switch (event) { case 'transaction.completed': // Update your database // Notify sender break; case 'transaction.failed': // Handle failure // Possibly refund sender break; } res.status(200).send('OK'); }); ``` --- ## Error Handling ```typescript import { MoneyGraphError } from '@moneygraph/sdk'; try { const tx = await mg.remittance.createTransaction(params); } catch (error) { if (error instanceof MoneyGraphError) { switch (error.code) { case 'KYC_PENDING': // Sender needs to complete KYC break; case 'INSUFFICIENT_BALANCE': // Sender wallet doesn't have enough funds break; case 'QUOTE_EXPIRED': // Get a new quote break; case 'VALIDATION_ERROR': // Check recipient details console.log('Field errors:', error.details); break; } } } ``` --- ## Testing in Sandbox ```typescript // Create instant verified sender const sender = await mg.onboard.createMockPersona('individual_verified'); // Use test recipient details const testRecipient = { first_name: 'Test', last_name: 'Recipient', email: 'test@example.com', phone: '09012345678', phone_iso2: 'NG', currency_id: ngnCurrencyId, transfer_purpose_id: purposeId, delivery_method: 'bank', bank_id: testBankId, acct_no: '0000000000', // Test account acct_name: 'Test Account', acct_type_id: accountTypeId, }; // Transaction will simulate: pending → processing → completed ``` --- ## Key Points to Remember 1. **Always look up corridor restrictions** before creating recipients 2. **KYC must be approved** before creating transactions 3. **Quotes expire in 2 minutes** - confirm promptly 4. **international_remittance_id** is required to specify the corridor 5. **Different delivery methods have different required fields** 6. **Set up webhooks** for production - don't poll 7. **Store external_id mapping** to your internal transaction IDs