From 7f5e9efbdf8b59f83f5d82cef8bd95e03caae0db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Habinshuti?= Date: Wed, 28 Oct 2020 13:27:23 +0300 Subject: [PATCH 1/3] Fix flutterwave transfer implementation --- .../payment/flutterwave-payment-provider.ts | 79 ++++++++++++++++--- 1 file changed, 69 insertions(+), 10 deletions(-) diff --git a/server/src/core/payment/flutterwave-payment-provider.ts b/server/src/core/payment/flutterwave-payment-provider.ts index c96aedb7..7f7fc555 100644 --- a/server/src/core/payment/flutterwave-payment-provider.ts +++ b/server/src/core/payment/flutterwave-payment-provider.ts @@ -3,7 +3,6 @@ import { PaymentProvider, PaymentRequestResult, ProviderTransactionInfo, SendFun import { User } from '../user'; import { generateId } from '../util'; import { createFlutterwaveApiError } from '../error'; -import { not } from '@hapi/joi'; const API_BASE_URL = 'https://api.flutterwave.com/v3'; @@ -28,6 +27,32 @@ interface FlutterwaveInitiatePaymentResponse { } } +interface FlutterwaveTransferInfo { + id: number; + account_number: string; + bank_code: string; + full_name: string; + created_at: string; + currency: string; + debit_currency: string; + amount: number; + fee: number; + status: string; + reference: string; + meta: any; + narration: string; + complete_message: string; + requires_approval: number; + is_approved: number; + bank_name: string; +}; + +interface FlutterwaveInitiateTransferResponse { + status: string; + message: string; + data: FlutterwaveTransferInfo +} + interface FlutterwaveTransactionInfo { id: string; tx_ref: string; @@ -51,7 +76,7 @@ interface FlutterwaveTransactionInfo { interface FlutterwaveNotification { event: string; 'event.type': string; - data: FlutterwaveTransactionInfo; + data: FlutterwaveTransactionInfo | FlutterwaveTransferInfo; } interface FlutterwaveTransactionResponse { @@ -61,15 +86,17 @@ interface FlutterwaveTransactionResponse { } function extractTransactionInfo(data: FlutterwaveTransactionInfo): ProviderTransactionInfo { - const status: TransactionStatus = data.status === 'successful' ? 'success' : - data.status === 'failed' ? 'failed' : 'pending'; + const flwStatus = data.status.toLowerCase(); + const status: TransactionStatus = flwStatus === 'successful' ? 'success' : + flwStatus === 'failed' ? 'failed' : 'pending'; // phone number comes in in 07... format, strip the 0 const flwPhone = data.customer?.phone_number?.substring(1); + const phone = flwPhone ? `254${flwPhone}` : ''; // convert to internal 254 format return { userData: { - phone: flwPhone ? `254${flwPhone}` : '' // convert to internal 254 format + phone, }, status, amount: data.amount, @@ -79,6 +106,31 @@ function extractTransactionInfo(data: FlutterwaveTransactionInfo): ProviderTrans }; } +function extractTransferInfo(data: FlutterwaveTransferInfo): ProviderTransactionInfo { + const flwStatus = data.status.toLowerCase(); + const status: TransactionStatus = flwStatus === 'successful' ? 'success' : + flwStatus === 'failed' ? 'failed' : 'pending'; + + let phone = ''; + // if it's an M-PESA transfer, then account_number is the phone number + if (data.bank_code === 'MPS') { + // phone number comes in in 07... format, strip the 0 + const flwPhone = data.account_number.substring(1); + phone = flwPhone ? `254${flwPhone}` : ''; // convert to internal 254 format + } + + return { + userData: { + phone + }, + status, + amount: data.amount, + providerTransactionId: data.reference, + metadata: data, + failureReason: status === 'failed' ? data.complete_message : '' + }; +} + export class FlutterwavePaymentProvider implements PaymentProvider { constructor(private args: FlutterwavePaymentProviderArgs) { @@ -134,7 +186,12 @@ export class FlutterwavePaymentProvider implements PaymentProvider { async handlePaymentNotification(payload: any): Promise { const notification: FlutterwaveNotification = payload; const { data } = notification; - return extractTransactionInfo(data); + + if (notification['event.type'] === 'Transfer') { + return extractTransferInfo(data as FlutterwaveTransferInfo); + } + + return extractTransactionInfo(data as FlutterwaveTransactionInfo); } async getTransaction(localTransaction: Transaction): Promise { @@ -177,14 +234,16 @@ export class FlutterwavePaymentProvider implements PaymentProvider { try { const url = getUrl(`/transfers`); - const res = await axios.default.post(url, transferArgs, { headers: { Authorization: `Bearer ${this.args.secretKey}`}}); + const res = await axios.default.post(url, transferArgs, { headers: { Authorization: `Bearer ${this.args.secretKey}`}}); const { data, status } = res.data; + console.log('Flutterwave transfer payload', JSON.stringify(res.data, null, 2)); return { - providerTransactionId: data.id, - status: status as TransactionStatus, - } + providerTransactionId: data.reference, + // success from the API means that the transaction was queued successfully, not that it's completed + status: status === 'success' ? 'pending' : 'failed' + }; } catch(e) { throw createFlutterwaveApiError(e.response.data && e.response.data.message || e.message); From d796ddacdd491a16c599a39fa66070075aff3c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Habinshuti?= Date: Wed, 28 Oct 2020 13:28:11 +0300 Subject: [PATCH 2/3] Log flutterwave notification regardless of success or failure --- server/src/webhooks/flutterwave.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/webhooks/flutterwave.ts b/server/src/webhooks/flutterwave.ts index ee1aa916..02dbc75d 100644 --- a/server/src/webhooks/flutterwave.ts +++ b/server/src/webhooks/flutterwave.ts @@ -6,12 +6,11 @@ export const flutterwaveRoutes = express.Router(); flutterwaveRoutes.post('/', (req: AppRequest, res) => { const notification = req.body; - console.log('Flutterwave payment notification'); + console.log('Flutterwave payment notification', JSON.stringify(req.body, null, 2)); req.core.transactions.handleProviderNotification(FLUTTERWAVE_PAYMENT_PROVIDER_NAME, notification) .then(_ => res.status(200).send()) .catch(e => { console.error('Flutterwave notification error', e); - console.error('Notification payload', JSON.stringify(req.body, null, 2)); res.status(400).send(); }); }); From 7c3c0219c481a2a72ed6c43a598f5d23bf632365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Habinshuti?= Date: Wed, 28 Oct 2020 13:37:04 +0300 Subject: [PATCH 3/3] Remove unnecessary log --- server/src/core/payment/flutterwave-payment-provider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/core/payment/flutterwave-payment-provider.ts b/server/src/core/payment/flutterwave-payment-provider.ts index 7f7fc555..fb4232c7 100644 --- a/server/src/core/payment/flutterwave-payment-provider.ts +++ b/server/src/core/payment/flutterwave-payment-provider.ts @@ -237,7 +237,6 @@ export class FlutterwavePaymentProvider implements PaymentProvider { const res = await axios.default.post(url, transferArgs, { headers: { Authorization: `Bearer ${this.args.secretKey}`}}); const { data, status } = res.data; - console.log('Flutterwave transfer payload', JSON.stringify(res.data, null, 2)); return { providerTransactionId: data.reference,