@moneygraph/sdk
Version:
AI-native SDK for global payouts powered by StratosPay
266 lines (222 loc) • 7.55 kB
Markdown
# MoneyGraph SDK - Base44 Integration Guide
Use this guide when building payment applications with Base44 AI app generator.
## Quick Setup
```bash
npm install @moneygraph/sdk
```
```typescript
import { MoneyGraph } from '@moneygraph/sdk';
const mg = new MoneyGraph({
apiKey: process.env.MONEYGRAPH_API_KEY // sk_test_* or sk_live_*
});
```
## Core Concepts
### 1. Mode Detection
The SDK automatically detects sandbox vs live mode from your API key:
- `sk_test_*` → Sandbox mode (mock data, safe for development)
- `sk_live_*` → Live mode (real money!)
```typescript
if (mg.isSandbox) {
// Use mock personas for instant testing
const customer = await mg.onboard.createMockPersona('business_verified');
}
```
### 2. The Golden Rule: KYC Before Payouts
**CRITICAL**: Customers MUST have approved KYC before sending payouts. The SDK enforces this automatically and will throw errors if you try to bypass it.
```typescript
// Always check before payout
const { allowed, status } = await mg.onboard.canPayout(customerId);
if (!allowed) {
// Show KYC completion UI
}
```
### 3. Quote & Confirm Pattern
FX quotes are valid for **2 minutes**. Always:
1. Get quote → Show to user
2. User confirms → Lock the rate
3. Execute payout
```typescript
// Step 1: Get quote
const quote = await mg.liquidity.getQuote({ from: 'USD', to: 'NGN', amount: 100 });
// Step 2: Show user the rate
// "You send $100, recipient gets ₦${quote.to_amount}"
// Step 3: On user confirmation, lock rate
await mg.liquidity.confirmQuote(quote.id);
// Step 4: Send payout
const payout = await mg.payouts.send({
quote_id: quote.id,
customer_id: customerId,
recipient: { name: 'Jane Doe', bank_code: '058', account_number: '0123456789' }
});
```
## Building Blocks for Base44
### Customer Registration Component
```typescript
// Create customer on signup
async function registerCustomer(formData: {
firstName: string;
lastName: string;
email: string;
phone: string;
country: string;
accountType: 'personal' | 'business';
businessName?: string;
}) {
if (formData.accountType === 'personal') {
return mg.onboard.createCustomer({
account_type: 'personal',
first_name: formData.firstName,
last_name: formData.lastName,
email: formData.email,
phone: formData.phone.replace(/\D/g, ''),
phone_iso2: formData.country,
country: formData.country,
});
} else {
return mg.onboard.createCustomer({
account_type: 'business',
first_name: formData.firstName,
last_name: formData.lastName,
email: formData.email,
phone: formData.phone.replace(/\D/g, ''),
phone_iso2: formData.country,
country: formData.country,
business_details: {
business_name: formData.businessName!,
},
});
}
}
```
### KYC Flow Component
```typescript
async function submitKYC(customerId: string, kycData: {
birthday: string; // DD-MM-YYYY format!
gender: 'male' | 'female';
idType: 'PASSPORT' | 'DRIVERS' | 'NATIONAL_ID';
idNumber: string;
sourceOfFundsId: string;
address: { state: string; city: string; street: string; postalCode: string };
}) {
// Update customer with KYC info
await mg.onboard.updateCustomer(customerId, {
birthday: kycData.birthday,
gender: kycData.gender,
id_type: kycData.idType,
id_number: kycData.idNumber,
source_of_fund_id: kycData.sourceOfFundsId,
state: kycData.address.state,
city: kycData.address.city,
street: kycData.address.street,
postal_code: kycData.address.postalCode,
});
// Submit for review
await mg.onboard.submitKyc(customerId);
return { status: 'pending', message: 'KYC submitted for review' };
}
// Get source of funds for dropdown
async function getSourceOfFundsOptions() {
return mg.reference.getSourceOfFunds();
}
```
### Send Money Component
```typescript
import { MoneyGraphError } from '@moneygraph/sdk';
async function sendMoney(params: {
customerId: string;
amount: number;
fromCurrency: string;
toCurrency: string;
recipient: {
name: string;
bankCode: string;
accountNumber: string;
};
}) {
try {
// One-liner that handles quote + confirm + send
const payout = await mg.sendPayout({
customerId: params.customerId,
from: params.fromCurrency as any,
to: params.toCurrency as any,
amount: params.amount,
recipient: {
name: params.recipient.name,
bank_code: params.recipient.bankCode,
account_number: params.recipient.accountNumber,
},
});
return { success: true, payoutId: payout.id };
} catch (error) {
if (error instanceof MoneyGraphError) {
switch (error.code) {
case 'KYC_PENDING':
return { success: false, error: 'Please complete KYC verification first' };
case 'KYC_REJECTED':
return { success: false, error: 'Your KYC was rejected. Please contact support.' };
case 'QUOTE_EXPIRED':
return { success: false, error: 'Rate expired. Please try again.' };
case 'INSUFFICIENT_BALANCE':
return { success: false, error: 'Insufficient balance' };
default:
return { success: false, error: error.message };
}
}
throw error;
}
}
```
### Transaction History Component
```typescript
async function getTransactionHistory(customerId: string) {
const transactions = await mg.onboard.listTransactions(customerId);
return transactions.data.map(tx => ({
id: tx.id,
amount: tx.human_readable_amount,
type: tx.type,
status: tx.status,
description: tx.description,
date: new Date(tx.created_at).toLocaleDateString(),
}));
}
```
## Data Types Quick Reference
### Customer Status
```typescript
type KycStatus = 'PENDING' | 'APPROVED' | 'REJECTED';
type AccountType = 'personal' | 'business';
```
### Payout Status
```typescript
type PayoutStatus = 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';
```
### Supported Currencies
- **Pay-In**: USD, EUR, NGN, KES, GHS + crypto (USDC, USDT, ETH, BTC)
- **Payout**: 107+ countries including NGN, KES, ZAR, GHS, INR, PHP, MXN, BRL, EUR, GBP
## Error Handling Cheat Sheet
| Error Code | User Message | Action |
|------------|--------------|--------|
| `KYC_PENDING` | "Please complete verification" | Show KYC form |
| `KYC_REJECTED` | "Verification failed" | Contact support |
| `QUOTE_EXPIRED` | "Rate expired, refreshing..." | Auto-retry |
| `CUSTOMER_NOT_FOUND` | "Account not found" | Check customer ID |
| `VALIDATION_ERROR` | Show field errors | Fix form inputs |
| `RATE_LIMITED` | "Too many requests" | Wait and retry |
## Testing in Base44
Use sandbox mode for development:
```typescript
// In sandbox, create instant verified customers
const testCustomer = await mg.onboard.createMockPersona('business_verified');
// Available personas:
// - 'business_verified' - Approved business with directors
// - 'individual_verified' - Approved personal account
// - 'pending_kyc' - Pending verification
// - 'rejected_kyc' - Rejected verification
```
## Important Notes
1. **Date Format**: Always use DD-MM-YYYY for birthday and registration dates
2. **Phone Numbers**: Remove all non-numeric characters before sending
3. **CORS**: Use a backend proxy (Supabase Edge Function, API route) for production
4. **Webhooks**: Set up webhooks for payout status updates in production
## Need the Backend Proxy?
See `recipes/supabase-edge-function.ts` for a ready-to-use Supabase Edge Function that handles CORS and API calls.