{"record":{"id":"69ffbe4860b25d42","repo":"moeru-ai/airi","slug":"invalid-request","errorCode":"INVALID_REQUEST","errorMessage":"Invalid checkout request","messagePattern":"Invalid checkout request","errorType":"validation","errorClass":"ApiError","httpStatus":400,"severity":"error","filePath":"server/apps/api/src/routes/stripe/operations/checkout.ts","lineNumber":36,"sourceCode":"  stripe: Stripe | null,\n  priceCatalog: StripePriceCatalog | null,\n  configKV: ConfigKVService,\n  env: Env,\n  metrics: RevenueMetrics | null,\n  productEventService: ProductEventService | null,\n) {\n  return async (\n    user: { id: string, email: string },\n    body: unknown,\n    request: Request,\n  ): Promise<{ url: string }> => {\n    const fluxProductId = await configKV.getOptional('STRIPE_FLUX_PRODUCT_ID')\n    if (!stripe || !priceCatalog || !fluxProductId)\n      throw createServiceUnavailableError('Stripe is not configured', 'STRIPE_NOT_CONFIGURED')\n\n    const parsed = safeParse(CheckoutBodySchema, body)\n    if (!parsed.success)\n      throw createBadRequestError('Invalid checkout request', 'INVALID_REQUEST', parsed.issues)\n\n    const { stripePriceId, currency } = parsed.output\n    const price = await priceCatalog.findActivePrice(fluxProductId, stripePriceId)\n    if (!price)\n      throw createBadRequestError('Invalid price', 'INVALID_PACKAGE', { stripePriceId })\n\n    const fluxAmount = Number(price.metadata.fluxAmount)\n    if (!Number.isFinite(fluxAmount) || fluxAmount <= 0)\n      throw createBadRequestError('Price is missing fluxAmount metadata', 'INVALID_PACKAGE', { stripePriceId })\n\n    const redirectBase = resolveCheckoutRedirectBase(request, env.ADDITIONAL_TRUSTED_ORIGINS, env.WEB_APP_URL)\n    const openpanelIdentity = readOpenpanelIdentityHeaders(request)\n\n    const order = await payment.openPending({\n      userId: user.id,\n      processor: 'stripe',\n      packKey: stripePriceId,\n      fluxAmount,","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/moeru-ai/airi/blob/438a067dde47aa0bdb46c2323d1fe293dc805218/server/apps/api/src/routes/stripe/operations/checkout.ts#L18-L54","documentation":"The checkout request body failed Valibot validation against CheckoutBodySchema. The API rejects it with 400 INVALID_REQUEST and includes the parsed issues, so the client sent a body that does not match the expected shape (missing/invalid stripePriceId or currency).","triggerScenarios":"POSTing to checkout with a body missing required fields (stripePriceId, currency), wrong types (e.g. number instead of string), or malformed JSON structure.","commonSituations":"Frontend sending snake_case vs camelCase mismatch, forgetting a field after a schema change, sending an old client version against an updated API schema, or tests hitting the endpoint with hand-written payloads.","solutions":["Read parsed.issues from the error response and fix the offending fields in the request body.","Validate the body against CheckoutBodySchema on the client before sending.","Ensure stripePriceId is a Stripe price ID string (price_...) and currency is a supported lowercase code.","Update the client if the API schema changed."],"exampleFix":"// before\nfetch('/checkout', { body: JSON.stringify({ priceId: 'price_x', Currency: 'USD' }) })\n// after\nfetch('/checkout', { body: JSON.stringify({ stripePriceId: 'price_x', currency: 'usd' }) })","handlingStrategy":"validation","validationCode":"import * as v from 'valibot'\nconst parsed = v.safeParse(CheckoutBodySchema, body)\nif (!parsed.success) {\n  // fix body before sending; log parsed.issues\n}","typeGuard":null,"tryCatchPattern":"try {\n  await checkout(body)\n} catch (e) {\n  if (e.code === 'INVALID_REQUEST') {\n    // map e.issues to form field errors\n  }\n}","preventionTips":["Share CheckoutBodySchema between client and server and validate before sending.","Use TypeScript types generated from the schema for request payloads.","Add integration tests that exercise the real schema."],"tags":["stripe","validation","bad-request","valibot"],"backgroundTag":"schema-validation-failed","analyzedSha":"438a067dde47aa0bdb46c2323d1fe293dc805218","analyzedAt":"2026-09-17T01:14:42.644Z","contentChangedAt":"2026-09-17T01:14:42.644Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}