fix: return wechat pay webhook validation failures
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { Context } from "hono";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
|
||||
import {
|
||||
DomesticPaymentIntentService,
|
||||
@@ -25,10 +26,22 @@ export function createWeChatPayWebhookHandler({
|
||||
return async (c: Context<HonoEnv>) => {
|
||||
const ctx = c.get("ctx");
|
||||
const body = await c.req.text();
|
||||
const notification = await adapter.parseNotification({
|
||||
body,
|
||||
headers: c.req.raw.headers,
|
||||
});
|
||||
let notification: Awaited<ReturnType<PaymentProcessorAdapter["parseNotification"]>>;
|
||||
|
||||
try {
|
||||
notification = await adapter.parseNotification({
|
||||
body,
|
||||
headers: c.req.raw.headers,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
code: "FAIL",
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
StatusCodes.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
await intentService.processParsedNotification({
|
||||
ctx,
|
||||
|
||||
@@ -92,4 +92,100 @@ describe("createWeChatPayWebhookHandler", () => {
|
||||
notification,
|
||||
});
|
||||
});
|
||||
|
||||
test("returns a WeChat FAIL response with HTTP 400 when notification parsing fails", async () => {
|
||||
const body = JSON.stringify({ id: "notify_bad" });
|
||||
const adapter: PaymentProcessorAdapter = {
|
||||
provider: DomesticPaymentProvider.WeChatPay,
|
||||
createPayment: async () => {
|
||||
throw new Error("createPayment is not used in this test");
|
||||
},
|
||||
queryPayment: async () => {
|
||||
throw new Error("queryPayment is not used in this test");
|
||||
},
|
||||
closePayment: async () => {
|
||||
throw new Error("closePayment is not used in this test");
|
||||
},
|
||||
parseNotification: mock(async () => {
|
||||
throw new Error("Invalid WeChat notification signature");
|
||||
}),
|
||||
};
|
||||
const processParsedNotification = mock(async () => {
|
||||
throw new Error("processParsedNotification must not be called");
|
||||
});
|
||||
const app = new Hono<HonoEnv>();
|
||||
app.use("*", async (c, next) => {
|
||||
c.set("ctx", ctx as HonoEnv["Variables"]["ctx"]);
|
||||
await next();
|
||||
});
|
||||
app.post(
|
||||
"/webhooks/wechatpay",
|
||||
createWeChatPayWebhookHandler({
|
||||
adapter,
|
||||
intentService: { processParsedNotification },
|
||||
}),
|
||||
);
|
||||
|
||||
const response = await app.request("/webhooks/wechatpay", {
|
||||
method: "POST",
|
||||
body,
|
||||
headers: {
|
||||
"Wechatpay-Signature": "bad_signature",
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
await expect(response.json()).resolves.toEqual({
|
||||
code: "FAIL",
|
||||
message: "Invalid WeChat notification signature",
|
||||
});
|
||||
expect(processParsedNotification).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("returns a server error after a parsed notification hits a service failure", async () => {
|
||||
const notification = {
|
||||
providerEventId: "notify_123",
|
||||
idempotencyKey: "wechatpay:notification:notify_123",
|
||||
eventType: DomesticPaymentEventType.WebhookPaid,
|
||||
providerOrderId: "dp_order_123",
|
||||
providerTransactionId: "wx_txn_123",
|
||||
status: DomesticPaymentIntentStatus.Paid,
|
||||
paidAt: 1_774_001_860_000,
|
||||
rawPayload: { id: "notify_123" },
|
||||
parsedPayload: { out_trade_no: "dp_order_123" },
|
||||
};
|
||||
const adapter: PaymentProcessorAdapter = {
|
||||
provider: DomesticPaymentProvider.WeChatPay,
|
||||
createPayment: async () => {
|
||||
throw new Error("createPayment is not used in this test");
|
||||
},
|
||||
queryPayment: async () => {
|
||||
throw new Error("queryPayment is not used in this test");
|
||||
},
|
||||
closePayment: async () => {
|
||||
throw new Error("closePayment is not used in this test");
|
||||
},
|
||||
parseNotification: mock(async () => notification),
|
||||
};
|
||||
const processParsedNotification = mock(async () => {
|
||||
throw new Error("database unavailable");
|
||||
});
|
||||
const handler = createWeChatPayWebhookHandler({
|
||||
adapter,
|
||||
intentService: { processParsedNotification },
|
||||
});
|
||||
const app = new Hono<HonoEnv>();
|
||||
app.use("*", async (c, next) => {
|
||||
c.set("ctx", ctx as HonoEnv["Variables"]["ctx"]);
|
||||
await next();
|
||||
});
|
||||
app.post("/webhooks/wechatpay", handler);
|
||||
|
||||
const response = await app.request("/webhooks/wechatpay", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ id: "notify_123" }),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user