Skip to content

Commit

Permalink
Merge pull request #171 from alphamanuscript/170-fix-flw-transfer
Browse files Browse the repository at this point in the history
Fix flutterwave transfers
  • Loading branch information
habbes authored Oct 28, 2020
2 parents 912066e + 7c3c021 commit 79e7799
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 12 deletions.
78 changes: 68 additions & 10 deletions server/src/core/payment/flutterwave-payment-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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;
Expand All @@ -51,7 +76,7 @@ interface FlutterwaveTransactionInfo {
interface FlutterwaveNotification {
event: string;
'event.type': string;
data: FlutterwaveTransactionInfo;
data: FlutterwaveTransactionInfo | FlutterwaveTransferInfo;
}

interface FlutterwaveTransactionResponse {
Expand All @@ -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,
Expand All @@ -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) {
Expand Down Expand Up @@ -134,7 +186,12 @@ export class FlutterwavePaymentProvider implements PaymentProvider {
async handlePaymentNotification(payload: any): Promise<ProviderTransactionInfo> {
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<ProviderTransactionInfo> {
Expand Down Expand Up @@ -177,14 +234,15 @@ export class FlutterwavePaymentProvider implements PaymentProvider {

try {
const url = getUrl(`/transfers`);
const res = await axios.default.post<FlutterwaveTransactionResponse>(url, transferArgs, { headers: { Authorization: `Bearer ${this.args.secretKey}`}});
const res = await axios.default.post<FlutterwaveInitiateTransferResponse>(url, transferArgs, { headers: { Authorization: `Bearer ${this.args.secretKey}`}});

const { data, status } = res.data;

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);
Expand Down
3 changes: 1 addition & 2 deletions server/src/webhooks/flutterwave.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});

0 comments on commit 79e7799

Please sign in to comment.