Zhou
Zhou MingxuanHead of R&D
Quality gate blocked
  • 2 critical findings exceed the limit of 0

Payment refund callback connects to third-party gateways

hanpay-apifeat/refund-callbackZhou Mubai · 2026-06-05 14:23
src/payment/refundCallback.ts+102
11 export async function handleRefundCallback(req: Request) {
2- const { orderId, amount } = req.body;
2+ const { orderId, amount, sign } = req.body;
A
AI reviewerAICritical
Missing idempotency: the payment gateway may retry a callback, allowing the same orderId to trigger duplicate refunds or notifications. Check the current order state and enforce a unique callback identifier before processing.
3+ // Verify the signature
4+ if (!verifySign(req.body, sign)) {
5+ return { ok: false, msg: "Signature validation failed" };
6+ }
A
AI reviewerAIMinor
A failed signature check should return an explicit 4xx response and emit a security alert. Returning only a business object can be ignored by the gateway.
7+ // Vulnerable: directly concatenates orderId into SQL
38 await db.query(
4- 'UPDATE orders SET status = "refunded" WHERE id = ' + orderId
9+ 'UPDATE orders SET status = "refunded", refund_amount = ' +
10+ amount +
11+ ' WHERE id = ' + orderId
A
AI reviewerAICritical
SQL injection: orderId and amount are concatenated directly into an UPDATE statement, so an attacker could inject a payload such as '1; DROP TABLE orders'. Use parameterized queries.
Suggested change
+23
11 await db.query(
2- 'UPDATE orders SET status = "refunded", refund_amount = ' +
3- amount +
4- ' WHERE id = ' + orderId
2+ 'UPDATE orders SET status = ?, refund_amount = ? WHERE id = ?',
3+ ['refunded', amount, orderId]
54 );
512 );
13+ await notifyUser(orderId);
614 return { ok: true };
715 }
A
AI reviewerAIMajor
Notification delivery is awaited without an error boundary, so a notification outage can fail an otherwise valid callback. Decouple the side effect, record failures, and retry them separately.