238 lines
7.6 KiB
TypeScript
238 lines
7.6 KiB
TypeScript
import { afterEach, expect, test } from "bun:test";
|
|
import {
|
|
exerciseGatewayContract,
|
|
paypalGateway,
|
|
razorpayGateway,
|
|
sandboxGateway,
|
|
signSandboxWebhook,
|
|
stripeGateway,
|
|
type GatewayIntent,
|
|
} from "../src/index.ts";
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
afterEach(() => {
|
|
globalThis.fetch = originalFetch;
|
|
});
|
|
const json = (value: unknown, status = 200) =>
|
|
new Response(JSON.stringify(value), { status, headers: { "content-type": "application/json" } });
|
|
const encoder = new TextEncoder();
|
|
async function signature(body: string, secret: string) {
|
|
const key = await crypto.subtle.importKey(
|
|
"raw",
|
|
encoder.encode(secret),
|
|
{ name: "HMAC", hash: "SHA-256" },
|
|
false,
|
|
["sign"],
|
|
);
|
|
return [...new Uint8Array(await crypto.subtle.sign("HMAC", key, encoder.encode(body)))]
|
|
.map((x) => x.toString(16).padStart(2, "0"))
|
|
.join("");
|
|
}
|
|
const input = {
|
|
id: "contract-pay",
|
|
subject: "contract-user",
|
|
amount: 1000,
|
|
currency: "USD",
|
|
idempotencyKey: "contract-init",
|
|
};
|
|
|
|
test("shared executable contract drives sandbox", async () => {
|
|
const adapter = sandboxGateway();
|
|
const report = await exerciseGatewayContract(adapter, {
|
|
input,
|
|
async webhook(intent) {
|
|
const signed = await signSandboxWebhook(
|
|
{
|
|
id: "sb-event",
|
|
intentRef: intent.ref,
|
|
type: "payment.captured",
|
|
status: "captured",
|
|
occurredAt: 100,
|
|
},
|
|
"contract-webhook-secret",
|
|
);
|
|
return new Request("https://test", {
|
|
method: "POST",
|
|
headers: { "x-wrnexus-signature": signed.signature },
|
|
body: signed.body,
|
|
});
|
|
},
|
|
});
|
|
expect(report).toEqual({ ok: true, issues: [] });
|
|
});
|
|
|
|
test("shared executable contract drives Stripe through its HTTP adapter", async () => {
|
|
globalThis.fetch = (async (value, init) => {
|
|
const url = String(value),
|
|
method = init?.method ?? "GET";
|
|
if (
|
|
url.includes("/payment_intents") &&
|
|
method === "POST" &&
|
|
!url.endsWith("/capture") &&
|
|
!url.endsWith("/cancel")
|
|
) {
|
|
const body = String(init?.body);
|
|
const id = body.includes("cancel") ? "pi_cancel" : "pi_contract";
|
|
return json({
|
|
id,
|
|
status: "requires_capture",
|
|
amount: 1000,
|
|
amount_received: 1000,
|
|
currency: "usd",
|
|
client_secret: "pi_client_safe",
|
|
});
|
|
}
|
|
if (url.endsWith("/capture"))
|
|
return json({
|
|
id: "pi_contract",
|
|
status: "succeeded",
|
|
amount_received: 1000,
|
|
currency: "usd",
|
|
});
|
|
if (url.endsWith("/cancel"))
|
|
return json({ id: "pi_cancel", status: "canceled", amount: 1000, currency: "usd" });
|
|
if (url.includes("/payment_intents/"))
|
|
return json({ id: "pi_contract", status: "requires_capture", amount: 1000, currency: "usd" });
|
|
if (url.endsWith("/refunds")) return json({ id: "re_1", status: "succeeded", amount: 500 });
|
|
if (url.endsWith("/customers")) return json({ id: "cus_1" });
|
|
if (url.includes("/payment_methods?")) return json({ data: [] });
|
|
if (url.includes("/payment_methods/") && url.endsWith("/attach"))
|
|
return json({
|
|
id: "pm_1",
|
|
card: { brand: "visa", last4: "4242", exp_month: 1, exp_year: 2030 },
|
|
});
|
|
if (url.includes("/payment_methods/") && url.endsWith("/detach")) return json({ id: "pm_1" });
|
|
return json({}, 404);
|
|
}) as typeof fetch;
|
|
const adapter = stripeGateway({
|
|
secretKey: "sk_test_secret",
|
|
publishableKey: "pk_test",
|
|
webhookSecret: "contract-webhook-secret",
|
|
});
|
|
const report = await exerciseGatewayContract(adapter, {
|
|
input,
|
|
async webhook(intent) {
|
|
const body = JSON.stringify({
|
|
id: "evt_1",
|
|
created: 100,
|
|
data: { object: { id: intent.ref, status: "succeeded" } },
|
|
type: "payment_intent.succeeded",
|
|
});
|
|
return new Request("https://test", {
|
|
method: "POST",
|
|
headers: {
|
|
"stripe-signature": `t=100,v1=${await signature(`100.${body}`, "contract-webhook-secret")}`,
|
|
},
|
|
body,
|
|
});
|
|
},
|
|
});
|
|
expect(report).toEqual({ ok: true, issues: [] });
|
|
});
|
|
|
|
test("shared executable contract drives Razorpay through its HTTP adapter", async () => {
|
|
globalThis.fetch = (async (value, init) => {
|
|
const url = String(value),
|
|
method = init?.method ?? "GET";
|
|
if (url.endsWith("/orders") && method === "POST") {
|
|
const body = String(init?.body);
|
|
return json({
|
|
id: body.includes("cancel") ? "order_cancel" : "order_1",
|
|
status: "created",
|
|
amount: 1000,
|
|
currency: "USD",
|
|
});
|
|
}
|
|
if (url.includes("/orders/"))
|
|
return json({
|
|
id: url.endsWith("order_cancel") ? "order_cancel" : "order_1",
|
|
status: "created",
|
|
amount: 1000,
|
|
currency: "USD",
|
|
});
|
|
if (url.includes("/refund")) return json({ id: "rfnd_1", status: "processed", amount: 500 });
|
|
return json({}, 404);
|
|
}) as typeof fetch;
|
|
const adapter = razorpayGateway({
|
|
keyId: "rzp_test",
|
|
keySecret: "key_secret",
|
|
webhookSecret: "contract-webhook-secret",
|
|
});
|
|
const report = await exerciseGatewayContract(adapter, {
|
|
input,
|
|
async webhook(intent) {
|
|
const body = JSON.stringify({
|
|
id: "rz_evt",
|
|
event: "payment.captured",
|
|
payload: {
|
|
payment: {
|
|
entity: { id: "pay_1", order_id: intent.ref, status: "captured", created_at: 100 },
|
|
},
|
|
},
|
|
});
|
|
return new Request("https://test", {
|
|
method: "POST",
|
|
headers: { "x-razorpay-signature": await signature(body, "contract-webhook-secret") },
|
|
body,
|
|
});
|
|
},
|
|
});
|
|
expect(report).toEqual({ ok: true, issues: [] });
|
|
});
|
|
|
|
test("shared executable contract drives PayPal through its HTTP adapter", async () => {
|
|
globalThis.fetch = (async (value, init) => {
|
|
const url = String(value),
|
|
method = init?.method ?? "GET";
|
|
if (url.endsWith("/oauth2/token")) return json({ access_token: "access" });
|
|
if (url.endsWith("/checkout/orders") && method === "POST") {
|
|
const body = String(init?.body);
|
|
return json({
|
|
id: body.includes("cancel") ? "PP-CANCEL" : "PP-ORDER",
|
|
status: "CREATED",
|
|
links: [{ rel: "approve", href: "https://paypal.test/approve" }],
|
|
});
|
|
}
|
|
if (url.includes("/checkout/orders/"))
|
|
return json({
|
|
id: url.endsWith("PP-CANCEL") ? "PP-CANCEL" : "PP-ORDER",
|
|
status: "CREATED",
|
|
purchase_units: [{ amount: { value: "10.00", currency_code: "USD" } }],
|
|
});
|
|
if (url.includes("/refund")) return json({ id: "PP-REFUND", status: "COMPLETED" });
|
|
if (url.endsWith("verify-webhook-signature")) return json({ verification_status: "SUCCESS" });
|
|
return json({}, 404);
|
|
}) as typeof fetch;
|
|
const adapter = paypalGateway({
|
|
clientId: "client",
|
|
clientSecret: "paypal_secret",
|
|
webhookId: "hook",
|
|
});
|
|
const report = await exerciseGatewayContract(adapter, {
|
|
input,
|
|
webhook(intent: GatewayIntent) {
|
|
return new Request("https://test", {
|
|
method: "POST",
|
|
headers: {
|
|
"paypal-auth-algo": "SHA256withRSA",
|
|
"paypal-cert-url": "https://paypal.test/cert",
|
|
"paypal-transmission-id": "tx",
|
|
"paypal-transmission-sig": "sig",
|
|
"paypal-transmission-time": "time",
|
|
},
|
|
body: JSON.stringify({
|
|
id: "WH-1",
|
|
create_time: "2026-08-23T00:00:00Z",
|
|
event_type: "PAYMENT.CAPTURE.COMPLETED",
|
|
resource: {
|
|
id: "CAPTURE",
|
|
status: "COMPLETED",
|
|
supplementary_data: { related_ids: { order_id: intent.ref } },
|
|
},
|
|
}),
|
|
});
|
|
},
|
|
});
|
|
expect(report).toEqual({ ok: true, issues: [] });
|
|
});
|