@stacksjs/commerce 0.72.37 → 0.72.39

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 (2) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ var LZ=Object.defineProperty;var NZ=($)=>$;function HZ($,J){this[$]=NZ.bind(null
17
17
  SET inventory_count = inventory_count + ${Z(1)},
18
18
  updated_at = ${Z(2)}
19
19
  WHERE id = ${Z(3)}
20
- AND inventory_count + ${Z(4)} >= 0`,parameters:[G,X,J,G]}}async function B6($,J,G,X){let Z=nZ(uZ.DB_CONNECTION||"sqlite",J,G,X),Y=await $.unsafe(Z.query,Z.parameters),K=typeof Y?.execute==="function"?await Y.execute():Y;return L(K)}var O6=!1;function A6($){let J=$,G=J?.message??"";return J?.code==="42P01"||G.includes("no such table")||G.includes("doesn't exist")||/relation "[^"]*" does not exist/i.test(G)}async function w$($){try{let J=await k$.selectFrom("order_idempotency").where("idempotency_key","=",$).selectAll().executeTakeFirst();if(!J)return null;return await k$.selectFrom("orders").where("id","=",J.order_id).selectAll().executeTakeFirst()??null}catch(J){if(A6(J)){if(!O6)O6=!0,console.warn("[commerce/orders] order_idempotency table missing - idempotency keys are accepted but NOT enforced. Run migrations to enable dedup.");return null}throw J}}async function U6($){if($.idempotencyKey){let J=await w$($.idempotencyKey);if(J)return{ok:!0,order:J}}try{let J=await k$.transaction(async(G)=>{let X=iZ(new Date),Z=M6(),Y={...$.order,status:$.order.status||"PENDING",uuid:Z,created_at:X,updated_at:X};await G.insertInto("orders").values(Y).executeTakeFirst();let K=await G.selectFrom("orders").where("uuid","=",Z).selectAll().executeTakeFirst(),W=Number(K?.id);if(!Number.isSafeInteger(W)||W<=0)throw Object.assign(Error("order insert could not be resolved by uuid"),{__placeFail:!0,failedAt:"order",reason:"unknown"});let Q;if($.items&&$.items.length>0){let H=$.items.map((z)=>{if(!Number.isInteger(z.productId)||z.productId<=0)throw Object.assign(Error("order item product id must be a positive integer"),{__placeFail:!0,failedAt:"items",reason:"unknown"});if(!Number.isInteger(z.quantity)||z.quantity<=0)throw Object.assign(Error("order item quantity must be a positive integer"),{__placeFail:!0,failedAt:"items",reason:"unknown"});if(!Number.isFinite(z.price)||z.price<0)throw Object.assign(Error("order item price must be a non-negative finite number"),{__placeFail:!0,failedAt:"items",reason:"unknown"});return{order_id:W,product_id:z.productId,quantity:z.quantity,price:z.price,special_instructions:z.specialInstructions||null,created_at:X,updated_at:X}});await G.insertInto("order_items").values(H).execute(),Q=await G.selectFrom("order_items").where("order_id","=",W).selectAll().execute()}let F;if($.payment)try{let H=M6();if(await G.insertInto("payments").values({...$.payment,order_id:W,uuid:H,created_at:X,updated_at:X}).executeTakeFirst(),F=await G.selectFrom("payments").where("uuid","=",H).selectAll().executeTakeFirst(),!F)throw Object.assign(Error("payment insert could not be resolved by uuid"),{__placeFail:!0,failedAt:"payment",reason:"unknown"})}catch(H){if(_6(H))throw Object.assign(Error("duplicate payment transaction_id"),{__placeFail:!0,failedAt:"payment",reason:"duplicate-payment"});throw Object.assign(H,{__placeFail:!0,failedAt:"payment",reason:"unknown"})}if($.inventory&&$.inventory.length>0)for(let H=0;H<$.inventory.length;H++){let{id:z,delta:x}=$.inventory[H];if(!Number.isFinite(x)||x===0)throw Object.assign(Error("inventory delta must be a non-zero finite number"),{__placeFail:!0,failedAt:"inventory",reason:"unknown"});if(!await B6(G,z,x,X))throw Object.assign(Error(`inventory adjust failed for product ${z}`),{__placeFail:!0,failedAt:"inventory",reason:"out-of-stock"})}if($.idempotencyKey)try{await G.insertInto("order_idempotency").values({idempotency_key:$.idempotencyKey,order_id:W,created_at:X}).execute()}catch(H){if(_6(H))throw Object.assign(Error("idempotency-key collision"),{__placeFail:!0,failedAt:"idempotency",reason:"duplicate-idempotency-key"});if(!A6(H))throw H}let N=await G.selectFrom("orders").where("id","=",W).selectAll().executeTakeFirst();if(!N)throw Object.assign(Error("order disappeared mid-transaction"),{__placeFail:!0,failedAt:"order",reason:"unknown"});return{order:N,items:Q,payment:F}});if(s0(J.order),J.payment)Y0(J.order,J.payment);return{ok:!0,order:J.order,items:J.items,payment:J.payment}}catch(J){if(J?.__placeFail){if(J.reason==="duplicate-idempotency-key"&&$.idempotencyKey){let G=await w$($.idempotencyKey);if(G)return{ok:!0,order:G}}return{ok:!1,reason:J.reason,failedAt:J.failedAt,error:J}}throw J}}R0();import{db as O1}from"@stacksjs/database";import{formatDate as A1}from"@stacksjs/orm";R0();async function P6($,J){if(!await r($))return;try{return await O1.updateTable("orders").set({...J,updated_at:A1(new Date)}).where("id","=",$).execute(),await r($)}catch(X){if(X instanceof Error)throw TypeError(`Failed to update order: ${X.message}`);throw X}}async function S6($,J){let G=await r($);if(!G)throw Error(`Order with ID ${$} not found`);let X=G.status??"PENDING";if(!i0(X,J))throw Error(`[commerce/orders] illegal status transition ${X} \u2192 ${J}`);try{await O1.updateTable("orders").set({status:J,updated_at:A1(new Date)}).where("id","=",$).execute();let Z=await r($);if(Z)o0(J,Z);return Z}catch(Z){if(Z instanceof Error)throw TypeError(`Failed to update order status: ${Z.message}`);throw Z}}async function R6($,J,G){let X=await r($);if(!X)throw Error(`Order with ID ${$} not found`);let Z={updated_at:A1(new Date)};if(J!==void 0)Z.delivery_address=J;if(G!==void 0)Z.estimated_delivery_time=G;if(Object.keys(Z).length===1)return X;try{return await O1.updateTable("orders").set(Z).where("id","=",$).execute(),await r($)}catch(Y){if(Y instanceof Error)throw TypeError(`Failed to update delivery information: ${Y.message}`);throw Y}}R0();import{db as T0}from"@stacksjs/database";import{formatDate as f$}from"@stacksjs/orm";async function U1($){let J=await T0.selectFrom("payments").where("transaction_id","=",$).selectAll().executeTakeFirst();if(!J)return null;return await T0.selectFrom("orders").where("id","=",J.order_id).selectAll().executeTakeFirst()??null}var T6=!1;function sZ($){let J=$,G=J?.message??"";return J?.code==="42P01"||G.includes("no such table")||G.includes("doesn't exist")||/relation "[^"]*" does not exist/i.test(G)}async function P1($,J){try{return await J.insertInto("stripe_webhook_events").values({event_id:$,processed_at:f$(new Date)}).execute(),!0}catch(G){if(G?.message?.includes("UNIQUE constraint")||G?.message?.includes("Duplicate entry"))return!1;if(sZ(G)){if(!T6)T6=!0,console.warn("[commerce/webhook] stripe_webhook_events table missing \u2014 Stripe retries may be processed multiple times. Run migrations to enable dedup.");return!0}throw G}}async function S1($){let G=$.data.object?.id;if(!G)return;await T0.transaction(async(Z)=>{if(!await P1($.id,Z))return;let K=await Z.selectFrom("payments").where("transaction_id","=",G).selectAll().executeTakeFirst();if(!K)return;let W=f$(new Date);await Z.updateTable("payments").set({status:"succeeded",updated_at:W}).where("id","=",K.id).execute();let Q=await Z.selectFrom("orders").where("id","=",K.order_id).selectAll().executeTakeFirst();if(Q&&Q.status==="PENDING")await Z.updateTable("orders").set({status:"PROCESSING",updated_at:W}).where("id","=",Q.id).execute()});let X=await U1(G);if(X)await Y0(X)}async function R1($){let J=$.data.object,G=J?.id;if(!G)return;let Z=J?.last_payment_error?.message??"payment failed";await T0.transaction(async(K)=>{if(!await P1($.id,K))return;let Q=await K.selectFrom("payments").where("transaction_id","=",G).selectAll().executeTakeFirst();if(!Q)return;let F=f$(new Date);await K.updateTable("payments").set({status:"failed",failure_reason:Z,updated_at:F}).where("id","=",Q.id).execute();let N=await K.selectFrom("orders").where("id","=",Q.order_id).selectAll().executeTakeFirst();if(N&&N.status==="PENDING")await K.updateTable("orders").set({status:"CANCELLED",updated_at:F}).where("id","=",N.id).execute()});let Y=await U1(G);if(Y)await P0(Y,Z)}async function T1($){let J=$.data.object,G=J?.payment_intent;if(!G)return;let X=J?.amount_refunded??0;await T0.transaction(async(Y)=>{if(!await P1($.id,Y))return;let W=await Y.selectFrom("payments").where("transaction_id","=",G).selectAll().executeTakeFirst();if(!W)return;let Q=f$(new Date);await Y.updateTable("payments").set({status:"refunded",refund_amount:X,updated_at:Q}).where("id","=",W.id).execute();let F=await Y.selectFrom("orders").where("id","=",W.order_id).selectAll().executeTakeFirst();if(F&&F.status!=="REFUNDED")await Y.updateTable("orders").set({status:"REFUNDED",updated_at:Q}).where("id","=",F.id).execute()});let Z=await U1(G);if(Z)await S0(Z,X)}async function E6(){let $=await import("@stacksjs/payments").catch(()=>null);if(!$)return console.warn("[commerce/webhook] @stacksjs/payments not installed \u2014 Stripe webhook handlers NOT registered"),()=>{};let{onPaymentIntent:J,onCharge:G}=$;if(typeof J!=="function"||typeof G!=="function")return console.warn("[commerce/webhook] @stacksjs/payments shape changed \u2014 handlers NOT registered"),()=>{};return J({succeeded:S1,failed:R1}),G({refunded:T1}),()=>{}}import{db as j6}from"@stacksjs/database";async function oZ($){if($.length===0)return new Map;let J=await j6.selectFrom("products").where("id","in",$).select(["id","price"]).execute(),G=new Map;for(let X of J){let Z=Number(X.price);if(Number.isFinite(Z))G.set(X.id,Math.round(Z))}return G}async function dZ($){try{let J=await j6.selectFrom("tax_rates").where("id","=",$).select(["rate"]).executeTakeFirst(),G=Number(J?.rate??0);return Number.isFinite(G)?G:0}catch{return 0}}async function q6($){let J=$.items.map((U)=>U.productId),G=await oZ(J),X=$.taxRateId!=null?await dZ($.taxRateId):0,Z=0,Y=[];for(let U of $.items){let O=U.unitPriceCents??G.get(U.productId)??0,P=Math.round(O*U.quantity);Z+=P,Y.push({productId:U.productId,quantity:U.quantity,unitPriceCents:O,lineCents:P})}let K=Math.max(0,$.discountCents??0),W=Math.max(0,Z-K),Q=Math.round(W*X),F=Math.max(0,$.shippingCents??0),N=Z-K+Q+F,H=$.maxDriftCents??5,z=!1,x=0;if($.clientTotalCents!=null)x=Math.abs(N-$.clientTotalCents),z=x>H;return{subtotalCents:Z,discountCents:K,taxCents:Q,shippingCents:F,totalCents:N,resolvedItems:Y,priceChanged:z,diffCents:x}}import{db as C$}from"@stacksjs/database";function k6($,J={}){let G=$.length;if(G<=1)return{ok:!0,count:G};if(!J.allowMultiple)return{ok:!1,count:G,reason:"multiple-not-allowed"};let X=J.maxStacked??Number.POSITIVE_INFINITY;if(G>X)return{ok:!1,count:G,reason:"exceeds-max-stacked"};return{ok:!0,count:G}}async function E1($,J){if(!Number.isFinite(J)||J<=0)return{ok:!1,productId:$,quantity:J,reason:"non-positive"};let G;try{G=await C$.selectFrom("products").where("id","=",$).select(["min_order_qty","max_order_qty"]).executeTakeFirst()}catch{return{ok:!0,productId:$,quantity:J}}if(!G)return{ok:!1,productId:$,quantity:J,reason:"product-missing"};let X=G.min_order_qty??null,Z=G.max_order_qty??null;if(X!=null&&J<X)return{ok:!1,productId:$,quantity:J,reason:"below-min",bound:X};if(Z!=null&&J>Z)return{ok:!1,productId:$,quantity:J,reason:"above-max",bound:Z};return{ok:!0,productId:$,quantity:J}}async function w6($){for(let G of $){let X=await E1(G.productId,G.quantity);if(!X.ok)return X}let J=$[$.length-1]??{productId:0,quantity:0};return{ok:!0,productId:J.productId,quantity:J.quantity}}async function f6($={}){let J=$.olderThanDays??30,G=$.limit??1000,Z=new Date(Date.now()-J*24*60*60*1000).toISOString().slice(0,19).replace("T"," ");try{let Y=await C$.unsafe("DELETE FROM carts WHERE updated_at < ? AND id IN (SELECT id FROM carts WHERE updated_at < ? LIMIT ?)",[Z,Z,G]),K=typeof Y?.execute==="function"?await Y.execute():Y;return{deleted:Number(K?.changes??K?.numDeletedRows??K?.[0]?.numDeletedRows??K?.affectedRows??0),cutoffAt:Z}}catch{try{let Y=await C$.selectFrom("carts").where("updated_at","<",Z).select(["id"]).limit(G).execute();if(Y.length===0)return{deleted:0,cutoffAt:Z};let K=Y.map((F)=>F.id),W=await C$.deleteFrom("carts").where("id","in",K).execute();return{deleted:Number(W?.numDeletedRows??W?.[0]?.numDeletedRows??W?.affectedRows??Y.length),cutoffAt:Z}}catch{return{deleted:0,cutoffAt:Z}}}}var q1={};B(q1,{update:()=>u6,store:()=>m6,recordRefund:()=>n6,fetchPaymentStatsByMethod:()=>v6,fetchPaymentStats:()=>D6,fetchMonthlyPaymentTrends:()=>y6,fetchById:()=>V0,fetchAll:()=>g6,destroy:()=>h6,bulkDestroy:()=>I6});import{db as C6}from"@stacksjs/database";async function h6($){try{let J=await C6.deleteFrom("payments").where("id","=",$).executeTakeFirst();return L(J)>0}catch(J){if(J instanceof Error)throw TypeError(`Failed to delete payment: ${J.message}`);throw J}}async function I6($){if(!$.length)return 0;try{let J=await C6.deleteFrom("payments").where("id","in",$).executeTakeFirst();return L(J)}catch(J){if(J instanceof Error)throw TypeError(`Failed to delete payments: ${J.message}`);throw J}}import{db as k}from"@stacksjs/database";import{formatDate as y}from"@stacksjs/orm";async function g6(){return await k.selectFrom("payments").selectAll().execute()}async function V0($){return await k.selectFrom("payments").selectAll().where("id","=",$).executeTakeFirst()}async function D6($=30){let J=new Date,G=new Date(J);G.setDate(J.getDate()-$);let X=new Date(G);X.setDate(X.getDate()-1);let Z=new Date(X);Z.setDate(X.getDate()-$);let Y=await k.selectFrom("payments").select([k.fn.count("id").as("transaction_count"),k.fn.sum("amount").as("total_revenue")]).where("created_at",">=",y(G)).where("created_at","<=",y(J)).where("status","=","completed").executeTakeFirst(),K=await k.selectFrom("payments").select([k.fn.count("id").as("transaction_count"),k.fn.sum("amount").as("total_revenue")]).where("created_at",">=",y(Z)).where("created_at","<=",y(X)).where("status","=","completed").executeTakeFirst(),W=await k.selectFrom("payments").select(k.fn.count("id").as("count")).where("created_at",">=",y(G)).where("created_at","<=",y(J)).executeTakeFirst(),Q=Number(Y?.transaction_count||0),F=Number(Y?.total_revenue||0),N=Q>0?F/Q:0,H=Number(K?.transaction_count||0),z=Number(K?.total_revenue||0),x=H>0?z/H:0,U=Q-H,O=F-z,P=N-x,c=H>0?U/H*100:Q>0?100:0,F0=z>0?O/z*100:F>0?100:0,L0=x>0?P/x*100:N>0?100:0,O0=Number(W?.count||0),u=O0>0?Q/O0*100:0;return{total_transactions:Q,total_revenue:F,average_transaction:N,successful_rate:u,comparison:{transactions:{difference:U,percentage:Math.abs(c),is_increase:U>=0},revenue:{difference:O,percentage:Math.abs(F0),is_increase:O>=0},average:{difference:P,percentage:Math.abs(L0),is_increase:P>=0}}}}async function v6($=30){let J=new Date,G=new Date(J);G.setDate(J.getDate()-$);let X=await k.selectFrom("payments").select([k.fn.count("id").as("total_count"),k.fn.sum("amount").as("total_revenue")]).where("created_at",">=",y(G)).where("created_at","<=",y(J)).where("status","=","completed").executeTakeFirst(),Z=Number(X?.total_count||0),Y=await k.selectFrom("payments").select(["method",k.fn.count("id").as("count"),k.fn.sum("amount").as("revenue")]).where("created_at",">=",y(G)).where("created_at","<=",y(J)).where("status","=","completed").groupBy("method").execute(),K={};return Y.forEach((W)=>{let Q=Number(W.count||0),F=Number(W.revenue||0),N=Z>0?Q/Z*100:0;K[W.method]={count:Q,revenue:F,percentage_of_total:N}}),K}async function y6(){let $=new Date,J=new Date($);J.setMonth($.getMonth()-11),J.setDate(1);let G=await k.selectFrom("payments").selectAll().where("created_at",">=",y(J)).where("status","=","completed").execute(),X={};for(let Y of G){let K=Y.created_at?new Date(Y.created_at):new Date,W=K.getFullYear(),Q=K.getMonth()+1,F=`${W}-${String(Q).padStart(2,"0")}`;if(!X[F])X[F]={year:W,month:Q,transactions:0,revenue:0};X[F].transactions++,X[F].revenue+=Number(Y.amount||0)}return Object.keys(X).sort().map((Y)=>{let K=X[Y],W=K.transactions,Q=K.revenue,F=W>0?Q/W:0;return{month:new Date(K.year,K.month-1,1).toLocaleString("default",{month:"short"}),year:K.year,transactions:W,revenue:Q,average:F}})}var{randomUUIDv7:aZ}=globalThis.Bun;import{db as b6}from"@stacksjs/database";import{HttpError as p6}from"@stacksjs/error-handling";import{isUniqueViolation as rZ}from"@stacksjs/orm";async function m6($){let J=aZ(),G={...$,status:$.status||"pending",currency:$.currency||"USD",uuid:J};try{return await b6.insertInto("payments").values(G).executeTakeFirst(),await b6.selectFrom("payments").where("uuid","=",J).selectAll().executeTakeFirst()}catch(X){if(X instanceof p6)throw X;if(rZ(X))throw new p6(409,"A payment with this transaction ID already exists");if(X instanceof Error){if(X.message.includes("Insufficient funds"))throw Error("Insufficient funds for this payment");throw Error(`Failed to create payment: ${X.message}`)}throw X}}import{db as l6,sqlHelpers as tZ}from"@stacksjs/database";import{env as eZ}from"@stacksjs/env";import{formatDate as c6}from"@stacksjs/orm";async function u6($,J){if(!await V0($))return;try{return await l6.updateTable("payments").set({...J,updated_at:c6(new Date)}).where("id","=",$).execute(),await V0($)}catch(X){if(X instanceof Error){if(X.message.includes("Duplicate entry")&&X.message.includes("transaction_id"))throw Error("A payment with this transaction ID already exists");throw Error(`Failed to update payment: ${X.message}`)}throw X}}var $Y=new Set(["completed","succeeded","partiallyRefunded"]);async function n6($,J){if(!Number.isSafeInteger($)||$<=0)throw TypeError("Payment ID must be a positive integer");if(!Number.isSafeInteger(J)||J<=0)throw TypeError("Refund amount must be a positive integer in minor units");let X=tZ(eZ.DB_CONNECTION||"sqlite").param,Z=c6(new Date),Y=await l6.unsafe(`UPDATE payments
20
+ AND inventory_count + ${Z(4)} >= 0`,parameters:[G,X,J,G]}}async function B6($,J,G,X){let Z=nZ(uZ.DB_CONNECTION||"sqlite",J,G,X),Y=await $.unsafe(Z.query,Z.parameters),K=typeof Y?.execute==="function"?await Y.execute():Y;return L(K)}var O6=!1;function A6($){let J=$,G=J?.message??"";return J?.code==="42P01"||G.includes("no such table")||G.includes("doesn't exist")||/relation "[^"]*" does not exist/i.test(G)}async function w$($){try{let J=await k$.selectFrom("order_idempotency").where("idempotency_key","=",$).selectAll().executeTakeFirst();if(!J)return null;return await k$.selectFrom("orders").where("id","=",J.order_id).selectAll().executeTakeFirst()??null}catch(J){if(A6(J)){if(!O6)O6=!0,console.warn("[commerce/orders] order_idempotency table missing - idempotency keys are accepted but NOT enforced. Run migrations to enable dedup.");return null}throw J}}async function U6($){if($.idempotencyKey){let J=await w$($.idempotencyKey);if(J)return{ok:!0,order:J}}try{let J=await k$.transaction(async(G)=>{let X=iZ(new Date),Z=M6(),Y={...$.order,status:$.order.status||"PENDING",uuid:Z,created_at:X,updated_at:X};await G.insertInto("orders").values(Y).executeTakeFirst();let K=await G.selectFrom("orders").where("uuid","=",Z).selectAll().executeTakeFirst(),W=Number(K?.id);if(!Number.isSafeInteger(W)||W<=0)throw Object.assign(Error("order insert could not be resolved by uuid"),{__placeFail:!0,failedAt:"order",reason:"unknown"});let Q;if($.items&&$.items.length>0){let H=$.items.map((z)=>{if(!Number.isInteger(z.productId)||z.productId<=0)throw Object.assign(Error("order item product id must be a positive integer"),{__placeFail:!0,failedAt:"items",reason:"unknown"});if(!Number.isInteger(z.quantity)||z.quantity<=0)throw Object.assign(Error("order item quantity must be a positive integer"),{__placeFail:!0,failedAt:"items",reason:"unknown"});if(!Number.isFinite(z.price)||z.price<0)throw Object.assign(Error("order item price must be a non-negative finite number"),{__placeFail:!0,failedAt:"items",reason:"unknown"});return{order_id:W,product_id:z.productId,quantity:z.quantity,price:z.price,special_instructions:z.specialInstructions||null,created_at:X,updated_at:X}});await G.insertInto("order_items").values(H).execute(),Q=await G.selectFrom("order_items").where("order_id","=",W).selectAll().execute()}let F;if($.payment)try{let H=M6();if(await G.insertInto("payments").values({...$.payment,order_id:W,uuid:H,created_at:X,updated_at:X}).executeTakeFirst(),F=await G.selectFrom("payments").where("uuid","=",H).selectAll().executeTakeFirst(),!F)throw Object.assign(Error("payment insert could not be resolved by uuid"),{__placeFail:!0,failedAt:"payment",reason:"unknown"})}catch(H){if(_6(H))throw Object.assign(Error("duplicate payment transaction_id"),{__placeFail:!0,failedAt:"payment",reason:"duplicate-payment"});throw Object.assign(H,{__placeFail:!0,failedAt:"payment",reason:"unknown"})}if($.inventory&&$.inventory.length>0)for(let H=0;H<$.inventory.length;H++){let{id:z,delta:x}=$.inventory[H];if(!Number.isFinite(x)||x===0)throw Object.assign(Error("inventory delta must be a non-zero finite number"),{__placeFail:!0,failedAt:"inventory",reason:"unknown"});if(!await B6(G,z,x,X))throw Object.assign(Error(`inventory adjust failed for product ${z}`),{__placeFail:!0,failedAt:"inventory",reason:"out-of-stock"})}if($.idempotencyKey)try{await G.insertInto("order_idempotency").values({idempotency_key:$.idempotencyKey,order_id:W,created_at:X}).execute()}catch(H){if(_6(H))throw Object.assign(Error("idempotency-key collision"),{__placeFail:!0,failedAt:"idempotency",reason:"duplicate-idempotency-key"});if(!A6(H))throw H}let N=await G.selectFrom("orders").where("id","=",W).selectAll().executeTakeFirst();if(!N)throw Object.assign(Error("order disappeared mid-transaction"),{__placeFail:!0,failedAt:"order",reason:"unknown"});return{order:N,items:Q,payment:F}});if(s0(J.order),J.payment)Y0(J.order,J.payment);return{ok:!0,order:J.order,items:J.items,payment:J.payment}}catch(J){if(J?.__placeFail){if(J.reason==="duplicate-idempotency-key"&&$.idempotencyKey){let G=await w$($.idempotencyKey);if(G)return{ok:!0,order:G}}return{ok:!1,reason:J.reason,failedAt:J.failedAt,error:J}}throw J}}R0();import{db as O1}from"@stacksjs/database";import{formatDate as A1}from"@stacksjs/orm";R0();async function P6($,J){if(!await r($))return;try{return await O1.updateTable("orders").set({...J,updated_at:A1(new Date)}).where("id","=",$).execute(),await r($)}catch(X){if(X instanceof Error)throw TypeError(`Failed to update order: ${X.message}`);throw X}}async function S6($,J){let G=await r($);if(!G)throw Error(`Order with ID ${$} not found`);let X=G.status??"PENDING";if(!i0(X,J))throw Error(`[commerce/orders] illegal status transition ${X} \u2192 ${J}`);try{await O1.updateTable("orders").set({status:J,updated_at:A1(new Date)}).where("id","=",$).execute();let Z=await r($);if(Z)o0(J,Z);return Z}catch(Z){if(Z instanceof Error)throw TypeError(`Failed to update order status: ${Z.message}`);throw Z}}async function R6($,J,G){let X=await r($);if(!X)throw Error(`Order with ID ${$} not found`);let Z={updated_at:A1(new Date)};if(J!==void 0)Z.delivery_address=J;if(G!==void 0)Z.estimated_delivery_time=G;if(Object.keys(Z).length===1)return X;try{return await O1.updateTable("orders").set(Z).where("id","=",$).execute(),await r($)}catch(Y){if(Y instanceof Error)throw TypeError(`Failed to update delivery information: ${Y.message}`);throw Y}}R0();import{db as T0}from"@stacksjs/database";import{formatDate as f$}from"@stacksjs/orm";async function U1($){let J=await T0.selectFrom("payments").where("transaction_id","=",$).selectAll().executeTakeFirst();if(!J)return null;return await T0.selectFrom("orders").where("id","=",J.order_id).selectAll().executeTakeFirst()??null}var T6=!1;function sZ($){let J=$,G=J?.message??"";return J?.code==="42P01"||G.includes("no such table")||G.includes("doesn't exist")||/relation "[^"]*" does not exist/i.test(G)}async function P1($,J){try{return await J.insertInto("stripe_webhook_events").values({event_id:$,processed_at:f$(new Date)}).execute(),!0}catch(G){if(G?.message?.includes("UNIQUE constraint")||G?.message?.includes("Duplicate entry"))return!1;if(sZ(G)){if(!T6)T6=!0,console.warn("[commerce/webhook] stripe_webhook_events table missing - Stripe retries may be processed multiple times. Run migrations to enable dedup.");return!0}throw G}}async function S1($){let G=$.data.object?.id;if(!G)return;await T0.transaction(async(Z)=>{if(!await P1($.id,Z))return;let K=await Z.selectFrom("payments").where("transaction_id","=",G).selectAll().executeTakeFirst();if(!K)return;let W=f$(new Date);await Z.updateTable("payments").set({status:"succeeded",updated_at:W}).where("id","=",K.id).execute();let Q=await Z.selectFrom("orders").where("id","=",K.order_id).selectAll().executeTakeFirst();if(Q&&Q.status==="PENDING")await Z.updateTable("orders").set({status:"PROCESSING",updated_at:W}).where("id","=",Q.id).execute()});let X=await U1(G);if(X)await Y0(X)}async function R1($){let J=$.data.object,G=J?.id;if(!G)return;let Z=J?.last_payment_error?.message??"payment failed";await T0.transaction(async(K)=>{if(!await P1($.id,K))return;let Q=await K.selectFrom("payments").where("transaction_id","=",G).selectAll().executeTakeFirst();if(!Q)return;let F=f$(new Date);await K.updateTable("payments").set({status:"failed",failure_reason:Z,updated_at:F}).where("id","=",Q.id).execute();let N=await K.selectFrom("orders").where("id","=",Q.order_id).selectAll().executeTakeFirst();if(N&&N.status==="PENDING")await K.updateTable("orders").set({status:"CANCELLED",updated_at:F}).where("id","=",N.id).execute()});let Y=await U1(G);if(Y)await P0(Y,Z)}async function T1($){let J=$.data.object,G=J?.payment_intent;if(!G)return;let X=J?.amount_refunded??0;await T0.transaction(async(Y)=>{if(!await P1($.id,Y))return;let W=await Y.selectFrom("payments").where("transaction_id","=",G).selectAll().executeTakeFirst();if(!W)return;let Q=f$(new Date);await Y.updateTable("payments").set({status:"refunded",refund_amount:X,updated_at:Q}).where("id","=",W.id).execute();let F=await Y.selectFrom("orders").where("id","=",W.order_id).selectAll().executeTakeFirst();if(F&&F.status!=="REFUNDED")await Y.updateTable("orders").set({status:"REFUNDED",updated_at:Q}).where("id","=",F.id).execute()});let Z=await U1(G);if(Z)await S0(Z,X)}async function E6(){let $=await import("@stacksjs/payments").catch(()=>null);if(!$)return console.warn("[commerce/webhook] @stacksjs/payments not installed - Stripe webhook handlers NOT registered"),()=>{};let{onPaymentIntent:J,onCharge:G}=$;if(typeof J!=="function"||typeof G!=="function")return console.warn("[commerce/webhook] @stacksjs/payments shape changed - handlers NOT registered"),()=>{};return J({succeeded:S1,failed:R1}),G({refunded:T1}),()=>{}}import{db as j6}from"@stacksjs/database";async function oZ($){if($.length===0)return new Map;let J=await j6.selectFrom("products").where("id","in",$).select(["id","price"]).execute(),G=new Map;for(let X of J){let Z=Number(X.price);if(Number.isFinite(Z))G.set(X.id,Math.round(Z))}return G}async function dZ($){try{let J=await j6.selectFrom("tax_rates").where("id","=",$).select(["rate"]).executeTakeFirst(),G=Number(J?.rate??0);return Number.isFinite(G)?G:0}catch{return 0}}async function q6($){let J=$.items.map((U)=>U.productId),G=await oZ(J),X=$.taxRateId!=null?await dZ($.taxRateId):0,Z=0,Y=[];for(let U of $.items){let O=U.unitPriceCents??G.get(U.productId)??0,P=Math.round(O*U.quantity);Z+=P,Y.push({productId:U.productId,quantity:U.quantity,unitPriceCents:O,lineCents:P})}let K=Math.max(0,$.discountCents??0),W=Math.max(0,Z-K),Q=Math.round(W*X),F=Math.max(0,$.shippingCents??0),N=Z-K+Q+F,H=$.maxDriftCents??5,z=!1,x=0;if($.clientTotalCents!=null)x=Math.abs(N-$.clientTotalCents),z=x>H;return{subtotalCents:Z,discountCents:K,taxCents:Q,shippingCents:F,totalCents:N,resolvedItems:Y,priceChanged:z,diffCents:x}}import{db as C$}from"@stacksjs/database";function k6($,J={}){let G=$.length;if(G<=1)return{ok:!0,count:G};if(!J.allowMultiple)return{ok:!1,count:G,reason:"multiple-not-allowed"};let X=J.maxStacked??Number.POSITIVE_INFINITY;if(G>X)return{ok:!1,count:G,reason:"exceeds-max-stacked"};return{ok:!0,count:G}}async function E1($,J){if(!Number.isFinite(J)||J<=0)return{ok:!1,productId:$,quantity:J,reason:"non-positive"};let G;try{G=await C$.selectFrom("products").where("id","=",$).select(["min_order_qty","max_order_qty"]).executeTakeFirst()}catch{return{ok:!0,productId:$,quantity:J}}if(!G)return{ok:!1,productId:$,quantity:J,reason:"product-missing"};let X=G.min_order_qty??null,Z=G.max_order_qty??null;if(X!=null&&J<X)return{ok:!1,productId:$,quantity:J,reason:"below-min",bound:X};if(Z!=null&&J>Z)return{ok:!1,productId:$,quantity:J,reason:"above-max",bound:Z};return{ok:!0,productId:$,quantity:J}}async function w6($){for(let G of $){let X=await E1(G.productId,G.quantity);if(!X.ok)return X}let J=$[$.length-1]??{productId:0,quantity:0};return{ok:!0,productId:J.productId,quantity:J.quantity}}async function f6($={}){let J=$.olderThanDays??30,G=$.limit??1000,Z=new Date(Date.now()-J*24*60*60*1000).toISOString().slice(0,19).replace("T"," ");try{let Y=await C$.unsafe("DELETE FROM carts WHERE updated_at < ? AND id IN (SELECT id FROM carts WHERE updated_at < ? LIMIT ?)",[Z,Z,G]),K=typeof Y?.execute==="function"?await Y.execute():Y;return{deleted:Number(K?.changes??K?.numDeletedRows??K?.[0]?.numDeletedRows??K?.affectedRows??0),cutoffAt:Z}}catch{try{let Y=await C$.selectFrom("carts").where("updated_at","<",Z).select(["id"]).limit(G).execute();if(Y.length===0)return{deleted:0,cutoffAt:Z};let K=Y.map((F)=>F.id),W=await C$.deleteFrom("carts").where("id","in",K).execute();return{deleted:Number(W?.numDeletedRows??W?.[0]?.numDeletedRows??W?.affectedRows??Y.length),cutoffAt:Z}}catch{return{deleted:0,cutoffAt:Z}}}}var q1={};B(q1,{update:()=>u6,store:()=>m6,recordRefund:()=>n6,fetchPaymentStatsByMethod:()=>v6,fetchPaymentStats:()=>D6,fetchMonthlyPaymentTrends:()=>y6,fetchById:()=>V0,fetchAll:()=>g6,destroy:()=>h6,bulkDestroy:()=>I6});import{db as C6}from"@stacksjs/database";async function h6($){try{let J=await C6.deleteFrom("payments").where("id","=",$).executeTakeFirst();return L(J)>0}catch(J){if(J instanceof Error)throw TypeError(`Failed to delete payment: ${J.message}`);throw J}}async function I6($){if(!$.length)return 0;try{let J=await C6.deleteFrom("payments").where("id","in",$).executeTakeFirst();return L(J)}catch(J){if(J instanceof Error)throw TypeError(`Failed to delete payments: ${J.message}`);throw J}}import{db as k}from"@stacksjs/database";import{formatDate as y}from"@stacksjs/orm";async function g6(){return await k.selectFrom("payments").selectAll().execute()}async function V0($){return await k.selectFrom("payments").selectAll().where("id","=",$).executeTakeFirst()}async function D6($=30){let J=new Date,G=new Date(J);G.setDate(J.getDate()-$);let X=new Date(G);X.setDate(X.getDate()-1);let Z=new Date(X);Z.setDate(X.getDate()-$);let Y=await k.selectFrom("payments").select([k.fn.count("id").as("transaction_count"),k.fn.sum("amount").as("total_revenue")]).where("created_at",">=",y(G)).where("created_at","<=",y(J)).where("status","=","completed").executeTakeFirst(),K=await k.selectFrom("payments").select([k.fn.count("id").as("transaction_count"),k.fn.sum("amount").as("total_revenue")]).where("created_at",">=",y(Z)).where("created_at","<=",y(X)).where("status","=","completed").executeTakeFirst(),W=await k.selectFrom("payments").select(k.fn.count("id").as("count")).where("created_at",">=",y(G)).where("created_at","<=",y(J)).executeTakeFirst(),Q=Number(Y?.transaction_count||0),F=Number(Y?.total_revenue||0),N=Q>0?F/Q:0,H=Number(K?.transaction_count||0),z=Number(K?.total_revenue||0),x=H>0?z/H:0,U=Q-H,O=F-z,P=N-x,c=H>0?U/H*100:Q>0?100:0,F0=z>0?O/z*100:F>0?100:0,L0=x>0?P/x*100:N>0?100:0,O0=Number(W?.count||0),u=O0>0?Q/O0*100:0;return{total_transactions:Q,total_revenue:F,average_transaction:N,successful_rate:u,comparison:{transactions:{difference:U,percentage:Math.abs(c),is_increase:U>=0},revenue:{difference:O,percentage:Math.abs(F0),is_increase:O>=0},average:{difference:P,percentage:Math.abs(L0),is_increase:P>=0}}}}async function v6($=30){let J=new Date,G=new Date(J);G.setDate(J.getDate()-$);let X=await k.selectFrom("payments").select([k.fn.count("id").as("total_count"),k.fn.sum("amount").as("total_revenue")]).where("created_at",">=",y(G)).where("created_at","<=",y(J)).where("status","=","completed").executeTakeFirst(),Z=Number(X?.total_count||0),Y=await k.selectFrom("payments").select(["method",k.fn.count("id").as("count"),k.fn.sum("amount").as("revenue")]).where("created_at",">=",y(G)).where("created_at","<=",y(J)).where("status","=","completed").groupBy("method").execute(),K={};return Y.forEach((W)=>{let Q=Number(W.count||0),F=Number(W.revenue||0),N=Z>0?Q/Z*100:0;K[W.method]={count:Q,revenue:F,percentage_of_total:N}}),K}async function y6(){let $=new Date,J=new Date($);J.setMonth($.getMonth()-11),J.setDate(1);let G=await k.selectFrom("payments").selectAll().where("created_at",">=",y(J)).where("status","=","completed").execute(),X={};for(let Y of G){let K=Y.created_at?new Date(Y.created_at):new Date,W=K.getFullYear(),Q=K.getMonth()+1,F=`${W}-${String(Q).padStart(2,"0")}`;if(!X[F])X[F]={year:W,month:Q,transactions:0,revenue:0};X[F].transactions++,X[F].revenue+=Number(Y.amount||0)}return Object.keys(X).sort().map((Y)=>{let K=X[Y],W=K.transactions,Q=K.revenue,F=W>0?Q/W:0;return{month:new Date(K.year,K.month-1,1).toLocaleString("default",{month:"short"}),year:K.year,transactions:W,revenue:Q,average:F}})}var{randomUUIDv7:aZ}=globalThis.Bun;import{db as b6}from"@stacksjs/database";import{HttpError as p6}from"@stacksjs/error-handling";import{isUniqueViolation as rZ}from"@stacksjs/orm";async function m6($){let J=aZ(),G={...$,status:$.status||"pending",currency:$.currency||"USD",uuid:J};try{return await b6.insertInto("payments").values(G).executeTakeFirst(),await b6.selectFrom("payments").where("uuid","=",J).selectAll().executeTakeFirst()}catch(X){if(X instanceof p6)throw X;if(rZ(X))throw new p6(409,"A payment with this transaction ID already exists");if(X instanceof Error){if(X.message.includes("Insufficient funds"))throw Error("Insufficient funds for this payment");throw Error(`Failed to create payment: ${X.message}`)}throw X}}import{db as l6,sqlHelpers as tZ}from"@stacksjs/database";import{env as eZ}from"@stacksjs/env";import{formatDate as c6}from"@stacksjs/orm";async function u6($,J){if(!await V0($))return;try{return await l6.updateTable("payments").set({...J,updated_at:c6(new Date)}).where("id","=",$).execute(),await V0($)}catch(X){if(X instanceof Error){if(X.message.includes("Duplicate entry")&&X.message.includes("transaction_id"))throw Error("A payment with this transaction ID already exists");throw Error(`Failed to update payment: ${X.message}`)}throw X}}var $Y=new Set(["completed","succeeded","partiallyRefunded"]);async function n6($,J){if(!Number.isSafeInteger($)||$<=0)throw TypeError("Payment ID must be a positive integer");if(!Number.isSafeInteger(J)||J<=0)throw TypeError("Refund amount must be a positive integer in minor units");let X=tZ(eZ.DB_CONNECTION||"sqlite").param,Z=c6(new Date),Y=await l6.unsafe(`UPDATE payments
21
21
  SET refund_amount = COALESCE(refund_amount, 0) + ${X(1)},
22
22
  status = CASE WHEN COALESCE(refund_amount, 0) + ${X(2)} = amount
23
23
  THEN 'refunded' ELSE 'partiallyRefunded' END,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/commerce",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.72.37",
5
+ "version": "0.72.39",
6
6
  "description": "Stacks commerce utilities.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [