Skip to content

Commit

Permalink
test(billing): functional tests for Stripe webhook
Browse files Browse the repository at this point in the history
  • Loading branch information
József Kozma committed Jan 29, 2025
1 parent bf007c8 commit 0d87729
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ export class StripeWebhookService {
return;
}

const checkoutSession = await this.stripe.checkout.sessions.retrieve(event.data.object.id, {
const checkoutSession = await this.stripe.checkout.sessions.retrieve(sessionId, {
expand: ["line_items"]
});

if (checkoutSession.payment_status !== "unpaid") {
await this.refillService.topUpWallet(checkoutSession.amount_subtotal, checkoutSessionCache.userId);
await this.checkoutSessionRepository.deleteBy({ sessionId: event.data.object.id });
await this.checkoutSessionRepository.deleteBy({ sessionId });
} else {
this.logger.error({ event: "PAYMENT_NOT_COMPLETED", sessionId });
}
Expand Down
132 changes: 132 additions & 0 deletions apps/api/test/functional/stripe-webhook.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { UserSetting } from "@akashnetwork/database/dbSchemas/user";
import { faker } from "@faker-js/faker";
import { eq } from "drizzle-orm";
import nock from "nock";
import stripe from "stripe";
import { container } from "tsyringe";

import { app } from "@src/app";
import { CheckoutSessionRepository } from "@src/billing/repositories";
import { ApiPgDatabase, POSTGRES_DB, resolveTable } from "@src/core";

import { DbTestingService } from "@test/services/db-testing.service";

jest.setTimeout(20000);

describe("Stripe webhook", () => {
const userWalletsTable = resolveTable("UserWallets");
const db = container.resolve<ApiPgDatabase>(POSTGRES_DB);
const userWalletsQuery = db.query.UserWallets;
const checkoutSessionRepository = container.resolve(CheckoutSessionRepository);
const dbService = container.resolve(DbTestingService);

afterEach(async () => {
await dbService.cleanAll();
});

const generatePayload = (sessionId: string, eventType: string) =>
JSON.stringify({
data: {
object: {
id: sessionId
}
},
type: eventType
});

const getWebhookResponse = async (sessionId: string, eventType: string) => {
const payload = generatePayload(sessionId, eventType);

return await app.request("/v1/stripe-webhook", {
method: "POST",
body: payload,
headers: new Headers({
"Content-Type": "text/plain",
"Stripe-Signature": stripe.webhooks.generateTestHeaderString({
payload,
secret: process.env.STRIPE_WEBHOOK_SECRET
})
})
});
};

describe("POST /v1/stripe-webhook", () => {
["checkout.session.completed", "checkout.session.async_payment_succeeded"].forEach(eventType => {
it(`tops up wallet and drops session from cache for event ${eventType}`, async () => {
const sessionId = faker.string.uuid();
const userId = faker.string.uuid();
await UserSetting.create({ id: userId });
await checkoutSessionRepository.create({
sessionId,
userId
});
nock("https://api.stripe.com").get(`/v1/checkout/sessions/${sessionId}?expand[0]=line_items`).reply(200, {
payment_status: "paid",
amount_subtotal: 100
});

const webhookResponse = await getWebhookResponse(sessionId, eventType);

const userWallet = await userWalletsQuery.findFirst({ where: eq(userWalletsTable.userId, userId) });
const checkoutSession = await checkoutSessionRepository.findOneBy({
sessionId
});
expect(webhookResponse.status).toBe(200);
expect(userWallet).toMatchObject({
userId,
deploymentAllowance: `1000000.00`,
isTrialing: false
});
expect(checkoutSession).toBeUndefined();
});
});

it("does not top up wallet and keeps cache if the payment is not done", async () => {
const sessionId = faker.string.uuid();
const userId = faker.string.uuid();
await UserSetting.create({ id: userId });
await checkoutSessionRepository.create({
sessionId,
userId
});
nock("https://api.stripe.com").get(`/v1/checkout/sessions/${sessionId}?expand[0]=line_items`).reply(200, {
payment_status: "unpaid",
amount_subtotal: 100
});

const webhookResponse = await getWebhookResponse(sessionId, "checkout.session.completed");

const userWallet = await userWalletsQuery.findFirst({ where: eq(userWalletsTable.userId, userId) });
const checkoutSession = await checkoutSessionRepository.findOneBy({
sessionId
});
expect(webhookResponse.status).toBe(200);
expect(userWallet).toBeUndefined();
expect(checkoutSession).toMatchObject({
sessionId
});
});

it("does not top up wallet and keeps cache if the event is different", async () => {
const sessionId = faker.string.uuid();
const userId = faker.string.uuid();
await UserSetting.create({ id: userId });
await checkoutSessionRepository.create({
sessionId,
userId
});

const webhookResponse = await getWebhookResponse(sessionId, "checkout.session.not-found");

const userWallet = await userWalletsQuery.findFirst({ where: eq(userWalletsTable.userId, userId) });
const checkoutSession = await checkoutSessionRepository.findOneBy({
sessionId
});
expect(webhookResponse.status).toBe(200);
expect(userWallet).toBeUndefined();
expect(checkoutSession).toMatchObject({
sessionId
});
});
});
});

0 comments on commit 0d87729

Please sign in to comment.