@tecafrik/africa-payment-sdk
Version:
A single SDK to integrate all african payment providers seamlessly
180 lines (163 loc) • 6 kB
text/typescript
import {
MobileMoneyCheckoutOptions,
CheckoutResult,
PaymentProvider,
RefundOptions,
RefundResult,
CreditCardCheckoutOptions,
RedirectCheckoutOptions,
TransactionStatus,
PaymentMethod,
HandleWebhookOptions,
Currency,
MobileMoneyPayoutOptions,
PayoutResult,
} from "../payment-provider.interface";
import EventEmitter2 from "eventemitter2";
import {
PaymentEventType,
PaymentFailedEvent,
PaymentInitiatedEvent,
PaymentSuccessfulEvent,
} from "../payment-events";
class BogusPaymentProvider implements PaymentProvider {
private eventEmitter?: EventEmitter2;
constructor(private config: BogusPaymentProviderConfig) {}
useEventEmitter(eventEmitter: EventEmitter2) {
this.eventEmitter = eventEmitter;
}
async checkout(
options:
| MobileMoneyCheckoutOptions
| CreditCardCheckoutOptions
| RedirectCheckoutOptions,
isFailure: boolean
): Promise<CheckoutResult> {
if (this.config.instantEvents) {
this.eventEmitter?.emit(PaymentEventType.PAYMENT_INITIATED, {
type: PaymentEventType.PAYMENT_INITIATED,
transactionAmount: options.amount,
transactionCurrency: options.currency,
transactionId: options.transactionId,
transactionReference: `${options.paymentMethod.toLowerCase()}-transaction-reference`,
paymentMethod: PaymentMethod.WAVE,
metadata: options.metadata,
paymentProvider: BogusPaymentProvider.name,
} as PaymentInitiatedEvent);
if (isFailure) {
this.eventEmitter?.emit(PaymentEventType.PAYMENT_FAILED, {
type: PaymentEventType.PAYMENT_FAILED,
transactionAmount: options.amount,
transactionCurrency: options.currency,
transactionId: options.transactionId,
transactionReference: `${options.paymentMethod.toLowerCase()}-transaction-reference`,
paymentMethod: PaymentMethod.WAVE,
metadata: options.metadata,
reason: "Payment failed",
paymentProvider: BogusPaymentProvider.name,
} as PaymentFailedEvent);
} else {
this.eventEmitter?.emit(PaymentEventType.PAYMENT_SUCCESSFUL, {
type: PaymentEventType.PAYMENT_SUCCESSFUL,
transactionAmount: options.amount,
transactionCurrency: options.currency,
transactionId: options.transactionId,
transactionReference: `${options.paymentMethod.toLowerCase()}-transaction-reference`,
paymentMethod: PaymentMethod.WAVE,
metadata: options.metadata,
paymentProvider: BogusPaymentProvider.name,
} as PaymentSuccessfulEvent);
}
}
return {
transactionAmount: options.amount,
transactionCurrency: options.currency,
transactionId: options.transactionId,
transactionReference: `${options.paymentMethod.toLowerCase()}-transaction-reference`,
transactionStatus: TransactionStatus.PENDING,
redirectUrl: isFailure
? options.failureRedirectUrl
: options.successRedirectUrl,
};
}
async checkoutMobileMoney(
options: MobileMoneyCheckoutOptions
): Promise<CheckoutResult> {
let isFailure = false;
if (options.paymentMethod === PaymentMethod.WAVE) {
isFailure = options.customer.phoneNumber.endsWith("13");
} else if (options.paymentMethod === PaymentMethod.ORANGE_MONEY) {
isFailure = options.authorizationCode?.endsWith("13") || false;
}
return this.checkout(options, isFailure);
}
async checkoutCreditCard(
options: CreditCardCheckoutOptions
): Promise<CheckoutResult> {
const isFailure = options.cardNumber?.endsWith("13") || false;
return this.checkout(options, isFailure);
}
async checkoutRedirect(
options: RedirectCheckoutOptions
): Promise<CheckoutResult> {
const isFailure = options.customer.email?.endsWith("failure.com") ?? false;
return this.checkout(options, isFailure);
}
async refund(options: RefundOptions): Promise<RefundResult> {
console.debug("Refunding transaction", options.transactionId, options);
return {
transactionAmount: options.refundedAmount || 0,
transactionCurrency: Currency.XOF,
transactionId: options.transactionId,
transactionReference: `refunded-transaction-reference`,
transactionStatus: TransactionStatus.PENDING,
};
}
async handleWebhook(rawBody: Buffer | string, options: HandleWebhookOptions) {
const body = JSON.parse(
rawBody.toString()
) as BogusPaymentProviderWebhookBody;
const isFailure = body.success === false;
const eventType = isFailure
? PaymentEventType.PAYMENT_FAILED
: PaymentEventType.PAYMENT_SUCCESSFUL;
const event = {
type: eventType,
transactionAmount: body.amount,
transactionCurrency: body.currency,
transactionId: body.transactionId,
transactionReference: body.transactionReference,
paymentMethod: body.paymentMethod,
metadata: body.metadata,
paymentProvider: BogusPaymentProvider.name,
} as PaymentSuccessfulEvent | PaymentFailedEvent;
this.eventEmitter?.emit(eventType, event);
return event;
}
payoutMobileMoney(options: MobileMoneyPayoutOptions): Promise<PayoutResult> {
console.debug("Paying out mobile money", options);
return Promise.resolve({
transactionAmount: options.amount,
transactionCurrency: options.currency,
transactionId: options.transactionId,
transactionReference: `payout-transaction-reference`,
transactionStatus: TransactionStatus.SUCCESS,
});
}
}
type BogusPaymentProviderConfig = {
/**
* Whether to emit events instantly or wait for the webhook to be called
*/
instantEvents: boolean;
};
export type BogusPaymentProviderWebhookBody = {
success: boolean;
amount: number;
transactionId: string;
transactionReference: string;
paymentMethod: PaymentMethod;
currency: Currency;
metadata?: Record<string, unknown>;
};
export default BogusPaymentProvider;