From 3d6144222bc2bd45649ca127bff27760f8a234b1 Mon Sep 17 00:00:00 2001 From: "Yeyang (Justin) Sun" Date: Mon, 19 Feb 2024 03:43:34 +1030 Subject: [PATCH] fix(payment): verify payment with `orderId` (#74) --- docs/redis.md | 3 ++- src/app/api/payment/route.ts | 9 +++------ src/server/verify-membership-payment.ts | 14 ++++++++------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/redis.md b/docs/redis.md index e5d961dc..d41497a7 100644 --- a/docs/redis.md +++ b/docs/redis.md @@ -5,6 +5,7 @@ We use Redis as a cache to store things like payment IDs when membership payment The [`node-redis`](https://www.npmjs.com/package/redis) library is used for communicating to the Redis server. We are currently using Vercel KV as our Redis instance. Note that when setting `REDIS_URI` in `.env.local`, you should be using `rediss://` rather than `redis://` as Vercel KV mandates that TLS be used. Otherwise, you will get [Error read ECONNRESET](https://vercel.com/docs/storage/vercel-kv/vercel-kv-error-codes#error-read-econnreset). The `REDIS_URI` environment variable should follow the Redis URI syntax: + ``` redis[s]://[[username][:password]@][host][:port][/db-number] ``` @@ -13,4 +14,4 @@ redis[s]://[[username][:password]@][host][:port][/db-number] The keys that are currently used -- `payment:membership:` stores a Redis hash that stores a `paymentId` and `createdAt` timestamp. +- `payment:membership:` stores a Redis hash that stores a `orderId` and `createdAt` timestamp. diff --git a/src/app/api/payment/route.ts b/src/app/api/payment/route.ts index a57894f4..aac9da3c 100644 --- a/src/app/api/payment/route.ts +++ b/src/app/api/payment/route.ts @@ -48,7 +48,7 @@ export async function POST(request: Request) { idempotencyKey: crypto.randomUUID(), description: 'Payment made from CS Club website', order: { - locationId: env.SQUARE_LOCATION_ID!, + locationId: env.SQUARE_LOCATION_ID, customerId: reqBody.data.customerId, lineItems: [lineItem], }, @@ -72,12 +72,9 @@ export async function POST(request: Request) { if (reqBody.data.product === 'membership') { // Add Clerk ID and payment ID to Redis cache - const paymentId = resp.result.paymentLink?.id ?? ''; + const orderId = resp.result.paymentLink?.orderId ?? ''; const createdAt = resp.result.paymentLink?.createdAt ?? ''; - await redisClient.hSet(`payment:membership:${user.id}`, { - paymentId: paymentId, - createdAt: createdAt, - }); + await redisClient.hSet(`payment:membership:${user.id}`, { orderId, createdAt }); } // The URL to direct the user is accessed from `url` and `long_url` diff --git a/src/server/verify-membership-payment.ts b/src/server/verify-membership-payment.ts index 38295e3c..dd1cd24b 100644 --- a/src/server/verify-membership-payment.ts +++ b/src/server/verify-membership-payment.ts @@ -19,16 +19,18 @@ export const verifyMembershipPayment = async (clerkId: string) => { return { paid: true as const, membershipExpiresAt: member.membershipExpiresAt }; } - const paymentId = await redisClient.hGet(`payment:membership:${clerkId}`, 'paymentId'); - if (!paymentId) { + const orderId = await redisClient.hGet(`payment:membership:${clerkId}`, 'orderId'); + if (!orderId) { // Membership payment for the user does not exist return { paid: false as const }; } - const resp = await squareClient.checkoutApi.retrievePaymentLink(paymentId); - const respFields = resp.result; - if (!respFields.paymentLink || respFields.paymentLink.id !== paymentId) { - // Payment has not been made + try { + const orderRes = await squareClient.ordersApi.retrieveOrder(orderId); + if (orderRes.result.order?.state !== 'COMPLETED') { + return { paid: false as const }; + } + } catch { return { paid: false as const }; }