feat(payment): verify Razorpay checkout callbacks

This commit is contained in:
Clintchiz committed 2026-08-28 09:35:30 +05:30
1 parent b1064d442a
commit 10771f1a2b
4 files changed
+36 -2

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/payment",
"version": "0.8.1",
"version": "0.8.2",
"private": true,
"type": "module",
"main": "src/index.ts",
+17
View File
@@ -343,6 +343,23 @@ export interface RazorpayConfig {
webhookSecret: string;
theme?: { color?: string };
}
export interface RazorpayCheckoutResult {
orderId: string;
paymentId: string;
signature: string;
}
/** Verify the signed result returned by Razorpay Standard Checkout. */
export async function verifyRazorpayCheckoutSignature(
result: RazorpayCheckoutResult,
keySecret: string,
) {
if (!result.orderId || !result.paymentId || !result.signature || !keySecret) return false;
return equal(
result.signature.toLowerCase(),
await hmac(`${result.orderId}|${result.paymentId}`, keySecret),
);
}
export function razorpayGateway(config: RazorpayConfig): PaymentGateway<RazorpayConfig> {
const auth = `Basic ${btoa(`${config.keyId}:${config.keySecret}`)}`;
const request = (path: string, init: RequestInit = {}) =>
@@ -3,6 +3,7 @@ import {
exerciseGatewayContract,
paypalGateway,
razorpayGateway,
verifyRazorpayCheckoutSignature,
sandboxGateway,
signSandboxWebhook,
stripeGateway,
@@ -180,6 +181,22 @@ test("shared executable contract drives Razorpay through its HTTP adapter", asyn
expect(report).toEqual({ ok: true, issues: [] });
});
test("Razorpay checkout signatures are verified without trusting browser data", async () => {
const secret = "checkout-secret";
const orderId = "order_1";
const paymentId = "pay_1";
const signed = await signature(`${orderId}|${paymentId}`, secret);
expect(
await verifyRazorpayCheckoutSignature({ orderId, paymentId, signature: signed }, secret),
).toBe(true);
expect(
await verifyRazorpayCheckoutSignature(
{ orderId, paymentId: "pay_tampered", signature: signed },
secret,
),
).toBe(false);
});
test("shared executable contract drives PayPal through its HTTP adapter", async () => {
globalThis.fetch = (async (value, init) => {
const url = String(value),