@lunora/payment 1.0.0-alpha.132 → 1.0.0-alpha.134

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createAdapterRegistry as r}from"./packem_shared/createAdapterRegistry-DyCsry1Q.mjs";import{lunoraDatabaseToPaymentDatabase as a,paymentsFromContext as n}from"./packem_shared/lunoraDatabaseToPaymentDatabase-CFPYSEu0.mjs";import{createPayment as y,webhookResponse as i}from"./packem_shared/createPayment-Dimez2zY.mjs";import{createDatabasePaymentStore as s}from"./packem_shared/createDatabasePaymentStore-BGcHX1uq.mjs";import{entitlementsForReference as c,featureNames as x,hasActivePrice as S,resolveEntitlements as l,usagePeriodStart as M}from"./packem_shared/entitlementsForReference-PMmhAOWa.mjs";import{LunoraPaymentError as u}from"./packem_shared/LunoraPaymentError-BSxyhWgu.mjs";import{idempotencyKey as b}from"./packem_shared/idempotencyKey-qQJLcEQX.mjs";import{addMoney as A,allocateMoney as E,compareMoney as N,formatMoney as h,fromMoneyJSON as R,isZeroDecimalCurrency as v,isZeroMoney as C,money as D,subtractMoney as I,toMoneyJSON as _,zeroMoney as g}from"./packem_shared/addMoney-B4ufWAnD.mjs";import{reconcile as L}from"./packem_shared/reconcile-fbxX8j9k.mjs";import{default as F}from"./packem_shared/paymentTables-Ve_lJxAt.mjs";import{PAYMENT_TERMINAL_STATES as W,SUBSCRIPTION_TERMINAL_STATES as Z,canTransitionPayment as q,canTransitionSubscription as w,nextPaymentState as z,nextSubscriptionState as B}from"./packem_shared/PAYMENT_TERMINAL_STATES-C-ICZkun.mjs";import{MemoryPaymentStore as K}from"./packem_shared/MemoryPaymentStore-CZbrrEb1.mjs";import{default as Y}from"./packem_shared/applyWebhookAction-CvLpZjKP.mjs";import{constantTimeEqual as G,hmacSha256Hex as Q,verifyCreemSignature as V,verifyStandardWebhook as X}from"./packem_shared/constantTimeEqual-ggrwGvRa.mjs";export{u as LunoraPaymentError,K as MemoryPaymentStore,W as PAYMENT_TERMINAL_STATES,Z as SUBSCRIPTION_TERMINAL_STATES,A as addMoney,E as allocateMoney,Y as applyWebhookAction,q as canTransitionPayment,w as canTransitionSubscription,N as compareMoney,G as constantTimeEqual,r as createAdapterRegistry,s as createDatabasePaymentStore,y as createPayment,c as entitlementsForReference,x as featureNames,h as formatMoney,R as fromMoneyJSON,S as hasActivePrice,Q as hmacSha256Hex,b as idempotencyKey,v as isZeroDecimalCurrency,C as isZeroMoney,a as lunoraDatabaseToPaymentDatabase,D as money,z as nextPaymentState,B as nextSubscriptionState,F as paymentTables,n as paymentsFromContext,L as reconcile,l as resolveEntitlements,I as subtractMoney,_ as toMoneyJSON,M as usagePeriodStart,V as verifyCreemSignature,X as verifyStandardWebhook,i as webhookResponse,g as zeroMoney};
1
+ import{createAdapterRegistry as r}from"./packem_shared/createAdapterRegistry-DyCsry1Q.mjs";import{lunoraDatabaseToPaymentDatabase as a,paymentsFromContext as n}from"./packem_shared/lunoraDatabaseToPaymentDatabase-DgCDEqqC.mjs";import{createPayment as y,webhookResponse as i}from"./packem_shared/createPayment-BB3ri82t.mjs";import{createDatabasePaymentStore as s}from"./packem_shared/createDatabasePaymentStore-DG4P5AzL.mjs";import{entitlementsForReference as c,featureNames as x,hasActivePrice as S,resolveEntitlements as l,usagePeriodStart as M}from"./packem_shared/entitlementsForReference-PMmhAOWa.mjs";import{LunoraPaymentError as u}from"./packem_shared/LunoraPaymentError-BSxyhWgu.mjs";import{idempotencyKey as b}from"./packem_shared/idempotencyKey-qQJLcEQX.mjs";import{addMoney as A,allocateMoney as E,compareMoney as N,formatMoney as h,fromMoneyJSON as R,isZeroDecimalCurrency as v,isZeroMoney as C,money as D,subtractMoney as I,toMoneyJSON as _,zeroMoney as g}from"./packem_shared/addMoney-B4ufWAnD.mjs";import{reconcile as L}from"./packem_shared/reconcile-fbxX8j9k.mjs";import{default as F}from"./packem_shared/paymentTables-Ve_lJxAt.mjs";import{PAYMENT_TERMINAL_STATES as W,SUBSCRIPTION_TERMINAL_STATES as Z,canTransitionPayment as q,canTransitionSubscription as w,nextPaymentState as z,nextSubscriptionState as B}from"./packem_shared/PAYMENT_TERMINAL_STATES-C-ICZkun.mjs";import{MemoryPaymentStore as K}from"./packem_shared/MemoryPaymentStore-CZbrrEb1.mjs";import{default as Y}from"./packem_shared/applyWebhookAction-DyRG8lhp.mjs";import{constantTimeEqual as G,hmacSha256Hex as Q,verifyCreemSignature as V,verifyStandardWebhook as X}from"./packem_shared/constantTimeEqual-ggrwGvRa.mjs";export{u as LunoraPaymentError,K as MemoryPaymentStore,W as PAYMENT_TERMINAL_STATES,Z as SUBSCRIPTION_TERMINAL_STATES,A as addMoney,E as allocateMoney,Y as applyWebhookAction,q as canTransitionPayment,w as canTransitionSubscription,N as compareMoney,G as constantTimeEqual,r as createAdapterRegistry,s as createDatabasePaymentStore,y as createPayment,c as entitlementsForReference,x as featureNames,h as formatMoney,R as fromMoneyJSON,S as hasActivePrice,Q as hmacSha256Hex,b as idempotencyKey,v as isZeroDecimalCurrency,C as isZeroMoney,a as lunoraDatabaseToPaymentDatabase,D as money,z as nextPaymentState,B as nextSubscriptionState,F as paymentTables,n as paymentsFromContext,L as reconcile,l as resolveEntitlements,I as subtractMoney,_ as toMoneyJSON,M as usagePeriodStart,V as verifyCreemSignature,X as verifyStandardWebhook,i as webhookResponse,g as zeroMoney};
@@ -0,0 +1 @@
1
+ import{LunoraPaymentError as A}from"./LunoraPaymentError-BSxyhWgu.mjs";import{localRefundKey as _,LOCAL_REFUND_CLAIM_TYPE as S}from"./idempotencyKey-qQJLcEQX.mjs";import{zeroMoney as l,compareMoney as y,addMoney as w}from"./addMoney-B4ufWAnD.mjs";import{n as c}from"./observability-BteZ2dJS.mjs";import{nextPaymentState as b,nextSubscriptionState as h}from"./PAYMENT_TERMINAL_STATES-C-ICZkun.mjs";const T={"payment.authorized":"authorize","payment.captured":"capture","payment.failed":"fail","payment.refunded":"refund"},R={"subscription.active":"active","subscription.canceled":"canceled","subscription.past_due":"past_due","subscription.paused":"paused"},k="orphan-retry:",O="marker.orphan_retry",Y={"subscription.active":"activate","subscription.canceled":"cancel","subscription.past_due":"mark_past_due","subscription.paused":"pause"},N=(t,e)=>y(t,e)>0?t:e,M=new Set(["authorized","initiated"]),I=async()=>{},q=async(t,e,r)=>{if(!r||!e.sessionId||!e.amount||e.amountKind==="absolute")return{action:e,release:I};const n=_(e.sessionId,e.refundId,e.amount),s=await t.markEventProcessed(e.provider,n,S),d={...e,amount:l(e.amount.currency)};return e.refundId===void 0?(await t.releaseEvent(e.provider,n),{action:s?e:d,release:I}):s?{action:e,release:async()=>{await t.releaseEvent(e.provider,n)}}:{action:d,release:I}},E=(t,e)=>{if(!e.amount||t.refundedAmount.currency!==e.amount.currency||t.capturedAmount.currency!==e.amount.currency)return;const r=e.amountKind==="absolute"?N(e.amount,t.refundedAmount):w(t.refundedAmount,e.amount);if(!(y(r,t.capturedAmount)>0))return r},C=(t,e)=>{if(!t)return"refund";const r=E(t,e);return r&&y(r,t.capturedAmount)<0?"partial_refund":"refund"},g=async(t,e,r)=>{if(!e.sessionId)return{applied:!1,reason:"unhandled"};const n=await t.getPaymentSession(e.provider,e.sessionId),s=n?.state??"initiated",d=Date.now(),u=e.amount?.currency??n?.amount.currency??"USD",i=r==="refund"?await q(t,e,n):void 0,f=i?.action??e,p=r==="refund"?C(n,f):r,m=b(s,p);if(!m){const a=r==="refund"&&M.has(s);return await i?.release(),{applied:!1,reason:a?"orphaned":"illegal_transition"}}const o=n??{amount:e.amount??l(u),capturedAmount:l(u),createdAt:d,id:e.sessionId,provider:e.provider,referenceId:e.referenceId??"",refundedAmount:l(u),state:s,updatedAt:d};let{capturedAmount:v,refundedAmount:P}=o;if(p==="capture"&&e.amount&&(v=e.amount),(p==="partial_refund"||p==="refund")&&f.amount){const a=E(o,f);if(!a)return await i?.release(),{applied:!1,reason:"invalid_refund_amount"};P=a}try{await t.upsertPaymentSession({...o,capturedAmount:v,referenceId:e.referenceId??o.referenceId,refundedAmount:P,state:m,updatedAt:d})}catch(a){throw await i?.release(),a}return{applied:!0,reason:"ok"}},B=(t,e,r)=>{if(e==="active"){if(t==="active")return"renew";if(t==="paused")return"resume"}return Y[r]},x=async(t,e)=>{if(!e.subscriptionId)return{applied:!1,reason:"unhandled"};const r=await t.getSubscription(e.provider,e.subscriptionId),n=Date.now();if(e.type==="subscription.updated")return r?(await t.upsertSubscription({...r,cancelAtPeriodEnd:e.cancelAtPeriodEnd??r.cancelAtPeriodEnd,currentPeriodEnd:e.currentPeriodEnd??r.currentPeriodEnd,currentPeriodStart:e.currentPeriodStart??r.currentPeriodStart,priceId:e.priceId??r.priceId,priceIds:e.priceIds??r.priceIds,quantity:e.quantity??r.quantity,updatedAt:n}),{applied:!0,reason:"ok"}):{applied:!1,reason:"orphaned"};const s=R[e.type];if(!s)return{applied:!1,reason:"unhandled"};if(!r)return await t.upsertSubscription({cancelAtPeriodEnd:e.cancelAtPeriodEnd??!1,createdAt:n,currentPeriodEnd:e.currentPeriodEnd,currentPeriodStart:e.currentPeriodStart??n,id:e.subscriptionId,priceId:e.priceId??"",priceIds:e.priceIds,provider:e.provider,quantity:e.quantity??1,referenceId:e.referenceId??"",state:s,updatedAt:n}),{applied:!0,reason:"ok"};const d=B(r.state,s,e.type),u=d?h(r.state,d):void 0;return u?(await t.upsertSubscription({...r,cancelAtPeriodEnd:e.cancelAtPeriodEnd??r.cancelAtPeriodEnd,currentPeriodEnd:e.currentPeriodEnd??r.currentPeriodEnd,currentPeriodStart:e.currentPeriodStart??r.currentPeriodStart,priceId:e.priceId??r.priceId,priceIds:e.priceIds??r.priceIds,quantity:e.quantity??r.quantity,state:u,updatedAt:n}),{applied:!0,reason:"ok"}):{applied:!1,reason:"illegal_transition"}},H=async(t,e,r)=>{if(e.type==="unhandled")return{applied:!1,reason:"unhandled"};if(!e.eventId?.trim())throw new A("WEBHOOK_EVENT_ID_MISSING",`webhook event id is missing or blank for provider "${e.provider}"`);if(!await t.markEventProcessed(e.provider,e.eventId,e.type))return c(r,{eventId:e.eventId,provider:e.provider,type:"webhook.duplicate"}),{applied:!1,reason:"duplicate"};const s=T[e.type];let d;try{d=s?await g(t,e,s):await x(t,e)}catch(u){throw await t.releaseEvent(e.provider,e.eventId),u}return d.reason==="orphaned"&&(await t.markEventProcessed(e.provider,`${k}${e.eventId}`,O)?await t.releaseEvent(e.provider,e.eventId):d={applied:!1,reason:"unhandled"}),c(r,{action:e.type,eventId:e.eventId,provider:e.provider,reason:d.reason,type:"webhook.applied"}),e.type==="payment.failed"?c(r,{provider:e.provider,referenceId:e.referenceId,sessionId:e.sessionId,type:"payment.failed"}):e.type==="subscription.past_due"&&c(r,{provider:e.provider,referenceId:e.referenceId,subscriptionId:e.subscriptionId,type:"subscription.past_due"}),d};export{H as default};
@@ -0,0 +1 @@
1
+ import{money as y}from"./addMoney-B4ufWAnD.mjs";import{foldUsage as m}from"./MemoryPaymentStore-CZbrrEb1.mjs";const o=(e,n)=>typeof e[n]=="string"?e[n]:"",l=(e,n)=>typeof e[n]=="string"?e[n]:void 0,c=(e,n)=>typeof e[n]=="number"?e[n]:0,v=(e,n)=>typeof e[n]=="number"?e[n]:void 0,A=(e,n)=>e[n]===!0,S=(e,n)=>{const t=e[n];return Array.isArray(t)?t.filter(d=>typeof d=="string"):void 0},f=(e,n)=>{const t=e[n];return typeof t=="bigint"?t:typeof t=="number"||typeof t=="string"?BigInt(t):0n},P=e=>({createdAt:e.createdAt,...e.email===void 0?{}:{email:e.email},provider:e.provider,providerCustomerId:e.id,referenceId:e.referenceId}),E=e=>({createdAt:c(e,"createdAt"),email:l(e,"email"),id:o(e,"providerCustomerId"),provider:o(e,"provider"),referenceId:o(e,"referenceId")}),M=e=>({amountMinor:e.amount.minorUnits,capturedMinor:e.capturedAmount.minorUnits,createdAt:e.createdAt,currency:e.amount.currency,provider:e.provider,providerSessionId:e.id,referenceId:e.referenceId,refundedMinor:e.refundedAmount.minorUnits,state:e.state,updatedAt:e.updatedAt}),T=e=>{const n=o(e,"currency");return{amount:y(f(e,"amountMinor"),n),capturedAmount:y(f(e,"capturedMinor"),n),createdAt:c(e,"createdAt"),id:o(e,"providerSessionId"),provider:o(e,"provider"),referenceId:o(e,"referenceId"),refundedAmount:y(f(e,"refundedMinor"),n),state:o(e,"state"),updatedAt:c(e,"updatedAt")}},q=e=>({cancelAtPeriodEnd:e.cancelAtPeriodEnd,createdAt:e.createdAt,...e.currentPeriodEnd===void 0?{}:{currentPeriodEnd:e.currentPeriodEnd},...e.currentPeriodStart===void 0?{}:{currentPeriodStart:e.currentPeriodStart},priceId:e.priceId,...e.priceIds===void 0?{}:{priceIds:[...e.priceIds]},provider:e.provider,providerSubscriptionId:e.id,quantity:e.quantity,referenceId:e.referenceId,state:e.state,updatedAt:e.updatedAt}),I=e=>({cancelAtPeriodEnd:A(e,"cancelAtPeriodEnd"),createdAt:c(e,"createdAt"),currentPeriodEnd:v(e,"currentPeriodEnd"),currentPeriodStart:v(e,"currentPeriodStart"),id:o(e,"providerSubscriptionId"),priceId:o(e,"priceId"),priceIds:S(e,"priceIds"),provider:o(e,"provider"),quantity:c(e,"quantity"),referenceId:o(e,"referenceId"),state:o(e,"state"),updatedAt:c(e,"updatedAt")}),K=e=>({createdAt:c(e,"createdAt"),featureId:o(e,"featureId"),idempotencyKey:o(e,"idempotencyKey"),mode:e.mode==="set"?"set":"add",provider:o(e,"provider"),quantity:c(e,"quantity"),referenceId:o(e,"referenceId"),reportedToProvider:A(e,"reportedToProvider")}),U=e=>({createdAt:e.createdAt,featureId:e.featureId,idempotencyKey:e.idempotencyKey,...e.mode==="set"?{mode:"set"}:{},provider:e.provider,quantity:e.quantity,referenceId:e.referenceId,reportedToProvider:e.reportedToProvider}),h=e=>{const n=async(t,d,r)=>{const a=await e.findFirst(t,d);if(a){await e.patch(a._id,r);return}await e.insert(t,r)};return{getCustomerByReference:async(t,d)=>{const r=await e.findFirst("customers",{provider:t,referenceId:d});return r?E(r):void 0},getPaymentSession:async(t,d)=>{const r=await e.findFirst("paymentSessions",{provider:t,providerSessionId:d});return r?T(r):void 0},getSubscription:async(t,d)=>{const r=await e.findFirst("subscriptions",{provider:t,providerSubscriptionId:d});return r?I(r):void 0},listSubscriptionsByReference:async t=>{const{rows:d}=await e.findMany("subscriptions",{referenceId:t});return d.map(r=>I(r))},listUnreportedUsage:async(t,d)=>{const r=Math.max(0,Math.floor(d));if(r===0)return[];const a=[{createdAt:"asc"},{idempotencyKey:"asc"}],s=[];let i;do{const u=await e.findMany("usageEvents",{provider:t,reportedToProvider:!1},{cursor:i,limit:r,orderBy:a});for(const g of u.rows){const p=K(g);if(p.mode!=="set"&&p.quantity>0&&(s.push(p),s.length===r))return s}i=u.cursor}while(i!==void 0);return s},markEventProcessed:async(t,d,r)=>await e.findFirst("events",{provider:t,providerEventId:d})?!1:(await e.insert("events",{processedAt:Date.now(),provider:t,providerEventId:d,type:r}),!0),releaseEvent:async(t,d)=>{const r=await e.findFirst("events",{provider:t,providerEventId:d});r&&await e.delete(r._id)},markUsageReported:async(t,d)=>{const r=await e.findFirst("usageEvents",{idempotencyKey:d,provider:t});r&&await e.patch(r._id,{reportedToProvider:!0})},recordUsage:async t=>await e.findFirst("usageEvents",{idempotencyKey:t.idempotencyKey,provider:t.provider})?!1:(await e.insert("usageEvents",U(t)),!0),sumUsage:async(t,d,r)=>{const{rows:a}=await e.findMany("usageEvents",{featureId:d,referenceId:t}),s=a.filter(i=>c(i,"createdAt")>=r).map(i=>({createdAt:c(i,"createdAt"),idempotencyKey:typeof i.idempotencyKey=="string"?i.idempotencyKey:"",mode:i.mode==="set"?"set":"add",quantity:c(i,"quantity")}));return m(s)},sumUsageByFeature:async(t,d,r)=>{const{rows:a}=await e.findMany("usageEvents",{referenceId:t}),s=new Map(d.map(i=>[i,[]]));for(const i of a)c(i,"createdAt")<r||s.get(typeof i.featureId=="string"?i.featureId:"")?.push({createdAt:c(i,"createdAt"),idempotencyKey:typeof i.idempotencyKey=="string"?i.idempotencyKey:"",mode:i.mode==="set"?"set":"add",quantity:c(i,"quantity")});return new Map([...s].map(([i,u])=>[i,m(u)]))},upsertCustomer:async t=>n("customers",{provider:t.provider,referenceId:t.referenceId},P(t)),upsertPaymentSession:async t=>n("paymentSessions",{provider:t.provider,providerSessionId:t.id},M(t)),upsertSubscription:async t=>n("subscriptions",{provider:t.provider,providerSubscriptionId:t.id},q(t))}};export{h as createDatabasePaymentStore};
@@ -1 +1 @@
1
- import{toErrorBody as N}from"@lunora/errors";import{usagePeriodStart as g,resolveEntitlements as A,featureNames as U,hasActivePrice as _}from"./entitlementsForReference-PMmhAOWa.mjs";import{LunoraPaymentError as f}from"./LunoraPaymentError-BSxyhWgu.mjs";import{derivedIdempotencyKey as b,localRefundKey as D,LOCAL_REFUND_CLAIM_TYPE as E,idempotencyKey as k}from"./idempotencyKey-qQJLcEQX.mjs";import{subtractMoney as K,addMoney as T,compareMoney as v,isZeroMoney as B}from"./addMoney-B4ufWAnD.mjs";import{n as C}from"./observability-BteZ2dJS.mjs";import F from"./applyWebhookAction-CvLpZjKP.mjs";const y=(o,n=200,s)=>{const u=new Headers({"content-type":"application/json"});for(const[l,w]of Object.entries({}))u.set(l,w);return Response.json(o,{headers:u,status:n})};let h=0;const $=()=>{const o=Date.now();return h=o>h?o:h+1,h},O=o=>o?`${o.currency}:${String(o.minorUnits)}`:"full",q=o=>o&&"referenceId"in o?Object.fromEntries(Object.entries(o).filter(([n])=>n!=="referenceId")):o,G=o=>y({applied:o.applied},o.status),H=o=>{const{adapter:n,store:s}=o,u=async e=>{if(!o.authorize)return;let t;try{t=await o.authorize(e)}catch{throw new f("FORBIDDEN",`caller not authorized for reference "${e}"`)}if(t!==!0)throw new f("FORBIDDEN",`caller not authorized for reference "${e}"`)},l=async e=>{await u(e.referenceId);const t=q(e.metadata);let r;const a=await s.getCustomerByReference(n.identifier,e.referenceId);if(a)r=a.id;else{const d=await n.getOrCreateCustomer({email:e.email,referenceId:e.referenceId});r=d.id,await s.upsertCustomer(d)}const i=e.idempotencyKey??await b("checkout",n.identifier,e.referenceId,e.priceId,e.mode,String(e.quantity??1),e.successUrl,e.cancelUrl,t?JSON.stringify(t):"");return n.createCheckout({...e,customerId:r,idempotencyKey:i,metadata:t})},w=(e,t,r)=>{const a=e-t;return{allowed:a>=r,balance:a,limit:e,unlimited:!1,used:t}},S=async(e,t,r,a,i)=>{const d=e.limit(a);if(d!==void 0){const c=await s.sumUsage(r,a,g(t));return w(d,c,i)}return{allowed:e.has(a),unlimited:e.has(a)}},p=async e=>{const t=()=>new f("NOT_FOUND",`payment session "${e}" not found`);let r=await s.getPaymentSession(n.identifier,e);if(!r)try{r=await n.getPaymentStatus(e)}catch{throw t()}if(!r.referenceId)throw t();try{await u(r.referenceId)}catch{throw t()}return r},I=async(e,t)=>{const r={...e,...t,updatedAt:Date.now()};return await s.upsertPaymentSession(r),r};return{adapter:n,attach:async e=>l({...e,mode:e.mode??"subscription"}),cancelPayment:async(e,t)=>{const r=await p(e),a=t?.idempotencyKey??k("cancel_payment",n.identifier,e),i=await n.cancelPayment(e,{...t,idempotencyKey:a});return I(r,{state:i.state})},cancelSubscription:async(e,t)=>{const r=await s.getSubscription(n.identifier,e);if(!r)throw new f("NOT_FOUND",`subscription "${e}" not found`);try{await u(r.referenceId)}catch{throw new f("NOT_FOUND",`subscription "${e}" not found`)}const a=t?.idempotencyKey??k("cancel_subscription",n.identifier,e),i=await n.cancelSubscription(e,{...t,idempotencyKey:a}),d={...i,referenceId:r.referenceId===""?i.referenceId:r.referenceId};return await s.upsertSubscription(d),d},capturePayment:async e=>{const t=await p(e.sessionId),r=e.idempotencyKey??await b("capture_payment",n.identifier,e.sessionId,O(e.amount)),a=await n.capturePayment({...e,idempotencyKey:r});return I(t,{capturedAmount:a.capturedAmount,state:a.state})},check:async e=>{if(await u(e.referenceId),e.featureId===void 0&&e.priceId===void 0)throw new f("VALIDATION_ERROR","check() requires a featureId or priceId");if(n.checkEntitlement)return n.checkEntitlement(e);const t=await s.listSubscriptionsByReference(e.referenceId);if(e.priceId!==void 0)return{allowed:_(t,e.priceId),unlimited:!1};if(e.featureId===void 0)throw new f("VALIDATION_ERROR","check() requires a featureId or priceId");if(!o.entitlements)throw new f("CONFIG_INVALID","check() requires `entitlements` to be configured");const r=A(o.entitlements,t);return S(r,t,e.referenceId,e.featureId,e.quantity??1)},createCheckout:async e=>l(e),createPortalSession:async(e,t)=>{await u(e);const r=await s.getCustomerByReference(n.identifier,e);if(!r)throw new f("NOT_FOUND",`no customer for reference "${e}"`);return n.createPortalSession({customerId:r.id,returnUrl:t})},handleWebhook:async e=>{let t;try{const a=await e.text();t=await n.parseWebhook({headers:e.headers,payload:a})}catch(a){if(a instanceof f){const{body:i,status:d}=N(a);return y({error:i.message},d)}return y({error:"webhook error"},400)}const r=await F(s,t,o.observability);return r.reason==="orphaned"?y({applied:r.applied,reason:r.reason},500):y({applied:r.applied,reason:r.reason},200)},listBalances:async e=>{if(await u(e),n.getBalances)return n.getBalances(e);if(!o.entitlements)throw new f("CONFIG_INVALID","listBalances() requires `entitlements` to be configured");const t=await s.listSubscriptionsByReference(e),r=A(o.entitlements,t),a=U(o.entitlements),i=a.filter(c=>r.limit(c)!==void 0),d=i.length===0?new Map:await s.sumUsageByFeature(e,i,g(t));return a.map(c=>{const m=r.limit(c);return m!==void 0?{featureId:c,...w(m,d.get(c)??0,1)}:{featureId:c,allowed:r.has(c),unlimited:r.has(c)}})},listSubscriptions:async e=>(await u(e),s.listSubscriptionsByReference(e)),refundPayment:async e=>{const t=await p(e.sessionId),r=e.amount??K(t.capturedAmount,t.refundedAmount),a=T(t.refundedAmount,r);if(v(a,t.capturedAmount)>0)throw new f("VALIDATION_ERROR",`refundPayment(): refunding ${String(e.amount?.minorUnits)} would exceed the captured amount on session "${e.sessionId}"`);if(B(r))return t;const i=e.amount??(v(r,t.capturedAmount)===0?void 0:r),d=e.idempotencyKey??await b("refund_payment",n.identifier,e.sessionId,O(i),e.reason??""),c=await n.refundPayment({...e,amount:i,idempotencyKey:d});if(c.pending)return t;const m=D(e.sessionId,c.refundId,r);if(!await s.markEventProcessed(n.identifier,m,E)&&c.refundId!==void 0)return t;try{return await I(t,{refundedAmount:a,state:v(a,t.capturedAmount)<0?"partially_refunded":"refunded"})}catch(P){throw await s.releaseEvent(n.identifier,m),P}},store:s,track:async e=>{await u(e.referenceId);const t=e.quantity===void 0?1:e.quantity;if(!Number.isSafeInteger(t)||t<0)throw new f("VALIDATION_ERROR",`track(): \`quantity\` must be a non-negative safe integer (got ${String(e.quantity)})`);const r=e.idempotencyKey??crypto.randomUUID(),a=e.mode==="set";if(a&&n.capabilities.usageMetering&&n.reportUsage)throw new f("VALIDATION_ERROR",`track(): \`mode: "set"\` is not supported on "${n.identifier}", whose meter is additive — a lowering set cannot be forwarded, so the provider would over-bill against the local period total. Use \`mode: "add"\`.`);const i=a?await s.listSubscriptionsByReference(e.referenceId):void 0,d=i===void 0?0:await s.sumUsage(e.referenceId,e.featureId,g(i));if(a?t===d:t===0)return{recorded:!1,reportedToProvider:!1};const c=t;if(!await s.recordUsage({createdAt:$(),featureId:e.featureId,idempotencyKey:r,...a?{mode:"set"}:{},provider:n.identifier,quantity:t,referenceId:e.referenceId,reportedToProvider:!1}))return{recorded:!1,reportedToProvider:!1};if(c<=0||!n.capabilities.usageMetering||!n.reportUsage)return{recorded:!0,reportedToProvider:!1};try{const R=await s.getCustomerByReference(n.identifier,e.referenceId);return await n.reportUsage({customerId:R?.id,featureId:e.featureId,idempotencyKey:r,quantity:c,referenceId:e.referenceId}),await s.markUsageReported(n.identifier,r),{recorded:!0,reportedToProvider:!0}}catch{return C(o.observability,{featureId:e.featureId,provider:n.identifier,referenceId:e.referenceId,type:"usage.report_failed"}),{recorded:!0,reportedToProvider:!1}}}}};export{H as createPayment,G as webhookResponse};
1
+ import{toErrorBody as N}from"@lunora/errors";import{usagePeriodStart as g,resolveEntitlements as A,featureNames as U,hasActivePrice as _}from"./entitlementsForReference-PMmhAOWa.mjs";import{LunoraPaymentError as f}from"./LunoraPaymentError-BSxyhWgu.mjs";import{derivedIdempotencyKey as b,localRefundKey as D,LOCAL_REFUND_CLAIM_TYPE as E,idempotencyKey as k}from"./idempotencyKey-qQJLcEQX.mjs";import{subtractMoney as K,addMoney as T,compareMoney as v,isZeroMoney as B}from"./addMoney-B4ufWAnD.mjs";import{n as C}from"./observability-BteZ2dJS.mjs";import F from"./applyWebhookAction-DyRG8lhp.mjs";const y=(o,n=200,s)=>{const u=new Headers({"content-type":"application/json"});for(const[l,w]of Object.entries({}))u.set(l,w);return Response.json(o,{headers:u,status:n})};let h=0;const $=()=>{const o=Date.now();return h=o>h?o:h+1,h},O=o=>o?`${o.currency}:${String(o.minorUnits)}`:"full",q=o=>o&&"referenceId"in o?Object.fromEntries(Object.entries(o).filter(([n])=>n!=="referenceId")):o,G=o=>y({applied:o.applied},o.status),H=o=>{const{adapter:n,store:s}=o,u=async e=>{if(!o.authorize)return;let t;try{t=await o.authorize(e)}catch{throw new f("FORBIDDEN",`caller not authorized for reference "${e}"`)}if(t!==!0)throw new f("FORBIDDEN",`caller not authorized for reference "${e}"`)},l=async e=>{await u(e.referenceId);const t=q(e.metadata);let r;const a=await s.getCustomerByReference(n.identifier,e.referenceId);if(a)r=a.id;else{const d=await n.getOrCreateCustomer({email:e.email,referenceId:e.referenceId});r=d.id,await s.upsertCustomer(d)}const i=e.idempotencyKey??await b("checkout",n.identifier,e.referenceId,e.priceId,e.mode,String(e.quantity??1),e.successUrl,e.cancelUrl,t?JSON.stringify(t):"");return n.createCheckout({...e,customerId:r,idempotencyKey:i,metadata:t})},w=(e,t,r)=>{const a=e-t;return{allowed:a>=r,balance:a,limit:e,unlimited:!1,used:t}},S=async(e,t,r,a,i)=>{const d=e.limit(a);if(d!==void 0){const c=await s.sumUsage(r,a,g(t));return w(d,c,i)}return{allowed:e.has(a),unlimited:e.has(a)}},p=async e=>{const t=()=>new f("NOT_FOUND",`payment session "${e}" not found`);let r=await s.getPaymentSession(n.identifier,e);if(!r)try{r=await n.getPaymentStatus(e)}catch{throw t()}if(!r.referenceId)throw t();try{await u(r.referenceId)}catch{throw t()}return r},I=async(e,t)=>{const r={...e,...t,updatedAt:Date.now()};return await s.upsertPaymentSession(r),r};return{adapter:n,attach:async e=>l({...e,mode:e.mode??"subscription"}),cancelPayment:async(e,t)=>{const r=await p(e),a=t?.idempotencyKey??k("cancel_payment",n.identifier,e),i=await n.cancelPayment(e,{...t,idempotencyKey:a});return I(r,{state:i.state})},cancelSubscription:async(e,t)=>{const r=await s.getSubscription(n.identifier,e);if(!r)throw new f("NOT_FOUND",`subscription "${e}" not found`);try{await u(r.referenceId)}catch{throw new f("NOT_FOUND",`subscription "${e}" not found`)}const a=t?.idempotencyKey??k("cancel_subscription",n.identifier,e),i=await n.cancelSubscription(e,{...t,idempotencyKey:a}),d={...i,referenceId:r.referenceId===""?i.referenceId:r.referenceId};return await s.upsertSubscription(d),d},capturePayment:async e=>{const t=await p(e.sessionId),r=e.idempotencyKey??await b("capture_payment",n.identifier,e.sessionId,O(e.amount)),a=await n.capturePayment({...e,idempotencyKey:r});return I(t,{capturedAmount:a.capturedAmount,state:a.state})},check:async e=>{if(await u(e.referenceId),e.featureId===void 0&&e.priceId===void 0)throw new f("VALIDATION_ERROR","check() requires a featureId or priceId");if(n.checkEntitlement)return n.checkEntitlement(e);const t=await s.listSubscriptionsByReference(e.referenceId);if(e.priceId!==void 0)return{allowed:_(t,e.priceId),unlimited:!1};if(e.featureId===void 0)throw new f("VALIDATION_ERROR","check() requires a featureId or priceId");if(!o.entitlements)throw new f("CONFIG_INVALID","check() requires `entitlements` to be configured");const r=A(o.entitlements,t);return S(r,t,e.referenceId,e.featureId,e.quantity??1)},createCheckout:async e=>l(e),createPortalSession:async(e,t)=>{await u(e);const r=await s.getCustomerByReference(n.identifier,e);if(!r)throw new f("NOT_FOUND",`no customer for reference "${e}"`);return n.createPortalSession({customerId:r.id,returnUrl:t})},handleWebhook:async e=>{let t;try{const a=await e.text();t=await n.parseWebhook({headers:e.headers,payload:a})}catch(a){if(a instanceof f){const{body:i,status:d}=N(a);return y({error:i.message},d)}return y({error:"webhook error"},400)}const r=await F(s,t,o.observability);return r.reason==="orphaned"?y({applied:r.applied,reason:r.reason},500):y({applied:r.applied,reason:r.reason},200)},listBalances:async e=>{if(await u(e),n.getBalances)return n.getBalances(e);if(!o.entitlements)throw new f("CONFIG_INVALID","listBalances() requires `entitlements` to be configured");const t=await s.listSubscriptionsByReference(e),r=A(o.entitlements,t),a=U(o.entitlements),i=a.filter(c=>r.limit(c)!==void 0),d=i.length===0?new Map:await s.sumUsageByFeature(e,i,g(t));return a.map(c=>{const m=r.limit(c);return m!==void 0?{featureId:c,...w(m,d.get(c)??0,1)}:{featureId:c,allowed:r.has(c),unlimited:r.has(c)}})},listSubscriptions:async e=>(await u(e),s.listSubscriptionsByReference(e)),refundPayment:async e=>{const t=await p(e.sessionId),r=e.amount??K(t.capturedAmount,t.refundedAmount),a=T(t.refundedAmount,r);if(v(a,t.capturedAmount)>0)throw new f("VALIDATION_ERROR",`refundPayment(): refunding ${String(e.amount?.minorUnits)} would exceed the captured amount on session "${e.sessionId}"`);if(B(r))return t;const i=e.amount??(v(r,t.capturedAmount)===0?void 0:r),d=e.idempotencyKey??await b("refund_payment",n.identifier,e.sessionId,O(i),e.reason??""),c=await n.refundPayment({...e,amount:i,idempotencyKey:d});if(c.pending)return t;const m=D(e.sessionId,c.refundId,r);if(!await s.markEventProcessed(n.identifier,m,E)&&c.refundId!==void 0)return t;try{return await I(t,{refundedAmount:a,state:v(a,t.capturedAmount)<0?"partially_refunded":"refunded"})}catch(P){throw await s.releaseEvent(n.identifier,m),P}},store:s,track:async e=>{await u(e.referenceId);const t=e.quantity===void 0?1:e.quantity;if(!Number.isSafeInteger(t)||t<0)throw new f("VALIDATION_ERROR",`track(): \`quantity\` must be a non-negative safe integer (got ${String(e.quantity)})`);const r=e.idempotencyKey??crypto.randomUUID(),a=e.mode==="set";if(a&&n.capabilities.usageMetering&&n.reportUsage)throw new f("VALIDATION_ERROR",`track(): \`mode: "set"\` is not supported on "${n.identifier}", whose meter is additive — a lowering set cannot be forwarded, so the provider would over-bill against the local period total. Use \`mode: "add"\`.`);const i=a?await s.listSubscriptionsByReference(e.referenceId):void 0,d=i===void 0?0:await s.sumUsage(e.referenceId,e.featureId,g(i));if(a?t===d:t===0)return{recorded:!1,reportedToProvider:!1};const c=t;if(!await s.recordUsage({createdAt:$(),featureId:e.featureId,idempotencyKey:r,...a?{mode:"set"}:{},provider:n.identifier,quantity:t,referenceId:e.referenceId,reportedToProvider:!1}))return{recorded:!1,reportedToProvider:!1};if(c<=0||!n.capabilities.usageMetering||!n.reportUsage)return{recorded:!0,reportedToProvider:!1};try{const R=await s.getCustomerByReference(n.identifier,e.referenceId);return await n.reportUsage({customerId:R?.id,featureId:e.featureId,idempotencyKey:r,quantity:c,referenceId:e.referenceId}),await s.markUsageReported(n.identifier,r),{recorded:!0,reportedToProvider:!0}}catch{return C(o.observability,{featureId:e.featureId,provider:n.identifier,referenceId:e.referenceId,type:"usage.report_failed"}),{recorded:!0,reportedToProvider:!1}}}}};export{H as createPayment,G as webhookResponse};
@@ -1 +1 @@
1
- import{createPayment as i}from"./createPayment-Dimez2zY.mjs";import{createDatabasePaymentStore as s}from"./createDatabasePaymentStore-BGcHX1uq.mjs";const o=r=>({delete:async t=>r.delete(t),findFirst:async(t,e)=>await r.findFirst(t,{where:e}),findMany:async(t,e,a)=>{const n=await r.findMany(t,{...a,where:e});return{cursor:n.continueCursor??void 0,rows:n.page}},insert:async(t,e)=>r.insert(t,e),patch:async(t,e)=>r.patch(t,e)}),y=(r,t)=>{const e=r.auth?.userId??void 0;return i({adapter:t.adapter,authorize:t.authorize??(a=>a.trim()!==""&&e!==void 0&&a===e),entitlements:t.entitlements,observability:t.observability,store:s(o(r.db))})};export{o as lunoraDatabaseToPaymentDatabase,y as paymentsFromContext};
1
+ import{createPayment as i}from"./createPayment-BB3ri82t.mjs";import{createDatabasePaymentStore as s}from"./createDatabasePaymentStore-DG4P5AzL.mjs";const o=r=>({delete:async t=>r.delete(t),findFirst:async(t,e)=>await r.findFirst(t,{where:e}),findMany:async(t,e,a)=>{const n=await r.findMany(t,{...a,where:e});return{cursor:n.continueCursor??void 0,rows:n.page}},insert:async(t,e)=>r.insert(t,e),patch:async(t,e)=>r.patch(t,e)}),y=(r,t)=>{const e=r.auth?.userId??void 0;return i({adapter:t.adapter,authorize:t.authorize??(a=>a.trim()!==""&&e!==void 0&&a===e),entitlements:t.entitlements,observability:t.observability,store:s(o(r.db))})};export{o as lunoraDatabaseToPaymentDatabase,y as paymentsFromContext};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/payment",
3
- "version": "1.0.0-alpha.132",
3
+ "version": "1.0.0-alpha.134",
4
4
  "description": "Provider-agnostic payments for Lunora: Stripe-first adapter, webhook sync, and subscription/payment state machine",
