Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(payment): verify payment with orderId #74

Merged
merged 1 commit into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/redis.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
```
Expand All @@ -13,4 +14,4 @@ redis[s]://[[username][:password]@][host][:port][/db-number]

The keys that are currently used

- `payment:membership:<userId>` stores a Redis hash that stores a `paymentId` and `createdAt` timestamp.
- `payment:membership:<userId>` stores a Redis hash that stores a `orderId` and `createdAt` timestamp.
9 changes: 3 additions & 6 deletions src/app/api/payment/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
},
Expand All @@ -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`
Expand Down
14 changes: 8 additions & 6 deletions src/server/verify-membership-payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}

Expand Down
Loading