feat: release design system engine, studio and component contracts
This commit is contained in:
1 parent
ff1065fb27
commit
3943b946d6
693 files changed
+175546
-3894
No files matched your search
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/payment",
|
||||
"version": "0.8.9",
|
||||
"version": "0.8.13",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -160,8 +160,8 @@ export async function signSandboxWebhook(payload: unknown, secret = "sandbox-sec
|
||||
return { body, signature: await hmac(body, secret) };
|
||||
}
|
||||
|
||||
async function jsonRequest(url: string, init: RequestInit) {
|
||||
const response = await fetch(url, init);
|
||||
async function jsonRequest(url: string, init: RequestInit, transport: typeof fetch = fetch) {
|
||||
const response = await transport(url, init);
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok) throw new Error(`payment gateway request failed (${response.status})`);
|
||||
return data as any;
|
||||
@@ -342,6 +342,8 @@ export interface RazorpayConfig {
|
||||
keySecret: string;
|
||||
webhookSecret: string;
|
||||
theme?: { color?: string };
|
||||
/** Server-only request transport, scoped to this gateway instance. */
|
||||
transport?: typeof fetch;
|
||||
}
|
||||
export interface RazorpayCheckoutResult {
|
||||
orderId: string;
|
||||
@@ -363,10 +365,18 @@ export async function verifyRazorpayCheckoutSignature(
|
||||
export function razorpayGateway(config: RazorpayConfig): PaymentGateway<RazorpayConfig> {
|
||||
const auth = `Basic ${btoa(`${config.keyId}:${config.keySecret}`)}`;
|
||||
const request = (path: string, init: RequestInit = {}) =>
|
||||
jsonRequest(`https://api.razorpay.com/v1${path}`, {
|
||||
...init,
|
||||
headers: { authorization: auth, "content-type": "application/json", ...(init.headers ?? {}) },
|
||||
});
|
||||
jsonRequest(
|
||||
`https://api.razorpay.com/v1${path}`,
|
||||
{
|
||||
...init,
|
||||
headers: {
|
||||
authorization: auth,
|
||||
"content-type": "application/json",
|
||||
...(init.headers ?? {}),
|
||||
},
|
||||
},
|
||||
config.transport,
|
||||
);
|
||||
return defineGateway({
|
||||
id: "razorpay",
|
||||
config,
|
||||
@@ -412,12 +422,13 @@ export function razorpayGateway(config: RazorpayConfig): PaymentGateway<Razorpay
|
||||
async refund(input) {
|
||||
const row = await request(`/payments/${encodeURIComponent(input.intentRef)}/refund`, {
|
||||
method: "POST",
|
||||
headers: { "X-Razorpay-Idempotency": input.idempotencyKey },
|
||||
headers: { "X-Refund-Idempotency": input.idempotencyKey },
|
||||
body: JSON.stringify({ amount: input.amount, notes: { reason: input.reason } }),
|
||||
});
|
||||
return {
|
||||
ref: row.id,
|
||||
status: row.status === "processed" ? "succeeded" : "pending",
|
||||
status:
|
||||
row.status === "processed" ? "succeeded" : row.status === "failed" ? "failed" : "pending",
|
||||
amount: row.amount,
|
||||
raw: row,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { razorpayGateway } from "../src/adapters.ts";
|
||||
test("Razorpay refunds use the provider idempotency header and preserve failed status", async () => {
|
||||
const gateway = razorpayGateway({
|
||||
keyId: "fixture",
|
||||
keySecret: "fixture-secret",
|
||||
webhookSecret: "fixture-webhook",
|
||||
transport: (async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
expect(String(input)).toBe("https://api.razorpay.com/v1/payments/pay_fixture/refund");
|
||||
expect(new Headers(init?.headers).get("X-Refund-Idempotency")).toBe("refund-fixture-key");
|
||||
expect(new Headers(init?.headers).has("X-Razorpay-Idempotency")).toBe(false);
|
||||
expect(JSON.parse(String(init?.body))).toEqual({
|
||||
amount: 100,
|
||||
notes: { reason: "Customer cancellation" },
|
||||
});
|
||||
return Response.json({ id: "rfnd_fixture", amount: 100, status: "failed" });
|
||||
}) as typeof fetch,
|
||||
});
|
||||
expect(
|
||||
(
|
||||
await gateway.refund({
|
||||
intentRef: "pay_fixture",
|
||||
idempotencyKey: "refund-fixture-key",
|
||||
amount: 100,
|
||||
currency: "INR",
|
||||
reason: "Customer cancellation",
|
||||
})
|
||||
).status,
|
||||
).toBe("failed");
|
||||
});
|
||||
test("Razorpay transports remain private to concurrent tenant gateway instances", async () => {
|
||||
const original = globalThis.fetch;
|
||||
const make = (id: string) =>
|
||||
razorpayGateway({
|
||||
keyId: id,
|
||||
keySecret: "fixture-secret",
|
||||
webhookSecret: "fixture-webhook",
|
||||
transport: (async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
expect(String(input)).toBe("https://api.razorpay.com/v1/orders");
|
||||
expect((init!.headers as Record<string, string>).authorization).toBe(
|
||||
"Basic " + btoa(id + ":fixture-secret"),
|
||||
);
|
||||
const value = JSON.parse(String(init!.body));
|
||||
return Response.json({ id: "order_" + id, amount: value.amount, currency: value.currency });
|
||||
}) as typeof fetch,
|
||||
});
|
||||
const input = {
|
||||
id: "fixture-order",
|
||||
subject: "fixture",
|
||||
amount: 100,
|
||||
currency: "INR",
|
||||
idempotencyKey: "fixture-idempotency",
|
||||
};
|
||||
const [a, b] = await Promise.all([
|
||||
make("tenant_a").createIntent(input),
|
||||
make("tenant_b").createIntent(input),
|
||||
]);
|
||||
expect(a.ref).toBe("order_tenant_a");
|
||||
expect(b.ref).toBe("order_tenant_b");
|
||||
expect(globalThis.fetch).toBe(original);
|
||||
});
|
||||
Reference in new issue
Block a user