@nextblock-cms/ecom 0.22.2 → 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/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
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) {
|
package/lib/freemius-coupons.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=require("./payment-config.cjs"),
|
|
1
|
+
const e=require("./freemius-pricing.cjs"),t=require("./payment-config.cjs"),n=require("./providers/freemius.cjs");let r=require("@nextblock-cms/utils");function i(e){if(!e)return null;let t=new Date(e);return Number.isNaN(t.getTime())?e:t.toISOString().replace(`T`,` `).replace(/\.\d{3}Z$/,``)}function a(e){return e===`percent`?`percentage`:`dollar`}function o(e){return n.resolveFreemiusCheckoutCredentials(e).apiKey||n.readFreemiusEnvValue(`FREEMIUS_API_KEY`)}async function s(e){await t.hydrateFreemiusEnvFromDb();let n=o(e.productId);if(!n)throw Error(`Missing Freemius API bearer token for product ${e.productId}. Set FREEMIUS_API_KEY or FREEMIUS_CHECKOUT_PRODUCTS_JSON[${e.productId}].apiKey.`);let r=e.couponId?`/${e.couponId}`:``,i=await fetch(`https://api.freemius.com/v1/products/${e.productId}/coupons${r}.json`,{method:e.method,headers:{Authorization:`Bearer ${n}`,"Content-Type":`application/json`,Accept:`application/json`},body:e.method===`DELETE`?void 0:JSON.stringify(e.body||{})}),a=await i.text(),s=a?JSON.parse(a):null;if(!i.ok)throw Error(s?.message||s?.error?.message||`Freemius coupon request failed with ${i.status}`);return s}function c(e,t){return{code:e.code,discount:e.discount_type===`fixed`?(0,r.minorUnitAmountToMajor)(e.discount_amount,`USD`):e.discount_amount,discount_type:a(e.discount_type),plans:t.planIds.length>0?t.planIds.join(`,`):null,licenses:null,billing_cycles:null,start_date:i(e.starts_at),end_date:i(e.ends_at),redemptions_limit:e.redemption_limit??null,has_renewals_discount:!1,has_addons_discount:!1,is_one_per_user:!1,is_active:e.is_active,user_type:`all`}}async function l(e,t){if(t.provider_scope===`stripe`)return[];let{data:n,error:r}=await e.from(`coupon_products`).select(`product_id`).eq(`coupon_id`,t.id);if(r)throw Error(r.message);let i=(n||[]).map(e=>e.product_id),a=e.from(`products`).select(`id, freemius_product_id, freemius_plan_id`).eq(`payment_provider`,`freemius`).not(`freemius_product_id`,`is`,null);i.length>0&&(a=a.in(`id`,i));let{data:o,error:s}=await a;if(s)throw Error(s.message);let c=new Map;for(let e of o||[]){let t=String(e.freemius_product_id||``).trim();if(!t)continue;let n=c.get(t)??{freemiusProductId:t,productId:e.id,planIds:[]};i.length>0&&e.freemius_plan_id&&n.planIds.push(String(e.freemius_plan_id)),c.set(t,n)}return[...c.values()].map(e=>({...e,planIds:[...new Set(e.planIds)]}))}async function u(e,t){let{data:n}=await e.from(`coupon_freemius_mappings`).select(`sync_status, sync_error`).eq(`coupon_id`,t),r=n||[];if(r.length===0){await e.from(`coupons`).update({freemius_sync_status:`not_required`,freemius_sync_error:null}).eq(`id`,t);return}let i=r.find(e=>e.sync_status===`failed`);await e.from(`coupons`).update({freemius_sync_status:i?`failed`:`synced`,freemius_sync_error:i?.sync_error??null}).eq(`id`,t)}async function d(e){let{data:t,error:n}=await e.client.from(`coupons`).select(`id, code, name, internal_note, provider_scope, discount_type, discount_amount, is_active, starts_at, ends_at, redemption_limit`).eq(`id`,e.couponId).single();if(n||!t)return{success:!1,error:n?.message||`Coupon not found`};let r=t,i=await l(e.client,r);await e.client.from(`coupons`).update({freemius_sync_status:i.length>0?`pending`:`not_required`,freemius_sync_error:null}).eq(`id`,r.id);for(let t of i){let{data:n}=await e.client.from(`coupon_freemius_mappings`).select(`id, freemius_coupon_id`).eq(`coupon_id`,r.id).eq(`freemius_product_id`,t.freemiusProductId).maybeSingle(),i=c(r,t);try{let a=await s({productId:t.freemiusProductId,method:n?.freemius_coupon_id?`PUT`:`POST`,couponId:n?.freemius_coupon_id??null,body:i}),o=a?.coupon??a;await e.client.from(`coupon_freemius_mappings`).upsert({id:n?.id,coupon_id:r.id,product_id:t.productId,freemius_product_id:t.freemiusProductId,freemius_coupon_id:o?.id?String(o.id):n?.freemius_coupon_id??null,freemius_coupon_code:r.code,sync_status:`synced`,sync_error:null,remote_payload:a,last_synced_at:new Date().toISOString()},{onConflict:`coupon_id,freemius_product_id`})}catch(i){await e.client.from(`coupon_freemius_mappings`).upsert({id:n?.id,coupon_id:r.id,product_id:t.productId,freemius_product_id:t.freemiusProductId,freemius_coupon_id:n?.freemius_coupon_id??null,freemius_coupon_code:r.code,sync_status:`failed`,sync_error:i.message||`Freemius sync failed`,last_synced_at:new Date().toISOString()},{onConflict:`coupon_id,freemius_product_id`})}}return await u(e.client,r.id),{success:!0,targetCount:i.length}}function f(e){let t=(e.sku||``).toUpperCase().replace(/[^A-Z0-9]/g,``);return t?`SALE${t}`.slice(0,32):`SALE${e.productId.replace(/[^A-Za-z0-9]/g,``).toUpperCase()}`.slice(0,32)}async function p(t){let{data:n,error:r}=await t.client.from(`products`).select(`id, sku, price, sale_price, sale_start_at, sale_end_at, payment_provider, freemius_product_id, freemius_plan_id`).eq(`id`,t.productId).maybeSingle();if(r||!n)return{success:!1,error:r?.message||`Product not found`};let i=n,a=String(i.freemius_product_id||``).trim();if(i.payment_provider!==`freemius`||!a)return{success:!0,skipped:!0,reason:`not_a_freemius_product`};let o=e.computeFreemiusSaleDiscountPercent(i.price,i.sale_price),l=o!==null,{data:u}=await t.client.from(`product_freemius_sale_coupons`).select(`id, freemius_coupon_id, freemius_coupon_code, discount_percent, is_active`).eq(`product_id`,i.id).maybeSingle();if(!l&&!u?.freemius_coupon_id)return u?.id&&await t.client.from(`product_freemius_sale_coupons`).update({is_active:!1,updated_at:new Date().toISOString()}).eq(`id`,u.id),{success:!0,active:!1};let d=u?.freemius_coupon_code||f({sku:i.sku,productId:i.id}),p=i.freemius_plan_id?[String(i.freemius_plan_id)]:[],m=o??u?.discount_percent??1,h=c({id:i.id,code:d,name:`Scheduled sale ${i.sku||i.id}`,provider_scope:`freemius`,discount_type:`percent`,discount_amount:m,is_active:l,starts_at:i.sale_start_at,ends_at:i.sale_end_at,redemption_limit:null},{freemiusProductId:a,productId:i.id,planIds:p});try{let e=await s({productId:a,method:u?.freemius_coupon_id?`PUT`:`POST`,couponId:u?.freemius_coupon_id??null,body:h}),n=e?.coupon??e;return await t.client.from(`product_freemius_sale_coupons`).upsert({id:u?.id,product_id:i.id,freemius_product_id:a,freemius_plan_id:i.freemius_plan_id?String(i.freemius_plan_id):null,freemius_coupon_id:n?.id?String(n.id):u?.freemius_coupon_id??null,freemius_coupon_code:d,discount_percent:o,starts_at:i.sale_start_at,ends_at:i.sale_end_at,is_active:l,sync_status:`synced`,sync_error:null,remote_payload:e,last_synced_at:new Date().toISOString()},{onConflict:`product_id`}),{success:!0,active:l,code:d}}catch(e){return await t.client.from(`product_freemius_sale_coupons`).upsert({id:u?.id,product_id:i.id,freemius_product_id:a,freemius_plan_id:i.freemius_plan_id?String(i.freemius_plan_id):null,freemius_coupon_id:u?.freemius_coupon_id??null,freemius_coupon_code:d,discount_percent:o,starts_at:i.sale_start_at,ends_at:i.sale_end_at,is_active:l,sync_status:`failed`,sync_error:e.message||`Freemius sale coupon sync failed`,last_synced_at:new Date().toISOString()},{onConflict:`product_id`}),{success:!1,error:e.message||`Freemius sale coupon sync failed`}}}async function m(e){let{data:t}=await e.client.from(`coupon_freemius_mappings`).select(`id, freemius_product_id, freemius_coupon_id`).eq(`coupon_id`,e.couponId).not(`freemius_coupon_id`,`is`,null);for(let n of t||[])try{await s({productId:n.freemius_product_id,couponId:n.freemius_coupon_id,method:`DELETE`}),await e.client.from(`coupon_freemius_mappings`).update({sync_status:`deleted`,sync_error:null,updated_at:new Date().toISOString()}).eq(`id`,n.id)}catch(t){await e.client.from(`coupon_freemius_mappings`).update({sync_status:`failed`,sync_error:t.message||`Freemius delete failed`,updated_at:new Date().toISOString()}).eq(`id`,n.id)}}exports.deleteCouponFromFreemius=m,exports.syncCouponToFreemius=d,exports.syncProductSaleCouponToFreemius=p;
|