@moneygraph/sdk
Version:
AI-native SDK for global payouts powered by StratosPay
367 lines (287 loc) • 9.66 kB
Markdown
# 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