@rovela-ai/sdk 0.6.0 → 0.7.0
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.
- package/dist/admin/api/discounts.d.ts +53 -0
- package/dist/admin/api/discounts.d.ts.map +1 -0
- package/dist/admin/api/discounts.js +325 -0
- package/dist/admin/api/discounts.js.map +1 -0
- package/dist/admin/api/index.d.ts +1 -0
- package/dist/admin/api/index.d.ts.map +1 -1
- package/dist/admin/api/index.js +2 -0
- package/dist/admin/api/index.js.map +1 -1
- package/dist/admin/api/orders.js +1 -1
- package/dist/admin/api/orders.js.map +1 -1
- package/dist/admin/api/settings.d.ts.map +1 -1
- package/dist/admin/api/settings.js +10 -0
- package/dist/admin/api/settings.js.map +1 -1
- package/dist/admin/components/CustomerAddressPanel.d.ts.map +1 -1
- package/dist/admin/components/CustomerAddressPanel.js +13 -6
- package/dist/admin/components/CustomerAddressPanel.js.map +1 -1
- package/dist/admin/components/DiscountsManager.d.ts +5 -0
- package/dist/admin/components/DiscountsManager.d.ts.map +1 -0
- package/dist/admin/components/DiscountsManager.js +363 -0
- package/dist/admin/components/DiscountsManager.js.map +1 -0
- package/dist/admin/components/ShippingSettings.d.ts.map +1 -1
- package/dist/admin/components/ShippingSettings.js +19 -2
- package/dist/admin/components/ShippingSettings.js.map +1 -1
- package/dist/admin/components/StoreSettings.d.ts.map +1 -1
- package/dist/admin/components/StoreSettings.js +8 -1
- package/dist/admin/components/StoreSettings.js.map +1 -1
- package/dist/admin/components/TaxSettings.d.ts.map +1 -1
- package/dist/admin/components/TaxSettings.js +8 -3
- package/dist/admin/components/TaxSettings.js.map +1 -1
- package/dist/admin/components/index.d.ts +2 -0
- package/dist/admin/components/index.d.ts.map +1 -1
- package/dist/admin/components/index.js +1 -0
- package/dist/admin/components/index.js.map +1 -1
- package/dist/admin/hooks/index.d.ts +2 -0
- package/dist/admin/hooks/index.d.ts.map +1 -1
- package/dist/admin/hooks/index.js +1 -0
- package/dist/admin/hooks/index.js.map +1 -1
- package/dist/admin/hooks/useAdminDiscounts.d.ts +12 -0
- package/dist/admin/hooks/useAdminDiscounts.d.ts.map +1 -0
- package/dist/admin/hooks/useAdminDiscounts.js +77 -0
- package/dist/admin/hooks/useAdminDiscounts.js.map +1 -0
- package/dist/admin/index.d.ts +3 -3
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +2 -2
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/types.d.ts +72 -0
- package/dist/admin/types.d.ts.map +1 -1
- package/dist/auth/components/AddressForm.d.ts.map +1 -1
- package/dist/auth/components/AddressForm.js +22 -7
- package/dist/auth/components/AddressForm.js.map +1 -1
- package/dist/checkout/api/checkout.d.ts.map +1 -1
- package/dist/checkout/api/checkout.js +15 -2
- package/dist/checkout/api/checkout.js.map +1 -1
- package/dist/checkout/api/discount.d.ts +47 -0
- package/dist/checkout/api/discount.d.ts.map +1 -0
- package/dist/checkout/api/discount.js +77 -0
- package/dist/checkout/api/discount.js.map +1 -0
- package/dist/checkout/api/index.d.ts +1 -0
- package/dist/checkout/api/index.d.ts.map +1 -1
- package/dist/checkout/api/index.js +1 -0
- package/dist/checkout/api/index.js.map +1 -1
- package/dist/checkout/api/manual.d.ts.map +1 -1
- package/dist/checkout/api/manual.js +106 -155
- package/dist/checkout/api/manual.js.map +1 -1
- package/dist/checkout/api/shipping.d.ts.map +1 -1
- package/dist/checkout/api/shipping.js +19 -27
- package/dist/checkout/api/shipping.js.map +1 -1
- package/dist/checkout/components/CheckoutFlow.d.ts.map +1 -1
- package/dist/checkout/components/CheckoutFlow.js +89 -9
- package/dist/checkout/components/CheckoutFlow.js.map +1 -1
- package/dist/checkout/components/OrderSummary.d.ts +1 -1
- package/dist/checkout/components/OrderSummary.d.ts.map +1 -1
- package/dist/checkout/components/OrderSummary.js +3 -3
- package/dist/checkout/components/OrderSummary.js.map +1 -1
- package/dist/checkout/components/ShippingForm.d.ts.map +1 -1
- package/dist/checkout/components/ShippingForm.js +42 -13
- package/dist/checkout/components/ShippingForm.js.map +1 -1
- package/dist/checkout/constants.d.ts +38 -0
- package/dist/checkout/constants.d.ts.map +1 -1
- package/dist/checkout/constants.js +39 -0
- package/dist/checkout/constants.js.map +1 -1
- package/dist/checkout/hooks/useCheckout.d.ts.map +1 -1
- package/dist/checkout/hooks/useCheckout.js +2 -0
- package/dist/checkout/hooks/useCheckout.js.map +1 -1
- package/dist/checkout/server/checkout-customer.d.ts +30 -0
- package/dist/checkout/server/checkout-customer.d.ts.map +1 -0
- package/dist/checkout/server/checkout-customer.js +47 -0
- package/dist/checkout/server/checkout-customer.js.map +1 -0
- package/dist/checkout/server/create-checkout-session.d.ts.map +1 -1
- package/dist/checkout/server/create-checkout-session.js +149 -9
- package/dist/checkout/server/create-checkout-session.js.map +1 -1
- package/dist/checkout/server/discounts.d.ts +75 -0
- package/dist/checkout/server/discounts.d.ts.map +1 -0
- package/dist/checkout/server/discounts.js +294 -0
- package/dist/checkout/server/discounts.js.map +1 -0
- package/dist/checkout/server/handle-webhook.js +23 -1
- package/dist/checkout/server/handle-webhook.js.map +1 -1
- package/dist/checkout/server/index.d.ts +4 -0
- package/dist/checkout/server/index.d.ts.map +1 -1
- package/dist/checkout/server/index.js +4 -0
- package/dist/checkout/server/index.js.map +1 -1
- package/dist/checkout/server/order-service.d.ts.map +1 -1
- package/dist/checkout/server/order-service.js +60 -10
- package/dist/checkout/server/order-service.js.map +1 -1
- package/dist/checkout/server/pricing.d.ts +164 -0
- package/dist/checkout/server/pricing.d.ts.map +1 -0
- package/dist/checkout/server/pricing.js +309 -0
- package/dist/checkout/server/pricing.js.map +1 -0
- package/dist/checkout/types.d.ts +8 -0
- package/dist/checkout/types.d.ts.map +1 -1
- package/dist/core/StoreSettingsProvider.d.ts +4 -0
- package/dist/core/StoreSettingsProvider.d.ts.map +1 -1
- package/dist/core/StoreSettingsProvider.js +6 -0
- package/dist/core/StoreSettingsProvider.js.map +1 -1
- package/dist/core/api/settings.d.ts +1 -0
- package/dist/core/api/settings.d.ts.map +1 -1
- package/dist/core/api/settings.js +2 -0
- package/dist/core/api/settings.js.map +1 -1
- package/dist/core/db/queries.d.ts +249 -1
- package/dist/core/db/queries.d.ts.map +1 -1
- package/dist/core/db/queries.js +301 -1
- package/dist/core/db/queries.js.map +1 -1
- package/dist/core/db/schema.d.ts +618 -0
- package/dist/core/db/schema.d.ts.map +1 -1
- package/dist/core/db/schema.js +78 -0
- package/dist/core/db/schema.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela/sdk/checkout/api/discount
|
|
3
|
+
*
|
|
4
|
+
* POST /api/checkout/discount — validate a redemption code against the
|
|
5
|
+
* current cart BEFORE placing the order (the checkout "Apply" button).
|
|
6
|
+
*
|
|
7
|
+
* The evaluation runs the SAME pricing-core pipeline (server re-price +
|
|
8
|
+
* native discounts engine) as order creation, so what the shopper sees here
|
|
9
|
+
* is exactly what the order will apply. Identity is session-resolved —
|
|
10
|
+
* group-gated / logged-in-only codes evaluate against the REAL customer.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // app/api/checkout/discount/route.ts
|
|
15
|
+
* export { POST } from '@rovela-ai/sdk/checkout/api/discount'
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
19
|
+
import type { CheckoutApiError } from '../types';
|
|
20
|
+
interface DiscountPreviewResponse {
|
|
21
|
+
success: true;
|
|
22
|
+
/** Validity of the ENTERED code (true when no code was sent). */
|
|
23
|
+
valid: boolean;
|
|
24
|
+
/** The normalized applied code, when one applies. */
|
|
25
|
+
code: string | null;
|
|
26
|
+
/** The entered code's display title (when valid). */
|
|
27
|
+
title: string | null;
|
|
28
|
+
/** Friendly rejection copy for the entered code (when invalid). */
|
|
29
|
+
error: string | null;
|
|
30
|
+
/** Machine-readable rejection reason — localized storefronts map their own copy. */
|
|
31
|
+
errorReason: 'INVALID_CODE' | 'EXPIRED' | 'NOT_ELIGIBLE' | 'MINIMUM_NOT_MET' | 'USAGE_LIMIT_REACHED' | 'ALREADY_USED' | 'NOT_APPLICABLE' | null;
|
|
32
|
+
/** TOTAL net discount off the goods (code + automatics), in cents. */
|
|
33
|
+
totalDiscountCents: number;
|
|
34
|
+
/** True when any applied discount zeroes the shipping cost. */
|
|
35
|
+
freeShipping: boolean;
|
|
36
|
+
/** Every applied discount, for review-step display. */
|
|
37
|
+
applied: Array<{
|
|
38
|
+
title: string;
|
|
39
|
+
code: string | null;
|
|
40
|
+
method: 'code' | 'automatic';
|
|
41
|
+
amountCents: number;
|
|
42
|
+
freeShipping: boolean;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
export declare function POST(request: NextRequest): Promise<NextResponse<DiscountPreviewResponse | CheckoutApiError>>;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=discount.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discount.d.ts","sourceRoot":"","sources":["../../../src/checkout/api/discount.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAIvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAchD,UAAU,uBAAuB;IAC/B,OAAO,EAAE,IAAI,CAAA;IACb,iEAAiE;IACjE,KAAK,EAAE,OAAO,CAAA;IACd,qDAAqD;IACrD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,qDAAqD;IACrD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,mEAAmE;IACnE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,oFAAoF;IACpF,WAAW,EACP,cAAc,GACd,SAAS,GACT,cAAc,GACd,iBAAiB,GACjB,qBAAqB,GACrB,cAAc,GACd,gBAAgB,GAChB,IAAI,CAAA;IACR,sEAAsE;IACtE,kBAAkB,EAAE,MAAM,CAAA;IAC1B,+DAA+D;IAC/D,YAAY,EAAE,OAAO,CAAA;IACrB,uDAAuD;IACvD,OAAO,EAAE,KAAK,CAAC;QACb,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;QACnB,MAAM,EAAE,MAAM,GAAG,WAAW,CAAA;QAC5B,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,OAAO,CAAA;KACtB,CAAC,CAAA;CACH;AAED,wBAAsB,IAAI,CACxB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,YAAY,CAAC,uBAAuB,GAAG,gBAAgB,CAAC,CAAC,CA+DnE"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela/sdk/checkout/api/discount
|
|
3
|
+
*
|
|
4
|
+
* POST /api/checkout/discount — validate a redemption code against the
|
|
5
|
+
* current cart BEFORE placing the order (the checkout "Apply" button).
|
|
6
|
+
*
|
|
7
|
+
* The evaluation runs the SAME pricing-core pipeline (server re-price +
|
|
8
|
+
* native discounts engine) as order creation, so what the shopper sees here
|
|
9
|
+
* is exactly what the order will apply. Identity is session-resolved —
|
|
10
|
+
* group-gated / logged-in-only codes evaluate against the REAL customer.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // app/api/checkout/discount/route.ts
|
|
15
|
+
* export { POST } from '@rovela-ai/sdk/checkout/api/discount'
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import { NextResponse } from 'next/server';
|
|
19
|
+
import { repriceItems, PricingError } from '../server/pricing';
|
|
20
|
+
import { evaluateDiscounts } from '../server/discounts';
|
|
21
|
+
import { resolveCheckoutCustomer } from '../server/checkout-customer';
|
|
22
|
+
export async function POST(request) {
|
|
23
|
+
try {
|
|
24
|
+
const body = (await request.json());
|
|
25
|
+
const code = typeof body.code === 'string' ? body.code.trim() : '';
|
|
26
|
+
// Server re-price ('floor' — mirrors the Stripe path so previews work for
|
|
27
|
+
// configurator carts too).
|
|
28
|
+
let repriced;
|
|
29
|
+
try {
|
|
30
|
+
repriced = await repriceItems(body.items ?? [], 'floor');
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
if (err instanceof PricingError) {
|
|
34
|
+
return NextResponse.json({ error: err.message, code: err.code }, { status: err.status });
|
|
35
|
+
}
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
const trustedCustomer = await resolveCheckoutCustomer();
|
|
39
|
+
const evaluation = await evaluateDiscounts({
|
|
40
|
+
lines: repriced.lines,
|
|
41
|
+
subtotalCents: repriced.subtotalCents,
|
|
42
|
+
categoryByProductId: repriced.categoryByProductId,
|
|
43
|
+
code: code || null,
|
|
44
|
+
customerId: trustedCustomer?.id ?? null,
|
|
45
|
+
customerGroupId: trustedCustomer?.groupId ?? null,
|
|
46
|
+
customerEmail: body.customerEmail || trustedCustomer?.email || null,
|
|
47
|
+
});
|
|
48
|
+
const appliedCode = evaluation.applied.find((a) => a.method === 'code') ?? null;
|
|
49
|
+
const codeInvalid = !!code && (!!evaluation.codeError || !appliedCode);
|
|
50
|
+
return NextResponse.json({
|
|
51
|
+
success: true,
|
|
52
|
+
valid: !codeInvalid,
|
|
53
|
+
code: appliedCode?.code ?? null,
|
|
54
|
+
title: appliedCode?.title ?? null,
|
|
55
|
+
error: codeInvalid
|
|
56
|
+
? evaluation.codeError?.message ?? "This code isn't valid."
|
|
57
|
+
: null,
|
|
58
|
+
errorReason: codeInvalid
|
|
59
|
+
? evaluation.codeError?.reason ?? 'INVALID_CODE'
|
|
60
|
+
: null,
|
|
61
|
+
totalDiscountCents: evaluation.discountCents,
|
|
62
|
+
freeShipping: evaluation.freeShipping,
|
|
63
|
+
applied: evaluation.applied.map((a) => ({
|
|
64
|
+
title: a.title,
|
|
65
|
+
code: a.code,
|
|
66
|
+
method: a.method,
|
|
67
|
+
amountCents: a.amountCents,
|
|
68
|
+
freeShipping: a.freeShipping,
|
|
69
|
+
})),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error('[Discount Preview API] error:', error);
|
|
74
|
+
return NextResponse.json({ error: 'Failed to validate the code. Please try again.', code: 'CHECKOUT_ERROR' }, { status: 500 });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=discount.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discount.js","sourceRoot":"","sources":["../../../src/checkout/api/discount.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,5 +10,6 @@ export { POST as ShippingPOST } from './shipping';
|
|
|
10
10
|
export { GET as CountriesGET } from './countries';
|
|
11
11
|
export { GET as PaymentMethodsGET } from './payment-methods';
|
|
12
12
|
export { POST as ManualCheckoutPOST } from './manual';
|
|
13
|
+
export { POST as DiscountPreviewPOST } from './discount';
|
|
13
14
|
export type { PaymentStatusResponse } from './payment-status';
|
|
14
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/checkout/api/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,WAAW,CAAA;AAC/C,OAAO,EAAE,GAAG,IAAI,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,EAAE,GAAG,IAAI,YAAY,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,GAAG,IAAI,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,EAAE,IAAI,IAAI,kBAAkB,EAAE,MAAM,UAAU,CAAA;AACrD,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/checkout/api/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,WAAW,CAAA;AAC/C,OAAO,EAAE,GAAG,IAAI,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,EAAE,GAAG,IAAI,YAAY,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,GAAG,IAAI,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,EAAE,IAAI,IAAI,kBAAkB,EAAE,MAAM,UAAU,CAAA;AACrD,OAAO,EAAE,IAAI,IAAI,mBAAmB,EAAE,MAAM,YAAY,CAAA;AACxD,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA"}
|
|
@@ -10,4 +10,5 @@ export { POST as ShippingPOST } from './shipping';
|
|
|
10
10
|
export { GET as CountriesGET } from './countries';
|
|
11
11
|
export { GET as PaymentMethodsGET } from './payment-methods';
|
|
12
12
|
export { POST as ManualCheckoutPOST } from './manual';
|
|
13
|
+
export { POST as DiscountPreviewPOST } from './discount';
|
|
13
14
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/checkout/api/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,WAAW,CAAA;AAC/C,OAAO,EAAE,GAAG,IAAI,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,EAAE,GAAG,IAAI,YAAY,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,GAAG,IAAI,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,EAAE,IAAI,IAAI,kBAAkB,EAAE,MAAM,UAAU,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/checkout/api/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,WAAW,CAAA;AAC/C,OAAO,EAAE,GAAG,IAAI,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,EAAE,GAAG,IAAI,YAAY,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,GAAG,IAAI,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,EAAE,IAAI,IAAI,kBAAkB,EAAE,MAAM,UAAU,CAAA;AACrD,OAAO,EAAE,IAAI,IAAI,mBAAmB,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manual.d.ts","sourceRoot":"","sources":["../../../src/checkout/api/manual.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"manual.d.ts","sourceRoot":"","sources":["../../../src/checkout/api/manual.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAkBvD,OAAO,KAAK,EAAE,gBAAgB,EAAoC,MAAM,UAAU,CAAA;AA2BlF,UAAU,sBAAsB;IAC9B,OAAO,EAAE,IAAI,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAA;IACjB,+CAA+C;IAC/C,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CAClE;AAUD,wBAAsB,IAAI,CACxB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,YAAY,CAAC,sBAAsB,GAAG,gBAAgB,CAAC,CAAC,CAiNlE"}
|
|
@@ -19,23 +19,15 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { randomUUID } from 'crypto';
|
|
21
21
|
import { NextResponse } from 'next/server';
|
|
22
|
-
import { findSettings, findPaymentMethodById,
|
|
22
|
+
import { findSettings, findPaymentMethodById, recordDiscountRedemption, } from '../../core/db/queries';
|
|
23
|
+
import { buildQuote, PricingError } from '../server/pricing';
|
|
24
|
+
import { resolveCheckoutCustomer } from '../server/checkout-customer';
|
|
23
25
|
import { createOrderFromCheckout } from '../server/order-service';
|
|
24
26
|
import { sendOrderConfirmation, formatPrice, formatDate, formatOrderNumber, } from '../../emails';
|
|
25
27
|
import { recordEvent } from '../../analytics/server';
|
|
26
|
-
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
27
28
|
function bad(error, status = 400) {
|
|
28
29
|
return NextResponse.json({ error, code: 'INVALID_REQUEST' }, { status });
|
|
29
30
|
}
|
|
30
|
-
/** Dollars (decimal string from the DB) → integer cents. */
|
|
31
|
-
function dollarsToCents(decimal) {
|
|
32
|
-
if (decimal == null)
|
|
33
|
-
return null;
|
|
34
|
-
const n = Number.parseFloat(decimal);
|
|
35
|
-
if (!Number.isFinite(n))
|
|
36
|
-
return null;
|
|
37
|
-
return Math.round(n * 100);
|
|
38
|
-
}
|
|
39
31
|
// =============================================================================
|
|
40
32
|
// Handler
|
|
41
33
|
// =============================================================================
|
|
@@ -44,8 +36,12 @@ export async function POST(request) {
|
|
|
44
36
|
const body = (await request.json());
|
|
45
37
|
// ── Gates ────────────────────────────────────────────────────────────────
|
|
46
38
|
const settings = await findSettings();
|
|
39
|
+
// Trusted identity: resolved from the SESSION COOKIE, never from the
|
|
40
|
+
// client body (body.customerId is spoofable on a money path — an order
|
|
41
|
+
// could be attached to another customer's account history).
|
|
42
|
+
const trustedCustomer = await resolveCheckoutCustomer();
|
|
47
43
|
const allowGuestCheckout = settings?.guestCheckout ?? true;
|
|
48
|
-
if (!allowGuestCheckout && !
|
|
44
|
+
if (!allowGuestCheckout && !trustedCustomer) {
|
|
49
45
|
return NextResponse.json({
|
|
50
46
|
error: 'Guest checkout is disabled. Please sign in to continue.',
|
|
51
47
|
code: 'GUEST_CHECKOUT_DISABLED',
|
|
@@ -63,118 +59,33 @@ export async function POST(request) {
|
|
|
63
59
|
if (!method || !method.enabled || method.kind !== 'manual') {
|
|
64
60
|
return bad('This payment method is not available', 400);
|
|
65
61
|
}
|
|
66
|
-
// ──
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
// ── The Quote: items → (discounts) → shipping → tax → total ─────────────
|
|
63
|
+
// The pricing core recomputes EVERYTHING server-side ('strict': client
|
|
64
|
+
// prices are used for nothing). See checkout/server/pricing.ts.
|
|
65
|
+
let quote;
|
|
66
|
+
try {
|
|
67
|
+
quote = await buildQuote({
|
|
68
|
+
items: body.items,
|
|
69
|
+
shippingAddress: body.shippingAddress,
|
|
70
|
+
selectedShipping: body.selectedShipping,
|
|
71
|
+
pricingMode: 'strict',
|
|
72
|
+
discountCode: body.discountCode ?? null,
|
|
73
|
+
customerId: trustedCustomer?.id ?? null,
|
|
74
|
+
customerGroupId: trustedCustomer?.groupId ?? null,
|
|
75
|
+
customerEmail: email,
|
|
76
|
+
}, { settings });
|
|
69
77
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const quantity = Number(item.quantity);
|
|
74
|
-
if (!Number.isInteger(quantity) || quantity < 1 || quantity > 999) {
|
|
75
|
-
return bad('Invalid item quantity');
|
|
76
|
-
}
|
|
77
|
-
if (!item.productId || !UUID_RE.test(item.productId)) {
|
|
78
|
-
// Manual orders are strict: every line must map to a real catalog
|
|
79
|
-
// product (no client-invented lines on an unsupervised money path).
|
|
80
|
-
return bad('Invalid item: unknown product');
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (err instanceof PricingError) {
|
|
80
|
+
return NextResponse.json({ error: err.message, code: err.code }, { status: err.status });
|
|
81
81
|
}
|
|
82
|
-
|
|
83
|
-
if (!product || product.status !== 'active') {
|
|
84
|
-
return bad('Invalid item: product unavailable');
|
|
85
|
-
}
|
|
86
|
-
let unitCents = dollarsToCents(product.price);
|
|
87
|
-
let name = product.name;
|
|
88
|
-
let variantId;
|
|
89
|
-
if (item.variantId) {
|
|
90
|
-
if (!UUID_RE.test(item.variantId))
|
|
91
|
-
return bad('Invalid item variant');
|
|
92
|
-
const variant = await findVariantById(item.variantId);
|
|
93
|
-
if (!variant || variant.productId !== product.id) {
|
|
94
|
-
return bad('Invalid item variant');
|
|
95
|
-
}
|
|
96
|
-
const variantCents = dollarsToCents(variant.price);
|
|
97
|
-
if (variantCents != null)
|
|
98
|
-
unitCents = variantCents;
|
|
99
|
-
name = `${product.name} - ${variant.name}`;
|
|
100
|
-
variantId = variant.id;
|
|
101
|
-
}
|
|
102
|
-
if (unitCents == null || unitCents <= 0) {
|
|
103
|
-
return bad('Invalid item: product has no price');
|
|
104
|
-
}
|
|
105
|
-
subtotalCents += unitCents * quantity;
|
|
106
|
-
repricedItems.push({
|
|
107
|
-
productId: product.id,
|
|
108
|
-
variantId,
|
|
109
|
-
name,
|
|
110
|
-
price: unitCents,
|
|
111
|
-
quantity,
|
|
112
|
-
image: product.images?.[0],
|
|
113
|
-
});
|
|
82
|
+
throw err;
|
|
114
83
|
}
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
let shippoRateId;
|
|
121
|
-
if (shippingEnabled) {
|
|
122
|
-
if (!body.shippingAddress)
|
|
123
|
-
return bad('Shipping address is required');
|
|
124
|
-
const a = body.shippingAddress;
|
|
125
|
-
if (!a.firstName || !a.lastName || !a.line1 || !a.city || !a.postalCode || !a.country) {
|
|
126
|
-
return bad('Complete shipping address is required');
|
|
127
|
-
}
|
|
128
|
-
if (!body.selectedShipping?.id)
|
|
129
|
-
return bad('A shipping method is required');
|
|
130
|
-
const sel = body.selectedShipping;
|
|
131
|
-
if (sel.source === 'shippo') {
|
|
132
|
-
// Shippo rates are ephemeral quotes served by our own /api/checkout/
|
|
133
|
-
// shipping moments earlier — they can't be deterministically re-derived.
|
|
134
|
-
// Accept with sanity bounds (the rate id is stored for reconciliation).
|
|
135
|
-
const price = Number(sel.price);
|
|
136
|
-
if (!Number.isFinite(price) || price < 0 || price > 1_000_000) {
|
|
137
|
-
return bad('Invalid shipping selection');
|
|
138
|
-
}
|
|
139
|
-
shippingCents = Math.round(price);
|
|
140
|
-
shippingMethodName = [sel.carrierName, sel.name].filter(Boolean).join(' - ') || sel.name;
|
|
141
|
-
carrier = sel.carrierCode || sel.carrierId || undefined;
|
|
142
|
-
shippoRateId = sel.id;
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
// Manual zone rates ARE deterministic — recompute and use the SERVER's
|
|
146
|
-
// price for the selected option id. Unknown id = tampered/stale → 400.
|
|
147
|
-
const grouped = await findShippingOptionsForCountry(a.country, subtotalCents / 100);
|
|
148
|
-
const flat = grouped.flatMap((g) => g.options);
|
|
149
|
-
const match = flat.find((o) => o.id === sel.id);
|
|
150
|
-
if (!match)
|
|
151
|
-
return bad('Selected shipping method is no longer available');
|
|
152
|
-
shippingCents = Math.round(Number(match.price) || 0);
|
|
153
|
-
shippingMethodName =
|
|
154
|
-
[match.carrierName, match.name].filter(Boolean).join(' - ') || match.name;
|
|
155
|
-
carrier = match.carrierCode || match.carrierId || undefined;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
// ── Tax: recompute server-side ───────────────────────────────────────────
|
|
159
|
-
const taxIncludedInPrices = settings?.taxIncludedInPrices ?? true;
|
|
160
|
-
let taxCents = 0;
|
|
161
|
-
if (body.shippingAddress?.country) {
|
|
162
|
-
const taxMatch = await findTaxRateForAddress(body.shippingAddress.country, body.shippingAddress.state || undefined);
|
|
163
|
-
const rate = taxMatch?.taxRate ?? 0;
|
|
164
|
-
if (rate > 0) {
|
|
165
|
-
const taxableCents = subtotalCents + shippingCents;
|
|
166
|
-
if (taxIncludedInPrices) {
|
|
167
|
-
const divisor = 1 + rate / 100;
|
|
168
|
-
taxCents = Math.round(taxableCents - taxableCents / divisor);
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
taxCents = Math.round(taxableCents * (rate / 100));
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
const totalCents = taxIncludedInPrices
|
|
176
|
-
? subtotalCents + shippingCents
|
|
177
|
-
: subtotalCents + shippingCents + taxCents;
|
|
84
|
+
const repricedItems = quote.lines;
|
|
85
|
+
const subtotalCents = quote.subtotalCents;
|
|
86
|
+
const shippingCents = quote.shippingCents;
|
|
87
|
+
const taxCents = quote.taxCents;
|
|
88
|
+
const totalCents = quote.totalCents;
|
|
178
89
|
// ── Idempotency reference ────────────────────────────────────────────────
|
|
179
90
|
// Stored in the shared payment-reference column so double-submits dedupe
|
|
180
91
|
// through the existing findOrderByPaymentIntent lookup.
|
|
@@ -190,21 +101,47 @@ export async function POST(request) {
|
|
|
190
101
|
tax: taxCents,
|
|
191
102
|
shipping: shippingCents,
|
|
192
103
|
total: totalCents,
|
|
104
|
+
currency: quote.currency,
|
|
105
|
+
discountCode: quote.discountCode ?? undefined,
|
|
106
|
+
discountAmount: quote.discountCents > 0 ? quote.discountCents : undefined,
|
|
193
107
|
shippingAddress: body.shippingAddress,
|
|
194
108
|
billingAddress: body.billingAddress,
|
|
195
109
|
stripePaymentIntentId: reference,
|
|
196
|
-
customerId:
|
|
197
|
-
shippingMethod: shippingMethodName,
|
|
198
|
-
carrier,
|
|
199
|
-
shippoRateId,
|
|
110
|
+
customerId: trustedCustomer?.id,
|
|
111
|
+
shippingMethod: quote.shippingMethodName,
|
|
112
|
+
carrier: quote.carrier,
|
|
113
|
+
shippoRateId: quote.shippoRateId,
|
|
200
114
|
status: 'pending',
|
|
201
115
|
paymentProvider: 'manual',
|
|
202
116
|
paymentMethodId: method.id,
|
|
203
117
|
paymentMethodLabel: method.name,
|
|
204
118
|
});
|
|
205
|
-
// ──
|
|
119
|
+
// ── Discount redemptions (usage limits + audit) ──────────────────────────
|
|
120
|
+
// Recorded once per NEW order. Best-effort: a redemption write failure
|
|
121
|
+
// must never fail the already-created order.
|
|
122
|
+
if (isNew && quote.appliedDiscounts.length > 0) {
|
|
123
|
+
for (const applied of quote.appliedDiscounts) {
|
|
124
|
+
try {
|
|
125
|
+
await recordDiscountRedemption({
|
|
126
|
+
discountId: applied.id,
|
|
127
|
+
orderId,
|
|
128
|
+
customerId: trustedCustomer?.id ?? null,
|
|
129
|
+
email,
|
|
130
|
+
amountCents: applied.amountCents,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
console.error('[Manual Checkout] redemption record failed:', err);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// ── Post-order side effects ──────────────────────────────────────────────
|
|
139
|
+
// AWAITED (not fire-and-forget): on serverless hosts the function freezes
|
|
140
|
+
// as soon as the response returns, killing un-awaited promises — the email
|
|
141
|
+
// would silently never send. Each is guarded so a failure can never fail
|
|
142
|
+
// the already-created order.
|
|
206
143
|
if (isNew) {
|
|
207
|
-
const currency =
|
|
144
|
+
const currency = quote.currency;
|
|
208
145
|
const storeUrl = process.env.NEXT_PUBLIC_APP_URL || '';
|
|
209
146
|
const emailItems = repricedItems.map((i) => ({
|
|
210
147
|
name: i.name,
|
|
@@ -222,39 +159,53 @@ export async function POST(request) {
|
|
|
222
159
|
postalCode: addr.postalCode,
|
|
223
160
|
country: addr.country,
|
|
224
161
|
};
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
162
|
+
try {
|
|
163
|
+
await sendOrderConfirmation({
|
|
164
|
+
to: email,
|
|
165
|
+
customerName: addr.firstName || email.split('@')[0] || 'Customer',
|
|
166
|
+
orderNumber: formatOrderNumber(orderId),
|
|
167
|
+
orderDate: formatDate(new Date()),
|
|
168
|
+
paymentStatus: `Pending — ${method.name}`,
|
|
169
|
+
items: emailItems,
|
|
170
|
+
subtotal: formatPrice(subtotalCents, currency),
|
|
171
|
+
discount: quote.discountCents > 0
|
|
172
|
+
? `-${formatPrice(quote.discountCents, currency)}`
|
|
173
|
+
: undefined,
|
|
174
|
+
discountCode: quote.discountCode ?? undefined,
|
|
175
|
+
shippingCost: shippingCents > 0 ? formatPrice(shippingCents, currency) : 'Free',
|
|
176
|
+
tax: formatPrice(taxCents, currency),
|
|
177
|
+
total: formatPrice(totalCents, currency),
|
|
178
|
+
shippingAddress: emailAddress,
|
|
179
|
+
orderTrackingUrl: `${storeUrl}/account/orders/${orderId}`,
|
|
180
|
+
paymentInstructions: method.instructions || undefined,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
console.error('[Manual Checkout] confirmation email failed:', err);
|
|
185
|
+
}
|
|
240
186
|
// Server-side purchase analytics — mirrors the Stripe webhook wrapper.
|
|
241
187
|
// The partial unique index on (order_id) WHERE event='purchase' dedupes.
|
|
242
188
|
const visitorId = body.metadata?.rovela_visitor_id;
|
|
243
189
|
const sessionId = body.metadata?.rovela_session_id;
|
|
244
190
|
if (visitorId && sessionId) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
event:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
191
|
+
try {
|
|
192
|
+
await recordEvent({
|
|
193
|
+
event: {
|
|
194
|
+
event: 'purchase',
|
|
195
|
+
visitor_id: visitorId,
|
|
196
|
+
session_id: sessionId,
|
|
197
|
+
order_id: orderId,
|
|
198
|
+
value_cents: totalCents,
|
|
199
|
+
currency: quote.currency,
|
|
200
|
+
path: '/checkout/success',
|
|
201
|
+
},
|
|
202
|
+
device: null,
|
|
203
|
+
country: null,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
console.error('[Manual Checkout] purchase analytics failed:', err);
|
|
208
|
+
}
|
|
258
209
|
}
|
|
259
210
|
}
|
|
260
211
|
return NextResponse.json({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manual.js","sourceRoot":"","sources":["../../../src/checkout/api/manual.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACnC,OAAO,EAAe,YAAY,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,
|
|
1
|
+
{"version":3,"file":"manual.js","sourceRoot":"","sources":["../../../src/checkout/api/manual.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shipping.d.ts","sourceRoot":"","sources":["../../../src/checkout/api/shipping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,KAAK,EACV,2BAA2B,EAG3B,gBAAgB,EACjB,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"shipping.d.ts","sourceRoot":"","sources":["../../../src/checkout/api/shipping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,KAAK,EACV,2BAA2B,EAG3B,gBAAgB,EACjB,MAAM,UAAU,CAAA;AAsCjB;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,IAAI,CACxB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,YAAY,CAAC,2BAA2B,GAAG,gBAAgB,CAAC,CAAC,CAiNvE"}
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* ```
|
|
12
12
|
*/
|
|
13
13
|
import { NextResponse } from 'next/server';
|
|
14
|
-
import { findSettings, findShippingOptionsForCountry
|
|
14
|
+
import { findSettings, findShippingOptionsForCountry } from '../../core/db/queries';
|
|
15
|
+
import { resolveTax } from '../server/pricing';
|
|
15
16
|
import { getShippoRates, groupShippoRatesByCarrier, shippingAddressToShippo, } from '../../shipping/shippo';
|
|
16
17
|
import { countryRequiresState } from '../constants';
|
|
17
18
|
// =============================================================================
|
|
@@ -168,33 +169,24 @@ export async function POST(request) {
|
|
|
168
169
|
// =========================================================================
|
|
169
170
|
let tax = null;
|
|
170
171
|
try {
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const taxDivisor = 1 + (taxRate / 100);
|
|
184
|
-
const preTaxAmount = orderAmountCents / taxDivisor;
|
|
185
|
-
taxAmountCents = Math.round(orderAmountCents - preTaxAmount);
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
// Prices are pre-tax - add tax on top
|
|
189
|
-
// Formula: taxAmount = subtotal * (taxRate/100)
|
|
190
|
-
taxAmountCents = Math.round(orderAmountCents * (taxRate / 100));
|
|
191
|
-
}
|
|
172
|
+
// Canonical tax formula (pricing core). No shipping is selected yet at
|
|
173
|
+
// this pre-checkout stage, so the quoted amount covers the subtotal
|
|
174
|
+
// only — the client (and later the ORDER-time quote) recomputes tax
|
|
175
|
+
// with the chosen shipping included.
|
|
176
|
+
const taxResolution = await resolveTax({
|
|
177
|
+
subtotalCents: orderAmountCents,
|
|
178
|
+
shippingCents: 0,
|
|
179
|
+
country: address.country.toUpperCase(),
|
|
180
|
+
state: address.state?.toUpperCase(),
|
|
181
|
+
taxIncludedInPrices: settings?.taxIncludedInPrices ?? true,
|
|
182
|
+
});
|
|
183
|
+
if (taxResolution.taxRate > 0) {
|
|
192
184
|
tax = {
|
|
193
|
-
zoneId:
|
|
194
|
-
zoneName:
|
|
195
|
-
taxRate: taxRate,
|
|
196
|
-
amount:
|
|
197
|
-
includedInPrices: taxIncludedInPrices,
|
|
185
|
+
zoneId: taxResolution.taxZoneId || '',
|
|
186
|
+
zoneName: taxResolution.taxZoneName || '',
|
|
187
|
+
taxRate: taxResolution.taxRate,
|
|
188
|
+
amount: taxResolution.taxCents,
|
|
189
|
+
includedInPrices: taxResolution.taxIncludedInPrices,
|
|
198
190
|
};
|
|
199
191
|
}
|
|
200
192
|
}
|