@nextblock-cms/ecom 0.22.1 → 0.22.3
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/README.md +43 -43
- package/index.cjs +1 -1
- package/index.es.js +31 -31
- package/lib/components/Checkout.cjs +1 -1
- package/lib/components/Checkout.es.js +1 -0
- package/lib/components/SubscriptionSelector.cjs +1 -1
- package/lib/components/SubscriptionSelector.es.js +91 -92
- package/lib/coupon-server.cjs +1 -1
- package/lib/coupon-server.es.js +78 -61
- package/lib/coupons.cjs +1 -1
- package/lib/coupons.d.ts +9 -0
- package/lib/coupons.es.js +11 -2
- package/lib/currency-sync.cjs +1 -1
- package/lib/currency-sync.es.js +1 -1
- package/lib/freemius-coupons.cjs +1 -1
- package/lib/freemius-coupons.es.js +88 -92
- package/lib/freemius-license-claim.cjs +1 -1
- package/lib/freemius-license-claim.d.ts +13 -0
- package/lib/freemius-license-claim.es.js +44 -29
- package/lib/freemius-license.cjs +1 -1
- package/lib/freemius-license.es.js +19 -17
- package/lib/freemius-order-sync.cjs +1 -1
- package/lib/freemius-order-sync.d.ts +44 -0
- package/lib/freemius-order-sync.es.js +164 -77
- package/lib/freemius-pricing-server.cjs +1 -0
- package/lib/freemius-pricing-server.d.ts +65 -0
- package/lib/freemius-pricing-server.es.js +82 -0
- package/lib/freemius-pricing.cjs +1 -0
- package/lib/freemius-pricing.d.ts +53 -0
- package/lib/freemius-pricing.es.js +51 -0
- package/lib/invoice-server.cjs +1 -1
- package/lib/invoice-server.es.js +1 -0
- package/lib/pages/cms/coupons/CouponsPage.cjs +1 -1
- package/lib/pages/cms/coupons/CouponsPage.es.js +1 -0
- package/lib/pages/cms/coupons/EditCouponPage.cjs +1 -1
- package/lib/pages/cms/coupons/EditCouponPage.es.js +1 -0
- package/lib/pages/cms/coupons/actions.cjs +1 -1
- package/lib/pages/cms/coupons/actions.es.js +1 -0
- package/lib/pages/cms/orders/actions.cjs +1 -1
- package/lib/pages/cms/orders/actions.es.js +1 -0
- package/lib/pages/cms/orders/export-actions.cjs +1 -1
- package/lib/pages/cms/orders/export-actions.es.js +1 -0
- package/lib/pages/cms/orders/server-actions.cjs +1 -1
- package/lib/pages/cms/orders/server-actions.es.js +1 -0
- package/lib/pages/cms/products/ProductsPage.cjs +1 -1
- package/lib/pages/cms/products/ProductsPage.es.js +1 -0
- package/lib/pages/cms/products/[id]/edit/EditProductPage.cjs +1 -1
- package/lib/pages/cms/products/[id]/edit/EditProductPage.es.js +1 -0
- package/lib/pages/cms/products/actions.cjs +3 -3
- package/lib/pages/cms/products/actions.d.ts +11 -5
- package/lib/pages/cms/products/actions.es.js +45 -12
- package/lib/pages/cms/products/inventory/actions.cjs +1 -1
- package/lib/pages/cms/products/inventory/actions.es.js +1 -0
- package/lib/pages/cms/products/new/NewProductPage.cjs +1 -1
- package/lib/pages/cms/products/new/NewProductPage.es.js +1 -0
- package/lib/pages/cms/products/server-actions.cjs +1 -1
- package/lib/pages/cms/products/server-actions.es.js +1 -0
- package/lib/pages/cms/shipping/ShippingPage.cjs +1 -1
- package/lib/pages/cms/shipping/ShippingPage.es.js +1 -0
- package/lib/pages/cms/shipping/server-actions.cjs +1 -1
- package/lib/pages/cms/shipping/server-actions.es.js +1 -0
- package/lib/pages/cms/taxes/TaxesPage.cjs +1 -1
- package/lib/pages/cms/taxes/TaxesPage.es.js +1 -0
- package/lib/payment-config.cjs +1 -1
- package/lib/payment-config.es.js +1 -0
- package/lib/providers/freemius.cjs +1 -1
- package/lib/providers/freemius.es.js +200 -187
- package/lib/providers/stripe.cjs +1 -1
- package/lib/providers/stripe.es.js +151 -150
- package/lib/server-actions/coupon-actions.cjs +1 -1
- package/lib/server-actions/coupon-actions.es.js +1 -0
- package/lib/server-actions/customer-actions.d.ts +6 -1
- package/lib/server-actions/require-ecom-role.cjs +1 -1
- package/lib/server-actions/require-ecom-role.es.js +22 -10
- package/lib/server-actions/tax-actions.cjs +1 -1
- package/lib/server-actions/tax-actions.es.js +1 -0
- package/lib/shared-inventory.cjs +1 -1
- package/lib/shared-inventory.es.js +1 -1
- package/lib/stripe/checkout.cjs +1 -1
- package/lib/stripe/checkout.es.js +1 -0
- package/lib/stripe/order-sync.cjs +1 -1
- package/lib/stripe/order-sync.es.js +1 -0
- package/lib/stripe/webhooks.cjs +1 -1
- package/lib/stripe/webhooks.d.ts +10 -0
- package/lib/stripe/webhooks.es.js +23 -10
- package/package.json +4 -4
- package/server.cjs +1 -1
- package/server.es.js +31 -31
|
@@ -4,143 +4,142 @@ import { useCurrency as t } from "../CurrencyProvider.es.js";
|
|
|
4
4
|
import { useCart as n } from "../use-cart.es.js";
|
|
5
5
|
import { usePriceFormatter as r } from "../use-price-formatter.es.js";
|
|
6
6
|
import { getTrialSummary as i } from "../trials.es.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
7
|
+
import { pickFreemiusPricingTier as a } from "../freemius-pricing.es.js";
|
|
8
|
+
import { getPublicFreemiusPricing as o } from "../pages/cms/products/actions.es.js";
|
|
9
|
+
import { Button as s } from "@nextblock-cms/ui/button";
|
|
10
|
+
import { useEffect as c, useState as l } from "react";
|
|
11
|
+
import { ShoppingCart as u } from "lucide-react";
|
|
12
|
+
import { majorUnitAmountToMinor as d, useTranslations as f } from "@nextblock-cms/utils";
|
|
13
|
+
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
14
|
+
import { toast as h } from "sonner";
|
|
15
|
+
import { Skeleton as g } from "@nextblock-cms/ui/Skeleton";
|
|
15
16
|
//#region src/lib/components/SubscriptionSelector.tsx
|
|
16
|
-
var
|
|
17
|
-
let
|
|
18
|
-
if (
|
|
17
|
+
var _ = ({ product: _ }) => {
|
|
18
|
+
let v = r(), y = n((e) => e), { t: b } = f(), { activeCurrencyCode: x, currencies: S, defaultCurrency: C } = t(), [w, T] = l([]), [E, D] = l(!0), [O, k] = l("annual");
|
|
19
|
+
if (c(() => {
|
|
19
20
|
async function e() {
|
|
20
21
|
try {
|
|
21
|
-
if (!
|
|
22
|
-
let e = await
|
|
23
|
-
|
|
22
|
+
if (!_.id) return;
|
|
23
|
+
let e = await o(_.id);
|
|
24
|
+
T(e);
|
|
24
25
|
} catch (e) {
|
|
25
26
|
console.error("Failed to load pricing:", e);
|
|
26
27
|
} finally {
|
|
27
|
-
|
|
28
|
+
D(!1);
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
e();
|
|
31
|
-
}, [
|
|
32
|
-
let { addItem:
|
|
33
|
-
let t =
|
|
34
|
-
j && (
|
|
35
|
-
let
|
|
32
|
+
}, [_.id]), !y) return /* @__PURE__ */ p(g, { className: "h-14 w-full" });
|
|
33
|
+
let { addItem: A } = y, j = a(w.flatMap((e) => e.pricing ?? [])), M = () => {
|
|
34
|
+
let t = _.price;
|
|
35
|
+
j && (O === "monthly" && j.monthly_price != null && (t = d(j.monthly_price, C.code)), O === "annual" && j.annual_price != null && (t = d(j.annual_price, C.code)), O === "lifetime" && j.lifetime_price != null && (t = d(j.lifetime_price, C.code)));
|
|
36
|
+
let n = S.reduce((n, r) => (n[r.code] = e({
|
|
36
37
|
amount: t,
|
|
37
|
-
fromCurrencyCode:
|
|
38
|
+
fromCurrencyCode: C.code,
|
|
38
39
|
toCurrencyCode: r.code,
|
|
39
|
-
currencies:
|
|
40
|
+
currencies: S,
|
|
40
41
|
applyRounding: !0
|
|
41
|
-
}), n), {}),
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
language_id: g.language_id,
|
|
53
|
-
translation_group_id: g.translation_group_id,
|
|
42
|
+
}), n), {}), r = n[x] ?? t, { success: i, error: a } = A({
|
|
43
|
+
id: _.id,
|
|
44
|
+
product_id: _.id,
|
|
45
|
+
title: _.title,
|
|
46
|
+
price: r,
|
|
47
|
+
prices: n,
|
|
48
|
+
image_url: _.image_url,
|
|
49
|
+
slug: _.slug,
|
|
50
|
+
sku: _.sku,
|
|
51
|
+
language_id: _.language_id,
|
|
52
|
+
translation_group_id: _.translation_group_id,
|
|
54
53
|
product_type: "digital",
|
|
55
54
|
payment_provider: "freemius",
|
|
56
55
|
provider: "freemius",
|
|
57
|
-
billing_cycle:
|
|
58
|
-
freemius_product_id:
|
|
59
|
-
freemius_plan_id:
|
|
60
|
-
trial_period_days:
|
|
61
|
-
trial_requires_payment_method:
|
|
62
|
-
is_taxable:
|
|
63
|
-
currency_code:
|
|
56
|
+
billing_cycle: O,
|
|
57
|
+
freemius_product_id: _.freemius_product_id,
|
|
58
|
+
freemius_plan_id: _.freemius_plan_id,
|
|
59
|
+
trial_period_days: _.trial_period_days ?? 0,
|
|
60
|
+
trial_requires_payment_method: _.trial_requires_payment_method ?? !1,
|
|
61
|
+
is_taxable: _.is_taxable,
|
|
62
|
+
currency_code: x
|
|
64
63
|
});
|
|
65
|
-
|
|
64
|
+
i ? h.success(b("ecommerce.added_to_cart_success", { item: _.title })) : h.error(a || b("ecommerce.added_to_cart_error"));
|
|
66
65
|
};
|
|
67
|
-
if (
|
|
68
|
-
if (!
|
|
66
|
+
if (E) return /* @__PURE__ */ p(g, { className: "h-32 w-full" });
|
|
67
|
+
if (!j) return /* @__PURE__ */ p("div", {
|
|
69
68
|
className: "p-4 border border-dashed rounded-lg text-center text-muted-foreground",
|
|
70
|
-
children:
|
|
69
|
+
children: b("ecommerce.pricing_unavailable")
|
|
71
70
|
});
|
|
72
71
|
let N = j.monthly_price != null, P = j.annual_price != null, F = j.lifetime_price != null;
|
|
73
|
-
|
|
74
|
-
let I = i(
|
|
75
|
-
return
|
|
76
|
-
amount:
|
|
77
|
-
fromCurrencyCode:
|
|
78
|
-
toCurrencyCode:
|
|
79
|
-
currencies:
|
|
72
|
+
O === "annual" && !P && (N ? k("monthly") : F && k("lifetime"));
|
|
73
|
+
let I = i(_, b), L = _.price;
|
|
74
|
+
return O === "monthly" && j.monthly_price != null && (L = e({
|
|
75
|
+
amount: d(j.monthly_price, C.code),
|
|
76
|
+
fromCurrencyCode: C.code,
|
|
77
|
+
toCurrencyCode: x,
|
|
78
|
+
currencies: S,
|
|
80
79
|
applyRounding: !0
|
|
81
|
-
})),
|
|
82
|
-
amount:
|
|
83
|
-
fromCurrencyCode:
|
|
84
|
-
toCurrencyCode:
|
|
85
|
-
currencies:
|
|
80
|
+
})), O === "annual" && j.annual_price != null && (L = e({
|
|
81
|
+
amount: d(j.annual_price, C.code),
|
|
82
|
+
fromCurrencyCode: C.code,
|
|
83
|
+
toCurrencyCode: x,
|
|
84
|
+
currencies: S,
|
|
86
85
|
applyRounding: !0
|
|
87
|
-
})),
|
|
88
|
-
amount:
|
|
89
|
-
fromCurrencyCode:
|
|
90
|
-
toCurrencyCode:
|
|
91
|
-
currencies:
|
|
86
|
+
})), O === "lifetime" && j.lifetime_price != null && (L = e({
|
|
87
|
+
amount: d(j.lifetime_price, C.code),
|
|
88
|
+
fromCurrencyCode: C.code,
|
|
89
|
+
toCurrencyCode: x,
|
|
90
|
+
currencies: S,
|
|
92
91
|
applyRounding: !0
|
|
93
|
-
})), /* @__PURE__ */
|
|
92
|
+
})), /* @__PURE__ */ m("div", {
|
|
94
93
|
className: "flex flex-col gap-4",
|
|
95
94
|
children: [
|
|
96
|
-
/* @__PURE__ */
|
|
95
|
+
/* @__PURE__ */ m("div", {
|
|
97
96
|
role: "group",
|
|
98
|
-
"aria-label":
|
|
97
|
+
"aria-label": b("ecommerce.billing_cycle") === "ecommerce.billing_cycle" ? "Billing cycle" : b("ecommerce.billing_cycle"),
|
|
99
98
|
className: "flex bg-secondary/35 p-1 rounded-lg w-full max-w-sm mx-auto shadow-inner",
|
|
100
99
|
children: [
|
|
101
|
-
N && /* @__PURE__ */
|
|
100
|
+
N && /* @__PURE__ */ p("button", {
|
|
102
101
|
type: "button",
|
|
103
|
-
"aria-pressed":
|
|
104
|
-
onClick: () =>
|
|
105
|
-
className: `flex-1 py-1.5 text-xs font-semibold rounded-md transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring ${
|
|
106
|
-
children:
|
|
102
|
+
"aria-pressed": O === "monthly",
|
|
103
|
+
onClick: () => k("monthly"),
|
|
104
|
+
className: `flex-1 py-1.5 text-xs font-semibold rounded-md transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring ${O === "monthly" ? "bg-background shadow-sm text-foreground" : "text-muted-foreground hover:text-foreground"}`,
|
|
105
|
+
children: b("ecommerce.monthly")
|
|
107
106
|
}),
|
|
108
|
-
P && /* @__PURE__ */
|
|
107
|
+
P && /* @__PURE__ */ p("button", {
|
|
109
108
|
type: "button",
|
|
110
|
-
"aria-pressed":
|
|
111
|
-
onClick: () =>
|
|
112
|
-
className: `flex-1 py-1.5 text-xs font-semibold rounded-md transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring ${
|
|
113
|
-
children:
|
|
109
|
+
"aria-pressed": O === "annual",
|
|
110
|
+
onClick: () => k("annual"),
|
|
111
|
+
className: `flex-1 py-1.5 text-xs font-semibold rounded-md transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring ${O === "annual" ? "bg-background shadow-sm text-foreground" : "text-muted-foreground hover:text-foreground"}`,
|
|
112
|
+
children: b("ecommerce.annual")
|
|
114
113
|
}),
|
|
115
|
-
F && /* @__PURE__ */
|
|
114
|
+
F && /* @__PURE__ */ p("button", {
|
|
116
115
|
type: "button",
|
|
117
|
-
"aria-pressed":
|
|
118
|
-
onClick: () =>
|
|
119
|
-
className: `flex-1 py-1.5 text-xs font-semibold rounded-md transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring ${
|
|
120
|
-
children:
|
|
116
|
+
"aria-pressed": O === "lifetime",
|
|
117
|
+
onClick: () => k("lifetime"),
|
|
118
|
+
className: `flex-1 py-1.5 text-xs font-semibold rounded-md transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring ${O === "lifetime" ? "bg-background shadow-sm text-foreground" : "text-muted-foreground hover:text-foreground"}`,
|
|
119
|
+
children: b("ecommerce.lifetime")
|
|
121
120
|
})
|
|
122
121
|
]
|
|
123
122
|
}),
|
|
124
|
-
/* @__PURE__ */
|
|
123
|
+
/* @__PURE__ */ m("div", {
|
|
125
124
|
className: "text-center",
|
|
126
125
|
children: [
|
|
127
|
-
/* @__PURE__ */
|
|
126
|
+
/* @__PURE__ */ p("span", {
|
|
128
127
|
className: "text-3xl font-extrabold text-foreground tabular-nums",
|
|
129
|
-
children:
|
|
128
|
+
children: v(L, x)
|
|
130
129
|
}),
|
|
131
|
-
|
|
130
|
+
O !== "lifetime" && /* @__PURE__ */ m("span", {
|
|
132
131
|
className: "text-muted-foreground text-sm ml-1.5",
|
|
133
|
-
children: ["/ ",
|
|
132
|
+
children: ["/ ", b(O === "annual" ? "ecommerce.year" : "ecommerce.month")]
|
|
134
133
|
}),
|
|
135
|
-
I && /* @__PURE__ */
|
|
134
|
+
I && /* @__PURE__ */ m("div", {
|
|
136
135
|
className: "mt-1 text-xs font-medium text-emerald-600 dark:text-emerald-400",
|
|
137
136
|
children: [
|
|
138
137
|
I.label,
|
|
139
|
-
/* @__PURE__ */
|
|
138
|
+
/* @__PURE__ */ p("span", {
|
|
140
139
|
className: "mx-2 text-muted-foreground/50",
|
|
141
140
|
children: "|"
|
|
142
141
|
}),
|
|
143
|
-
/* @__PURE__ */
|
|
142
|
+
/* @__PURE__ */ p("span", {
|
|
144
143
|
className: "text-muted-foreground",
|
|
145
144
|
children: I.paymentRequirementLabel
|
|
146
145
|
})
|
|
@@ -148,13 +147,13 @@ var g = ({ product: g }) => {
|
|
|
148
147
|
})
|
|
149
148
|
]
|
|
150
149
|
}),
|
|
151
|
-
/* @__PURE__ */
|
|
150
|
+
/* @__PURE__ */ m(s, {
|
|
152
151
|
onClick: M,
|
|
153
152
|
className: "w-full h-12 text-base font-bold shadow-md transition-shadow hover:shadow-lg active:scale-[0.98]",
|
|
154
|
-
children: [/* @__PURE__ */
|
|
153
|
+
children: [/* @__PURE__ */ p(u, { className: "mr-2 h-4 w-4" }), I?.label ? b("ecommerce.start_trial", { trial: I.label }) === "ecommerce.start_trial" ? `Start ${I.label}` : b("ecommerce.start_trial", { trial: I.label }) : b("ecommerce.get_license")]
|
|
155
154
|
})
|
|
156
155
|
]
|
|
157
156
|
});
|
|
158
157
|
};
|
|
159
158
|
//#endregion
|
|
160
|
-
export {
|
|
159
|
+
export { _ as SubscriptionSelector };
|
package/lib/coupon-server.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=require("./currency.cjs"),t=require("./coupons.cjs");require("server-only");function
|
|
1
|
+
const e=require("./currency.cjs"),t=require("./coupons.cjs"),n=require("./freemius-pricing-server.cjs");require("server-only");function r(e){if(!e)return null;let t=new Date(e);return Number.isNaN(t.getTime())?null:t}function i(e,t){return e===`all`||e===t}function a(e){return e===`stripe`||e===`freemius`?e:null}async function o(t){let{data:n,error:r}=await t.from(`currencies`).select(`code, symbol, exchange_rate, is_default, is_active, auto_sync_product_prices, auto_update_exchange_rate, exchange_rate_source, exchange_rate_updated_at, rounding_mode, rounding_increment, rounding_charm_amount`).eq(`is_active`,!0).order(`code`,{ascending:!0});if(r)throw Error(r.message);return(n||[]).map(t=>e.normalizeCurrencyRecord(t))}async function s(r){let{client:i,items:s}=r,c=await o(i);if(c.length===0)throw Error(`No active currencies are configured.`);let l=c.find(e=>e.code===(r.currencyCode||``).toUpperCase())??e.getDefaultCurrency(c),u=[...new Set(s.map(e=>e.product_id).filter(Boolean))],d=[...new Set(s.map(e=>e.variant_id).filter(e=>!!e))];if(u.length===0)return{lines:[],currencies:c,selectedCurrency:l};let[{data:f,error:p},m]=await Promise.all([i.from(`products`).select(`${n.FREEMIUS_PRICED_PRODUCT_COLUMNS}, title, product_type, payment_provider, freemius_product_id, freemius_plan_id`).in(`id`,u),d.length?i.from(`product_variants`).select(`id, product_id, price, prices, sale_price, sale_prices, sale_start_at, sale_end_at, scheduled_price, scheduled_prices, scheduled_price_at`).in(`id`,d):Promise.resolve({data:[],error:null})]);if(p)throw Error(p.message);if(m.error)throw Error(m.error.message);let h=f||[],g=m.data||[],_=new Map(h.map(e=>[e.id,e])),v=new Map(g.map(e=>[e.id,e])),y=[],b=new Map;for(let r of s){let o=_.get(r.product_id);if(!o)continue;let s=a(o.payment_provider)??(o.product_type===`digital`?`freemius`:`stripe`),u=r.variant_id?v.get(r.variant_id):null,d;if(s===`freemius`){let e=await n.resolveFreemiusUnitAmount({client:i,product:o,billingCycle:r.billing_cycle,currencyCode:l.code,currencies:c,tierCache:b});if(!e.ok)continue;d=e.unitAmount}else{let t=u&&u.product_id===o.id?u:o,n=e.resolveEffectivePriceForCurrency({prices:e.normalizePriceMap(t.prices),salePrices:e.normalizeSalePriceMap(t.sale_prices),fallbackPrice:t.price,fallbackSalePrice:t.sale_price,saleStartAt:t.sale_start_at,saleEndAt:t.sale_end_at,scheduledPrice:t.scheduled_price,scheduledPrices:e.normalizePriceMap(t.scheduled_prices),scheduledPriceAt:t.scheduled_price_at,currencyCode:l.code,currencies:c});d=n.sale_price??n.price}let f=s===`freemius`?1:Math.max(1,Number(r.quantity)||1),p=Math.max(0,d);y.push({key:t.getCartLineCouponKey(r),product_id:o.id,variant_id:u?.id??null,title:o.title,quantity:f,provider:s,subtotal:p*f,freemius_product_id:o.freemius_product_id??null,freemius_plan_id:o.freemius_plan_id??null})}return{lines:y,currencies:c,selectedCurrency:l}}async function c(e,t){let{data:n,error:r}=await e.from(`coupon_products`).select(`product_id`).eq(`coupon_id`,t);if(r)throw Error(r.message);return new Set((n||[]).map(e=>e.product_id))}function l(e,t){let n=e.reduce((e,t)=>e+t.subtotal,0),r=Math.min(t,n);if(n<=0||r<=0)return new Map;let i=new Map,a=0;return e.forEach((t,o)=>{let s=o===e.length-1?r-a:Math.min(t.subtotal,Math.floor(r*t.subtotal/n));a+=s,i.set(t.key,s)}),i}var u={not_found:{error:`Coupon code not found.`,errorKey:`ecommerce.coupon_not_found`},inactive:{error:`This coupon is not active.`,errorKey:`ecommerce.coupon_inactive`},not_started:{error:`This coupon is not active yet.`,errorKey:`ecommerce.coupon_not_started`},expired:{error:`This coupon has expired.`,errorKey:`ecommerce.coupon_expired`},limit_reached:{error:`This coupon has reached its redemption limit.`,errorKey:`ecommerce.coupon_limit_reached`},not_applicable:{error:`This coupon does not apply to the items in your cart.`,errorKey:`ecommerce.coupon_not_applicable`}};function d(e,n){return{success:!1,...n?u[e]:{error:t.COUPON_INVALID_ERROR_MESSAGE,errorKey:t.COUPON_INVALID_ERROR_KEY}}}async function f(e){let n=t.normalizeCouponCode(e.code),a=e.detailedErrors===!0;if(!n)return{success:!1,error:`Enter a coupon code.`,errorKey:`ecommerce.coupon_code_required`};if(!e.items.length)return{success:!1,error:`Add an item to your cart before applying a coupon.`,errorKey:`ecommerce.coupon_cart_empty`};let{data:o,error:u}=await e.client.from(`coupons`).select(`id, code, name, provider_scope, discount_type, discount_amount, is_active, starts_at, ends_at, redemption_limit, redemptions_count`).eq(`code`,n).maybeSingle();if(u)throw Error(u.message);if(!o)return d(`not_found`,a);let f=o,p=new Date,m=r(f.starts_at),h=r(f.ends_at);if(!f.is_active)return d(`inactive`,a);if(m&&m>p)return d(`not_started`,a);if(h&&h<=p)return d(`expired`,a);if(f.redemption_limit!==null&&(f.redemptions_count??0)>=f.redemption_limit)return d(`limit_reached`,a);let[{lines:g},_]=await Promise.all([s({client:e.client,items:e.items,currencyCode:e.currencyCode}),c(e.client,f.id)]),v=_.size>0,y=g.filter(e=>e.subtotal>0&&i(f.provider_scope,e.provider)&&(!v||_.has(e.product_id)));if(y.length===0)return d(`not_applicable`,a);let b=f.discount_type===`fixed`?l(y,f.discount_amount):null,x=t.emptyProviderDiscounts(),S=y.map(e=>{let t=f.discount_type===`percent`?Math.min(e.subtotal,Math.round(e.subtotal*f.discount_amount/100)):b?.get(e.key)??0;return x[e.provider]+=t,{key:e.key,product_id:e.product_id,variant_id:e.variant_id,provider:e.provider,title:e.title,quantity:e.quantity,subtotal:e.subtotal,discount:t}});return{success:!0,quote:{couponId:f.id,code:f.code,name:f.name,discountType:f.discount_type,discountAmount:f.discount_amount,providerScope:f.provider_scope,eligibleSubtotal:y.reduce((e,t)=>e+t.subtotal,0),discountTotal:x.stripe+x.freemius,providerDiscounts:x,lineDiscounts:S}}}function p(e){let t=new Map;for(let n of e?.lineDiscounts??[])t.set(n.key,n.discount);return t}async function m(e){let n=e=>({success:!1,reason:e,error:t.COUPON_INVALID_ERROR_MESSAGE,errorKey:t.COUPON_INVALID_ERROR_KEY}),{data:r,error:i}=await e.client.from(`coupon_redemptions`).select(`id`).eq(`order_id`,e.orderId).maybeSingle();if(i)return console.error(`Failed to look up existing coupon redemption:`,i),n(`error`);if(r)return{success:!0,alreadyRecorded:!0};let{data:a,error:o}=await e.client.rpc(`redeem_coupon_atomically`,{p_coupon_id:e.quote.couponId});if(o)return console.error(`Failed to reserve coupon redemption:`,o),n(`error`);if(a!==!0)return n(`limit_reached`);let{error:s}=await e.client.from(`coupon_redemptions`).insert({coupon_id:e.quote.couponId,order_id:e.orderId,coupon_code:e.quote.code,provider:e.provider,discount_total:Math.max(0,e.discountTotal),user_id:e.userId||null,customer_email:e.customerEmail||null,metadata:e.metadata||{}});return s&&console.error(`Failed to record coupon redemption:`,s),{success:!0,alreadyRecorded:!1}}var h=30,g=12/60,_=5e3,v=`unknown`;function y(e){let t=e?.capacity??h,n=e?.refillPerSecond??g,r=e?.maxBuckets??_,i=t/n*1e3,a=new Map;function o(e){for(let[t,n]of a)e-n.updatedAt>=i&&a.delete(t);for(let e of a.keys()){if(a.size<r)break;a.delete(e)}}return{consume(e,i=Date.now()){let s=a.get(e);if(!s)a.size>=r&&o(i),s={tokens:t,updatedAt:i},a.set(e,s);else{let e=Math.max(0,i-s.updatedAt)/1e3;s.tokens=Math.min(t,s.tokens+e*n),s.updatedAt=i}return s.tokens<1?!1:(--s.tokens,!0)},get size(){return a.size}}}var b=y();function x(e,t){return b.consume(e,t)}function S(e){let[t]=(e??``).split(`,`);return(t?.trim()??``)||null}function C(e){return S(e.get(`x-vercel-forwarded-for`))??S(e.get(`x-real-ip`))??S(e.get(`x-forwarded-for`))??`unknown`}exports.COUPON_VALIDATION_UNKNOWN_CLIENT_KEY=v,exports.consumeCouponValidationToken=x,exports.createCouponValidationThrottle=y,exports.getCouponQuote=f,exports.getQuoteLineDiscountMap=p,exports.recordCouponRedemption=m,exports.resolveCouponValidationThrottleKey=C;
|
package/lib/coupon-server.es.js
CHANGED
|
@@ -1,79 +1,96 @@
|
|
|
1
1
|
import { getDefaultCurrency as e, normalizeCurrencyRecord as t, normalizePriceMap as n, normalizeSalePriceMap as r, resolveEffectivePriceForCurrency as i } from "./currency.es.js";
|
|
2
2
|
import { COUPON_INVALID_ERROR_KEY as a, COUPON_INVALID_ERROR_MESSAGE as o, emptyProviderDiscounts as s, getCartLineCouponKey as c, normalizeCouponCode as l } from "./coupons.es.js";
|
|
3
|
+
import { FREEMIUS_PRICED_PRODUCT_COLUMNS as u, resolveFreemiusUnitAmount as d } from "./freemius-pricing-server.es.js";
|
|
3
4
|
import "server-only";
|
|
4
5
|
//#region src/lib/coupon-server.ts
|
|
5
|
-
function
|
|
6
|
+
function f(e) {
|
|
6
7
|
if (!e) return null;
|
|
7
8
|
let t = new Date(e);
|
|
8
9
|
return Number.isNaN(t.getTime()) ? null : t;
|
|
9
10
|
}
|
|
10
|
-
function
|
|
11
|
+
function p(e, t) {
|
|
11
12
|
return e === "all" || e === t;
|
|
12
13
|
}
|
|
13
|
-
function
|
|
14
|
+
function m(e) {
|
|
14
15
|
return e === "stripe" || e === "freemius" ? e : null;
|
|
15
16
|
}
|
|
16
|
-
async function
|
|
17
|
+
async function h(e) {
|
|
17
18
|
let { data: n, error: r } = await e.from("currencies").select("code, symbol, exchange_rate, is_default, is_active, auto_sync_product_prices, auto_update_exchange_rate, exchange_rate_source, exchange_rate_updated_at, rounding_mode, rounding_increment, rounding_charm_amount").eq("is_active", !0).order("code", { ascending: !0 });
|
|
18
19
|
if (r) throw Error(r.message);
|
|
19
20
|
return (n || []).map((e) => t(e));
|
|
20
21
|
}
|
|
21
|
-
async function
|
|
22
|
-
let { client: a, items: o } = t, s = await
|
|
22
|
+
async function g(t) {
|
|
23
|
+
let { client: a, items: o } = t, s = await h(a);
|
|
23
24
|
if (s.length === 0) throw Error("No active currencies are configured.");
|
|
24
|
-
let l = s.find((e) => e.code === (t.currencyCode || "").toUpperCase()) ?? e(s),
|
|
25
|
-
if (
|
|
25
|
+
let l = s.find((e) => e.code === (t.currencyCode || "").toUpperCase()) ?? e(s), f = [...new Set(o.map((e) => e.product_id).filter(Boolean))], p = [...new Set(o.map((e) => e.variant_id).filter((e) => !!e))];
|
|
26
|
+
if (f.length === 0) return {
|
|
26
27
|
lines: [],
|
|
27
28
|
currencies: s,
|
|
28
29
|
selectedCurrency: l
|
|
29
30
|
};
|
|
30
|
-
let [{ data:
|
|
31
|
+
let [{ data: g, error: _ }, v] = await Promise.all([a.from("products").select(`${u}, title, product_type, payment_provider, freemius_product_id, freemius_plan_id`).in("id", f), p.length ? a.from("product_variants").select("id, product_id, price, prices, sale_price, sale_prices, sale_start_at, sale_end_at, scheduled_price, scheduled_prices, scheduled_price_at").in("id", p) : Promise.resolve({
|
|
31
32
|
data: [],
|
|
32
33
|
error: null
|
|
33
34
|
})]);
|
|
34
|
-
if (
|
|
35
|
-
if (
|
|
36
|
-
let
|
|
35
|
+
if (_) throw Error(_.message);
|
|
36
|
+
if (v.error) throw Error(v.error.message);
|
|
37
|
+
let y = g || [], b = v.data || [], x = new Map(y.map((e) => [e.id, e])), S = new Map(b.map((e) => [e.id, e])), C = [], w = /* @__PURE__ */ new Map();
|
|
37
38
|
for (let e of o) {
|
|
38
|
-
let t =
|
|
39
|
+
let t = x.get(e.product_id);
|
|
39
40
|
if (!t) continue;
|
|
40
|
-
let
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
41
|
+
let o = m(t.payment_provider) ?? (t.product_type === "digital" ? "freemius" : "stripe"), u = e.variant_id ? S.get(e.variant_id) : null, f;
|
|
42
|
+
if (o === "freemius") {
|
|
43
|
+
let n = await d({
|
|
44
|
+
client: a,
|
|
45
|
+
product: t,
|
|
46
|
+
billingCycle: e.billing_cycle,
|
|
47
|
+
currencyCode: l.code,
|
|
48
|
+
currencies: s,
|
|
49
|
+
tierCache: w
|
|
50
|
+
});
|
|
51
|
+
if (!n.ok) continue;
|
|
52
|
+
f = n.unitAmount;
|
|
53
|
+
} else {
|
|
54
|
+
let e = u && u.product_id === t.id ? u : t, a = i({
|
|
55
|
+
prices: n(e.prices),
|
|
56
|
+
salePrices: r(e.sale_prices),
|
|
57
|
+
fallbackPrice: e.price,
|
|
58
|
+
fallbackSalePrice: e.sale_price,
|
|
59
|
+
saleStartAt: e.sale_start_at,
|
|
60
|
+
saleEndAt: e.sale_end_at,
|
|
61
|
+
scheduledPrice: e.scheduled_price,
|
|
62
|
+
scheduledPrices: n(e.scheduled_prices),
|
|
63
|
+
scheduledPriceAt: e.scheduled_price_at,
|
|
64
|
+
currencyCode: l.code,
|
|
65
|
+
currencies: s
|
|
66
|
+
});
|
|
67
|
+
f = a.sale_price ?? a.price;
|
|
68
|
+
}
|
|
69
|
+
let p = o === "freemius" ? 1 : Math.max(1, Number(e.quantity) || 1), h = Math.max(0, f);
|
|
70
|
+
C.push({
|
|
54
71
|
key: c(e),
|
|
55
72
|
product_id: t.id,
|
|
56
|
-
variant_id:
|
|
73
|
+
variant_id: u?.id ?? null,
|
|
57
74
|
title: t.title,
|
|
58
75
|
quantity: p,
|
|
59
|
-
provider:
|
|
60
|
-
subtotal:
|
|
76
|
+
provider: o,
|
|
77
|
+
subtotal: h * p,
|
|
61
78
|
freemius_product_id: t.freemius_product_id ?? null,
|
|
62
79
|
freemius_plan_id: t.freemius_plan_id ?? null
|
|
63
80
|
});
|
|
64
81
|
}
|
|
65
82
|
return {
|
|
66
|
-
lines:
|
|
83
|
+
lines: C,
|
|
67
84
|
currencies: s,
|
|
68
85
|
selectedCurrency: l
|
|
69
86
|
};
|
|
70
87
|
}
|
|
71
|
-
async function
|
|
88
|
+
async function _(e, t) {
|
|
72
89
|
let { data: n, error: r } = await e.from("coupon_products").select("product_id").eq("coupon_id", t);
|
|
73
90
|
if (r) throw Error(r.message);
|
|
74
91
|
return new Set((n || []).map((e) => e.product_id));
|
|
75
92
|
}
|
|
76
|
-
function
|
|
93
|
+
function v(e, t) {
|
|
77
94
|
let n = e.reduce((e, t) => e + t.subtotal, 0), r = Math.min(t, n);
|
|
78
95
|
if (n <= 0 || r <= 0) return /* @__PURE__ */ new Map();
|
|
79
96
|
let i = /* @__PURE__ */ new Map(), a = 0;
|
|
@@ -82,7 +99,7 @@ function g(e, t) {
|
|
|
82
99
|
a += s, i.set(t.key, s);
|
|
83
100
|
}), i;
|
|
84
101
|
}
|
|
85
|
-
var
|
|
102
|
+
var y = {
|
|
86
103
|
not_found: {
|
|
87
104
|
error: "Coupon code not found.",
|
|
88
105
|
errorKey: "ecommerce.coupon_not_found"
|
|
@@ -108,16 +125,16 @@ var _ = {
|
|
|
108
125
|
errorKey: "ecommerce.coupon_not_applicable"
|
|
109
126
|
}
|
|
110
127
|
};
|
|
111
|
-
function
|
|
128
|
+
function b(e, t) {
|
|
112
129
|
return {
|
|
113
130
|
success: !1,
|
|
114
|
-
...t ?
|
|
131
|
+
...t ? y[e] : {
|
|
115
132
|
error: o,
|
|
116
133
|
errorKey: a
|
|
117
134
|
}
|
|
118
135
|
};
|
|
119
136
|
}
|
|
120
|
-
async function
|
|
137
|
+
async function x(e) {
|
|
121
138
|
let t = l(e.code), n = e.detailedErrors === !0;
|
|
122
139
|
if (!t) return {
|
|
123
140
|
success: !1,
|
|
@@ -129,21 +146,21 @@ async function y(e) {
|
|
|
129
146
|
error: "Add an item to your cart before applying a coupon.",
|
|
130
147
|
errorKey: "ecommerce.coupon_cart_empty"
|
|
131
148
|
};
|
|
132
|
-
let { data: r, error: i } = await e.client.from("coupons").select("id, code, name, provider_scope, discount_type, discount_amount, is_active, starts_at, ends_at, redemption_limit, redemptions_count").
|
|
149
|
+
let { data: r, error: i } = await e.client.from("coupons").select("id, code, name, provider_scope, discount_type, discount_amount, is_active, starts_at, ends_at, redemption_limit, redemptions_count").eq("code", t).maybeSingle();
|
|
133
150
|
if (i) throw Error(i.message);
|
|
134
|
-
if (!r) return
|
|
135
|
-
let a = r, o = /* @__PURE__ */ new Date(), c =
|
|
136
|
-
if (!a.is_active) return
|
|
137
|
-
if (c && c > o) return
|
|
138
|
-
if (
|
|
139
|
-
if (a.redemption_limit !== null && (a.redemptions_count ?? 0) >= a.redemption_limit) return
|
|
140
|
-
let [{ lines:
|
|
151
|
+
if (!r) return b("not_found", n);
|
|
152
|
+
let a = r, o = /* @__PURE__ */ new Date(), c = f(a.starts_at), u = f(a.ends_at);
|
|
153
|
+
if (!a.is_active) return b("inactive", n);
|
|
154
|
+
if (c && c > o) return b("not_started", n);
|
|
155
|
+
if (u && u <= o) return b("expired", n);
|
|
156
|
+
if (a.redemption_limit !== null && (a.redemptions_count ?? 0) >= a.redemption_limit) return b("limit_reached", n);
|
|
157
|
+
let [{ lines: d }, m] = await Promise.all([g({
|
|
141
158
|
client: e.client,
|
|
142
159
|
items: e.items,
|
|
143
160
|
currencyCode: e.currencyCode
|
|
144
|
-
}),
|
|
145
|
-
if (
|
|
146
|
-
let x = a.discount_type === "fixed" ?
|
|
161
|
+
}), _(e.client, a.id)]), h = m.size > 0, y = d.filter((e) => e.subtotal > 0 && p(a.provider_scope, e.provider) && (!h || m.has(e.product_id)));
|
|
162
|
+
if (y.length === 0) return b("not_applicable", n);
|
|
163
|
+
let x = a.discount_type === "fixed" ? v(y, a.discount_amount) : null, S = s(), C = y.map((e) => {
|
|
147
164
|
let t = a.discount_type === "percent" ? Math.min(e.subtotal, Math.round(e.subtotal * a.discount_amount / 100)) : x?.get(e.key) ?? 0;
|
|
148
165
|
return S[e.provider] += t, {
|
|
149
166
|
key: e.key,
|
|
@@ -165,19 +182,19 @@ async function y(e) {
|
|
|
165
182
|
discountType: a.discount_type,
|
|
166
183
|
discountAmount: a.discount_amount,
|
|
167
184
|
providerScope: a.provider_scope,
|
|
168
|
-
eligibleSubtotal:
|
|
185
|
+
eligibleSubtotal: y.reduce((e, t) => e + t.subtotal, 0),
|
|
169
186
|
discountTotal: S.stripe + S.freemius,
|
|
170
187
|
providerDiscounts: S,
|
|
171
188
|
lineDiscounts: C
|
|
172
189
|
}
|
|
173
190
|
};
|
|
174
191
|
}
|
|
175
|
-
function
|
|
192
|
+
function S(e) {
|
|
176
193
|
let t = /* @__PURE__ */ new Map();
|
|
177
194
|
for (let n of e?.lineDiscounts ?? []) t.set(n.key, n.discount);
|
|
178
195
|
return t;
|
|
179
196
|
}
|
|
180
|
-
async function
|
|
197
|
+
async function C(e) {
|
|
181
198
|
let t = (e) => ({
|
|
182
199
|
success: !1,
|
|
183
200
|
reason: e,
|
|
@@ -207,9 +224,9 @@ async function x(e) {
|
|
|
207
224
|
alreadyRecorded: !1
|
|
208
225
|
};
|
|
209
226
|
}
|
|
210
|
-
var
|
|
211
|
-
function
|
|
212
|
-
let t = e?.capacity ??
|
|
227
|
+
var w = 30, T = 12 / 60, E = 5e3, D = "unknown";
|
|
228
|
+
function O(e) {
|
|
229
|
+
let t = e?.capacity ?? w, n = e?.refillPerSecond ?? T, r = e?.maxBuckets ?? E, i = t / n * 1e3, a = /* @__PURE__ */ new Map();
|
|
213
230
|
function o(e) {
|
|
214
231
|
for (let [t, n] of a) e - n.updatedAt >= i && a.delete(t);
|
|
215
232
|
for (let e of a.keys()) {
|
|
@@ -235,16 +252,16 @@ function E(e) {
|
|
|
235
252
|
}
|
|
236
253
|
};
|
|
237
254
|
}
|
|
238
|
-
var
|
|
239
|
-
function
|
|
240
|
-
return
|
|
255
|
+
var k = O();
|
|
256
|
+
function A(e, t) {
|
|
257
|
+
return k.consume(e, t);
|
|
241
258
|
}
|
|
242
|
-
function
|
|
259
|
+
function j(e) {
|
|
243
260
|
let [t] = (e ?? "").split(",");
|
|
244
261
|
return (t?.trim() ?? "") || null;
|
|
245
262
|
}
|
|
246
|
-
function
|
|
247
|
-
return
|
|
263
|
+
function M(e) {
|
|
264
|
+
return j(e.get("x-vercel-forwarded-for")) ?? j(e.get("x-real-ip")) ?? j(e.get("x-forwarded-for")) ?? "unknown";
|
|
248
265
|
}
|
|
249
266
|
//#endregion
|
|
250
|
-
export {
|
|
267
|
+
export { D as COUPON_VALIDATION_UNKNOWN_CLIENT_KEY, A as consumeCouponValidationToken, O as createCouponValidationThrottle, x as getCouponQuote, S as getQuoteLineDiscountMap, C as recordCouponRedemption, M as resolveCouponValidationThrottleKey };
|
package/lib/coupons.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=`ecommerce.coupon_invalid`,t=`This coupon code is invalid or can't be applied to your cart.`,n=`ecommerce.coupon_validation_failed`,r=`Too many coupon attempts. Wait a moment and try again.`;function i(e){return(e||``).trim().replace(/\s+/g,``).toUpperCase()}function a(e){return`${e.product_id}:${e.variant_id||`base`}`}function o(){return{stripe:0,freemius:0}}exports.COUPON_INVALID_ERROR_KEY=e,exports.COUPON_INVALID_ERROR_MESSAGE=t,exports.COUPON_THROTTLED_ERROR_KEY=n,exports.COUPON_THROTTLED_ERROR_MESSAGE=r,exports.emptyProviderDiscounts=
|
|
1
|
+
var e=`ecommerce.coupon_invalid`,t=`This coupon code is invalid or can't be applied to your cart.`,n=`ecommerce.coupon_validation_failed`,r=`Too many coupon attempts. Wait a moment and try again.`;function i(e){return(e||``).trim().replace(/\s+/g,``).toUpperCase()}function a(e){return`${e.product_id}:${e.variant_id||`base`}`}function o(e,t){let n=new Map(e.map(e=>[a(e),e])),r=new Set,i=[];for(let e of t){let t=a(e);r.has(t)||(r.add(t),i.push(n.get(t)??e))}for(let[e,t]of n)r.has(e)||i.push(t);return i}function s(){return{stripe:0,freemius:0}}exports.COUPON_INVALID_ERROR_KEY=e,exports.COUPON_INVALID_ERROR_MESSAGE=t,exports.COUPON_THROTTLED_ERROR_KEY=n,exports.COUPON_THROTTLED_ERROR_MESSAGE=r,exports.emptyProviderDiscounts=s,exports.getCartLineCouponKey=a,exports.normalizeCouponCode=i,exports.reconcileCouponContextItems=o;
|
package/lib/coupons.d.ts
CHANGED
|
@@ -51,4 +51,13 @@ export declare const COUPON_THROTTLED_ERROR_KEY = "ecommerce.coupon_validation_f
|
|
|
51
51
|
export declare const COUPON_THROTTLED_ERROR_MESSAGE = "Too many coupon attempts. Wait a moment and try again.";
|
|
52
52
|
export declare function normalizeCouponCode(code?: string | null): string;
|
|
53
53
|
export declare function getCartLineCouponKey(item: Pick<CartItem, 'product_id' | 'variant_id'>): string;
|
|
54
|
+
/**
|
|
55
|
+
* The lines a coupon is quoted on at checkout. The browser sends a "coupon context" (the whole
|
|
56
|
+
* cart, both providers) so a cart-wide coupon is priced on the whole cart, but it never decides
|
|
57
|
+
* the quantity of a line that is being paid for here: every line of `cartItems` replaces its
|
|
58
|
+
* context twin, and a cart line the context left out is added back. Context lines for other
|
|
59
|
+
* products are kept (the other provider's half of the cart). 2026-10-21: a context quantity of
|
|
60
|
+
* 10 on a one-unit cart line made a 10 % coupon take 100 % off that line.
|
|
61
|
+
*/
|
|
62
|
+
export declare function reconcileCouponContextItems<T extends Pick<CartItem, 'product_id' | 'variant_id'>>(cartItems: T[], contextItems: T[]): T[];
|
|
54
63
|
export declare function emptyProviderDiscounts(): Record<EcommercePaymentProvider, number>;
|
package/lib/coupons.es.js
CHANGED
|
@@ -6,11 +6,20 @@ function i(e) {
|
|
|
6
6
|
function a(e) {
|
|
7
7
|
return `${e.product_id}:${e.variant_id || "base"}`;
|
|
8
8
|
}
|
|
9
|
-
function o() {
|
|
9
|
+
function o(e, t) {
|
|
10
|
+
let n = new Map(e.map((e) => [a(e), e])), r = /* @__PURE__ */ new Set(), i = [];
|
|
11
|
+
for (let e of t) {
|
|
12
|
+
let t = a(e);
|
|
13
|
+
r.has(t) || (r.add(t), i.push(n.get(t) ?? e));
|
|
14
|
+
}
|
|
15
|
+
for (let [e, t] of n) r.has(e) || i.push(t);
|
|
16
|
+
return i;
|
|
17
|
+
}
|
|
18
|
+
function s() {
|
|
10
19
|
return {
|
|
11
20
|
stripe: 0,
|
|
12
21
|
freemius: 0
|
|
13
22
|
};
|
|
14
23
|
}
|
|
15
24
|
//#endregion
|
|
16
|
-
export { e as COUPON_INVALID_ERROR_KEY, t as COUPON_INVALID_ERROR_MESSAGE, n as COUPON_THROTTLED_ERROR_KEY, r as COUPON_THROTTLED_ERROR_MESSAGE,
|
|
25
|
+
export { e as COUPON_INVALID_ERROR_KEY, t as COUPON_INVALID_ERROR_MESSAGE, n as COUPON_THROTTLED_ERROR_KEY, r as COUPON_THROTTLED_ERROR_MESSAGE, s as emptyProviderDiscounts, a as getCartLineCouponKey, i as normalizeCouponCode, o as reconcileCouponContextItems };
|
package/lib/currency-sync.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=require("./currency.cjs");let t=require("@nextblock-cms/utils")
|
|
1
|
+
const e=require("./currency.cjs");let t=require("@nextblock-cms/utils");require("server-only");let n=require("@nextblock-cms/db/server");var r=`https://api.frankfurter.dev`,i=`id, code, symbol, exchange_rate, is_default, is_active, auto_update_exchange_rate, auto_sync_product_prices, exchange_rate_source, exchange_rate_updated_at, updated_at, rounding_mode, rounding_increment, rounding_charm_amount`;function a(e){if(!e.id)throw Error(`Currency ${(0,t.normalizeCurrencyCode)(e.code)} is missing its ID.`);return e.id}function o(){return process.env.FX_API_BASE_URL?.trim().replace(/\/+$/,``)||r}function s(e){try{let t=new URL(e),n=t.pathname.replace(/\/+$/,``);return`${t.host}${n&&n!==`/`?n:``}`}catch{return e}}function c(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.base==`string`&&typeof t.date==`string`&&typeof t.quote==`string`&&typeof t.rate==`number`&&Number.isFinite(t.rate)&&t.rate>0}function l(e){return Array.isArray(e)&&e.every(c)}async function u(){let e=(0,n.getServiceRoleSupabaseClient)(),{data:t,error:r}=await e.from(`currencies`).select(i).order(`code`,{ascending:!0});if(r)throw Error(r.message);return{supabase:e,currencies:t||[]}}async function d(e){let n=(0,t.normalizeCurrencyCode)(e.newDefaultCurrencyCode),r=Number(e.previousBaseRate);if(!Number.isFinite(r)||r<=0)throw Error(`Cannot rebase exchange rates without a valid previous base rate.`);let{supabase:i,currencies:o}=await u();if(!o.find(e=>(0,t.normalizeCurrencyCode)(e.code)===n))throw Error(`Currency ${n} was not found for rebasing.`);let s=new Date().toISOString(),c=[],l=o.map(e=>{let i=a(e),o=(0,t.normalizeCurrencyCode)(e.code),l=o===n?1:Number((e.exchange_rate/r).toFixed(10));return c.push(o),{...e,id:i,exchange_rate:l,auto_update_exchange_rate:o!==n&&e.auto_update_exchange_rate,exchange_rate_source:o===n?`store-default`:e.exchange_rate_source||`rebased-default`,exchange_rate_updated_at:s,updated_at:s}}),{error:d}=await i.from(`currencies`).upsert(l,{onConflict:`id`});if(d)throw Error(d.message);return{rebasedAt:s,newDefaultCurrencyCode:n,updatedCurrencies:c}}async function f(){let{supabase:n,currencies:r}=await u();if(!r.length)throw Error(`No currencies are configured yet.`);let i=r.map(t=>e.normalizeCurrencyRecord(t)),c=e.getDefaultCurrency(i),d=(0,t.normalizeCurrencyCode)(c.code),f=r.find(e=>(0,t.normalizeCurrencyCode)(e.code)===d);if(!f)throw Error(`Default currency ${d} could not be found for syncing.`);let p=r.filter(e=>(0,t.normalizeCurrencyCode)(e.code)!==d&&e.auto_update_exchange_rate!==!1),m=o(),h=s(m),g=new Date().toISOString(),{error:_}=await n.from(`currencies`).upsert({...f,id:a(f),exchange_rate:1,auto_update_exchange_rate:!1,exchange_rate_source:`store-default`,exchange_rate_updated_at:g,updated_at:g},{onConflict:`id`});if(_)throw Error(_.message);if(!p.length)return{baseCurrencyCode:d,fetchedAt:g,provider:h,providerUrl:m,skippedCurrencies:[],updatedCurrencies:[]};let v=new URL(`${m}/v2/rates`);v.searchParams.set(`base`,d),v.searchParams.set(`quotes`,[...new Set(p.map(e=>(0,t.normalizeCurrencyCode)(e.code)))].join(`,`));let y=await fetch(v.toString(),{cache:`no-store`,headers:{Accept:`application/json`}});if(!y.ok){let e=await y.text();throw Error(`FX provider request failed (${y.status}): ${e||`Unexpected response body.`}`)}let b=await y.json();if(!l(b))throw Error(`FX provider returned an unexpected response payload.`);let x=new Map;for(let e of b)x.set((0,t.normalizeCurrencyCode)(e.quote),e.rate);let S=[],C=[],w=[];for(let e of p){let n=(0,t.normalizeCurrencyCode)(e.code),r=x.get(n);if(!r||!Number.isFinite(r)||r<=0){C.push(n);continue}w.push({...e,id:a(e),exchange_rate:Number(r.toFixed(10)),exchange_rate_source:h,exchange_rate_updated_at:g,updated_at:g}),S.push(n)}if(w.length){let{error:e}=await n.from(`currencies`).upsert(w,{onConflict:`id`});if(e)throw Error(e.message)}return{baseCurrencyCode:d,fetchedAt:g,provider:h,providerUrl:m,skippedCurrencies:C,updatedCurrencies:S}}async function p(e){let r=(0,t.normalizeCurrencyCode)(e),{error:i}=await(0,n.getServiceRoleSupabaseClient)().rpc(`clear_currency_price_overrides`,{target_currency:r});if(i)throw Error(i.message);return{currencyCode:r}}exports.clearAutoSyncedCurrencyPriceOverrides=p,exports.rebaseStoreCurrencyExchangeRates=d,exports.syncStoreCurrencyRates=f;
|
package/lib/currency-sync.es.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getDefaultCurrency as e, normalizeCurrencyRecord as t } from "./currency.es.js";
|
|
2
2
|
import { normalizeCurrencyCode as n } from "@nextblock-cms/utils";
|
|
3
|
-
import { getServiceRoleSupabaseClient as r } from "@nextblock-cms/db/server";
|
|
4
3
|
import "server-only";
|
|
4
|
+
import { getServiceRoleSupabaseClient as r } from "@nextblock-cms/db/server";
|
|
5
5
|
//#region src/lib/currency-sync.ts
|
|
6
6
|
var i = "https://api.frankfurter.dev", a = "id, code, symbol, exchange_rate, is_default, is_active, auto_update_exchange_rate, auto_sync_product_prices, exchange_rate_source, exchange_rate_updated_at, updated_at, rounding_mode, rounding_increment, rounding_charm_amount";
|
|
7
7
|
function o(e) {
|