From a30a864ed23fe583a1d3b0923060dc71a611eb95 Mon Sep 17 00:00:00 2001 From: Bilal Godil Date: Tue, 19 Aug 2025 17:43:35 -0700 Subject: [PATCH 1/7] type fix --- .../src/lib/stack-app/apps/implementations/client-app-impl.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts b/packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts index 97e822d999..92400d4933 100644 --- a/packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts +++ b/packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts @@ -2,7 +2,6 @@ import { WebAuthnError, startAuthentication, startRegistration } from "@simplewe import { KnownErrors, StackClientInterface } from "@stackframe/stack-shared"; import { ContactChannelsCrud } from "@stackframe/stack-shared/dist/interface/crud/contact-channels"; import { CurrentUserCrud } from "@stackframe/stack-shared/dist/interface/crud/current-user"; -import { NotificationPreferenceCrud } from "@stackframe/stack-shared/dist/interface/crud/notification-preferences"; import { TeamApiKeysCrud, UserApiKeysCrud, teamApiKeysCreateOutputSchema, userApiKeysCreateOutputSchema } from "@stackframe/stack-shared/dist/interface/crud/project-api-keys"; import { ProjectPermissionsCrud } from "@stackframe/stack-shared/dist/interface/crud/project-permissions"; import { ClientProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects"; From d6397faa18dd101410b3ab14ed188ae974f33710 Mon Sep 17 00:00:00 2001 From: Bilal Godil Date: Wed, 20 Aug 2025 11:00:14 -0700 Subject: [PATCH 2/7] custom customer type --- .../migration.sql | 15 ++++ apps/backend/prisma/schema.prisma | 22 ++--- .../[customer_id]/[item_id]/route.ts | 11 ++- .../[item_id]/update-quantity/route.ts | 14 ++- .../purchases/create-purchase-url/route.ts | 12 ++- .../accept/verification-code-handler.tsx | 1 + apps/backend/src/lib/payments.tsx | 75 ++-------------- .../[projectId]/payments/page-client.tsx | 14 ++- .../data-table/payment-item-table.tsx | 8 +- apps/e2e/tests/backend/backend-helpers.ts | 1 + .../v1/payments/create-purchase-url.test.ts | 15 +++- .../endpoints/api/v1/payments/items.test.ts | 69 +++++++++++---- .../api/v1/payments/purchase-session.test.ts | 1 + apps/e2e/tests/js/payments.test.ts | 61 ++++++++++++- .../src/interface/admin-interface.ts | 15 ---- .../src/interface/client-interface.ts | 36 ++++++-- .../src/interface/server-interface.ts | 36 +++++--- packages/stack-shared/src/known-errors.tsx | 4 +- packages/stack-shared/src/schema-fields.ts | 2 +- .../apps/implementations/admin-app-impl.ts | 16 ++-- .../apps/implementations/client-app-impl.ts | 49 +++++++++-- .../apps/implementations/server-app-impl.ts | 87 +++++++++++++++---- .../stack-app/apps/interfaces/admin-app.ts | 7 +- .../stack-app/apps/interfaces/client-app.ts | 7 ++ .../stack-app/apps/interfaces/server-app.ts | 11 ++- 25 files changed, 396 insertions(+), 193 deletions(-) create mode 100644 apps/backend/prisma/migrations/20250820164831_custom_customer_types/migration.sql rename apps/backend/src/app/api/latest/payments/items/{ => [customer_type]}/[customer_id]/[item_id]/route.ts (81%) rename apps/backend/src/app/api/latest/payments/items/{ => [customer_type]}/[customer_id]/[item_id]/update-quantity/route.ts (82%) diff --git a/apps/backend/prisma/migrations/20250820164831_custom_customer_types/migration.sql b/apps/backend/prisma/migrations/20250820164831_custom_customer_types/migration.sql new file mode 100644 index 0000000000..2e6eb8bfca --- /dev/null +++ b/apps/backend/prisma/migrations/20250820164831_custom_customer_types/migration.sql @@ -0,0 +1,15 @@ +/* + Warnings: + + - Added the required column `customerType` to the `ItemQuantityChange` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterEnum +ALTER TYPE "CustomerType" ADD VALUE 'CUSTOM'; + +-- AlterTable +ALTER TABLE "ItemQuantityChange" ADD COLUMN "customerType" "CustomerType" NOT NULL, +ALTER COLUMN "customerId" SET DATA TYPE TEXT; + +-- AlterTable +ALTER TABLE "Subscription" ALTER COLUMN "customerId" SET DATA TYPE TEXT; diff --git a/apps/backend/prisma/schema.prisma b/apps/backend/prisma/schema.prisma index c2f8ff2c4f..3d8320aecf 100644 --- a/apps/backend/prisma/schema.prisma +++ b/apps/backend/prisma/schema.prisma @@ -19,7 +19,7 @@ model Project { displayName String description String @default("") isProductionMode Boolean - ownerTeamId String? @db.Uuid + ownerTeamId String? @db.Uuid logoUrl String? fullLogoUrl String? @@ -715,6 +715,7 @@ model ThreadMessage { enum CustomerType { USER TEAM + CUSTOM } enum SubscriptionStatus { @@ -731,7 +732,7 @@ enum SubscriptionStatus { model Subscription { id String @default(uuid()) @db.Uuid tenancyId String @db.Uuid - customerId String @db.Uuid + customerId String customerType CustomerType offer Json @@ -749,14 +750,15 @@ model Subscription { } model ItemQuantityChange { - id String @default(uuid()) @db.Uuid - tenancyId String @db.Uuid - customerId String @db.Uuid - itemId String - quantity Int - description String? - expiresAt DateTime? - createdAt DateTime @default(now()) + id String @default(uuid()) @db.Uuid + tenancyId String @db.Uuid + customerType CustomerType + customerId String + itemId String + quantity Int + description String? + expiresAt DateTime? + createdAt DateTime @default(now()) @@id([tenancyId, id]) @@index([tenancyId, customerId, expiresAt]) diff --git a/apps/backend/src/app/api/latest/payments/items/[customer_id]/[item_id]/route.ts b/apps/backend/src/app/api/latest/payments/items/[customer_type]/[customer_id]/[item_id]/route.ts similarity index 81% rename from apps/backend/src/app/api/latest/payments/items/[customer_id]/[item_id]/route.ts rename to apps/backend/src/app/api/latest/payments/items/[customer_type]/[customer_id]/[item_id]/route.ts index 2eae2e60b1..8199dfcd1f 100644 --- a/apps/backend/src/app/api/latest/payments/items/[customer_id]/[item_id]/route.ts +++ b/apps/backend/src/app/api/latest/payments/items/[customer_type]/[customer_id]/[item_id]/route.ts @@ -1,8 +1,8 @@ -import { ensureItemCustomerTypeMatches, getItemQuantityForCustomer } from "@/lib/payments"; import { getPrismaClientForTenancy } from "@/prisma-client"; import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; import { KnownErrors } from "@stackframe/stack-shared"; import { adaptSchema, clientOrHigherAuthTypeSchema, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields"; +import { getItemQuantityForCustomer } from "@/lib/payments"; import { getOrUndefined } from "@stackframe/stack-shared/dist/utils/objects"; @@ -17,6 +17,7 @@ export const GET = createSmartRouteHandler({ tenancy: adaptSchema.defined(), }).defined(), params: yupObject({ + customer_type: yupString().oneOf(["user", "team", "custom"]).defined(), customer_id: yupString().defined(), item_id: yupString().defined(), }).defined(), @@ -38,16 +39,18 @@ export const GET = createSmartRouteHandler({ if (!itemConfig) { throw new KnownErrors.ItemNotFound(req.params.item_id); } + if (req.params.customer_type !== itemConfig.customerType) { + throw new KnownErrors.ItemCustomerTypeDoesNotMatch(req.params.item_id, req.params.customer_id, itemConfig.customerType, req.params.customer_type); + } - await ensureItemCustomerTypeMatches(req.params.item_id, itemConfig.customerType, req.params.customer_id, tenancy); const prisma = await getPrismaClientForTenancy(tenancy); const totalQuantity = await getItemQuantityForCustomer({ prisma, tenancy, itemId: req.params.item_id, customerId: req.params.customer_id, + customerType: req.params.customer_type, }); - return { statusCode: 200, bodyType: "json", @@ -59,3 +62,5 @@ export const GET = createSmartRouteHandler({ }; }, }); + + diff --git a/apps/backend/src/app/api/latest/payments/items/[customer_id]/[item_id]/update-quantity/route.ts b/apps/backend/src/app/api/latest/payments/items/[customer_type]/[customer_id]/[item_id]/update-quantity/route.ts similarity index 82% rename from apps/backend/src/app/api/latest/payments/items/[customer_id]/[item_id]/update-quantity/route.ts rename to apps/backend/src/app/api/latest/payments/items/[customer_type]/[customer_id]/[item_id]/update-quantity/route.ts index 0d2efe5394..dc46dbbe25 100644 --- a/apps/backend/src/app/api/latest/payments/items/[customer_id]/[item_id]/update-quantity/route.ts +++ b/apps/backend/src/app/api/latest/payments/items/[customer_type]/[customer_id]/[item_id]/update-quantity/route.ts @@ -1,9 +1,10 @@ -import { ensureItemCustomerTypeMatches, getItemQuantityForCustomer } from "@/lib/payments"; +import { adaptSchema, serverOrHigherAuthTypeSchema, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields"; +import { getItemQuantityForCustomer } from "@/lib/payments"; import { getPrismaClientForTenancy, retryTransaction } from "@/prisma-client"; import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; import { KnownErrors } from "@stackframe/stack-shared"; -import { adaptSchema, serverOrHigherAuthTypeSchema, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields"; import { getOrUndefined } from "@stackframe/stack-shared/dist/utils/objects"; +import { typedToUppercase } from "@stackframe/stack-shared/dist/utils/strings"; export const POST = createSmartRouteHandler({ metadata: { @@ -16,6 +17,7 @@ export const POST = createSmartRouteHandler({ tenancy: adaptSchema.defined(), }).defined(), params: yupObject({ + customer_type: yupString().oneOf(["user", "team", "custom"]).defined(), customer_id: yupString().defined(), item_id: yupString().defined(), }).defined(), @@ -44,7 +46,9 @@ export const POST = createSmartRouteHandler({ throw new KnownErrors.ItemNotFound(req.params.item_id); } - await ensureItemCustomerTypeMatches(req.params.item_id, itemConfig.customerType, req.params.customer_id, tenancy); + if (req.params.customer_type !== itemConfig.customerType) { + throw new KnownErrors.ItemCustomerTypeDoesNotMatch(req.params.item_id, req.params.customer_id, itemConfig.customerType, req.params.customer_type); + } const prisma = await getPrismaClientForTenancy(tenancy); const changeId = await retryTransaction(prisma, async (tx) => { @@ -53,6 +57,7 @@ export const POST = createSmartRouteHandler({ tenancy, itemId: req.params.item_id, customerId: req.params.customer_id, + customerType: req.params.customer_type, }); if (!allowNegative && (totalQuantity + req.body.delta < 0)) { throw new KnownErrors.ItemQuantityInsufficientAmount(req.params.item_id, req.params.customer_id, req.body.delta); @@ -61,6 +66,7 @@ export const POST = createSmartRouteHandler({ data: { tenancyId: tenancy.id, customerId: req.params.customer_id, + customerType: typedToUppercase(req.params.customer_type), itemId: req.params.item_id, quantity: req.body.delta, description: req.body.description, @@ -77,3 +83,5 @@ export const POST = createSmartRouteHandler({ }; }, }); + + diff --git a/apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts b/apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts index f8bdd1c377..38035e6b78 100644 --- a/apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts +++ b/apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts @@ -1,11 +1,12 @@ -import { ensureOfferCustomerTypeMatches, ensureOfferIdOrInlineOffer } from "@/lib/payments"; +import { adaptSchema, clientOrHigherAuthTypeSchema, inlineOfferSchema, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields"; +import { ensureOfferIdOrInlineOffer } from "@/lib/payments"; import { getStripeForAccount } from "@/lib/stripe"; import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; -import { adaptSchema, clientOrHigherAuthTypeSchema, inlineOfferSchema, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields"; import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; import { throwErr } from "@stackframe/stack-shared/dist/utils/errors"; import { purchaseUrlVerificationCodeHandler } from "../verification-code-handler"; import { CustomerType } from "@prisma/client"; +import { KnownErrors } from "@stackframe/stack-shared/dist/known-errors"; export const POST = createSmartRouteHandler({ metadata: { @@ -18,7 +19,8 @@ export const POST = createSmartRouteHandler({ tenancy: adaptSchema.defined(), }).defined(), body: yupObject({ - customer_id: yupString().uuid().defined(), + customer_type: yupString().oneOf(["user", "team", "custom"]).defined(), + customer_id: yupString().defined(), offer_id: yupString().optional(), offer_inline: inlineOfferSchema.optional(), }), @@ -34,8 +36,10 @@ export const POST = createSmartRouteHandler({ const { tenancy } = req.auth; const stripe = getStripeForAccount({ tenancy }); const offerConfig = await ensureOfferIdOrInlineOffer(tenancy, req.auth.type, req.body.offer_id, req.body.offer_inline); - await ensureOfferCustomerTypeMatches(req.body.offer_id, offerConfig.customerType, req.body.customer_id, tenancy); const customerType = offerConfig.customerType ?? throwErr("Customer type not found"); + if (req.body.customer_type !== customerType) { + throw new KnownErrors.OfferCustomerTypeDoesNotMatch(req.body.offer_id, req.body.customer_id, req.body.customer_type, customerType); + } const stripeCustomerSearch = await stripe.customers.search({ query: `metadata['customerId']:'${req.body.customer_id}'`, diff --git a/apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx b/apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx index d0c74e2170..2ecceb4bc9 100644 --- a/apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx +++ b/apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx @@ -87,6 +87,7 @@ export const teamInvitationCodeHandler = createVerificationCodeHandler({ tenancy, customerId: data.team_id, itemId: "dashboard_admins", + customerType: "team", }); if (currentMemberCount + 1 > maxDashboardAdmins) { throw new KnownErrors.ItemQuantityInsufficientAmount("dashboard_admins", data.team_id, -1); diff --git a/apps/backend/src/lib/payments.tsx b/apps/backend/src/lib/payments.tsx index 952b201f28..e22f78e2dd 100644 --- a/apps/backend/src/lib/payments.tsx +++ b/apps/backend/src/lib/payments.tsx @@ -1,14 +1,13 @@ -import { teamsCrudHandlers } from "@/app/api/latest/teams/crud"; -import { usersCrudHandlers } from "@/app/api/latest/users/crud"; +import { PrismaClientTransaction } from "@/prisma-client"; +import { SubscriptionStatus } from "@prisma/client"; import { KnownErrors } from "@stackframe/stack-shared"; import { inlineOfferSchema, offerSchema } from "@stackframe/stack-shared/dist/schema-fields"; +import { typedToUppercase } from "@stackframe/stack-shared/dist/utils/strings"; +import { SUPPORTED_CURRENCIES } from "@stackframe/stack-shared/dist/utils/currencies"; import { StackAssertionError, StatusError } from "@stackframe/stack-shared/dist/utils/errors"; import { getOrUndefined, typedFromEntries } from "@stackframe/stack-shared/dist/utils/objects"; import * as yup from "yup"; import { Tenancy } from "./tenancies"; -import { SUPPORTED_CURRENCIES } from "@stackframe/stack-shared/dist/utils/currencies"; -import { SubscriptionStatus } from "@prisma/client"; -import { PrismaClientTransaction } from "@/prisma-client"; export async function ensureOfferIdOrInlineOffer( tenancy: Tenancy, @@ -56,80 +55,19 @@ export async function ensureOfferIdOrInlineOffer( } } -export async function ensureItemCustomerTypeMatches(itemId: string, itemCustomerType: "user" | "team" | undefined, customerId: string, tenancy: Tenancy) { - const actualCustomerType = await getCustomerType(tenancy, customerId); - if (itemCustomerType !== actualCustomerType) { - throw new KnownErrors.ItemCustomerTypeDoesNotMatch(itemId, customerId, itemCustomerType, actualCustomerType); - } -} - -export async function ensureOfferCustomerTypeMatches(offerId: string | undefined, offerCustomerType: "user" | "team" | undefined, customerId: string, tenancy: Tenancy) { - const actualCustomerType = await getCustomerType(tenancy, customerId); - if (offerCustomerType !== actualCustomerType) { - throw new KnownErrors.OfferCustomerTypeDoesNotMatch(offerId, customerId, offerCustomerType, actualCustomerType); - } -} - -export async function getCustomerType(tenancy: Tenancy, customerId: string) { - let user; - try { - user = await usersCrudHandlers.adminRead( - { - user_id: customerId, - tenancy, - allowedErrorTypes: [ - KnownErrors.UserNotFound, - ], - } - ); - } catch (e) { - if (KnownErrors.UserNotFound.isInstance(e)) { - user = null; - } else { - throw e; - } - } - let team; - try { - team = await teamsCrudHandlers.adminRead({ - team_id: customerId, - tenancy, - allowedErrorTypes: [ - KnownErrors.TeamNotFound, - ], - }); - } catch (e) { - if (KnownErrors.TeamNotFound.isInstance(e)) { - team = null; - } else { - throw e; - } - } - - if (user && team) { - throw new StackAssertionError("Found a customer that is both user and team at the same time? This should never happen!", { customerId, user, team, tenancy }); - } - - if (user) { - return "user"; - } - if (team) { - return "team"; - } - throw new KnownErrors.CustomerDoesNotExist(customerId); -} - export async function getItemQuantityForCustomer(options: { prisma: PrismaClientTransaction, tenancy: Tenancy, itemId: string, customerId: string, + customerType: "user" | "team" | "custom", }) { const itemConfig = getOrUndefined(options.tenancy.config.payments.items, options.itemId); const defaultQuantity = itemConfig?.default.quantity ?? 0; const subscriptions = await options.prisma.subscription.findMany({ where: { tenancyId: options.tenancy.id, + customerType: typedToUppercase(options.customerType), customerId: options.customerId, status: { in: [SubscriptionStatus.active, SubscriptionStatus.trialing], @@ -146,6 +84,7 @@ export async function getItemQuantityForCustomer(options: { const { _sum } = await options.prisma.itemQuantityChange.aggregate({ where: { tenancyId: options.tenancy.id, + customerType: typedToUppercase(options.customerType), customerId: options.customerId, itemId: options.itemId, OR: [ diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/page-client.tsx index 3b63ed38c9..ed97f06ee1 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/page-client.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/page-client.tsx @@ -1,7 +1,5 @@ "use client"; -import * as yup from "yup"; -import { useState } from "react"; import { PaymentItemTable } from "@/components/data-table/payment-item-table"; import { PaymentOfferTable } from "@/components/data-table/payment-offer-table"; import { FormDialog, SmartFormDialog } from "@/components/form-dialog"; @@ -9,24 +7,22 @@ import { InputField, SelectField, SwitchField } from "@/components/form-fields"; import { IncludedItemEditorField } from "@/components/payments/included-item-editor"; import { PriceEditorField } from "@/components/payments/price-editor"; import { AdminProject } from "@stackframe/stack"; -import { KnownErrors } from "@stackframe/stack-shared"; import { offerPriceSchema, userSpecifiedIdSchema, yupRecord } from "@stackframe/stack-shared/dist/schema-fields"; import { wait } from "@stackframe/stack-shared/dist/utils/promises"; -import { Result } from "@stackframe/stack-shared/dist/utils/results"; import { - ActionDialog, Button, Card, CardContent, - InlineCode, Typography, toast } from "@stackframe/stack-ui"; import { ArrowRight, BarChart3, Repeat, Shield, Wallet, Webhook } from "lucide-react"; +import { useState } from "react"; +import * as yup from "yup"; import { PageLayout } from "../page-layout"; import { useAdminApp } from "../use-admin-app"; @@ -172,7 +168,7 @@ function CreateOfferDialog({ const offerSchema = yup.object({ offerId: yup.string().defined().label("Offer ID"), displayName: yup.string().defined().label("Display Name"), - customerType: yup.string().oneOf(["user", "team"]).defined().label("Customer Type"), + customerType: yup.string().oneOf(["user", "team", "custom"]).defined().label("Customer Type"), prices: yupRecord(userSpecifiedIdSchema("priceId"), offerPriceSchema).defined().label("Prices").test("at-least-one-price", "At least one price is required", (value) => { return Object.keys(value).length > 0; }), @@ -214,6 +210,7 @@ function CreateOfferDialog({ @@ -233,11 +230,12 @@ function CreateItemDialog({ open, onOpenChange, project }: { open: boolean, onOp const itemSchema = yup.object({ itemId: yup.string().defined().label("Item ID"), displayName: yup.string().optional().label("Display Name"), - customerType: yup.string().oneOf(["user", "team"]).defined().label("Customer Type").meta({ + customerType: yup.string().oneOf(["user", "team", "custom"]).defined().label("Customer Type").meta({ stackFormFieldRender: (props) => ( ), }), diff --git a/apps/dashboard/src/components/data-table/payment-item-table.tsx b/apps/dashboard/src/components/data-table/payment-item-table.tsx index 0a120f37ba..c3592970ef 100644 --- a/apps/dashboard/src/components/data-table/payment-item-table.tsx +++ b/apps/dashboard/src/components/data-table/payment-item-table.tsx @@ -110,6 +110,7 @@ function CreateItemQuantityChangeDialog({ open, onOpenChange, itemId }: { open: const stackAdminApp = useAdminApp(); const schema = yup.object({ + customerType: yup.string().oneOf(["user", "team", "custom"]).defined().label("Customer Type"), customerId: yup.string().uuid().defined().label("Customer ID"), quantity: yup.number().defined().label("Quantity"), description: yup.string().optional().label("Description"), @@ -118,7 +119,12 @@ function CreateItemQuantityChangeDialog({ open, onOpenChange, itemId }: { open: const submit = async (values: yup.InferType) => { const result = await Result.fromPromise(stackAdminApp.createItemQuantityChange({ - customerId: values.customerId, + ...(values.customerType === "user" ? + { userId: values.customerId } : + values.customerType === "team" ? + { teamId: values.customerId } : + { customId: values.customerId } + ), itemId, quantity: values.quantity, expiresAt: values.expiresAt ? values.expiresAt.toISOString() : undefined, diff --git a/apps/e2e/tests/backend/backend-helpers.ts b/apps/e2e/tests/backend/backend-helpers.ts index ca363f89ab..bf16e59fe1 100644 --- a/apps/e2e/tests/backend/backend-helpers.ts +++ b/apps/e2e/tests/backend/backend-helpers.ts @@ -1419,6 +1419,7 @@ export namespace Payments { method: "POST", accessType: "client", body: { + customer_type: "user", customer_id: userId, offer_id: "test-offer", }, diff --git a/apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts index d854b20d7b..ab91184d8c 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts @@ -13,6 +13,7 @@ it("should not be able to create purchase URL without offer_id or offer_inline", method: "POST", accessType: "client", body: { + customer_type: "user", customer_id: generateUuid(), }, }); @@ -52,6 +53,7 @@ it("should error for non-existent offer_id", async ({ expect }) => { method: "POST", accessType: "client", body: { + customer_type: "user", customer_id: generateUuid(), offer_id: "non-existent-offer", }, @@ -103,6 +105,7 @@ it("should error for invalid customer_id", async ({ expect }) => { method: "POST", accessType: "client", body: { + customer_type: "team", customer_id: generateUuid(), offer_id: "test-offer", }, @@ -150,6 +153,7 @@ it("should error for no connected stripe account", async ({ expect }) => { method: "POST", accessType: "client", body: { + customer_type: "user", customer_id: user.userId, offer_id: "test-offer", }, @@ -177,6 +181,7 @@ it("should not allow offer_inline when calling from client", async ({ expect }) method: "POST", accessType: "client", body: { + customer_type: "user", customer_id: userId, offer_inline: { display_name: "Inline Test Offer", @@ -208,6 +213,7 @@ it("should allow offer_inline when calling from server", async ({ expect }) => { method: "POST", accessType: "server", body: { + customer_type: "user", customer_id: userId, offer_inline: { display_name: "Inline Test Offer", @@ -223,7 +229,13 @@ it("should allow offer_inline when calling from server", async ({ expect }) => { }, }, }); - expect(response.status).toBe(200); + expect(response).toMatchInlineSnapshot(` + NiceResponse { + "status": 200, + "body": { "url": "http://localhost:8101/purchase/_cngg259jnn72d55dxfzmafzan54vcw7n429evq7bfbaa0" }, + "headers": Headers {