@open-mercato/checkout 0.6.8-develop.6962.1.bd029a2bb2 → 0.6.8-develop.6964.1.36b364cfd8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,11 +2,10 @@ import { NextResponse } from "next/server";
2
2
  import { loadCustomFieldValues } from "@open-mercato/shared/lib/crud/custom-fields";
3
3
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
4
  import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
5
- import { checkRateLimit } from "@open-mercato/shared/lib/ratelimit/helpers";
6
5
  import { CheckoutLink } from "../../../data/entities.js";
7
6
  import { CHECKOUT_ENTITY_IDS } from "../../../lib/constants.js";
8
7
  import { resolveCheckoutPublicCustomFields } from "../../../lib/customFields.js";
9
- import { buildCheckoutRateLimitKey, checkoutPublicViewRateLimitConfig } from "../../../lib/rateLimiter.js";
8
+ import { checkoutPublicViewRateLimitConfig, enforceCheckoutRateLimit } from "../../../lib/rateLimiter.js";
10
9
  import { handleCheckoutRouteError, readCheckoutAccessCookie, requirePreviewContext } from "../../helpers.js";
11
10
  import {
12
11
  isCheckoutLinkPublic,
@@ -27,13 +26,15 @@ async function GET(req, { params }) {
27
26
  const container = await createRequestContainer();
28
27
  if (previewRequested) await requirePreviewContext(req);
29
28
  if (!previewRequested) {
30
- try {
31
- const rateLimiter = container.resolve("rateLimiterService");
32
- const key = buildCheckoutRateLimitKey(req, rateLimiter, "checkout-public-view");
33
- const rateLimitResponse = await checkRateLimit(rateLimiter, checkoutPublicViewRateLimitConfig, key, "Too many checkout page requests. Please try again later.");
34
- if (rateLimitResponse) return rateLimitResponse;
35
- } catch {
36
- }
29
+ const rateLimitResponse = await enforceCheckoutRateLimit({
30
+ req,
31
+ container,
32
+ config: checkoutPublicViewRateLimitConfig,
33
+ namespace: "checkout-public-view",
34
+ errorMessage: "Too many checkout page requests. Please try again later.",
35
+ posture: "fail-open"
36
+ });
37
+ if (rateLimitResponse) return rateLimitResponse;
37
38
  }
38
39
  const em = container.resolve("em");
39
40
  const link = await findOneWithDecryption(em, CheckoutLink, {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/checkout/api/pay/%5Bslug%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { RateLimiterService } from '@open-mercato/shared/lib/ratelimit/service'\nimport { checkRateLimit } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport { CheckoutLink } from '../../../data/entities'\nimport { CHECKOUT_ENTITY_IDS } from '../../../lib/constants'\nimport { resolveCheckoutPublicCustomFields } from '../../../lib/customFields'\nimport { buildCheckoutRateLimitKey, checkoutPublicViewRateLimitConfig } from '../../../lib/rateLimiter'\nimport { handleCheckoutRouteError, readCheckoutAccessCookie, requirePreviewContext } from '../../helpers'\nimport {\n isCheckoutLinkPublic,\n resolveLoadedCheckoutCustomFields,\n serializeTemplateOrLink,\n verifyCheckoutAccessToken,\n} from '../../../lib/utils'\nimport { checkoutTag } from '../../openapi'\n\nexport const metadata = {\n path: '/checkout/pay/[slug]',\n GET: { requireAuth: false },\n}\n\nexport async function GET(req: Request, { params }: { params: Promise<{ slug: string }> | { slug: string } }) {\n try {\n const resolvedParams = await params\n const url = new URL(req.url)\n const previewRequested = url.searchParams.get('preview') === 'true'\n const container = await createRequestContainer()\n if (previewRequested) await requirePreviewContext(req)\n if (!previewRequested) {\n try {\n const rateLimiter = container.resolve('rateLimiterService') as RateLimiterService\n const key = buildCheckoutRateLimitKey(req, rateLimiter, 'checkout-public-view')\n const rateLimitResponse = await checkRateLimit(rateLimiter, checkoutPublicViewRateLimitConfig, key, 'Too many checkout page requests. Please try again later.')\n if (rateLimitResponse) return rateLimitResponse\n } catch {\n // Rate limiting is fail-open\n }\n }\n const em = container.resolve('em')\n const link = await findOneWithDecryption(em, CheckoutLink, {\n slug: resolvedParams.slug,\n deletedAt: null,\n })\n if (!link || (!previewRequested && !isCheckoutLinkPublic(link.status))) {\n return NextResponse.json({ error: 'Payment link not found' }, { status: 404 })\n }\n const available = link.maxCompletions == null\n ? true\n : (link.completionCount + link.activeReservationCount) < link.maxCompletions\n const token = readCheckoutAccessCookie(req)\n const passwordVerified = previewRequested || !link.passwordHash || verifyCheckoutAccessToken(token, link.slug, {\n linkId: link.id,\n sessionVersion: link.passwordHash,\n })\n if (!passwordVerified) {\n return NextResponse.json({\n requiresPassword: true,\n title: link.title ?? link.name,\n })\n }\n const customValues = await loadCustomFieldValues({\n em,\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordIds: [link.id],\n tenantIdByRecord: { [link.id]: link.tenantId },\n organizationIdByRecord: { [link.id]: link.organizationId },\n })\n const normalizedCustomValues = resolveLoadedCheckoutCustomFields(customValues[link.id])\n const publicCustomFields = await resolveCheckoutPublicCustomFields({\n em,\n entityId: CHECKOUT_ENTITY_IDS.link,\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n customFieldsetCode: link.customFieldsetCode ?? null,\n customValues: normalizedCustomValues,\n displayCustomFieldsOnPage: link.displayCustomFieldsOnPage,\n })\n return NextResponse.json({\n ...serializeTemplateOrLink(link),\n publicCustomFields,\n available: previewRequested ? false : available,\n remainingUses: link.maxCompletions == null\n ? null\n : Math.max(0, link.maxCompletions - link.completionCount - link.activeReservationCount),\n requiresPassword: false,\n preview: previewRequested,\n gatewaySettings: undefined,\n })\n } catch (error) {\n return handleCheckoutRouteError(error)\n }\n}\n\nexport const openApi = {\n tags: [checkoutTag],\n}\n\nexport default GET\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CheckoutLink } from '../../../data/entities'\nimport { CHECKOUT_ENTITY_IDS } from '../../../lib/constants'\nimport { resolveCheckoutPublicCustomFields } from '../../../lib/customFields'\nimport { checkoutPublicViewRateLimitConfig, enforceCheckoutRateLimit } from '../../../lib/rateLimiter'\nimport { handleCheckoutRouteError, readCheckoutAccessCookie, requirePreviewContext } from '../../helpers'\nimport {\n isCheckoutLinkPublic,\n resolveLoadedCheckoutCustomFields,\n serializeTemplateOrLink,\n verifyCheckoutAccessToken,\n} from '../../../lib/utils'\nimport { checkoutTag } from '../../openapi'\n\nexport const metadata = {\n path: '/checkout/pay/[slug]',\n GET: { requireAuth: false },\n}\n\nexport async function GET(req: Request, { params }: { params: Promise<{ slug: string }> | { slug: string } }) {\n try {\n const resolvedParams = await params\n const url = new URL(req.url)\n const previewRequested = url.searchParams.get('preview') === 'true'\n const container = await createRequestContainer()\n if (previewRequested) await requirePreviewContext(req)\n if (!previewRequested) {\n // Fail-open: this endpoint is read-only.\n const rateLimitResponse = await enforceCheckoutRateLimit({\n req,\n container,\n config: checkoutPublicViewRateLimitConfig,\n namespace: 'checkout-public-view',\n errorMessage: 'Too many checkout page requests. Please try again later.',\n posture: 'fail-open',\n })\n if (rateLimitResponse) return rateLimitResponse\n }\n const em = container.resolve('em')\n const link = await findOneWithDecryption(em, CheckoutLink, {\n slug: resolvedParams.slug,\n deletedAt: null,\n })\n if (!link || (!previewRequested && !isCheckoutLinkPublic(link.status))) {\n return NextResponse.json({ error: 'Payment link not found' }, { status: 404 })\n }\n const available = link.maxCompletions == null\n ? true\n : (link.completionCount + link.activeReservationCount) < link.maxCompletions\n const token = readCheckoutAccessCookie(req)\n const passwordVerified = previewRequested || !link.passwordHash || verifyCheckoutAccessToken(token, link.slug, {\n linkId: link.id,\n sessionVersion: link.passwordHash,\n })\n if (!passwordVerified) {\n return NextResponse.json({\n requiresPassword: true,\n title: link.title ?? link.name,\n })\n }\n const customValues = await loadCustomFieldValues({\n em,\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordIds: [link.id],\n tenantIdByRecord: { [link.id]: link.tenantId },\n organizationIdByRecord: { [link.id]: link.organizationId },\n })\n const normalizedCustomValues = resolveLoadedCheckoutCustomFields(customValues[link.id])\n const publicCustomFields = await resolveCheckoutPublicCustomFields({\n em,\n entityId: CHECKOUT_ENTITY_IDS.link,\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n customFieldsetCode: link.customFieldsetCode ?? null,\n customValues: normalizedCustomValues,\n displayCustomFieldsOnPage: link.displayCustomFieldsOnPage,\n })\n return NextResponse.json({\n ...serializeTemplateOrLink(link),\n publicCustomFields,\n available: previewRequested ? false : available,\n remainingUses: link.maxCompletions == null\n ? null\n : Math.max(0, link.maxCompletions - link.completionCount - link.activeReservationCount),\n requiresPassword: false,\n preview: previewRequested,\n gatewaySettings: undefined,\n })\n } catch (error) {\n return handleCheckoutRouteError(error)\n }\n}\n\nexport const openApi = {\n tags: [checkoutTag],\n}\n\nexport default GET\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,8 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
3
3
  import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
- import { checkRateLimit } from "@open-mercato/shared/lib/ratelimit/helpers";
5
4
  import { CheckoutLink, CheckoutTransaction } from "../../../../../data/entities.js";
6
- import { buildCheckoutRateLimitKey, checkoutStatusRateLimitConfig } from "../../../../../lib/rateLimiter.js";
5
+ import { checkoutStatusRateLimitConfig, enforceCheckoutRateLimit } from "../../../../../lib/rateLimiter.js";
7
6
  import { handleCheckoutRouteError, requireCheckoutPasswordSession } from "../../../../helpers.js";
8
7
  import { checkoutTag } from "../../../../openapi.js";
9
8
  const metadata = {
@@ -14,13 +13,15 @@ async function GET(req, { params }) {
14
13
  try {
15
14
  const resolvedParams = await params;
16
15
  const container = await createRequestContainer();
17
- try {
18
- const rateLimiter = container.resolve("rateLimiterService");
19
- const key = buildCheckoutRateLimitKey(req, rateLimiter, "checkout-status");
20
- const rateLimitResponse = await checkRateLimit(rateLimiter, checkoutStatusRateLimitConfig, key, "Too many checkout status requests. Please try again later.");
21
- if (rateLimitResponse) return rateLimitResponse;
22
- } catch {
23
- }
16
+ const rateLimitResponse = await enforceCheckoutRateLimit({
17
+ req,
18
+ container,
19
+ config: checkoutStatusRateLimitConfig,
20
+ namespace: "checkout-status",
21
+ errorMessage: "Too many checkout status requests. Please try again later.",
22
+ posture: "fail-open"
23
+ });
24
+ if (rateLimitResponse) return rateLimitResponse;
24
25
  const em = container.resolve("em");
25
26
  const paymentGatewayService = container.resolve("paymentGatewayService");
26
27
  const link = await findOneWithDecryption(em, CheckoutLink, {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/checkout/api/pay/%5Bslug%5D/status/%5BtransactionId%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { RateLimiterService } from '@open-mercato/shared/lib/ratelimit/service'\nimport { checkRateLimit } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport type { PaymentGatewayService } from '@open-mercato/core/modules/payment_gateways/lib/gateway-service'\nimport { CheckoutLink, CheckoutTransaction } from '../../../../../data/entities'\nimport { buildCheckoutRateLimitKey, checkoutStatusRateLimitConfig } from '../../../../../lib/rateLimiter'\nimport { handleCheckoutRouteError, requireCheckoutPasswordSession } from '../../../../helpers'\nimport { checkoutTag } from '../../../../openapi'\n\nexport const metadata = {\n path: '/checkout/pay/[slug]/status/[transactionId]',\n GET: { requireAuth: false },\n}\n\nexport async function GET(req: Request, { params }: { params: Promise<{ slug: string; transactionId: string }> | { slug: string; transactionId: string } }) {\n try {\n const resolvedParams = await params\n const container = await createRequestContainer()\n try {\n const rateLimiter = container.resolve('rateLimiterService') as RateLimiterService\n const key = buildCheckoutRateLimitKey(req, rateLimiter, 'checkout-status')\n const rateLimitResponse = await checkRateLimit(rateLimiter, checkoutStatusRateLimitConfig, key, 'Too many checkout status requests. Please try again later.')\n if (rateLimitResponse) return rateLimitResponse\n } catch {\n // Rate limiting is fail-open\n }\n const em = container.resolve('em')\n const paymentGatewayService = container.resolve('paymentGatewayService') as PaymentGatewayService\n const link = await findOneWithDecryption(em, CheckoutLink, {\n slug: resolvedParams.slug,\n deletedAt: null,\n })\n if (!link) {\n return NextResponse.json({ error: 'Payment link not found' }, { status: 404 })\n }\n if (link.passwordHash) {\n requireCheckoutPasswordSession(req, link.slug, {\n linkId: link.id,\n sessionVersion: link.passwordHash,\n })\n }\n let transaction = await findOneWithDecryption(em, CheckoutTransaction, {\n id: resolvedParams.transactionId,\n linkId: link.id,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }, undefined, { organizationId: link.organizationId, tenantId: link.tenantId })\n if (!transaction) {\n return NextResponse.json({ error: 'Transaction not found' }, { status: 404 })\n }\n if (transaction.gatewayTransactionId && (transaction.status === 'processing' || transaction.status === 'pending')) {\n await paymentGatewayService.getPaymentStatus(transaction.gatewayTransactionId, {\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }).catch(() => null)\n transaction = await findOneWithDecryption(em, CheckoutTransaction, {\n id: resolvedParams.transactionId,\n linkId: link.id,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }, undefined, { organizationId: link.organizationId, tenantId: link.tenantId })\n }\n if (!transaction) {\n return NextResponse.json({ error: 'Transaction not found' }, { status: 404 })\n }\n return NextResponse.json({\n status: transaction.status,\n paymentStatus: transaction.paymentStatus ?? null,\n link: {\n title: link.title ?? link.name ?? null,\n successTitle: link.successTitle ?? null,\n successMessage: link.successMessage ?? null,\n cancelTitle: link.cancelTitle ?? null,\n cancelMessage: link.cancelMessage ?? null,\n errorTitle: link.errorTitle ?? null,\n errorMessage: link.errorMessage ?? null,\n },\n })\n } catch (error) {\n return handleCheckoutRouteError(error)\n }\n}\n\nexport const openApi = {\n tags: [checkoutTag],\n}\n\nexport default GET\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { PaymentGatewayService } from '@open-mercato/core/modules/payment_gateways/lib/gateway-service'\nimport { CheckoutLink, CheckoutTransaction } from '../../../../../data/entities'\nimport { checkoutStatusRateLimitConfig, enforceCheckoutRateLimit } from '../../../../../lib/rateLimiter'\nimport { handleCheckoutRouteError, requireCheckoutPasswordSession } from '../../../../helpers'\nimport { checkoutTag } from '../../../../openapi'\n\nexport const metadata = {\n path: '/checkout/pay/[slug]/status/[transactionId]',\n GET: { requireAuth: false },\n}\n\nexport async function GET(req: Request, { params }: { params: Promise<{ slug: string; transactionId: string }> | { slug: string; transactionId: string } }) {\n try {\n const resolvedParams = await params\n const container = await createRequestContainer()\n // Fail-open: this endpoint is read-only.\n const rateLimitResponse = await enforceCheckoutRateLimit({\n req,\n container,\n config: checkoutStatusRateLimitConfig,\n namespace: 'checkout-status',\n errorMessage: 'Too many checkout status requests. Please try again later.',\n posture: 'fail-open',\n })\n if (rateLimitResponse) return rateLimitResponse\n const em = container.resolve('em')\n const paymentGatewayService = container.resolve('paymentGatewayService') as PaymentGatewayService\n const link = await findOneWithDecryption(em, CheckoutLink, {\n slug: resolvedParams.slug,\n deletedAt: null,\n })\n if (!link) {\n return NextResponse.json({ error: 'Payment link not found' }, { status: 404 })\n }\n if (link.passwordHash) {\n requireCheckoutPasswordSession(req, link.slug, {\n linkId: link.id,\n sessionVersion: link.passwordHash,\n })\n }\n let transaction = await findOneWithDecryption(em, CheckoutTransaction, {\n id: resolvedParams.transactionId,\n linkId: link.id,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }, undefined, { organizationId: link.organizationId, tenantId: link.tenantId })\n if (!transaction) {\n return NextResponse.json({ error: 'Transaction not found' }, { status: 404 })\n }\n if (transaction.gatewayTransactionId && (transaction.status === 'processing' || transaction.status === 'pending')) {\n await paymentGatewayService.getPaymentStatus(transaction.gatewayTransactionId, {\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }).catch(() => null)\n transaction = await findOneWithDecryption(em, CheckoutTransaction, {\n id: resolvedParams.transactionId,\n linkId: link.id,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }, undefined, { organizationId: link.organizationId, tenantId: link.tenantId })\n }\n if (!transaction) {\n return NextResponse.json({ error: 'Transaction not found' }, { status: 404 })\n }\n return NextResponse.json({\n status: transaction.status,\n paymentStatus: transaction.paymentStatus ?? null,\n link: {\n title: link.title ?? link.name ?? null,\n successTitle: link.successTitle ?? null,\n successMessage: link.successMessage ?? null,\n cancelTitle: link.cancelTitle ?? null,\n cancelMessage: link.cancelMessage ?? null,\n errorTitle: link.errorTitle ?? null,\n errorMessage: link.errorMessage ?? null,\n },\n })\n } catch (error) {\n return handleCheckoutRouteError(error)\n }\n}\n\nexport const openApi = {\n tags: [checkoutTag],\n}\n\nexport default GET\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,7 +2,6 @@ import { NextResponse } from "next/server";
2
2
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
3
3
  import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
4
4
  import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
5
- import { checkRateLimit } from "@open-mercato/shared/lib/ratelimit/helpers";
6
5
  import { GatewayTransaction } from "@open-mercato/core/modules/payment_gateways/data/entities";
7
6
  import { CheckoutLink, CheckoutTransaction } from "../../../../data/entities.js";
8
7
  import { publicSubmitSchema } from "../../../../data/validators.js";
@@ -16,7 +15,8 @@ import {
16
15
  validateDescriptorCurrencies
17
16
  } from "../../../../lib/utils.js";
18
17
  import { validateCheckoutCustomerData } from "../../../../lib/customerDataValidation.js";
19
- import { buildCheckoutRateLimitKey, checkoutSubmitRateLimitConfig } from "../../../../lib/rateLimiter.js";
18
+ import { checkoutSubmitRateLimitConfig, enforceCheckoutRateLimit } from "../../../../lib/rateLimiter.js";
19
+ import { rateLimitErrorSchema } from "@open-mercato/shared/lib/ratelimit/helpers";
20
20
  import { checkoutTag } from "../../../openapi.js";
21
21
  import { createLogger } from "@open-mercato/shared/lib/logger";
22
22
  const logger = createLogger("checkout");
@@ -182,13 +182,15 @@ const metadata = {
182
182
  async function POST(req, { params }) {
183
183
  try {
184
184
  const container = await createRequestContainer();
185
- try {
186
- const rateLimiter = container.resolve("rateLimiterService");
187
- const key = buildCheckoutRateLimitKey(req, rateLimiter, "checkout-submit");
188
- const rateLimitResponse = await checkRateLimit(rateLimiter, checkoutSubmitRateLimitConfig, key, "Too many payment attempts. Please try again later.");
189
- if (rateLimitResponse) return rateLimitResponse;
190
- } catch {
191
- }
185
+ const rateLimitResponse = await enforceCheckoutRateLimit({
186
+ req,
187
+ container,
188
+ config: checkoutSubmitRateLimitConfig,
189
+ namespace: "checkout-submit",
190
+ errorMessage: "Too many payment attempts. Please try again later.",
191
+ posture: "fail-closed"
192
+ });
193
+ if (rateLimitResponse) return rateLimitResponse;
192
194
  const resolvedParams = await params;
193
195
  const allowedOrigins = buildAllowedOrigins();
194
196
  if (allowedOrigins.length > 0) {
@@ -453,7 +455,15 @@ async function POST(req, { params }) {
453
455
  }
454
456
  }
455
457
  const openApi = {
456
- tags: [checkoutTag]
458
+ tags: [checkoutTag],
459
+ methods: {
460
+ POST: {
461
+ errors: [
462
+ { status: 429, description: "Too many payment attempts", schema: rateLimitErrorSchema },
463
+ { status: 503, description: "Rate limiting could not be enforced, so the payment was rejected", schema: rateLimitErrorSchema }
464
+ ]
465
+ }
466
+ }
457
467
  };
458
468
  var route_default = POST;
459
469
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/checkout/api/pay/%5Bslug%5D/submit/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { RateLimiterService } from '@open-mercato/shared/lib/ratelimit/service'\nimport { checkRateLimit } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport type { PaymentGatewayClientSession } from '@open-mercato/shared/modules/payment_gateways/types'\nimport type { PaymentGatewayService } from '@open-mercato/core/modules/payment_gateways/lib/gateway-service'\nimport { GatewayTransaction } from '@open-mercato/core/modules/payment_gateways/data/entities'\nimport { CheckoutLink, CheckoutTransaction } from '../../../../data/entities'\nimport { publicSubmitSchema } from '../../../../data/validators'\nimport { handleCheckoutRouteError, requireCheckoutPasswordSession } from '../../../helpers'\nimport { emitCheckoutEvent } from '../../../../events'\nimport {\n buildConsentProof,\n isCheckoutLinkPublic,\n mapGatewayStatusToCheckoutStatus,\n resolveSubmittedAmount,\n validateDescriptorCurrencies,\n} from '../../../../lib/utils'\nimport { validateCheckoutCustomerData } from '../../../../lib/customerDataValidation'\nimport { buildCheckoutRateLimitKey, checkoutSubmitRateLimitConfig } from '../../../../lib/rateLimiter'\nimport { checkoutTag } from '../../../openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('checkout')\n\ntype CachedSubmitResponse = {\n transactionId: string\n redirectUrl?: string | null\n paymentSession?: (PaymentGatewayClientSession & {\n providerKey: string | null\n gatewayTransactionId: string\n }) | null\n}\n\ntype CheckoutLegalDocumentRequirement = {\n required?: boolean\n}\n\ntype CheckoutCustomerFieldRequirement = {\n key: string\n required?: boolean\n}\n\nfunction normalizePort(url: URL): string {\n if (url.port) return url.port\n return url.protocol === 'https:' ? '443' : '80'\n}\n\nfunction normalizeConfiguredOrigin(candidate: string): string | null {\n try {\n return new URL(candidate).origin\n } catch {\n return null\n }\n}\n\nfunction isLoopbackHostname(hostname: string): boolean {\n return hostname === '127.0.0.1' || hostname === 'localhost'\n}\n\nfunction splitHeaderValues(value: string | null): string[] {\n if (!value) return []\n return value\n .split(',')\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0)\n}\n\nfunction isAllowedRequestOrigin(submittedOrigin: string, allowedOrigin: string): boolean {\n if (submittedOrigin === allowedOrigin) return true\n try {\n const submittedUrl = new URL(submittedOrigin)\n const allowedUrl = new URL(allowedOrigin)\n return submittedUrl.protocol === allowedUrl.protocol\n && normalizePort(submittedUrl) === normalizePort(allowedUrl)\n && isLoopbackHostname(submittedUrl.hostname)\n && isLoopbackHostname(allowedUrl.hostname)\n } catch {\n return false\n }\n}\n\nconst CONFIGURED_ALLOWED_ORIGINS: string[] = (process.env.CHECKOUT_ALLOWED_ORIGINS ?? '')\n .split(',')\n .map((origin) => origin.trim())\n .filter((origin) => origin.length > 0)\n\nfunction addAllowedOrigin(allowedOrigins: Set<string>, candidate: string) {\n const normalized = normalizeConfiguredOrigin(candidate)\n if (!normalized) return\n allowedOrigins.add(normalized)\n try {\n const parsed = new URL(normalized)\n if (!isLoopbackHostname(parsed.hostname)) return\n for (const hostname of ['127.0.0.1', 'localhost']) {\n for (const protocol of ['http:', 'https:']) {\n const variant = new URL(normalized)\n variant.hostname = hostname\n variant.protocol = protocol\n allowedOrigins.add(variant.origin)\n }\n }\n } catch {\n // Ignore invalid variants.\n }\n}\n\nfunction collectConfiguredOrigins(): string[] {\n const origins = [...CONFIGURED_ALLOWED_ORIGINS]\n for (const origin of [process.env.APP_URL, process.env.NEXT_PUBLIC_APP_URL]) {\n if (origin) origins.push(origin)\n }\n return origins\n}\n\n// Allowed origins are built ONLY from server-configured values. The inbound\n// request URL, Host and X-Forwarded-Host headers are attacker-controllable on\n// deployments that do not normalize them at the edge, so they must never seed\n// the allowlist (otherwise a spoofed Host self-passes the origin check).\nfunction buildAllowedOrigins(): string[] {\n const allowedOrigins = new Set<string>()\n for (const origin of collectConfiguredOrigins()) {\n addAllowedOrigin(allowedOrigins, origin)\n }\n return Array.from(allowedOrigins)\n}\n\n// Resolve the origin used to build gateway-bound success/cancel/return URLs.\n// Always prefer a server-pinned configured origin; only fall back to the request\n// origin on loopback (local dev/test), where the Host cannot be spoofed by a\n// remote attacker. A non-loopback request with no configured origin is a\n// misconfiguration and is rejected rather than emitting an attacker-controllable URL.\nfunction resolveServerOrigin(req: Request): string {\n for (const candidate of collectConfiguredOrigins()) {\n const normalized = normalizeConfiguredOrigin(candidate)\n if (normalized) return normalized\n }\n const requestUrl = new URL(req.url)\n if (isLoopbackHostname(requestUrl.hostname)) return requestUrl.origin\n throw new CrudHttpError(500, { error: 'Checkout origin is not configured' })\n}\n\n// Normalize a host value (bare host, host:port, or full origin) to its authority\n// (`hostname` plus any non-default port), lower-cased. Protocol is intentionally\n// dropped: the gateway-bound URLs are already pinned to the configured origin, so\n// the host guard below only needs to reject foreign hosts \u2014 not enforce protocol\n// or internal-upstream-host parity, which would 403 legitimate proxied requests.\nfunction hostAuthority(value: string): string | null {\n const candidate = value.includes('://') ? value : `https://${value}`\n try {\n const url = new URL(candidate)\n if (!url.hostname) return null\n const port = url.port && url.port !== '80' && url.port !== '443' ? `:${url.port}` : ''\n return `${url.hostname.toLowerCase()}${port}`\n } catch {\n return null\n }\n}\n\nfunction allowedHostAuthorities(allowedOrigins: string[]): Set<string> {\n const authorities = new Set<string>()\n for (const origin of allowedOrigins) {\n const authority = hostAuthority(origin)\n if (authority) authorities.add(authority)\n }\n return authorities\n}\n\n// The externally-asserted host: the proxy-forwarded X-Forwarded-Host when present,\n// otherwise the Host header. The internal upstream Host (and protocol) are ignored\n// so a correctly-proxied request is not rejected for not matching the public origin.\nfunction requestHostAuthorities(req: Request): string[] {\n const forwarded = splitHeaderValues(req.headers.get('x-forwarded-host'))\n const hostValues = forwarded.length > 0 ? forwarded : splitHeaderValues(req.headers.get('host'))\n const authorities: string[] = []\n for (const value of hostValues) {\n const authority = hostAuthority(value)\n if (authority) authorities.push(authority)\n }\n return authorities\n}\n\nfunction isIdempotencyConflict(error: unknown): boolean {\n const message = error instanceof Error ? error.message : ''\n return message.includes('checkout_transactions_organization_id_tenant_id_link_idempotency_key_index')\n || message.includes('checkout_transactions_organization_id_tenant_id_link_idempotency_key_unique')\n || message.includes('duplicate key value')\n}\n\nfunction readClientSession(\n metadata: Record<string, unknown> | null | undefined,\n): PaymentGatewayClientSession | null {\n const candidate = metadata?.clientSession\n if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) return null\n const type = (candidate as { type?: unknown }).type\n if (type === 'redirect') {\n const redirectUrl = (candidate as { redirectUrl?: unknown }).redirectUrl\n if (typeof redirectUrl !== 'string' || redirectUrl.trim().length === 0) return null\n const target = (candidate as { target?: unknown }).target\n return {\n type: 'redirect',\n redirectUrl,\n target: target === 'top' ? 'top' : 'self',\n }\n }\n if (type !== 'embedded') return null\n if (typeof (candidate as { rendererKey?: unknown }).rendererKey !== 'string') return null\n const payload = (candidate as { payload?: unknown }).payload\n const settings = (candidate as { settings?: unknown }).settings\n return {\n type: 'embedded',\n rendererKey: (candidate as { rendererKey: string }).rendererKey,\n payload: payload && typeof payload === 'object' && !Array.isArray(payload)\n ? payload as Record<string, unknown>\n : undefined,\n settings: settings && typeof settings === 'object' && !Array.isArray(settings)\n ? settings as Record<string, unknown>\n : undefined,\n }\n}\n\nasync function buildSubmitResponse(\n req: Request,\n em: EntityManager,\n link: CheckoutLink,\n transaction: CheckoutTransaction,\n providerKey: string | null | undefined,\n): Promise<CachedSubmitResponse> {\n const gatewayTransaction = transaction.gatewayTransactionId\n ? await em.findOne(GatewayTransaction, {\n id: transaction.gatewayTransactionId,\n organizationId: transaction.organizationId,\n tenantId: transaction.tenantId,\n deletedAt: null,\n })\n : null\n const serverOrigin = resolveServerOrigin(req)\n const clientSession = gatewayTransaction\n ? readClientSession(gatewayTransaction.gatewayMetadata)\n : null\n const paymentSession = (clientSession && gatewayTransaction)\n ? {\n ...clientSession,\n ...(clientSession.type === 'embedded'\n ? {\n payload: {\n ...(clientSession.payload ?? {}),\n returnUrl: `${serverOrigin}/pay/${encodeURIComponent(link.slug)}/success/${encodeURIComponent(transaction.id)}`,\n cancelUrl: `${serverOrigin}/pay/${encodeURIComponent(link.slug)}/cancel/${encodeURIComponent(transaction.id)}`,\n },\n }\n : {}),\n providerKey: providerKey ?? gatewayTransaction.providerKey ?? null,\n gatewayTransactionId: gatewayTransaction.id,\n }\n : null\n return {\n transactionId: transaction.id,\n redirectUrl: gatewayTransaction?.redirectUrl ?? null,\n paymentSession,\n }\n}\n\nexport const metadata = {\n path: '/checkout/pay/[slug]/submit',\n POST: { requireAuth: false },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ slug: string }> | { slug: string } }) {\n try {\n const container = await createRequestContainer()\n try {\n const rateLimiter = container.resolve('rateLimiterService') as RateLimiterService\n const key = buildCheckoutRateLimitKey(req, rateLimiter, 'checkout-submit')\n const rateLimitResponse = await checkRateLimit(rateLimiter, checkoutSubmitRateLimitConfig, key, 'Too many payment attempts. Please try again later.')\n if (rateLimitResponse) return rateLimitResponse\n } catch {\n // Rate limiting is fail-open\n }\n const resolvedParams = await params\n\n const allowedOrigins = buildAllowedOrigins()\n if (allowedOrigins.length > 0) {\n // Reject spoofed Host / X-Forwarded-Host before any business logic runs so\n // an attacker-controlled host can never flow into gateway-bound URLs. Hosts\n // are matched by authority (protocol-independent) so a correctly TLS-proxied\n // request \u2014 internal http, X-Forwarded-Proto https \u2014 is not rejected.\n const allowedAuthorities = allowedHostAuthorities(allowedOrigins)\n const hostAuthorities = requestHostAuthorities(req)\n if (\n hostAuthorities.length > 0\n && !hostAuthorities.every((authority) => allowedAuthorities.has(authority))\n ) {\n return NextResponse.json({ error: 'Invalid request host' }, { status: 403 })\n }\n\n const origin = req.headers.get('origin')\n const referer = req.headers.get('referer')\n if (origin || referer) {\n const submittedOrigin = origin ?? (referer ? new URL(referer).origin : null)\n if (submittedOrigin && !allowedOrigins.some((allowedOrigin) => isAllowedRequestOrigin(submittedOrigin, allowedOrigin))) {\n return NextResponse.json({ error: 'Invalid request origin' }, { status: 403 })\n }\n }\n }\n\n const idempotencyKey = req.headers.get('Idempotency-Key')?.trim()\n if (!idempotencyKey) {\n return NextResponse.json({ error: 'Idempotency-Key header is required' }, { status: 400 })\n }\n if (idempotencyKey.length < 16 || idempotencyKey.length > 128) {\n return NextResponse.json({ error: 'Idempotency-Key must be between 16 and 128 characters' }, { status: 400 })\n }\n\n const body = publicSubmitSchema.parse(await req.json().catch(() => ({})))\n const em = container.resolve('em')\n const commandBus = container.resolve('commandBus') as CommandBus\n const paymentGatewayService = container.resolve('paymentGatewayService') as PaymentGatewayService\n const link = await findOneWithDecryption(em, CheckoutLink, {\n slug: resolvedParams.slug,\n deletedAt: null,\n })\n if (!link) {\n return NextResponse.json({ error: 'Payment link not found' }, { status: 404 })\n }\n if (!isCheckoutLinkPublic(link.status)) {\n throw new CrudHttpError(422, { error: 'This payment link is not currently accepting payments' })\n }\n if (link.passwordHash) {\n requireCheckoutPasswordSession(req, link.slug, {\n linkId: link.id,\n sessionVersion: link.passwordHash,\n })\n }\n if (!link.gatewayProviderKey) {\n throw new CrudHttpError(422, { error: 'A payment gateway must be configured before this link can be used' })\n }\n const legalDocuments = link.legalDocuments && typeof link.legalDocuments === 'object'\n ? link.legalDocuments as Partial<Record<'terms' | 'privacyPolicy', CheckoutLegalDocumentRequirement>>\n : {}\n const legalConsentErrors: Record<string, string> = {}\n for (const key of ['terms', 'privacyPolicy'] as const) {\n const document = legalDocuments[key]\n if (document?.required === true && body.acceptedLegalConsents?.[key] !== true) {\n legalConsentErrors[`acceptedLegalConsents.${key}`] = 'checkout.payPage.validation.documentRequired'\n }\n }\n if (Object.keys(legalConsentErrors).length > 0) {\n throw new CrudHttpError(422, {\n error: 'checkout.payPage.validation.fixErrors',\n fieldErrors: legalConsentErrors,\n })\n }\n const collectedCustomerData = link.collectCustomerDetails === false ? {} : body.customerData\n const customerFields = Array.isArray(link.customerFieldsSchema)\n ? link.customerFieldsSchema as CheckoutCustomerFieldRequirement[]\n : []\n if (link.collectCustomerDetails !== false) {\n const customerFieldErrors = validateCheckoutCustomerData(customerFields, collectedCustomerData)\n if (Object.keys(customerFieldErrors).length > 0) {\n throw new CrudHttpError(422, {\n error: 'checkout.payPage.validation.fixErrors',\n fieldErrors: customerFieldErrors,\n })\n }\n }\n const resolvedAmount = resolveSubmittedAmount(link, body)\n const existingTransaction = await findOneWithDecryption(em, CheckoutTransaction, {\n linkId: link.id,\n idempotencyKey,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }, undefined, { organizationId: link.organizationId, tenantId: link.tenantId })\n validateDescriptorCurrencies(link.gatewayProviderKey, [\n existingTransaction?.currencyCode ?? resolvedAmount.currencyCode,\n ])\n if (existingTransaction?.gatewayTransactionId) {\n return NextResponse.json(\n await buildSubmitResponse(req, em, link, existingTransaction, link.gatewayProviderKey),\n )\n }\n\n const transactionInput = {\n linkId: link.id,\n amount: resolvedAmount.amount,\n currencyCode: resolvedAmount.currencyCode,\n idempotencyKey,\n customerData: collectedCustomerData,\n firstName: typeof collectedCustomerData.firstName === 'string' ? collectedCustomerData.firstName : null,\n lastName: typeof collectedCustomerData.lastName === 'string' ? collectedCustomerData.lastName : null,\n email: typeof collectedCustomerData.email === 'string' ? collectedCustomerData.email : null,\n phone: typeof collectedCustomerData.phone === 'string' ? collectedCustomerData.phone : null,\n selectedPriceItemId: resolvedAmount.selectedPriceItemId,\n acceptedLegalConsents: buildConsentProof(link, body.acceptedLegalConsents),\n ipAddress: req.headers.get('x-forwarded-for') ?? null,\n userAgent: req.headers.get('user-agent') ?? null,\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n }\n const ctx: CommandRuntimeContext = {\n container,\n auth: null,\n organizationScope: null,\n selectedOrganizationId: link.organizationId,\n organizationIds: [link.organizationId],\n request: req,\n }\n let transactionId = existingTransaction?.id ?? null\n if (!transactionId) {\n try {\n const created = await commandBus.execute<typeof transactionInput, { id: string }>('checkout.transaction.create', {\n input: transactionInput,\n ctx,\n })\n transactionId = created.result.id\n } catch (error) {\n if (!isIdempotencyConflict(error)) {\n throw error\n }\n const duplicated = await findOneWithDecryption(em, CheckoutTransaction, {\n linkId: link.id,\n idempotencyKey,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }, undefined, { organizationId: link.organizationId, tenantId: link.tenantId })\n if (!duplicated) {\n throw error\n }\n transactionId = duplicated.id\n }\n }\n if (!transactionId) {\n throw new CrudHttpError(500, { error: 'Failed to initialize checkout transaction' })\n }\n const serverOrigin = resolveServerOrigin(req)\n const successUrl = `${serverOrigin}/pay/${encodeURIComponent(link.slug)}/success/${encodeURIComponent(transactionId)}`\n const cancelUrl = `${serverOrigin}/pay/${encodeURIComponent(link.slug)}/cancel/${encodeURIComponent(transactionId)}`\n const transaction = await findOneWithDecryption(em, CheckoutTransaction, {\n id: transactionId,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }, undefined, { organizationId: link.organizationId, tenantId: link.tenantId })\n if (!transaction) {\n throw new CrudHttpError(404, { error: 'Transaction not found' })\n }\n const sessionAmount = Number(transaction.amount)\n if (!Number.isFinite(sessionAmount)) {\n throw new CrudHttpError(500, { error: 'Invalid checkout transaction amount' })\n }\n const sessionCurrencyCode = transaction.currencyCode\n if (!transaction.gatewayTransactionId) {\n const configuredPaymentTypes = Array.isArray(link.gatewaySettings?.paymentTypes)\n ? link.gatewaySettings.paymentTypes.filter(\n (value: unknown): value is string => typeof value === 'string' && value.trim().length > 0,\n )\n : []\n const rendererKey = typeof link.gatewaySettings?.rendererKey === 'string' && link.gatewaySettings.rendererKey.trim().length > 0\n ? link.gatewaySettings.rendererKey.trim()\n : undefined\n const rendererSettings = link.gatewaySettings?.rendererSettings\n && typeof link.gatewaySettings.rendererSettings === 'object'\n && !Array.isArray(link.gatewaySettings.rendererSettings)\n ? link.gatewaySettings.rendererSettings as Record<string, unknown>\n : undefined\n const presentationMode = link.gatewaySettings?.presentationMode === 'embedded'\n || link.gatewaySettings?.presentationMode === 'redirect'\n || link.gatewaySettings?.presentationMode === 'auto'\n ? link.gatewaySettings.presentationMode\n : undefined\n\n let sessionResult: Awaited<ReturnType<typeof paymentGatewayService.createPaymentSession>>\n try {\n sessionResult = await paymentGatewayService.createPaymentSession({\n providerKey: link.gatewayProviderKey,\n paymentId: transactionId,\n idempotencyKey,\n amount: sessionAmount,\n currencyCode: sessionCurrencyCode,\n paymentTypes: configuredPaymentTypes.length > 0 ? configuredPaymentTypes : undefined,\n description: link.title ?? link.name,\n successUrl,\n cancelUrl,\n metadata: {\n checkoutLinkId: link.id,\n checkoutSlug: link.slug,\n },\n presentation: rendererKey || rendererSettings || presentationMode\n ? {\n ...(presentationMode ? { mode: presentationMode } : {}),\n ...(rendererKey ? { rendererKey } : {}),\n ...(rendererSettings ? { rendererSettings } : {}),\n }\n : undefined,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n })\n } catch (error) {\n // The gateway itself failed \u2014 mark the transaction failed and surface 502.\n await commandBus.execute('checkout.transaction.updateStatus', {\n input: {\n id: transaction.id,\n status: 'failed',\n paymentStatus: transaction.paymentStatus ?? 'failed',\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n },\n ctx,\n }).catch(() => undefined)\n logger.error('Failed to create payment session', {\n linkId: link.id,\n transactionId: transaction.id,\n providerKey: link.gatewayProviderKey,\n err: error,\n })\n throw new CrudHttpError(502, { error: 'checkout.payPage.errors.sessionStart' })\n }\n\n // Update the transaction status to reflect the gateway response.\n // If the webhook already raced ahead and completed this transaction,\n // updateStatus will throw a 409 conflict \u2014 that is correct behavior and\n // means the payment succeeded, so we treat it as benign and fall through\n // to return the real, already-terminal transaction below.\n let statusConflict = false\n try {\n await commandBus.execute('checkout.transaction.updateStatus', {\n input: {\n id: transaction.id,\n status: mapGatewayStatusToCheckoutStatus(sessionResult.transaction.unifiedStatus),\n paymentStatus: sessionResult.transaction.unifiedStatus,\n gatewayTransactionId: sessionResult.transaction.id,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n },\n ctx,\n })\n } catch (error) {\n const isConflict = isCrudHttpError(error)\n && error.status === 409\n && (\n (error.body as Record<string, unknown> | undefined)?.code === 'concurrent_status_update'\n || (error.body as Record<string, unknown> | undefined)?.code === 'invalid_status_transition'\n )\n if (!isConflict) throw error\n logger.info('Transaction status already advanced by concurrent writer \u2014 webhook won the race', {\n linkId: link.id,\n transactionId: transaction.id,\n conflictCode: (error.body as Record<string, unknown> | undefined)?.code,\n })\n statusConflict = true\n }\n\n const refreshedTransaction = await findOneWithDecryption(em, CheckoutTransaction, {\n id: transaction.id,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }, undefined, { organizationId: link.organizationId, tenantId: link.tenantId })\n if (!refreshedTransaction) {\n throw new CrudHttpError(404, { error: 'Transaction not found' })\n }\n if (!statusConflict) {\n await emitCheckoutEvent('checkout.transaction.sessionStarted', {\n transactionId: refreshedTransaction.id,\n linkId: refreshedTransaction.linkId,\n templateId: link.templateId ?? null,\n slug: link.slug,\n status: refreshedTransaction.status,\n paymentStatus: refreshedTransaction.paymentStatus ?? null,\n amount: Number(refreshedTransaction.amount),\n currency: refreshedTransaction.currencyCode,\n gatewayProvider: link.gatewayProviderKey,\n gatewayTransactionId: refreshedTransaction.gatewayTransactionId ?? null,\n occurredAt: new Date().toISOString(),\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n }).catch(() => undefined)\n }\n return NextResponse.json(\n await buildSubmitResponse(req, em, link, refreshedTransaction, link.gatewayProviderKey),\n { status: 201 },\n )\n }\n\n return NextResponse.json(await buildSubmitResponse(req, em, link, transaction, link.gatewayProviderKey), { status: 201 })\n } catch (error) {\n return handleCheckoutRouteError(error)\n }\n}\n\nexport const openApi = {\n tags: [checkoutTag],\n}\n\nexport default POST\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { PaymentGatewayClientSession } from '@open-mercato/shared/modules/payment_gateways/types'\nimport type { PaymentGatewayService } from '@open-mercato/core/modules/payment_gateways/lib/gateway-service'\nimport { GatewayTransaction } from '@open-mercato/core/modules/payment_gateways/data/entities'\nimport { CheckoutLink, CheckoutTransaction } from '../../../../data/entities'\nimport { publicSubmitSchema } from '../../../../data/validators'\nimport { handleCheckoutRouteError, requireCheckoutPasswordSession } from '../../../helpers'\nimport { emitCheckoutEvent } from '../../../../events'\nimport {\n buildConsentProof,\n isCheckoutLinkPublic,\n mapGatewayStatusToCheckoutStatus,\n resolveSubmittedAmount,\n validateDescriptorCurrencies,\n} from '../../../../lib/utils'\nimport { validateCheckoutCustomerData } from '../../../../lib/customerDataValidation'\nimport { checkoutSubmitRateLimitConfig, enforceCheckoutRateLimit } from '../../../../lib/rateLimiter'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport { checkoutTag } from '../../../openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('checkout')\n\ntype CachedSubmitResponse = {\n transactionId: string\n redirectUrl?: string | null\n paymentSession?: (PaymentGatewayClientSession & {\n providerKey: string | null\n gatewayTransactionId: string\n }) | null\n}\n\ntype CheckoutLegalDocumentRequirement = {\n required?: boolean\n}\n\ntype CheckoutCustomerFieldRequirement = {\n key: string\n required?: boolean\n}\n\nfunction normalizePort(url: URL): string {\n if (url.port) return url.port\n return url.protocol === 'https:' ? '443' : '80'\n}\n\nfunction normalizeConfiguredOrigin(candidate: string): string | null {\n try {\n return new URL(candidate).origin\n } catch {\n return null\n }\n}\n\nfunction isLoopbackHostname(hostname: string): boolean {\n return hostname === '127.0.0.1' || hostname === 'localhost'\n}\n\nfunction splitHeaderValues(value: string | null): string[] {\n if (!value) return []\n return value\n .split(',')\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0)\n}\n\nfunction isAllowedRequestOrigin(submittedOrigin: string, allowedOrigin: string): boolean {\n if (submittedOrigin === allowedOrigin) return true\n try {\n const submittedUrl = new URL(submittedOrigin)\n const allowedUrl = new URL(allowedOrigin)\n return submittedUrl.protocol === allowedUrl.protocol\n && normalizePort(submittedUrl) === normalizePort(allowedUrl)\n && isLoopbackHostname(submittedUrl.hostname)\n && isLoopbackHostname(allowedUrl.hostname)\n } catch {\n return false\n }\n}\n\nconst CONFIGURED_ALLOWED_ORIGINS: string[] = (process.env.CHECKOUT_ALLOWED_ORIGINS ?? '')\n .split(',')\n .map((origin) => origin.trim())\n .filter((origin) => origin.length > 0)\n\nfunction addAllowedOrigin(allowedOrigins: Set<string>, candidate: string) {\n const normalized = normalizeConfiguredOrigin(candidate)\n if (!normalized) return\n allowedOrigins.add(normalized)\n try {\n const parsed = new URL(normalized)\n if (!isLoopbackHostname(parsed.hostname)) return\n for (const hostname of ['127.0.0.1', 'localhost']) {\n for (const protocol of ['http:', 'https:']) {\n const variant = new URL(normalized)\n variant.hostname = hostname\n variant.protocol = protocol\n allowedOrigins.add(variant.origin)\n }\n }\n } catch {\n // Ignore invalid variants.\n }\n}\n\nfunction collectConfiguredOrigins(): string[] {\n const origins = [...CONFIGURED_ALLOWED_ORIGINS]\n for (const origin of [process.env.APP_URL, process.env.NEXT_PUBLIC_APP_URL]) {\n if (origin) origins.push(origin)\n }\n return origins\n}\n\n// Allowed origins are built ONLY from server-configured values. The inbound\n// request URL, Host and X-Forwarded-Host headers are attacker-controllable on\n// deployments that do not normalize them at the edge, so they must never seed\n// the allowlist (otherwise a spoofed Host self-passes the origin check).\nfunction buildAllowedOrigins(): string[] {\n const allowedOrigins = new Set<string>()\n for (const origin of collectConfiguredOrigins()) {\n addAllowedOrigin(allowedOrigins, origin)\n }\n return Array.from(allowedOrigins)\n}\n\n// Resolve the origin used to build gateway-bound success/cancel/return URLs.\n// Always prefer a server-pinned configured origin; only fall back to the request\n// origin on loopback (local dev/test), where the Host cannot be spoofed by a\n// remote attacker. A non-loopback request with no configured origin is a\n// misconfiguration and is rejected rather than emitting an attacker-controllable URL.\nfunction resolveServerOrigin(req: Request): string {\n for (const candidate of collectConfiguredOrigins()) {\n const normalized = normalizeConfiguredOrigin(candidate)\n if (normalized) return normalized\n }\n const requestUrl = new URL(req.url)\n if (isLoopbackHostname(requestUrl.hostname)) return requestUrl.origin\n throw new CrudHttpError(500, { error: 'Checkout origin is not configured' })\n}\n\n// Normalize a host value (bare host, host:port, or full origin) to its authority\n// (`hostname` plus any non-default port), lower-cased. Protocol is intentionally\n// dropped: the gateway-bound URLs are already pinned to the configured origin, so\n// the host guard below only needs to reject foreign hosts \u2014 not enforce protocol\n// or internal-upstream-host parity, which would 403 legitimate proxied requests.\nfunction hostAuthority(value: string): string | null {\n const candidate = value.includes('://') ? value : `https://${value}`\n try {\n const url = new URL(candidate)\n if (!url.hostname) return null\n const port = url.port && url.port !== '80' && url.port !== '443' ? `:${url.port}` : ''\n return `${url.hostname.toLowerCase()}${port}`\n } catch {\n return null\n }\n}\n\nfunction allowedHostAuthorities(allowedOrigins: string[]): Set<string> {\n const authorities = new Set<string>()\n for (const origin of allowedOrigins) {\n const authority = hostAuthority(origin)\n if (authority) authorities.add(authority)\n }\n return authorities\n}\n\n// The externally-asserted host: the proxy-forwarded X-Forwarded-Host when present,\n// otherwise the Host header. The internal upstream Host (and protocol) are ignored\n// so a correctly-proxied request is not rejected for not matching the public origin.\nfunction requestHostAuthorities(req: Request): string[] {\n const forwarded = splitHeaderValues(req.headers.get('x-forwarded-host'))\n const hostValues = forwarded.length > 0 ? forwarded : splitHeaderValues(req.headers.get('host'))\n const authorities: string[] = []\n for (const value of hostValues) {\n const authority = hostAuthority(value)\n if (authority) authorities.push(authority)\n }\n return authorities\n}\n\nfunction isIdempotencyConflict(error: unknown): boolean {\n const message = error instanceof Error ? error.message : ''\n return message.includes('checkout_transactions_organization_id_tenant_id_link_idempotency_key_index')\n || message.includes('checkout_transactions_organization_id_tenant_id_link_idempotency_key_unique')\n || message.includes('duplicate key value')\n}\n\nfunction readClientSession(\n metadata: Record<string, unknown> | null | undefined,\n): PaymentGatewayClientSession | null {\n const candidate = metadata?.clientSession\n if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) return null\n const type = (candidate as { type?: unknown }).type\n if (type === 'redirect') {\n const redirectUrl = (candidate as { redirectUrl?: unknown }).redirectUrl\n if (typeof redirectUrl !== 'string' || redirectUrl.trim().length === 0) return null\n const target = (candidate as { target?: unknown }).target\n return {\n type: 'redirect',\n redirectUrl,\n target: target === 'top' ? 'top' : 'self',\n }\n }\n if (type !== 'embedded') return null\n if (typeof (candidate as { rendererKey?: unknown }).rendererKey !== 'string') return null\n const payload = (candidate as { payload?: unknown }).payload\n const settings = (candidate as { settings?: unknown }).settings\n return {\n type: 'embedded',\n rendererKey: (candidate as { rendererKey: string }).rendererKey,\n payload: payload && typeof payload === 'object' && !Array.isArray(payload)\n ? payload as Record<string, unknown>\n : undefined,\n settings: settings && typeof settings === 'object' && !Array.isArray(settings)\n ? settings as Record<string, unknown>\n : undefined,\n }\n}\n\nasync function buildSubmitResponse(\n req: Request,\n em: EntityManager,\n link: CheckoutLink,\n transaction: CheckoutTransaction,\n providerKey: string | null | undefined,\n): Promise<CachedSubmitResponse> {\n const gatewayTransaction = transaction.gatewayTransactionId\n ? await em.findOne(GatewayTransaction, {\n id: transaction.gatewayTransactionId,\n organizationId: transaction.organizationId,\n tenantId: transaction.tenantId,\n deletedAt: null,\n })\n : null\n const serverOrigin = resolveServerOrigin(req)\n const clientSession = gatewayTransaction\n ? readClientSession(gatewayTransaction.gatewayMetadata)\n : null\n const paymentSession = (clientSession && gatewayTransaction)\n ? {\n ...clientSession,\n ...(clientSession.type === 'embedded'\n ? {\n payload: {\n ...(clientSession.payload ?? {}),\n returnUrl: `${serverOrigin}/pay/${encodeURIComponent(link.slug)}/success/${encodeURIComponent(transaction.id)}`,\n cancelUrl: `${serverOrigin}/pay/${encodeURIComponent(link.slug)}/cancel/${encodeURIComponent(transaction.id)}`,\n },\n }\n : {}),\n providerKey: providerKey ?? gatewayTransaction.providerKey ?? null,\n gatewayTransactionId: gatewayTransaction.id,\n }\n : null\n return {\n transactionId: transaction.id,\n redirectUrl: gatewayTransaction?.redirectUrl ?? null,\n paymentSession,\n }\n}\n\nexport const metadata = {\n path: '/checkout/pay/[slug]/submit',\n POST: { requireAuth: false },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ slug: string }> | { slug: string } }) {\n try {\n const container = await createRequestContainer()\n // Fail-closed: this endpoint creates payment sessions that can charge cards.\n const rateLimitResponse = await enforceCheckoutRateLimit({\n req,\n container,\n config: checkoutSubmitRateLimitConfig,\n namespace: 'checkout-submit',\n errorMessage: 'Too many payment attempts. Please try again later.',\n posture: 'fail-closed',\n })\n if (rateLimitResponse) return rateLimitResponse\n const resolvedParams = await params\n\n const allowedOrigins = buildAllowedOrigins()\n if (allowedOrigins.length > 0) {\n // Reject spoofed Host / X-Forwarded-Host before any business logic runs so\n // an attacker-controlled host can never flow into gateway-bound URLs. Hosts\n // are matched by authority (protocol-independent) so a correctly TLS-proxied\n // request \u2014 internal http, X-Forwarded-Proto https \u2014 is not rejected.\n const allowedAuthorities = allowedHostAuthorities(allowedOrigins)\n const hostAuthorities = requestHostAuthorities(req)\n if (\n hostAuthorities.length > 0\n && !hostAuthorities.every((authority) => allowedAuthorities.has(authority))\n ) {\n return NextResponse.json({ error: 'Invalid request host' }, { status: 403 })\n }\n\n const origin = req.headers.get('origin')\n const referer = req.headers.get('referer')\n if (origin || referer) {\n const submittedOrigin = origin ?? (referer ? new URL(referer).origin : null)\n if (submittedOrigin && !allowedOrigins.some((allowedOrigin) => isAllowedRequestOrigin(submittedOrigin, allowedOrigin))) {\n return NextResponse.json({ error: 'Invalid request origin' }, { status: 403 })\n }\n }\n }\n\n const idempotencyKey = req.headers.get('Idempotency-Key')?.trim()\n if (!idempotencyKey) {\n return NextResponse.json({ error: 'Idempotency-Key header is required' }, { status: 400 })\n }\n if (idempotencyKey.length < 16 || idempotencyKey.length > 128) {\n return NextResponse.json({ error: 'Idempotency-Key must be between 16 and 128 characters' }, { status: 400 })\n }\n\n const body = publicSubmitSchema.parse(await req.json().catch(() => ({})))\n const em = container.resolve('em')\n const commandBus = container.resolve('commandBus') as CommandBus\n const paymentGatewayService = container.resolve('paymentGatewayService') as PaymentGatewayService\n const link = await findOneWithDecryption(em, CheckoutLink, {\n slug: resolvedParams.slug,\n deletedAt: null,\n })\n if (!link) {\n return NextResponse.json({ error: 'Payment link not found' }, { status: 404 })\n }\n if (!isCheckoutLinkPublic(link.status)) {\n throw new CrudHttpError(422, { error: 'This payment link is not currently accepting payments' })\n }\n if (link.passwordHash) {\n requireCheckoutPasswordSession(req, link.slug, {\n linkId: link.id,\n sessionVersion: link.passwordHash,\n })\n }\n if (!link.gatewayProviderKey) {\n throw new CrudHttpError(422, { error: 'A payment gateway must be configured before this link can be used' })\n }\n const legalDocuments = link.legalDocuments && typeof link.legalDocuments === 'object'\n ? link.legalDocuments as Partial<Record<'terms' | 'privacyPolicy', CheckoutLegalDocumentRequirement>>\n : {}\n const legalConsentErrors: Record<string, string> = {}\n for (const key of ['terms', 'privacyPolicy'] as const) {\n const document = legalDocuments[key]\n if (document?.required === true && body.acceptedLegalConsents?.[key] !== true) {\n legalConsentErrors[`acceptedLegalConsents.${key}`] = 'checkout.payPage.validation.documentRequired'\n }\n }\n if (Object.keys(legalConsentErrors).length > 0) {\n throw new CrudHttpError(422, {\n error: 'checkout.payPage.validation.fixErrors',\n fieldErrors: legalConsentErrors,\n })\n }\n const collectedCustomerData = link.collectCustomerDetails === false ? {} : body.customerData\n const customerFields = Array.isArray(link.customerFieldsSchema)\n ? link.customerFieldsSchema as CheckoutCustomerFieldRequirement[]\n : []\n if (link.collectCustomerDetails !== false) {\n const customerFieldErrors = validateCheckoutCustomerData(customerFields, collectedCustomerData)\n if (Object.keys(customerFieldErrors).length > 0) {\n throw new CrudHttpError(422, {\n error: 'checkout.payPage.validation.fixErrors',\n fieldErrors: customerFieldErrors,\n })\n }\n }\n const resolvedAmount = resolveSubmittedAmount(link, body)\n const existingTransaction = await findOneWithDecryption(em, CheckoutTransaction, {\n linkId: link.id,\n idempotencyKey,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }, undefined, { organizationId: link.organizationId, tenantId: link.tenantId })\n validateDescriptorCurrencies(link.gatewayProviderKey, [\n existingTransaction?.currencyCode ?? resolvedAmount.currencyCode,\n ])\n if (existingTransaction?.gatewayTransactionId) {\n return NextResponse.json(\n await buildSubmitResponse(req, em, link, existingTransaction, link.gatewayProviderKey),\n )\n }\n\n const transactionInput = {\n linkId: link.id,\n amount: resolvedAmount.amount,\n currencyCode: resolvedAmount.currencyCode,\n idempotencyKey,\n customerData: collectedCustomerData,\n firstName: typeof collectedCustomerData.firstName === 'string' ? collectedCustomerData.firstName : null,\n lastName: typeof collectedCustomerData.lastName === 'string' ? collectedCustomerData.lastName : null,\n email: typeof collectedCustomerData.email === 'string' ? collectedCustomerData.email : null,\n phone: typeof collectedCustomerData.phone === 'string' ? collectedCustomerData.phone : null,\n selectedPriceItemId: resolvedAmount.selectedPriceItemId,\n acceptedLegalConsents: buildConsentProof(link, body.acceptedLegalConsents),\n ipAddress: req.headers.get('x-forwarded-for') ?? null,\n userAgent: req.headers.get('user-agent') ?? null,\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n }\n const ctx: CommandRuntimeContext = {\n container,\n auth: null,\n organizationScope: null,\n selectedOrganizationId: link.organizationId,\n organizationIds: [link.organizationId],\n request: req,\n }\n let transactionId = existingTransaction?.id ?? null\n if (!transactionId) {\n try {\n const created = await commandBus.execute<typeof transactionInput, { id: string }>('checkout.transaction.create', {\n input: transactionInput,\n ctx,\n })\n transactionId = created.result.id\n } catch (error) {\n if (!isIdempotencyConflict(error)) {\n throw error\n }\n const duplicated = await findOneWithDecryption(em, CheckoutTransaction, {\n linkId: link.id,\n idempotencyKey,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }, undefined, { organizationId: link.organizationId, tenantId: link.tenantId })\n if (!duplicated) {\n throw error\n }\n transactionId = duplicated.id\n }\n }\n if (!transactionId) {\n throw new CrudHttpError(500, { error: 'Failed to initialize checkout transaction' })\n }\n const serverOrigin = resolveServerOrigin(req)\n const successUrl = `${serverOrigin}/pay/${encodeURIComponent(link.slug)}/success/${encodeURIComponent(transactionId)}`\n const cancelUrl = `${serverOrigin}/pay/${encodeURIComponent(link.slug)}/cancel/${encodeURIComponent(transactionId)}`\n const transaction = await findOneWithDecryption(em, CheckoutTransaction, {\n id: transactionId,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }, undefined, { organizationId: link.organizationId, tenantId: link.tenantId })\n if (!transaction) {\n throw new CrudHttpError(404, { error: 'Transaction not found' })\n }\n const sessionAmount = Number(transaction.amount)\n if (!Number.isFinite(sessionAmount)) {\n throw new CrudHttpError(500, { error: 'Invalid checkout transaction amount' })\n }\n const sessionCurrencyCode = transaction.currencyCode\n if (!transaction.gatewayTransactionId) {\n const configuredPaymentTypes = Array.isArray(link.gatewaySettings?.paymentTypes)\n ? link.gatewaySettings.paymentTypes.filter(\n (value: unknown): value is string => typeof value === 'string' && value.trim().length > 0,\n )\n : []\n const rendererKey = typeof link.gatewaySettings?.rendererKey === 'string' && link.gatewaySettings.rendererKey.trim().length > 0\n ? link.gatewaySettings.rendererKey.trim()\n : undefined\n const rendererSettings = link.gatewaySettings?.rendererSettings\n && typeof link.gatewaySettings.rendererSettings === 'object'\n && !Array.isArray(link.gatewaySettings.rendererSettings)\n ? link.gatewaySettings.rendererSettings as Record<string, unknown>\n : undefined\n const presentationMode = link.gatewaySettings?.presentationMode === 'embedded'\n || link.gatewaySettings?.presentationMode === 'redirect'\n || link.gatewaySettings?.presentationMode === 'auto'\n ? link.gatewaySettings.presentationMode\n : undefined\n\n let sessionResult: Awaited<ReturnType<typeof paymentGatewayService.createPaymentSession>>\n try {\n sessionResult = await paymentGatewayService.createPaymentSession({\n providerKey: link.gatewayProviderKey,\n paymentId: transactionId,\n idempotencyKey,\n amount: sessionAmount,\n currencyCode: sessionCurrencyCode,\n paymentTypes: configuredPaymentTypes.length > 0 ? configuredPaymentTypes : undefined,\n description: link.title ?? link.name,\n successUrl,\n cancelUrl,\n metadata: {\n checkoutLinkId: link.id,\n checkoutSlug: link.slug,\n },\n presentation: rendererKey || rendererSettings || presentationMode\n ? {\n ...(presentationMode ? { mode: presentationMode } : {}),\n ...(rendererKey ? { rendererKey } : {}),\n ...(rendererSettings ? { rendererSettings } : {}),\n }\n : undefined,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n })\n } catch (error) {\n // The gateway itself failed \u2014 mark the transaction failed and surface 502.\n await commandBus.execute('checkout.transaction.updateStatus', {\n input: {\n id: transaction.id,\n status: 'failed',\n paymentStatus: transaction.paymentStatus ?? 'failed',\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n },\n ctx,\n }).catch(() => undefined)\n logger.error('Failed to create payment session', {\n linkId: link.id,\n transactionId: transaction.id,\n providerKey: link.gatewayProviderKey,\n err: error,\n })\n throw new CrudHttpError(502, { error: 'checkout.payPage.errors.sessionStart' })\n }\n\n // Update the transaction status to reflect the gateway response.\n // If the webhook already raced ahead and completed this transaction,\n // updateStatus will throw a 409 conflict \u2014 that is correct behavior and\n // means the payment succeeded, so we treat it as benign and fall through\n // to return the real, already-terminal transaction below.\n let statusConflict = false\n try {\n await commandBus.execute('checkout.transaction.updateStatus', {\n input: {\n id: transaction.id,\n status: mapGatewayStatusToCheckoutStatus(sessionResult.transaction.unifiedStatus),\n paymentStatus: sessionResult.transaction.unifiedStatus,\n gatewayTransactionId: sessionResult.transaction.id,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n },\n ctx,\n })\n } catch (error) {\n const isConflict = isCrudHttpError(error)\n && error.status === 409\n && (\n (error.body as Record<string, unknown> | undefined)?.code === 'concurrent_status_update'\n || (error.body as Record<string, unknown> | undefined)?.code === 'invalid_status_transition'\n )\n if (!isConflict) throw error\n logger.info('Transaction status already advanced by concurrent writer \u2014 webhook won the race', {\n linkId: link.id,\n transactionId: transaction.id,\n conflictCode: (error.body as Record<string, unknown> | undefined)?.code,\n })\n statusConflict = true\n }\n\n const refreshedTransaction = await findOneWithDecryption(em, CheckoutTransaction, {\n id: transaction.id,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }, undefined, { organizationId: link.organizationId, tenantId: link.tenantId })\n if (!refreshedTransaction) {\n throw new CrudHttpError(404, { error: 'Transaction not found' })\n }\n if (!statusConflict) {\n await emitCheckoutEvent('checkout.transaction.sessionStarted', {\n transactionId: refreshedTransaction.id,\n linkId: refreshedTransaction.linkId,\n templateId: link.templateId ?? null,\n slug: link.slug,\n status: refreshedTransaction.status,\n paymentStatus: refreshedTransaction.paymentStatus ?? null,\n amount: Number(refreshedTransaction.amount),\n currency: refreshedTransaction.currencyCode,\n gatewayProvider: link.gatewayProviderKey,\n gatewayTransactionId: refreshedTransaction.gatewayTransactionId ?? null,\n occurredAt: new Date().toISOString(),\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n }).catch(() => undefined)\n }\n return NextResponse.json(\n await buildSubmitResponse(req, em, link, refreshedTransaction, link.gatewayProviderKey),\n { status: 201 },\n )\n }\n\n return NextResponse.json(await buildSubmitResponse(req, em, link, transaction, link.gatewayProviderKey), { status: 201 })\n } catch (error) {\n return handleCheckoutRouteError(error)\n }\n}\n\nexport const openApi = {\n tags: [checkoutTag],\n methods: {\n POST: {\n errors: [\n { status: 429, description: 'Too many payment attempts', schema: rateLimitErrorSchema },\n { status: 503, description: 'Rate limiting could not be enforced, so the payment was rejected', schema: rateLimitErrorSchema },\n ],\n },\n },\n}\n\nexport default POST\n"],
5
+ "mappings": "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",
6
6
  "names": ["metadata"]
7
7
  }
@@ -1,7 +1,6 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
3
3
  import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
- import { checkRateLimit } from "@open-mercato/shared/lib/ratelimit/helpers";
5
4
  import { CheckoutLink } from "../../../../data/entities.js";
6
5
  import { CHECKOUT_PASSWORD_COOKIE } from "../../../../lib/constants.js";
7
6
  import { publicPasswordVerifySchema } from "../../../../data/validators.js";
@@ -11,7 +10,8 @@ import {
11
10
  signCheckoutAccessToken,
12
11
  verifyCheckoutPassword
13
12
  } from "../../../../lib/utils.js";
14
- import { buildCheckoutRateLimitKey, checkoutPasswordRateLimitConfig } from "../../../../lib/rateLimiter.js";
13
+ import { checkoutPasswordRateLimitConfig, enforceCheckoutRateLimit } from "../../../../lib/rateLimiter.js";
14
+ import { rateLimitErrorSchema } from "@open-mercato/shared/lib/ratelimit/helpers";
15
15
  import { checkoutTag } from "../../../openapi.js";
16
16
  const metadata = {
17
17
  path: "/checkout/pay/[slug]/verify-password",
@@ -20,13 +20,15 @@ const metadata = {
20
20
  async function POST(req, { params }) {
21
21
  try {
22
22
  const container = await createRequestContainer();
23
- try {
24
- const rateLimiter = container.resolve("rateLimiterService");
25
- const key = buildCheckoutRateLimitKey(req, rateLimiter, "checkout-password");
26
- const rateLimitResponse = await checkRateLimit(rateLimiter, checkoutPasswordRateLimitConfig, key, "Too many password attempts. Please try again later.");
27
- if (rateLimitResponse) return rateLimitResponse;
28
- } catch {
29
- }
23
+ const rateLimitResponse = await enforceCheckoutRateLimit({
24
+ req,
25
+ container,
26
+ config: checkoutPasswordRateLimitConfig,
27
+ namespace: "checkout-password",
28
+ errorMessage: "Too many password attempts. Please try again later.",
29
+ posture: "fail-closed"
30
+ });
31
+ if (rateLimitResponse) return rateLimitResponse;
30
32
  const resolvedParams = await params;
31
33
  const body = publicPasswordVerifySchema.parse(await req.json().catch(() => ({})));
32
34
  const em = container.resolve("em");
@@ -58,7 +60,15 @@ async function POST(req, { params }) {
58
60
  }
59
61
  }
60
62
  const openApi = {
61
- tags: [checkoutTag]
63
+ tags: [checkoutTag],
64
+ methods: {
65
+ POST: {
66
+ errors: [
67
+ { status: 429, description: "Too many password attempts", schema: rateLimitErrorSchema },
68
+ { status: 503, description: "Rate limiting could not be enforced, so the attempt was rejected", schema: rateLimitErrorSchema }
69
+ ]
70
+ }
71
+ }
62
72
  };
63
73
  var route_default = POST;
64
74
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/checkout/api/pay/%5Bslug%5D/verify-password/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { RateLimiterService } from '@open-mercato/shared/lib/ratelimit/service'\nimport { checkRateLimit } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport { CheckoutLink } from '../../../../data/entities'\nimport { CHECKOUT_PASSWORD_COOKIE } from '../../../../lib/constants'\nimport { publicPasswordVerifySchema } from '../../../../data/validators'\nimport { handleCheckoutRouteError } from '../../../helpers'\nimport {\n isCheckoutLinkPublic,\n signCheckoutAccessToken,\n verifyCheckoutPassword,\n} from '../../../../lib/utils'\nimport { buildCheckoutRateLimitKey, checkoutPasswordRateLimitConfig } from '../../../../lib/rateLimiter'\nimport { checkoutTag } from '../../../openapi'\n\nexport const metadata = {\n path: '/checkout/pay/[slug]/verify-password',\n POST: { requireAuth: false },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ slug: string }> | { slug: string } }) {\n try {\n const container = await createRequestContainer()\n try {\n const rateLimiter = container.resolve('rateLimiterService') as RateLimiterService\n const key = buildCheckoutRateLimitKey(req, rateLimiter, 'checkout-password')\n const rateLimitResponse = await checkRateLimit(rateLimiter, checkoutPasswordRateLimitConfig, key, 'Too many password attempts. Please try again later.')\n if (rateLimitResponse) return rateLimitResponse\n } catch {\n // Rate limiting is fail-open\n }\n const resolvedParams = await params\n const body = publicPasswordVerifySchema.parse(await req.json().catch(() => ({})))\n const em = container.resolve('em')\n const link = await findOneWithDecryption(em, CheckoutLink, {\n slug: resolvedParams.slug,\n deletedAt: null,\n })\n if (!link || !isCheckoutLinkPublic(link.status) || !link.passwordHash) {\n return NextResponse.json({ error: 'checkout.payPage.errors.password' }, { status: 401 })\n }\n const ok = await verifyCheckoutPassword(body.password, link.passwordHash)\n if (!ok) {\n return NextResponse.json({ error: 'checkout.payPage.errors.password' }, { status: 401 })\n }\n const response = NextResponse.json({ ok: true })\n response.cookies.set(CHECKOUT_PASSWORD_COOKIE, signCheckoutAccessToken(link.slug, {\n linkId: link.id,\n sessionVersion: link.passwordHash,\n }), {\n httpOnly: true,\n secure: true,\n sameSite: 'strict',\n path: '/',\n maxAge: 60 * 60,\n })\n return response\n } catch (error) {\n return handleCheckoutRouteError(error)\n }\n}\n\nexport const openApi = {\n tags: [checkoutTag],\n}\n\nexport default POST\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,6BAA6B;AAEtC,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAC7B,SAAS,gCAAgC;AACzC,SAAS,kCAAkC;AAC3C,SAAS,gCAAgC;AACzC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,2BAA2B,uCAAuC;AAC3E,SAAS,mBAAmB;AAErB,MAAM,WAAW;AAAA,EACtB,MAAM;AAAA,EACN,MAAM,EAAE,aAAa,MAAM;AAC7B;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA6D;AAC7G,MAAI;AACF,UAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAI;AACF,YAAM,cAAc,UAAU,QAAQ,oBAAoB;AAC1D,YAAM,MAAM,0BAA0B,KAAK,aAAa,mBAAmB;AAC3E,YAAM,oBAAoB,MAAM,eAAe,aAAa,iCAAiC,KAAK,qDAAqD;AACvJ,UAAI,kBAAmB,QAAO;AAAA,IAChC,QAAQ;AAAA,IAER;AACA,UAAM,iBAAiB,MAAM;AAC7B,UAAM,OAAO,2BAA2B,MAAM,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE,CAAC;AAChF,UAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,UAAM,OAAO,MAAM,sBAAsB,IAAI,cAAc;AAAA,MACzD,MAAM,eAAe;AAAA,MACrB,WAAW;AAAA,IACb,CAAC;AACD,QAAI,CAAC,QAAQ,CAAC,qBAAqB,KAAK,MAAM,KAAK,CAAC,KAAK,cAAc;AACrE,aAAO,aAAa,KAAK,EAAE,OAAO,mCAAmC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACzF;AACA,UAAM,KAAK,MAAM,uBAAuB,KAAK,UAAU,KAAK,YAAY;AACxE,QAAI,CAAC,IAAI;AACP,aAAO,aAAa,KAAK,EAAE,OAAO,mCAAmC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACzF;AACA,UAAM,WAAW,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAC/C,aAAS,QAAQ,IAAI,0BAA0B,wBAAwB,KAAK,MAAM;AAAA,MAChF,QAAQ,KAAK;AAAA,MACb,gBAAgB,KAAK;AAAA,IACvB,CAAC,GAAG;AAAA,MACF,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,MAAM;AAAA,MACN,QAAQ,KAAK;AAAA,IACf,CAAC;AACD,WAAO;AAAA,EACT,SAAS,OAAO;AACd,WAAO,yBAAyB,KAAK;AAAA,EACvC;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,MAAM,CAAC,WAAW;AACpB;AAEA,IAAO,gBAAQ;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CheckoutLink } from '../../../../data/entities'\nimport { CHECKOUT_PASSWORD_COOKIE } from '../../../../lib/constants'\nimport { publicPasswordVerifySchema } from '../../../../data/validators'\nimport { handleCheckoutRouteError } from '../../../helpers'\nimport {\n isCheckoutLinkPublic,\n signCheckoutAccessToken,\n verifyCheckoutPassword,\n} from '../../../../lib/utils'\nimport { checkoutPasswordRateLimitConfig, enforceCheckoutRateLimit } from '../../../../lib/rateLimiter'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport { checkoutTag } from '../../../openapi'\n\nexport const metadata = {\n path: '/checkout/pay/[slug]/verify-password',\n POST: { requireAuth: false },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ slug: string }> | { slug: string } }) {\n try {\n const container = await createRequestContainer()\n // Fail-closed: an unenforced limit allows password brute force.\n const rateLimitResponse = await enforceCheckoutRateLimit({\n req,\n container,\n config: checkoutPasswordRateLimitConfig,\n namespace: 'checkout-password',\n errorMessage: 'Too many password attempts. Please try again later.',\n posture: 'fail-closed',\n })\n if (rateLimitResponse) return rateLimitResponse\n const resolvedParams = await params\n const body = publicPasswordVerifySchema.parse(await req.json().catch(() => ({})))\n const em = container.resolve('em')\n const link = await findOneWithDecryption(em, CheckoutLink, {\n slug: resolvedParams.slug,\n deletedAt: null,\n })\n if (!link || !isCheckoutLinkPublic(link.status) || !link.passwordHash) {\n return NextResponse.json({ error: 'checkout.payPage.errors.password' }, { status: 401 })\n }\n const ok = await verifyCheckoutPassword(body.password, link.passwordHash)\n if (!ok) {\n return NextResponse.json({ error: 'checkout.payPage.errors.password' }, { status: 401 })\n }\n const response = NextResponse.json({ ok: true })\n response.cookies.set(CHECKOUT_PASSWORD_COOKIE, signCheckoutAccessToken(link.slug, {\n linkId: link.id,\n sessionVersion: link.passwordHash,\n }), {\n httpOnly: true,\n secure: true,\n sameSite: 'strict',\n path: '/',\n maxAge: 60 * 60,\n })\n return response\n } catch (error) {\n return handleCheckoutRouteError(error)\n }\n}\n\nexport const openApi = {\n tags: [checkoutTag],\n methods: {\n POST: {\n errors: [\n { status: 429, description: 'Too many password attempts', schema: rateLimitErrorSchema },\n { status: 503, description: 'Rate limiting could not be enforced, so the attempt was rejected', schema: rateLimitErrorSchema },\n ],\n },\n },\n}\n\nexport default POST\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,6BAA6B;AACtC,SAAS,oBAAoB;AAC7B,SAAS,gCAAgC;AACzC,SAAS,kCAAkC;AAC3C,SAAS,gCAAgC;AACzC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,iCAAiC,gCAAgC;AAC1E,SAAS,4BAA4B;AACrC,SAAS,mBAAmB;AAErB,MAAM,WAAW;AAAA,EACtB,MAAM;AAAA,EACN,MAAM,EAAE,aAAa,MAAM;AAC7B;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA6D;AAC7G,MAAI;AACF,UAAM,YAAY,MAAM,uBAAuB;AAE/C,UAAM,oBAAoB,MAAM,yBAAyB;AAAA,MACvD;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,cAAc;AAAA,MACd,SAAS;AAAA,IACX,CAAC;AACD,QAAI,kBAAmB,QAAO;AAC9B,UAAM,iBAAiB,MAAM;AAC7B,UAAM,OAAO,2BAA2B,MAAM,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE,CAAC;AAChF,UAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,UAAM,OAAO,MAAM,sBAAsB,IAAI,cAAc;AAAA,MACzD,MAAM,eAAe;AAAA,MACrB,WAAW;AAAA,IACb,CAAC;AACD,QAAI,CAAC,QAAQ,CAAC,qBAAqB,KAAK,MAAM,KAAK,CAAC,KAAK,cAAc;AACrE,aAAO,aAAa,KAAK,EAAE,OAAO,mCAAmC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACzF;AACA,UAAM,KAAK,MAAM,uBAAuB,KAAK,UAAU,KAAK,YAAY;AACxE,QAAI,CAAC,IAAI;AACP,aAAO,aAAa,KAAK,EAAE,OAAO,mCAAmC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACzF;AACA,UAAM,WAAW,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAC/C,aAAS,QAAQ,IAAI,0BAA0B,wBAAwB,KAAK,MAAM;AAAA,MAChF,QAAQ,KAAK;AAAA,MACb,gBAAgB,KAAK;AAAA,IACvB,CAAC,GAAG;AAAA,MACF,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,MAAM;AAAA,MACN,QAAQ,KAAK;AAAA,IACf,CAAC;AACD,WAAO;AAAA,EACT,SAAS,OAAO;AACd,WAAO,yBAAyB,KAAK;AAAA,EACvC;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,MAAM,CAAC,WAAW;AAAA,EAClB,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,8BAA8B,QAAQ,qBAAqB;AAAA,QACvF,EAAE,QAAQ,KAAK,aAAa,oEAAoE,QAAQ,qBAAqB;AAAA,MAC/H;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,51 @@
1
+ import { NextResponse } from "next/server";
2
+ import { createLogger } from "@open-mercato/shared/lib/logger";
1
3
  import { readEndpointRateLimitConfig } from "@open-mercato/shared/lib/ratelimit/config";
2
- import { getClientIp, RATE_LIMIT_FALLBACK_KEY } from "@open-mercato/shared/lib/ratelimit/helpers";
4
+ import {
5
+ checkRateLimit,
6
+ getClientIp,
7
+ RATE_LIMIT_FALLBACK_KEY,
8
+ RATE_LIMIT_UNAVAILABLE_FALLBACK
9
+ } from "@open-mercato/shared/lib/ratelimit/helpers";
10
+ const logger = createLogger("checkout");
3
11
  function buildCheckoutRateLimitKey(req, rateLimiter, namespace) {
4
12
  const clientKey = getClientIp(req, rateLimiter.trustProxyDepth) ?? RATE_LIMIT_FALLBACK_KEY;
5
13
  return `${namespace}:${clientKey}`;
6
14
  }
15
+ function resolveOptionalRateLimiter(container) {
16
+ if (typeof container.hasRegistration === "function" && !container.hasRegistration("rateLimiterService")) {
17
+ return null;
18
+ }
19
+ try {
20
+ return container.resolve("rateLimiterService") ?? null;
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+ async function enforceCheckoutRateLimit(options) {
26
+ const { req, container, config, namespace, errorMessage, posture } = options;
27
+ const rateLimiter = resolveOptionalRateLimiter(container);
28
+ if (!rateLimiter) {
29
+ logger.error("Rate limiter service is not registered \u2014 check RATE_LIMIT_* configuration; checkout rate limiting is not enforced", {
30
+ namespace,
31
+ posture
32
+ });
33
+ return null;
34
+ }
35
+ try {
36
+ const key = buildCheckoutRateLimitKey(req, rateLimiter, namespace);
37
+ return await checkRateLimit(rateLimiter, config, key, errorMessage, {
38
+ failClosed: posture === "fail-closed"
39
+ });
40
+ } catch (error) {
41
+ if (posture === "fail-closed") {
42
+ logger.error("Checkout rate limit check failed, rejecting request", { err: error, namespace });
43
+ return NextResponse.json({ error: RATE_LIMIT_UNAVAILABLE_FALLBACK }, { status: 503 });
44
+ }
45
+ logger.warn("Checkout rate limit check failed, allowing request", { err: error, namespace });
46
+ return null;
47
+ }
48
+ }
7
49
  const checkoutPublicViewRateLimitConfig = readEndpointRateLimitConfig("CHECKOUT_PUBLIC_VIEW", {
8
50
  points: 60,
9
51
  duration: 60,
@@ -33,6 +75,7 @@ export {
33
75
  checkoutPasswordRateLimitConfig,
34
76
  checkoutPublicViewRateLimitConfig,
35
77
  checkoutStatusRateLimitConfig,
36
- checkoutSubmitRateLimitConfig
78
+ checkoutSubmitRateLimitConfig,
79
+ enforceCheckoutRateLimit
37
80
  };
38
81
  //# sourceMappingURL=rateLimiter.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/checkout/lib/rateLimiter.ts"],
4
- "sourcesContent": ["import { readEndpointRateLimitConfig } from '@open-mercato/shared/lib/ratelimit/config'\nimport { getClientIp, RATE_LIMIT_FALLBACK_KEY } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport type { RateLimiterService } from '@open-mercato/shared/lib/ratelimit/service'\n\nexport function buildCheckoutRateLimitKey(\n req: Request,\n rateLimiter: RateLimiterService,\n namespace: string,\n): string {\n const clientKey = getClientIp(req, rateLimiter.trustProxyDepth) ?? RATE_LIMIT_FALLBACK_KEY\n return `${namespace}:${clientKey}`\n}\n\nexport const checkoutPublicViewRateLimitConfig = readEndpointRateLimitConfig('CHECKOUT_PUBLIC_VIEW', {\n points: 60,\n duration: 60,\n blockDuration: 60,\n keyPrefix: 'checkout-public-view',\n})\n\nexport const checkoutStatusRateLimitConfig = readEndpointRateLimitConfig('CHECKOUT_STATUS', {\n points: 120,\n duration: 60,\n blockDuration: 60,\n keyPrefix: 'checkout-status',\n})\n\nexport const checkoutSubmitRateLimitConfig = readEndpointRateLimitConfig('CHECKOUT_SUBMIT', {\n points: 10,\n duration: 60,\n blockDuration: 60,\n keyPrefix: 'checkout-submit',\n})\n\nexport const checkoutPasswordRateLimitConfig = readEndpointRateLimitConfig('CHECKOUT_PASSWORD', {\n points: 5,\n duration: 60,\n blockDuration: 120,\n keyPrefix: 'checkout-password',\n})\n"],
5
- "mappings": "AAAA,SAAS,mCAAmC;AAC5C,SAAS,aAAa,+BAA+B;AAG9C,SAAS,0BACd,KACA,aACA,WACQ;AACR,QAAM,YAAY,YAAY,KAAK,YAAY,eAAe,KAAK;AACnE,SAAO,GAAG,SAAS,IAAI,SAAS;AAClC;AAEO,MAAM,oCAAoC,4BAA4B,wBAAwB;AAAA,EACnG,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,eAAe;AAAA,EACf,WAAW;AACb,CAAC;AAEM,MAAM,gCAAgC,4BAA4B,mBAAmB;AAAA,EAC1F,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,eAAe;AAAA,EACf,WAAW;AACb,CAAC;AAEM,MAAM,gCAAgC,4BAA4B,mBAAmB;AAAA,EAC1F,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,eAAe;AAAA,EACf,WAAW;AACb,CAAC;AAEM,MAAM,kCAAkC,4BAA4B,qBAAqB;AAAA,EAC9F,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,eAAe;AAAA,EACf,WAAW;AACb,CAAC;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { readEndpointRateLimitConfig } from '@open-mercato/shared/lib/ratelimit/config'\nimport {\n checkRateLimit,\n getClientIp,\n RATE_LIMIT_FALLBACK_KEY,\n RATE_LIMIT_UNAVAILABLE_FALLBACK,\n} from '@open-mercato/shared/lib/ratelimit/helpers'\nimport type { RateLimiterService } from '@open-mercato/shared/lib/ratelimit/service'\nimport type { RateLimitConfig } from '@open-mercato/shared/lib/ratelimit/types'\n\nconst logger = createLogger('checkout')\n\nexport function buildCheckoutRateLimitKey(\n req: Request,\n rateLimiter: RateLimiterService,\n namespace: string,\n): string {\n const clientKey = getClientIp(req, rateLimiter.trustProxyDepth) ?? RATE_LIMIT_FALLBACK_KEY\n return `${namespace}:${clientKey}`\n}\n\n/**\n * `fail-closed` rejects with 503 when the limiter cannot decide, for endpoints where an\n * unenforced limit is worse than a rejected request. `fail-open` lets the request through\n * and only logs, for read-only endpoints.\n */\nexport type CheckoutRateLimitPosture = 'fail-open' | 'fail-closed'\n\ntype RateLimiterContainer = {\n resolve: (name: string) => unknown\n hasRegistration?: (name: string) => boolean\n}\n\n/**\n * `rateLimiterService` is registered conditionally \u2014 `getCachedRateLimiterService()` returns\n * null when the rate-limit configuration itself is invalid, and then the DI key never exists.\n * That is a deployment-time misconfiguration rather than a transient outage, so it is kept\n * distinct from a limiter that is registered but cannot decide.\n */\nfunction resolveOptionalRateLimiter(container: RateLimiterContainer): RateLimiterService | null {\n if (typeof container.hasRegistration === 'function' && !container.hasRegistration('rateLimiterService')) {\n return null\n }\n try {\n return (container.resolve('rateLimiterService') as RateLimiterService | undefined) ?? null\n } catch {\n return null\n }\n}\n\n/**\n * Run the checkout rate-limit guard for one request and return the response that should be\n * sent instead of handling it, or null to continue.\n *\n * Three outcomes are deliberately distinguished:\n * - the limiter is not configured at all \u2192 logged as a configuration error and the check is\n * skipped, matching `sales/quotes/accept`; the endpoint is never reported as temporarily\n * unavailable for a condition that lasts until an operator fixes the environment\n * - the limiter is configured but could not decide (degraded, or the check threw) \u2192 503 under\n * `fail-closed`, a logged warning under `fail-open`\n * - the limiter decided the quota is exhausted \u2192 the 429 from `checkRateLimit`\n */\nexport async function enforceCheckoutRateLimit(options: {\n req: Request\n container: RateLimiterContainer\n config: RateLimitConfig\n namespace: string\n errorMessage: string\n posture: CheckoutRateLimitPosture\n}): Promise<NextResponse | null> {\n const { req, container, config, namespace, errorMessage, posture } = options\n const rateLimiter = resolveOptionalRateLimiter(container)\n if (!rateLimiter) {\n logger.error('Rate limiter service is not registered \u2014 check RATE_LIMIT_* configuration; checkout rate limiting is not enforced', {\n namespace,\n posture,\n })\n return null\n }\n\n try {\n const key = buildCheckoutRateLimitKey(req, rateLimiter, namespace)\n return await checkRateLimit(rateLimiter, config, key, errorMessage, {\n failClosed: posture === 'fail-closed',\n })\n } catch (error) {\n if (posture === 'fail-closed') {\n logger.error('Checkout rate limit check failed, rejecting request', { err: error, namespace })\n return NextResponse.json({ error: RATE_LIMIT_UNAVAILABLE_FALLBACK }, { status: 503 })\n }\n logger.warn('Checkout rate limit check failed, allowing request', { err: error, namespace })\n return null\n }\n}\n\nexport const checkoutPublicViewRateLimitConfig = readEndpointRateLimitConfig('CHECKOUT_PUBLIC_VIEW', {\n points: 60,\n duration: 60,\n blockDuration: 60,\n keyPrefix: 'checkout-public-view',\n})\n\nexport const checkoutStatusRateLimitConfig = readEndpointRateLimitConfig('CHECKOUT_STATUS', {\n points: 120,\n duration: 60,\n blockDuration: 60,\n keyPrefix: 'checkout-status',\n})\n\nexport const checkoutSubmitRateLimitConfig = readEndpointRateLimitConfig('CHECKOUT_SUBMIT', {\n points: 10,\n duration: 60,\n blockDuration: 60,\n keyPrefix: 'checkout-submit',\n})\n\nexport const checkoutPasswordRateLimitConfig = readEndpointRateLimitConfig('CHECKOUT_PASSWORD', {\n points: 5,\n duration: 60,\n blockDuration: 120,\n keyPrefix: 'checkout-password',\n})\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,mCAAmC;AAC5C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAIP,MAAM,SAAS,aAAa,UAAU;AAE/B,SAAS,0BACd,KACA,aACA,WACQ;AACR,QAAM,YAAY,YAAY,KAAK,YAAY,eAAe,KAAK;AACnE,SAAO,GAAG,SAAS,IAAI,SAAS;AAClC;AAoBA,SAAS,2BAA2B,WAA4D;AAC9F,MAAI,OAAO,UAAU,oBAAoB,cAAc,CAAC,UAAU,gBAAgB,oBAAoB,GAAG;AACvG,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAQ,UAAU,QAAQ,oBAAoB,KAAwC;AAAA,EACxF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAcA,eAAsB,yBAAyB,SAOd;AAC/B,QAAM,EAAE,KAAK,WAAW,QAAQ,WAAW,cAAc,QAAQ,IAAI;AACrE,QAAM,cAAc,2BAA2B,SAAS;AACxD,MAAI,CAAC,aAAa;AAChB,WAAO,MAAM,0HAAqH;AAAA,MAChI;AAAA,MACA;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,MAAM,0BAA0B,KAAK,aAAa,SAAS;AACjE,WAAO,MAAM,eAAe,aAAa,QAAQ,KAAK,cAAc;AAAA,MAClE,YAAY,YAAY;AAAA,IAC1B,CAAC;AAAA,EACH,SAAS,OAAO;AACd,QAAI,YAAY,eAAe;AAC7B,aAAO,MAAM,uDAAuD,EAAE,KAAK,OAAO,UAAU,CAAC;AAC7F,aAAO,aAAa,KAAK,EAAE,OAAO,gCAAgC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACtF;AACA,WAAO,KAAK,sDAAsD,EAAE,KAAK,OAAO,UAAU,CAAC;AAC3F,WAAO;AAAA,EACT;AACF;AAEO,MAAM,oCAAoC,4BAA4B,wBAAwB;AAAA,EACnG,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,eAAe;AAAA,EACf,WAAW;AACb,CAAC;AAEM,MAAM,gCAAgC,4BAA4B,mBAAmB;AAAA,EAC1F,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,eAAe;AAAA,EACf,WAAW;AACb,CAAC;AAEM,MAAM,gCAAgC,4BAA4B,mBAAmB;AAAA,EAC1F,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,eAAe;AAAA,EACf,WAAW;AACb,CAAC;AAEM,MAAM,kCAAkC,4BAA4B,qBAAqB;AAAA,EAC9F,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,eAAe;AAAA,EACf,WAAW;AACb,CAAC;",
6
6
  "names": []
7
7
  }