File size: 4,637 Bytes
6dd9bad | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | import { logger } from '../logger';
export type PaymentProvider = 'ORANGE_MONEY' | 'WAVE';
export interface PaymentInitRequest {
amount: number;
currency: string;
reference: string;
notifyUrl: string;
returnUrl: string;
description: string;
customerPhone?: string;
}
export interface PaymentInitResult {
paymentUrl: string;
sessionId: string;
provider: PaymentProvider;
}
export interface WebhookPayload {
status: 'SUCCESS' | 'FAILED' | 'PENDING';
reference: string;
transactionId?: string;
amount?: number;
provider: PaymentProvider;
}
// Orange Money Merchant API (West Africa)
async function initOrangeMoney(req: PaymentInitRequest): Promise<PaymentInitResult> {
const apiKey = process.env.ORANGE_MONEY_API_KEY;
const merchantId = process.env.ORANGE_MONEY_MERCHANT_ID;
const apiUrl = process.env.ORANGE_MONEY_API_URL || 'https://api.orange.com/orange-money-webpay/dev/v1';
if (!apiKey || !merchantId) throw new Error('Orange Money credentials not configured');
const res = await fetch(`${apiUrl}/webpayment`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
merchant_key: merchantId,
currency: req.currency,
order_id: req.reference,
amount: req.amount,
return_url: req.returnUrl,
cancel_url: req.returnUrl,
notif_url: req.notifyUrl,
lang: 'fr',
reference: req.reference,
}),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}) ) as { message?: string };
throw new Error(`Orange Money init failed: ${err.message || res.status}`);
}
const data = await res.json() as { payment_url: string; pay_token: string };
return {
paymentUrl: data.payment_url,
sessionId: data.pay_token,
provider: 'ORANGE_MONEY',
};
}
// Wave API (Senegal)
async function initWave(req: PaymentInitRequest): Promise<PaymentInitResult> {
const apiKey = process.env.WAVE_API_KEY;
const apiUrl = 'https://api.wave.com/v1/checkout/sessions';
if (!apiKey) throw new Error('Wave API key not configured');
const res = await fetch(apiUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: String(req.amount),
currency: req.currency,
error_url: req.returnUrl,
success_url: `${req.returnUrl}?success=1&ref=${req.reference}`,
client_reference: req.reference,
}),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}) ) as { message?: string };
throw new Error(`Wave init failed: ${err.message || res.status}`);
}
const data = await res.json() as { wave_launch_url: string; id: string };
return {
paymentUrl: data.wave_launch_url,
sessionId: data.id,
provider: 'WAVE',
};
}
export async function initiatePayment(provider: PaymentProvider, req: PaymentInitRequest): Promise<PaymentInitResult> {
logger.info({ provider, reference: req.reference, amount: req.amount }, '[PAYMENT] Initiating payment');
if (provider === 'ORANGE_MONEY') return initOrangeMoney(req);
if (provider === 'WAVE') return initWave(req);
throw new Error(`Unsupported provider: ${provider}`);
}
// Parse and normalize webhooks from Orange Money and Wave into a unified format
export function parseWebhook(provider: PaymentProvider, body: Record<string, unknown>): WebhookPayload {
if (provider === 'ORANGE_MONEY') {
const statusMap: Record<string, WebhookPayload['status']> = {
SUCCESS: 'SUCCESS',
FAILED: 'FAILED',
INITIATED: 'PENDING',
};
return {
provider,
reference: String(body.order_id || body.reference || ''),
transactionId: String(body.txnid || ''),
amount: Number(body.amount || 0),
status: statusMap[String(body.status)] ?? 'FAILED',
};
}
if (provider === 'WAVE') {
return {
provider,
reference: String(body.client_reference || ''),
transactionId: String(body.id || ''),
amount: Number(body.amount || 0),
status: body.payment_status === 'succeeded' ? 'SUCCESS' : 'FAILED',
};
}
throw new Error(`Unknown provider: ${provider}`);
}
|