@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.
Files changed (87) hide show
  1. package/index.cjs +1 -1
  2. package/index.es.js +31 -31
  3. package/lib/components/Checkout.cjs +1 -1
  4. package/lib/components/Checkout.es.js +1 -0
  5. package/lib/components/SubscriptionSelector.cjs +1 -1
  6. package/lib/components/SubscriptionSelector.es.js +91 -92
  7. package/lib/coupon-server.cjs +1 -1
  8. package/lib/coupon-server.es.js +78 -61
  9. package/lib/coupons.cjs +1 -1
  10. package/lib/coupons.d.ts +9 -0
  11. package/lib/coupons.es.js +11 -2
  12. package/lib/currency-sync.cjs +1 -1
  13. package/lib/currency-sync.es.js +1 -1
  14. package/lib/freemius-coupons.cjs +1 -1
  15. package/lib/freemius-coupons.es.js +88 -92
  16. package/lib/freemius-license-claim.cjs +1 -1
  17. package/lib/freemius-license-claim.d.ts +13 -0
  18. package/lib/freemius-license-claim.es.js +44 -29
  19. package/lib/freemius-license.cjs +1 -1
  20. package/lib/freemius-license.es.js +19 -17
  21. package/lib/freemius-order-sync.cjs +1 -1
  22. package/lib/freemius-order-sync.d.ts +44 -0
  23. package/lib/freemius-order-sync.es.js +164 -77
  24. package/lib/freemius-pricing-server.cjs +1 -0
  25. package/lib/freemius-pricing-server.d.ts +65 -0
  26. package/lib/freemius-pricing-server.es.js +82 -0
  27. package/lib/freemius-pricing.cjs +1 -0
  28. package/lib/freemius-pricing.d.ts +53 -0
  29. package/lib/freemius-pricing.es.js +51 -0
  30. package/lib/invoice-server.cjs +1 -1
  31. package/lib/invoice-server.es.js +1 -0
  32. package/lib/pages/cms/coupons/CouponsPage.cjs +1 -1
  33. package/lib/pages/cms/coupons/CouponsPage.es.js +1 -0
  34. package/lib/pages/cms/coupons/EditCouponPage.cjs +1 -1
  35. package/lib/pages/cms/coupons/EditCouponPage.es.js +1 -0
  36. package/lib/pages/cms/coupons/actions.cjs +1 -1
  37. package/lib/pages/cms/coupons/actions.es.js +1 -0
  38. package/lib/pages/cms/orders/actions.cjs +1 -1
  39. package/lib/pages/cms/orders/actions.es.js +1 -0
  40. package/lib/pages/cms/orders/export-actions.cjs +1 -1
  41. package/lib/pages/cms/orders/export-actions.es.js +1 -0
  42. package/lib/pages/cms/orders/server-actions.cjs +1 -1
  43. package/lib/pages/cms/orders/server-actions.es.js +1 -0
  44. package/lib/pages/cms/products/ProductsPage.cjs +1 -1
  45. package/lib/pages/cms/products/ProductsPage.es.js +1 -0
  46. package/lib/pages/cms/products/[id]/edit/EditProductPage.cjs +1 -1
  47. package/lib/pages/cms/products/[id]/edit/EditProductPage.es.js +1 -0
  48. package/lib/pages/cms/products/actions.cjs +3 -3
  49. package/lib/pages/cms/products/actions.d.ts +11 -5
  50. package/lib/pages/cms/products/actions.es.js +45 -12
  51. package/lib/pages/cms/products/inventory/actions.cjs +1 -1
  52. package/lib/pages/cms/products/inventory/actions.es.js +1 -0
  53. package/lib/pages/cms/products/new/NewProductPage.cjs +1 -1
  54. package/lib/pages/cms/products/new/NewProductPage.es.js +1 -0
  55. package/lib/pages/cms/products/server-actions.cjs +1 -1
  56. package/lib/pages/cms/products/server-actions.es.js +1 -0
  57. package/lib/pages/cms/shipping/ShippingPage.cjs +1 -1
  58. package/lib/pages/cms/shipping/ShippingPage.es.js +1 -0
  59. package/lib/pages/cms/shipping/server-actions.cjs +1 -1
  60. package/lib/pages/cms/shipping/server-actions.es.js +1 -0
  61. package/lib/pages/cms/taxes/TaxesPage.cjs +1 -1
  62. package/lib/pages/cms/taxes/TaxesPage.es.js +1 -0
  63. package/lib/payment-config.cjs +1 -1
  64. package/lib/payment-config.es.js +1 -0
  65. package/lib/providers/freemius.cjs +1 -1
  66. package/lib/providers/freemius.es.js +200 -187
  67. package/lib/providers/stripe.cjs +1 -1
  68. package/lib/providers/stripe.es.js +151 -150
  69. package/lib/server-actions/coupon-actions.cjs +1 -1
  70. package/lib/server-actions/coupon-actions.es.js +1 -0
  71. package/lib/server-actions/customer-actions.d.ts +6 -1
  72. package/lib/server-actions/require-ecom-role.cjs +1 -1
  73. package/lib/server-actions/require-ecom-role.es.js +22 -10
  74. package/lib/server-actions/tax-actions.cjs +1 -1
  75. package/lib/server-actions/tax-actions.es.js +1 -0
  76. package/lib/shared-inventory.cjs +1 -1
  77. package/lib/shared-inventory.es.js +1 -1
  78. package/lib/stripe/checkout.cjs +1 -1
  79. package/lib/stripe/checkout.es.js +1 -0
  80. package/lib/stripe/order-sync.cjs +1 -1
  81. package/lib/stripe/order-sync.es.js +1 -0
  82. package/lib/stripe/webhooks.cjs +1 -1
  83. package/lib/stripe/webhooks.d.ts +10 -0
  84. package/lib/stripe/webhooks.es.js +23 -10
  85. package/package.json +4 -4
  86. package/server.cjs +1 -1
  87. package/server.es.js +31 -31
