@moneygraph/sdk
Version:
AI-native SDK for global payouts powered by StratosPay
621 lines (513 loc) • 14.6 kB
Markdown
# 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 {};
```