A payment-provider interface so MonCash, NatCash and cards are interchangeable
asked 4 months ago627 viewsen
I'm starting with MonCash, adding NatCash when Natcom gives us access, and maybe cards later for the diaspora. What interface should the rest of my code depend on?
AI diagnosis
AI-generated. Not an answer — the community below confirms or corrects it. Always verify before relying on it.
1 Answer
Keep the interface small and model what your business needs, not any provider's API:
export type PaymentStatus = "PENDING" | "SUCCEEDED" | "FAILED";
export interface PaymentProvider {
readonly id: "MONCASH" | "NATCASH" | "CARD";
/** Start a payment for one attempt. Returns where to send the customer (or instructions). */
start(input: { attemptId: string; amountHtgCents: number; customerPhone?: string }):
Promise<{ kind: "redirect"; url: string } | { kind: "instructions"; text: string }>;
/** Ask the provider, server to server, what really happened. Never trust the browser. */
status(attemptId: string): Promise<{
status: PaymentStatus;
providerReference?: string;
amountHtgCents?: number;
}>;
}
export const providers: Record<PaymentProvider["id"], PaymentProvider> = {
MONCASH: new MonCashProvider(),
NATCASH: new NatCashProvider(),
CARD: new CardProvider(),
};
One confirmation routine serves every provider:
export async function confirmAttempt(attemptId: string) {
const attempt = await db.paymentAttempt.findUniqueOrThrow({ where: { id: attemptId } });
const result = await providers[attempt.provider].status(attempt.id);
if (result.status !== "SUCCEEDED") return result.status;
if (result.amountHtgCents !== attempt.amountHtgCents) {
await flagForReview(attempt.id, "amount mismatch");
return "FAILED";
}
await markPaidOnce(attempt.orderId, attempt.provider, result.providerReference!);
return "SUCCEEDED";
}
Why kind: "instructions": some providers, or a manual fallback, don't redirect. They tell the customer to dial a USSD menu or send to a merchant number. The UI handles both without knowing which provider it is.
Write a fake provider too. Your tests and local development use it, and it doubles as living documentation of the contract.
Sign in to tell others whether this worked.
Sign in and verify your email to post an answer.