@@ -1 +1 @@
1
- const e=require("./currency.cjs"),t=require("./coupons.cjs");require("server-only");function n(e){if(!e)return null;let t=new Date(e);return Number.isNaN(t.getTime())?null:t}function r(e,t){return e===`all`||e===t}function i(e){return e===`stripe`||e===`freemius`?e:null}async function a(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 o(n){let{client:r,items:o}=n,s=await a(r);if(s.length===0)throw Error(`No active currencies are configured.`);let c=s.find(e=>e.code===(n.currencyCode||``).toUpperCase())??e.getDefaultCurrency(s),l=[...new Set(o.map(e=>e.product_id).filter(Boolean))],u=[...new Set(o.map(e=>e.variant_id).filter(e=>!!e))];if(l.length===0)return{lines:[],currencies:s,selectedCurrency:c};let[{data:d,error:f},p]=await Promise.all([r.from(`products`).select(`id, title, price, prices, sale_price, sale_prices, sale_start_at, sale_end_at, scheduled_price, scheduled_prices, scheduled_price_at, product_type, payment_provider, freemius_product_id, freemius_plan_id`).in(`id`,l),u.length?r.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`,u):Promise.resolve({data:[],error:null})]);if(f)throw Error(f.message);if(p.error)throw Error(p.error.message);let m=d||[],h=p.data||[],g=new Map(m.map(e=>[e.id,e])),_=new Map(h.map(e=>[e.id,e])),v=[];for(let n of o){let r=g.get(n.product_id);if(!r)continue;let a=i(r.payment_provider)??(r.product_type===`digital`?`freemius`:`stripe`),o=n.variant_id?_.get(n.variant_id):null,l=o&&o.product_id===r.id?o:r,u=e.resolveEffectivePriceForCurrency({prices:e.normalizePriceMap(l.prices),salePrices:e.normalizeSalePriceMap(l.sale_prices),fallbackPrice:l.price,fallbackSalePrice:l.sale_price,saleStartAt:l.sale_start_at,saleEndAt:l.sale_end_at,scheduledPrice:l.scheduled_price,scheduledPrices:e.normalizePriceMap(l.scheduled_prices),scheduledPriceAt:l.scheduled_price_at,currencyCode:c.code,currencies:s}),d=a===`freemius`?1:Math.max(1,Number(n.quantity)||1),f=Math.max(0,u.sale_price??u.price);v.push({key:t.getCartLineCouponKey(n),product_id:r.id,variant_id:o?.id??null,title:r.title,quantity:d,provider:a,subtotal:f*d,freemius_product_id:r.freemius_product_id??null,freemius_plan_id:r.freemius_plan_id??null})}return{lines:v,currencies:s,selectedCurrency:c}}async function s(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 c(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 l={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 u(e,n){return{success:!1,...n?l[e]:{error:t.COUPON_INVALID_ERROR_MESSAGE,errorKey:t.COUPON_INVALID_ERROR_KEY}}}async function d(e){let i=t.normalizeCouponCode(e.code),a=e.detailedErrors===!0;if(!i)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:l,error:d}=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`).ilike(`code`,i).maybeSingle();if(d)throw Error(d.message);if(!l)return u(`not_found`,a);let f=l,p=new Date,m=n(f.starts_at),h=n(f.ends_at);if(!f.is_active)return u(`inactive`,a);if(m&&m>p)return u(`not_started`,a);if(h&&h<=p)return u(`expired`,a);if(f.redemption_limit!==null&&(f.redemptions_count??0)>=f.redemption_limit)return u(`limit_reached`,a);let[{lines:g},_]=await Promise.all([o({client:e.client,items:e.items,currencyCode:e.currencyCode}),s(e.client,f.id)]),v=_.size>0,y=g.filter(e=>e.subtotal>0&&r(f.provider_scope,e.provider)&&(!v||_.has(e.product_id)));if(y.length===0)return u(`not_applicable`,a);let b=f.discount_type===`fixed`?c(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 f(e){let t=new Map;for(let n of e?.lineDiscounts??[])t.set(n.key,n.discount);return t}async function p(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 m=30,h=12/60,g=5e3,_=`unknown`;function v(e){let t=e?.capacity??m,n=e?.refillPerSecond??h,r=e?.maxBuckets??g,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 y=v();function b(e,t){return y.consume(e,t)}function x(e){let[t]=(e??``).split(`,`);return(t?.trim()??``)||null}function S(e){return x(e.get(`x-vercel-forwarded-for`))??x(e.get(`x-real-ip`))??x(e.get(`x-forwarded-for`))??`unknown`}exports.COUPON_VALIDATION_UNKNOWN_CLIENT_KEY=_,exports.consumeCouponValidationToken=b,exports.createCouponValidationThrottle=v,exports.getCouponQuote=d,exports.getQuoteLineDiscountMap=f,exports.recordCouponRedemption=p,exports.resolveCouponValidationThrottleKey=S;
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;
@@ -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 u(e) {
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 d(e, t) {
11
+ function p(e, t) {
11
12
  return e === "all" || e === t;
12
13
  }
13
- function f(e) {
14
+ function m(e) {
14
15
  return e === "stripe" || e === "freemius" ? e : null;
15
16
  }
16
- async function p(e) {
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 m(t) {
22
- let { client: a, items: o } = t, s = await p(a);
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), u = [...new Set(o.map((e) => e.product_id).filter(Boolean))], d = [...new Set(o.map((e) => e.variant_id).filter((e) => !!e))];
25
- if (u.length === 0) return {
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: m, error: h }, g] = await Promise.all([a.from("products").select("id, title, price, prices, sale_price, sale_prices, sale_start_at, sale_end_at, scheduled_price, scheduled_prices, scheduled_price_at, product_type, payment_provider, freemius_product_id, freemius_plan_id").in("id", u), d.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", d) : Promise.resolve({
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 (h) throw Error(h.message);
35
- if (g.error) throw Error(g.error.message);
36
- let _ = m || [], v = g.data || [], y = new Map(_.map((e) => [e.id, e])), b = new Map(v.map((e) => [e.id, e])), x = [];
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 = y.get(e.product_id);
39
+ let t = x.get(e.product_id);
39
40
  if (!t) continue;
40
- let a = f(t.payment_provider) ?? (t.product_type === "digital" ? "freemius" : "stripe"), o = e.variant_id ? b.get(e.variant_id) : null, u = o && o.product_id === t.id ? o : t, d = i({
41
- prices: n(u.prices),
42
- salePrices: r(u.sale_prices),
43
- fallbackPrice: u.price,
44
- fallbackSalePrice: u.sale_price,
45
- saleStartAt: u.sale_start_at,
46
- saleEndAt: u.sale_end_at,
47
- scheduledPrice: u.scheduled_price,
48
- scheduledPrices: n(u.scheduled_prices),
49
- scheduledPriceAt: u.scheduled_price_at,
50
- currencyCode: l.code,
51
- currencies: s
52
- }), p = a === "freemius" ? 1 : Math.max(1, Number(e.quantity) || 1), m = Math.max(0, d.sale_price ?? d.price);
53
- x.push({
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: o?.id ?? null,
73
+ variant_id: u?.id ?? null,
57
74
  title: t.title,
58
75
  quantity: p,
59
- provider: a,
60
- subtotal: m * p,
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: x,
83
+ lines: C,
67
84
  currencies: s,
68
85
  selectedCurrency: l
69
86
  };
70
87
  }
71
- async function h(e, t) {
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 g(e, t) {
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 v(e, t) {
128
+ function b(e, t) {
112
129
  return {
113
130
  success: !1,
114
- ...t ? _[e] : {
131
+ ...t ? y[e] : {
115
132
  error: o,
116
133
  errorKey: a
117
134
  }
118
135
  };
119
136
  }
120
- async function y(e) {
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").ilike("code", t).maybeSingle();
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 v("not_found", n);
135
- let a = r, o = /* @__PURE__ */ new Date(), c = u(a.starts_at), f = u(a.ends_at);
136
- if (!a.is_active) return v("inactive", n);
137
- if (c && c > o) return v("not_started", n);
138
- if (f && f <= o) return v("expired", n);
139
- if (a.redemption_limit !== null && (a.redemptions_count ?? 0) >= a.redemption_limit) return v("limit_reached", n);
140
- let [{ lines: p }, _] = await Promise.all([m({
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
- }), h(e.client, a.id)]), y = _.size > 0, b = p.filter((e) => e.subtotal > 0 && d(a.provider_scope, e.provider) && (!y || _.has(e.product_id)));
145
- if (b.length === 0) return v("not_applicable", n);
146
- let x = a.discount_type === "fixed" ? g(b, a.discount_amount) : null, S = s(), C = b.map((e) => {
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: b.reduce((e, t) => e + t.subtotal, 0),
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 b(e) {
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 x(e) {
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 S = 30, C = 12 / 60, w = 5e3, T = "unknown";
211
- function E(e) {
212
- let t = e?.capacity ?? S, n = e?.refillPerSecond ?? C, r = e?.maxBuckets ?? w, i = t / n * 1e3, a = /* @__PURE__ */ new Map();
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 D = E();
239
- function O(e, t) {
240
- return D.consume(e, t);
255
+ var k = O();
256
+ function A(e, t) {
257
+ return k.consume(e, t);
241
258
  }
242
- function k(e) {
259
+ function j(e) {
243
260
  let [t] = (e ?? "").split(",");
244
261
  return (t?.trim() ?? "") || null;
245
262
  }
246
- function A(e) {
247
- return k(e.get("x-vercel-forwarded-for")) ?? k(e.get("x-real-ip")) ?? k(e.get("x-forwarded-for")) ?? "unknown";
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 { T as COUPON_VALIDATION_UNKNOWN_CLIENT_KEY, O as consumeCouponValidationToken, E as createCouponValidationThrottle, y as getCouponQuote, b as getQuoteLineDiscountMap, x as recordCouponRedemption, A as resolveCouponValidationThrottleKey };
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=o,exports.getCartLineCouponKey=a,exports.normalizeCouponCode=i;
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, o as emptyProviderDiscounts, a as getCartLineCouponKey, i as normalizeCouponCode };
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 };
@@ -1 +1 @@
1
- const e=require("./currency.cjs");let t=require("@nextblock-cms/utils"),n=require("@nextblock-cms/db/server");require("server-only");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;
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;
@@ -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) {
@@ -1 +1 @@
1
- const e=require("./payment-config.cjs"),t=require("./providers/freemius.cjs");let n=require("@nextblock-cms/utils");function r(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 i(e){return e===`percent`?`percentage`:`dollar`}function a(e){return t.resolveFreemiusCheckoutCredentials(e).apiKey||t.readFreemiusEnvValue(`FREEMIUS_API_KEY`)}async function o(t){await e.hydrateFreemiusEnvFromDb();let n=a(t.productId);if(!n)throw Error(`Missing Freemius API bearer token for product ${t.productId}. Set FREEMIUS_API_KEY or FREEMIUS_CHECKOUT_PRODUCTS_JSON[${t.productId}].apiKey.`);let r=t.couponId?`/${t.couponId}`:``,i=await fetch(`https://api.freemius.com/v1/products/${t.productId}/coupons${r}.json`,{method:t.method,headers:{Authorization:`Bearer ${n}`,"Content-Type":`application/json`,Accept:`application/json`},body:t.method===`DELETE`?void 0:JSON.stringify(t.body||{})}),o=await i.text(),s=o?JSON.parse(o):null;if(!i.ok)throw Error(s?.message||s?.error?.message||`Freemius coupon request failed with ${i.status}`);return s}function s(e,t){return{code:e.code,discount:e.discount_type===`fixed`?(0,n.minorUnitAmountToMajor)(e.discount_amount,`USD`):e.discount_amount,discount_type:i(e.discount_type),plans:t.planIds.length>0?t.planIds.join(`,`):null,licenses:null,billing_cycles:null,start_date:r(e.starts_at),end_date:r(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 c(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 l(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 u(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 c(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=s(r,t);try{let a=await o({productId:t.freemiusProductId,method:n?.freemius_coupon_id?`PUT`:`POST`,couponId:n?.freemius_coupon_id??null,body:i}),s=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:s?.id?String(s.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 l(e.client,r.id),{success:!0,targetCount:i.length}}function d(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)}function f(e,t){if(typeof e!=`number`||e<=0||typeof t!=`number`||t<0||t>=e)return null;let n=Math.round((1-t/e)*100);return n<=0||n>100?null:n}async function p(e){let{data:t,error:n}=await e.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`,e.productId).maybeSingle();if(n||!t)return{success:!1,error:n?.message||`Product not found`};let r=t,i=String(r.freemius_product_id||``).trim();if(r.payment_provider!==`freemius`||!i)return{success:!0,skipped:!0,reason:`not_a_freemius_product`};let a=f(r.price,r.sale_price),c=a!==null,{data:l}=await e.client.from(`product_freemius_sale_coupons`).select(`id, freemius_coupon_id, freemius_coupon_code, discount_percent, is_active`).eq(`product_id`,r.id).maybeSingle();if(!c&&!l?.freemius_coupon_id)return l?.id&&await e.client.from(`product_freemius_sale_coupons`).update({is_active:!1,updated_at:new Date().toISOString()}).eq(`id`,l.id),{success:!0,active:!1};let u=l?.freemius_coupon_code||d({sku:r.sku,productId:r.id}),p=r.freemius_plan_id?[String(r.freemius_plan_id)]:[],m=a??l?.discount_percent??1,h=s({id:r.id,code:u,name:`Scheduled sale ${r.sku||r.id}`,provider_scope:`freemius`,discount_type:`percent`,discount_amount:m,is_active:c,starts_at:r.sale_start_at,ends_at:r.sale_end_at,redemption_limit:null},{freemiusProductId:i,productId:r.id,planIds:p});try{let t=await o({productId:i,method:l?.freemius_coupon_id?`PUT`:`POST`,couponId:l?.freemius_coupon_id??null,body:h}),n=t?.coupon??t;return await e.client.from(`product_freemius_sale_coupons`).upsert({id:l?.id,product_id:r.id,freemius_product_id:i,freemius_plan_id:r.freemius_plan_id?String(r.freemius_plan_id):null,freemius_coupon_id:n?.id?String(n.id):l?.freemius_coupon_id??null,freemius_coupon_code:u,discount_percent:a,starts_at:r.sale_start_at,ends_at:r.sale_end_at,is_active:c,sync_status:`synced`,sync_error:null,remote_payload:t,last_synced_at:new Date().toISOString()},{onConflict:`product_id`}),{success:!0,active:c,code:u}}catch(t){return await e.client.from(`product_freemius_sale_coupons`).upsert({id:l?.id,product_id:r.id,freemius_product_id:i,freemius_plan_id:r.freemius_plan_id?String(r.freemius_plan_id):null,freemius_coupon_id:l?.freemius_coupon_id??null,freemius_coupon_code:u,discount_percent:a,starts_at:r.sale_start_at,ends_at:r.sale_end_at,is_active:c,sync_status:`failed`,sync_error:t.message||`Freemius sale coupon sync failed`,last_synced_at:new Date().toISOString()},{onConflict:`product_id`}),{success:!1,error:t.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 o({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=u,exports.syncProductSaleCouponToFreemius=p;
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;