5
5
  "keywords": [
6
6
  "billing",
@@ -72,7 +72,7 @@
72
72
  },
73
73
  "dependencies": {
74
74
  "@lunora/errors": "1.0.0-alpha.39",
75
- "@lunora/server": "1.0.0-alpha.130",
75
+ "@lunora/server": "1.0.0-alpha.132",
76
76
  "@lunora/values": "1.0.0-alpha.49",
77
77
  "dinero.js": "2.0.2"
78
78
  },
@@ -1 +0,0 @@
1
- import{LunoraPaymentError as P}from"./LunoraPaymentError-BSxyhWgu.mjs";import{localRefundKey as A,LOCAL_REFUND_CLAIM_TYPE as E}from"./idempotencyKey-qQJLcEQX.mjs";import{zeroMoney as o,compareMoney as l,addMoney as _}from"./addMoney-B4ufWAnD.mjs";import{n as a}from"./observability-BteZ2dJS.mjs";import{nextPaymentState as S,nextSubscriptionState as w}from"./PAYMENT_TERMINAL_STATES-C-ICZkun.mjs";const b={"payment.authorized":"authorize","payment.captured":"capture","payment.failed":"fail","payment.refunded":"refund"},h={"subscription.active":"active","subscription.canceled":"canceled","subscription.past_due":"past_due","subscription.paused":"paused"},T="orphan-retry:",R="marker.orphan_retry",k={"subscription.active":"activate","subscription.canceled":"cancel","subscription.past_due":"mark_past_due","subscription.paused":"pause"},O=(t,e)=>l(t,e)>0?t:e,Y=new Set(["authorized","initiated"]),N=async(t,e,r)=>{if(!r||!e.sessionId||!e.amount||e.amountKind==="absolute")return e;const d=A(e.sessionId,e.refundId,e.amount),n=await t.markEventProcessed(e.provider,d,E);return await t.releaseEvent(e.provider,d),n?e:{...e,amount:o(e.amount.currency)}},v=(t,e)=>{if(!e.amount||t.refundedAmount.currency!==e.amount.currency||t.capturedAmount.currency!==e.amount.currency)return;const r=e.amountKind==="absolute"?O(e.amount,t.refundedAmount):_(t.refundedAmount,e.amount);if(!(l(r,t.capturedAmount)>0))return r},M=(t,e)=>{if(!t)return"refund";const r=v(t,e);return r&&l(r,t.capturedAmount)<0?"partial_refund":"refund"},q=async(t,e,r)=>{if(!e.sessionId)return{applied:!1,reason:"unhandled"};const d=await t.getPaymentSession(e.provider,e.sessionId),n=d?.state??"initiated",u=Date.now(),s=e.amount?.currency??d?.amount.currency??"USD",c=r==="refund"?await N(t,e,d):e,p=r==="refund"?M(d,c):r,I=S(n,p);if(!I)return{applied:!1,reason:r==="refund"&&Y.has(n)?"orphaned":"illegal_transition"};const i=d??{amount:e.amount??o(s),capturedAmount:o(s),createdAt:u,id:e.sessionId,provider:e.provider,referenceId:e.referenceId??"",refundedAmount:o(s),state:n,updatedAt:u};let{capturedAmount:m,refundedAmount:y}=i;if(p==="capture"&&e.amount&&(m=e.amount),(p==="partial_refund"||p==="refund")&&c.amount){const f=v(i,c);if(!f)return{applied:!1,reason:"invalid_refund_amount"};y=f}return await t.upsertPaymentSession({...i,capturedAmount:m,referenceId:e.referenceId??i.referenceId,refundedAmount:y,state:I,updatedAt:u}),{applied:!0,reason:"ok"}},C=(t,e,r)=>{if(e==="active"){if(t==="active")return"renew";if(t==="paused")return"resume"}return k[r]},g=async(t,e)=>{if(!e.subscriptionId)return{applied:!1,reason:"unhandled"};const r=await t.getSubscription(e.provider,e.subscriptionId),d=Date.now();if(e.type==="subscription.updated")return r?(await t.upsertSubscription({...r,cancelAtPeriodEnd:e.cancelAtPeriodEnd??r.cancelAtPeriodEnd,currentPeriodEnd:e.currentPeriodEnd??r.currentPeriodEnd,currentPeriodStart:e.currentPeriodStart??r.currentPeriodStart,priceId:e.priceId??r.priceId,priceIds:e.priceIds??r.priceIds,quantity:e.quantity??r.quantity,updatedAt:d}),{applied:!0,reason:"ok"}):{applied:!1,reason:"orphaned"};const n=h[e.type];if(!n)return{applied:!1,reason:"unhandled"};if(!r)return await t.upsertSubscription({cancelAtPeriodEnd:e.cancelAtPeriodEnd??!1,createdAt:d,currentPeriodEnd:e.currentPeriodEnd,currentPeriodStart:e.currentPeriodStart??d,id:e.subscriptionId,priceId:e.priceId??"",priceIds:e.priceIds,provider:e.provider,quantity:e.quantity??1,referenceId:e.referenceId??"",state:n,updatedAt:d}),{applied:!0,reason:"ok"};const u=C(r.state,n,e.type),s=u?w(r.state,u):void 0;return s?(await t.upsertSubscription({...r,cancelAtPeriodEnd:e.cancelAtPeriodEnd??r.cancelAtPeriodEnd,currentPeriodEnd:e.currentPeriodEnd??r.currentPeriodEnd,currentPeriodStart:e.currentPeriodStart??r.currentPeriodStart,priceId:e.priceId??r.priceId,priceIds:e.priceIds??r.priceIds,quantity:e.quantity??r.quantity,state:s,updatedAt:d}),{applied:!0,reason:"ok"}):{applied:!1,reason:"illegal_transition"}},U=async(t,e,r)=>{if(e.type==="unhandled")return{applied:!1,reason:"unhandled"};if(!e.eventId?.trim())throw new P("WEBHOOK_EVENT_ID_MISSING",`webhook event id is missing or blank for provider "${e.provider}"`);if(!await t.markEventProcessed(e.provider,e.eventId,e.type))return a(r,{eventId:e.eventId,provider:e.provider,type:"webhook.duplicate"}),{applied:!1,reason:"duplicate"};const n=b[e.type];let u;try{u=n?await q(t,e,n):await g(t,e)}catch(s){throw await t.releaseEvent(e.provider,e.eventId),s}return u.reason==="orphaned"&&(await t.markEventProcessed(e.provider,`${T}${e.eventId}`,R)?await t.releaseEvent(e.provider,e.eventId):u={applied:!1,reason:"unhandled"}),a(r,{action:e.type,eventId:e.eventId,provider:e.provider,reason:u.reason,type:"webhook.applied"}),e.type==="payment.failed"?a(r,{provider:e.provider,referenceId:e.referenceId,sessionId:e.sessionId,type:"payment.failed"}):e.type==="subscription.past_due"&&a(r,{provider:e.provider,referenceId:e.referenceId,subscriptionId:e.subscriptionId,type:"subscription.past_due"}),u};export{U as default};
@@ -1 +0,0 @@
1
- import{money as y}from"./addMoney-B4ufWAnD.mjs";import{foldUsage as m}from"./MemoryPaymentStore-CZbrrEb1.mjs";const o=(e,n)=>typeof e[n]=="string"?e[n]:"",l=(e,n)=>typeof e[n]=="string"?e[n]:void 0,c=(e,n)=>typeof e[n]=="number"?e[n]:0,v=(e,n)=>typeof e[n]=="number"?e[n]:void 0,A=(e,n)=>e[n]===!0,S=(e,n)=>{const t=e[n];return Array.isArray(t)?t.filter(d=>typeof d=="string"):void 0},f=(e,n)=>{const t=e[n];return typeof t=="bigint"?t:typeof t=="number"||typeof t=="string"?BigInt(t):0n},P=e=>({createdAt:e.createdAt,email:e.email,provider:e.provider,providerCustomerId:e.id,referenceId:e.referenceId}),E=e=>({createdAt:c(e,"createdAt"),email:l(e,"email"),id:o(e,"providerCustomerId"),provider:o(e,"provider"),referenceId:o(e,"referenceId")}),M=e=>({amountMinor:e.amount.minorUnits,capturedMinor:e.capturedAmount.minorUnits,createdAt:e.createdAt,currency:e.amount.currency,provider:e.provider,providerSessionId:e.id,referenceId:e.referenceId,refundedMinor:e.refundedAmount.minorUnits,state:e.state,updatedAt:e.updatedAt}),T=e=>{const n=o(e,"currency");return{amount:y(f(e,"amountMinor"),n),capturedAmount:y(f(e,"capturedMinor"),n),createdAt:c(e,"createdAt"),id:o(e,"providerSessionId"),provider:o(e,"provider"),referenceId:o(e,"referenceId"),refundedAmount:y(f(e,"refundedMinor"),n),state:o(e,"state"),updatedAt:c(e,"updatedAt")}},q=e=>({cancelAtPeriodEnd:e.cancelAtPeriodEnd,createdAt:e.createdAt,currentPeriodEnd:e.currentPeriodEnd,currentPeriodStart:e.currentPeriodStart,priceId:e.priceId,priceIds:e.priceIds===void 0?void 0:[...e.priceIds],provider:e.provider,providerSubscriptionId:e.id,quantity:e.quantity,referenceId:e.referenceId,state:e.state,updatedAt:e.updatedAt}),I=e=>({cancelAtPeriodEnd:A(e,"cancelAtPeriodEnd"),createdAt:c(e,"createdAt"),currentPeriodEnd:v(e,"currentPeriodEnd"),currentPeriodStart:v(e,"currentPeriodStart"),id:o(e,"providerSubscriptionId"),priceId:o(e,"priceId"),priceIds:S(e,"priceIds"),provider:o(e,"provider"),quantity:c(e,"quantity"),referenceId:o(e,"referenceId"),state:o(e,"state"),updatedAt:c(e,"updatedAt")}),K=e=>({createdAt:c(e,"createdAt"),featureId:o(e,"featureId"),idempotencyKey:o(e,"idempotencyKey"),mode:e.mode==="set"?"set":"add",provider:o(e,"provider"),quantity:c(e,"quantity"),referenceId:o(e,"referenceId"),reportedToProvider:A(e,"reportedToProvider")}),U=e=>({createdAt:e.createdAt,featureId:e.featureId,idempotencyKey:e.idempotencyKey,...e.mode==="set"?{mode:"set"}:{},provider:e.provider,quantity:e.quantity,referenceId:e.referenceId,reportedToProvider:e.reportedToProvider}),h=e=>{const n=async(t,d,r)=>{const a=await e.findFirst(t,d);if(a){await e.patch(a._id,r);return}await e.insert(t,r)};return{getCustomerByReference:async(t,d)=>{const r=await e.findFirst("customers",{provider:t,referenceId:d});return r?E(r):void 0},getPaymentSession:async(t,d)=>{const r=await e.findFirst("paymentSessions",{provider:t,providerSessionId:d});return r?T(r):void 0},getSubscription:async(t,d)=>{const r=await e.findFirst("subscriptions",{provider:t,providerSubscriptionId:d});return r?I(r):void 0},listSubscriptionsByReference:async t=>{const{rows:d}=await e.findMany("subscriptions",{referenceId:t});return d.map(r=>I(r))},listUnreportedUsage:async(t,d)=>{const r=Math.max(0,Math.floor(d));if(r===0)return[];const a=[{createdAt:"asc"},{idempotencyKey:"asc"}],s=[];let i;do{const u=await e.findMany("usageEvents",{provider:t,reportedToProvider:!1},{cursor:i,limit:r,orderBy:a});for(const g of u.rows){const p=K(g);if(p.mode!=="set"&&p.quantity>0&&(s.push(p),s.length===r))return s}i=u.cursor}while(i!==void 0);return s},markEventProcessed:async(t,d,r)=>await e.findFirst("events",{provider:t,providerEventId:d})?!1:(await e.insert("events",{processedAt:Date.now(),provider:t,providerEventId:d,type:r}),!0),releaseEvent:async(t,d)=>{const r=await e.findFirst("events",{provider:t,providerEventId:d});r&&await e.delete(r._id)},markUsageReported:async(t,d)=>{const r=await e.findFirst("usageEvents",{idempotencyKey:d,provider:t});r&&await e.patch(r._id,{reportedToProvider:!0})},recordUsage:async t=>await e.findFirst("usageEvents",{idempotencyKey:t.idempotencyKey,provider:t.provider})?!1:(await e.insert("usageEvents",U(t)),!0),sumUsage:async(t,d,r)=>{const{rows:a}=await e.findMany("usageEvents",{featureId:d,referenceId:t}),s=a.filter(i=>c(i,"createdAt")>=r).map(i=>({createdAt:c(i,"createdAt"),idempotencyKey:typeof i.idempotencyKey=="string"?i.idempotencyKey:"",mode:i.mode==="set"?"set":"add",quantity:c(i,"quantity")}));return m(s)},sumUsageByFeature:async(t,d,r)=>{const{rows:a}=await e.findMany("usageEvents",{referenceId:t}),s=new Map(d.map(i=>[i,[]]));for(const i of a)c(i,"createdAt")<r||s.get(typeof i.featureId=="string"?i.featureId:"")?.push({createdAt:c(i,"createdAt"),idempotencyKey:typeof i.idempotencyKey=="string"?i.idempotencyKey:"",mode:i.mode==="set"?"set":"add",quantity:c(i,"quantity")});return new Map([...s].map(([i,u])=>[i,m(u)]))},upsertCustomer:async t=>n("customers",{provider:t.provider,referenceId:t.referenceId},P(t)),upsertPaymentSession:async t=>n("paymentSessions",{provider:t.provider,providerSessionId:t.id},M(t)),upsertSubscription:async t=>n("subscriptions",{provider:t.provider,providerSubscriptionId:t.id},q(t))}};export{h as createDatabasePaymentStore};