@lunora/payment 1.0.0-alpha.31 → 1.0.0-alpha.32
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 +1 -14
- package/dist/packem_shared/LunoraPaymentError-DWQ-v_NC.mjs +1 -0
- package/dist/packem_shared/MemoryPaymentStore-BICI1RdZ.mjs +1 -0
- package/dist/packem_shared/PAYMENT_TERMINAL_STATES-DVNbZxBy.mjs +1 -0
- package/dist/packem_shared/addMoney-BiiMn67T.mjs +1 -0
- package/dist/packem_shared/applyWebhookAction-BMVcvJ3t.mjs +1 -0
- package/dist/packem_shared/constantTimeEqual-BpVBvmN-.mjs +1 -0
- package/dist/packem_shared/createAdapterRegistry-CNIpj46I.mjs +1 -0
- package/dist/packem_shared/createDatabasePaymentStore-IG8Cb3_L.mjs +1 -0
- package/dist/packem_shared/createPayment-CW_iR6Hg.mjs +1 -0
- package/dist/packem_shared/entitlementsForReference-BbEOKu5d.mjs +1 -0
- package/dist/packem_shared/idempotencyKey-CfJKqcE3.mjs +1 -0
- package/dist/packem_shared/json-D85Nbd92.mjs +1 -0
- package/dist/packem_shared/lunoraDatabaseToPaymentDatabase-BFBlKLG3.mjs +1 -0
- package/dist/packem_shared/not-supported-DwGy4SQL.mjs +1 -0
- package/dist/packem_shared/observability-C9tz8mj_.mjs +1 -0
- package/dist/packem_shared/paymentTables-BMtUrXkM.mjs +1 -0
- package/dist/packem_shared/reconcile-DDdfivoy.mjs +1 -0
- package/dist/packem_shared/subscription-event-C-71ye9J.mjs +1 -0
- package/dist/providers/autumn-features.mjs +1 -95
- package/dist/providers/autumn.mjs +1 -275
- package/dist/providers/creem.mjs +1 -236
- package/dist/providers/dodopayments.mjs +1 -259
- package/dist/providers/polar.mjs +1 -231
- package/dist/providers/stripe.mjs +1 -271
- package/package.json +2 -2
- package/dist/packem_shared/LunoraPaymentError-BeQlhkZj.mjs +0 -19
- package/dist/packem_shared/MemoryPaymentStore-DvgdWa3C.mjs +0 -72
- package/dist/packem_shared/PAYMENT_TERMINAL_STATES-DrxV0clv.mjs +0 -26
- package/dist/packem_shared/addMoney-jSh_7TfF.mjs +0 -60
- package/dist/packem_shared/applyWebhookAction-CLAx4svt.mjs +0 -177
- package/dist/packem_shared/constantTimeEqual-BQjoW85H.mjs +0 -71
- package/dist/packem_shared/createAdapterRegistry-Ds7bx_TK.mjs +0 -24
- package/dist/packem_shared/createDatabasePaymentStore-C0NUCj1H.mjs +0 -172
- package/dist/packem_shared/createPayment-CKQAznOL.mjs +0 -200
- package/dist/packem_shared/entitlementsForReference-CzZGXPoZ.mjs +0 -51
- package/dist/packem_shared/idempotencyKey-BzaHGdHD.mjs +0 -9
- package/dist/packem_shared/json-D0Cb5aMv.mjs +0 -29
- package/dist/packem_shared/lunoraDatabaseToPaymentDatabase-DTsgPvfO.mjs +0 -29
- package/dist/packem_shared/not-supported-C0onRyia.mjs +0 -7
- package/dist/packem_shared/observability-CvhJ205g.mjs +0 -11
- package/dist/packem_shared/paymentTables-D4TbhaIk.mjs +0 -59
- package/dist/packem_shared/reconcile-qE3ERi3D.mjs +0 -85
- package/dist/packem_shared/subscription-event-CwEsWRCK.mjs +0 -17
package/dist/providers/polar.mjs
CHANGED
|
@@ -1,231 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { money, zeroMoney } from '../packem_shared/addMoney-jSh_7TfF.mjs';
|
|
3
|
-
import { verifyStandardWebhook } from '../packem_shared/constantTimeEqual-BQjoW85H.mjs';
|
|
4
|
-
import { m as makeNotSupported } from '../packem_shared/not-supported-C0onRyia.mjs';
|
|
5
|
-
import { s as stateToEventType } from '../packem_shared/subscription-event-CwEsWRCK.mjs';
|
|
6
|
-
|
|
7
|
-
const toEpochMs = (value) => {
|
|
8
|
-
if (value instanceof Date) {
|
|
9
|
-
return value.getTime();
|
|
10
|
-
}
|
|
11
|
-
return parseTimestamp(typeof value === "string" ? value : void 0);
|
|
12
|
-
};
|
|
13
|
-
const PAYMENT_STATE_BY_POLAR_ORDER_STATUS = {
|
|
14
|
-
draft: "initiated",
|
|
15
|
-
paid: "captured",
|
|
16
|
-
partially_refunded: "partially_refunded",
|
|
17
|
-
pending: "initiated",
|
|
18
|
-
refunded: "refunded",
|
|
19
|
-
void: "canceled"
|
|
20
|
-
};
|
|
21
|
-
const SUBSCRIPTION_STATE_BY_POLAR_STATUS = {
|
|
22
|
-
active: "active",
|
|
23
|
-
canceled: "canceled",
|
|
24
|
-
// SECURITY: `incomplete` (first payment not completed) must not map to an
|
|
25
|
-
// entitling state — see the equivalent note in the Stripe adapter. Treat it as
|
|
26
|
-
// non-entitling `past_due`; reserve `trialing` for a genuine trial.
|
|
27
|
-
incomplete: "past_due",
|
|
28
|
-
incomplete_expired: "canceled",
|
|
29
|
-
past_due: "past_due",
|
|
30
|
-
trialing: "trialing",
|
|
31
|
-
unpaid: "past_due"
|
|
32
|
-
};
|
|
33
|
-
const notSupported = makeNotSupported("polar (merchant-of-record)");
|
|
34
|
-
const subscriptionFromPolar = (input) => {
|
|
35
|
-
const subscription = asRecord(input);
|
|
36
|
-
const now = Date.now();
|
|
37
|
-
return {
|
|
38
|
-
cancelAtPeriodEnd: readBoolean(subscription, "cancelAtPeriodEnd") ?? false,
|
|
39
|
-
createdAt: now,
|
|
40
|
-
currentPeriodEnd: toEpochMs(subscription.currentPeriodEnd),
|
|
41
|
-
currentPeriodStart: toEpochMs(subscription.currentPeriodStart),
|
|
42
|
-
id: readString(subscription, "id") ?? "",
|
|
43
|
-
priceId: readString(subscription, "productId") ?? "",
|
|
44
|
-
provider: "polar",
|
|
45
|
-
quantity: 1,
|
|
46
|
-
referenceId: referenceFromMetadata(subscription) ?? "",
|
|
47
|
-
state: SUBSCRIPTION_STATE_BY_POLAR_STATUS[readString(subscription, "status") ?? ""] ?? "active",
|
|
48
|
-
updatedAt: now
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
|
-
const orderToSession = (input) => {
|
|
52
|
-
const order = asRecord(input);
|
|
53
|
-
const now = Date.now();
|
|
54
|
-
const currency = readString(order, "currency") ?? "usd";
|
|
55
|
-
const amount = money(BigInt(Math.round(readNumber(order, "totalAmount") ?? readNumber(order, "amount") ?? 0)), currency);
|
|
56
|
-
const state = PAYMENT_STATE_BY_POLAR_ORDER_STATUS[readString(order, "status") ?? ""] ?? "initiated";
|
|
57
|
-
const settled = state === "captured" || state === "partially_refunded" || state === "refunded";
|
|
58
|
-
return {
|
|
59
|
-
amount,
|
|
60
|
-
capturedAmount: settled ? amount : zeroMoney(currency),
|
|
61
|
-
createdAt: now,
|
|
62
|
-
id: readString(order, "id") ?? "",
|
|
63
|
-
provider: "polar",
|
|
64
|
-
// Polar copies checkout metadata onto the order, so recover the framework-pinned `referenceId`
|
|
65
|
-
// rather than blanking it — a reconcile sweep would otherwise orphan the row from `by_reference`.
|
|
66
|
-
referenceId: referenceFromMetadata(order) ?? "",
|
|
67
|
-
refundedAmount: state === "refunded" ? amount : zeroMoney(currency),
|
|
68
|
-
state,
|
|
69
|
-
updatedAt: now
|
|
70
|
-
};
|
|
71
|
-
};
|
|
72
|
-
const mapEvent = (eventId, eventType, object) => {
|
|
73
|
-
const base = { eventId, provider: "polar", raw: { object, type: eventType } };
|
|
74
|
-
const currency = readString(object, "currency") ?? "usd";
|
|
75
|
-
switch (eventType) {
|
|
76
|
-
case "order.created":
|
|
77
|
-
case "order.paid": {
|
|
78
|
-
if (eventType === "order.created" && PAYMENT_STATE_BY_POLAR_ORDER_STATUS[readString(object, "status") ?? ""] !== "captured") {
|
|
79
|
-
return { ...base, type: "unhandled" };
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
...base,
|
|
83
|
-
amount: money(BigInt(readNumber(object, "total_amount") ?? readNumber(object, "amount") ?? 0), currency),
|
|
84
|
-
customerId: readString(object, "customer_id"),
|
|
85
|
-
referenceId: referenceFromMetadata(object),
|
|
86
|
-
sessionId: readString(object, "id"),
|
|
87
|
-
subscriptionId: readString(object, "subscription_id"),
|
|
88
|
-
type: "payment.captured"
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
case "refund.created": {
|
|
92
|
-
return {
|
|
93
|
-
...base,
|
|
94
|
-
amount: money(BigInt(readNumber(object, "amount") ?? 0), currency),
|
|
95
|
-
referenceId: referenceFromMetadata(object),
|
|
96
|
-
sessionId: readString(object, "order_id") ?? readString(object, "id"),
|
|
97
|
-
type: "payment.refunded"
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
case "subscription.active":
|
|
101
|
-
case "subscription.canceled":
|
|
102
|
-
case "subscription.created":
|
|
103
|
-
case "subscription.revoked":
|
|
104
|
-
case "subscription.uncanceled":
|
|
105
|
-
case "subscription.updated": {
|
|
106
|
-
const type = eventType === "subscription.revoked" ? "subscription.canceled" : stateToEventType(SUBSCRIPTION_STATE_BY_POLAR_STATUS[readString(object, "status") ?? ""]);
|
|
107
|
-
return {
|
|
108
|
-
...base,
|
|
109
|
-
cancelAtPeriodEnd: readBoolean(object, "cancel_at_period_end"),
|
|
110
|
-
currentPeriodEnd: parseTimestamp(readString(object, "current_period_end")),
|
|
111
|
-
currentPeriodStart: parseTimestamp(readString(object, "current_period_start")),
|
|
112
|
-
customerId: readString(object, "customer_id"),
|
|
113
|
-
priceId: readString(object, "product_id"),
|
|
114
|
-
referenceId: referenceFromMetadata(object),
|
|
115
|
-
subscriptionId: readString(object, "id"),
|
|
116
|
-
type
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
default: {
|
|
120
|
-
return { ...base, type: "unhandled" };
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
const createPolarAdapter = (options) => {
|
|
125
|
-
const { webhookSecret } = options;
|
|
126
|
-
const client = options.client;
|
|
127
|
-
return {
|
|
128
|
-
cancelPayment: () => notSupported("manual payment cancellation"),
|
|
129
|
-
cancelSubscription: async (subscriptionId, cancelOptions) => {
|
|
130
|
-
const subscription = cancelOptions?.atPeriodEnd ? await client.subscriptions.update({ id: subscriptionId, subscriptionUpdate: { cancelAtPeriodEnd: true } }) : await client.subscriptions.revoke({ id: subscriptionId });
|
|
131
|
-
return subscriptionFromPolar(subscription);
|
|
132
|
-
},
|
|
133
|
-
capabilities: { merchantOfRecord: true, portal: true, usageMetering: true },
|
|
134
|
-
capturePayment: (_input) => notSupported("manual capture"),
|
|
135
|
-
createCheckout: async (input) => {
|
|
136
|
-
const checkout = await client.checkouts.create({
|
|
137
|
-
// Bind the checkout to the customer the facade already reused/minted for this reference,
|
|
138
|
-
// else Polar mints a SECOND orphan customer at completion (leaving the stored customer
|
|
139
|
-
// with no subscription — its portal empty, its metered usage misrouted). `externalCustomerId`
|
|
140
|
-
// carries our reference for the direct-adapter path; `customerEmail` pre-fills when neither is known.
|
|
141
|
-
customerEmail: input.customerId ? void 0 : input.email,
|
|
142
|
-
customerId: input.customerId,
|
|
143
|
-
externalCustomerId: input.referenceId,
|
|
144
|
-
// Pin the framework-controlled `referenceId` LAST so caller metadata can never override it.
|
|
145
|
-
metadata: { ...input.metadata, referenceId: input.referenceId },
|
|
146
|
-
products: [input.priceId],
|
|
147
|
-
// Polar shows a back button to this URL; map the caller's cancel URL onto it.
|
|
148
|
-
returnUrl: input.cancelUrl,
|
|
149
|
-
successUrl: input.successUrl
|
|
150
|
-
});
|
|
151
|
-
return { id: checkout.id, provider: "polar", url: checkout.url };
|
|
152
|
-
},
|
|
153
|
-
createPortalSession: async (input) => {
|
|
154
|
-
const session = await client.customerSessions.create({ customerId: input.customerId });
|
|
155
|
-
return { url: session.customerPortalUrl };
|
|
156
|
-
},
|
|
157
|
-
getOrCreateCustomer: async (ref) => {
|
|
158
|
-
const customer = await client.customers.create({
|
|
159
|
-
email: ref.email ?? "",
|
|
160
|
-
externalId: ref.referenceId,
|
|
161
|
-
metadata: { ...ref.metadata, referenceId: ref.referenceId },
|
|
162
|
-
type: "individual"
|
|
163
|
-
});
|
|
164
|
-
return { createdAt: Date.now(), email: customer.email ?? void 0, id: customer.id, provider: "polar", referenceId: ref.referenceId };
|
|
165
|
-
},
|
|
166
|
-
getPaymentStatus: async (sessionId) => orderToSession(await client.orders.get({ id: sessionId })),
|
|
167
|
-
getSubscriptionStatus: async (subscriptionId) => subscriptionFromPolar(await client.subscriptions.get({ id: subscriptionId })),
|
|
168
|
-
identifier: "polar",
|
|
169
|
-
parseWebhook: async ({ headers, payload }) => {
|
|
170
|
-
const webhookId = headers.get("webhook-id") ?? "";
|
|
171
|
-
await verifyStandardWebhook({
|
|
172
|
-
payload,
|
|
173
|
-
secret: webhookSecret,
|
|
174
|
-
toleranceSeconds: options.webhookToleranceSeconds,
|
|
175
|
-
webhookId,
|
|
176
|
-
webhookSignature: headers.get("webhook-signature") ?? "",
|
|
177
|
-
webhookTimestamp: headers.get("webhook-timestamp") ?? ""
|
|
178
|
-
});
|
|
179
|
-
const event = asRecord(JSON.parse(payload));
|
|
180
|
-
return mapEvent(webhookId, readString(event, "type") ?? "", asRecord(event.data));
|
|
181
|
-
},
|
|
182
|
-
refundPayment: async (input) => {
|
|
183
|
-
const order = input.amount ? void 0 : await client.orders.get({ id: input.sessionId });
|
|
184
|
-
const currency = input.amount?.currency ?? order?.currency ?? "usd";
|
|
185
|
-
const amountMinor = input.amount ? Number(input.amount.minorUnits) : order?.totalAmount ?? 0;
|
|
186
|
-
await client.refunds.create({
|
|
187
|
-
amount: amountMinor,
|
|
188
|
-
orderId: input.sessionId,
|
|
189
|
-
// `reason` is an open enum; a caller-supplied string is cast onto it (defaults to customer_request).
|
|
190
|
-
reason: input.reason ?? "customer_request"
|
|
191
|
-
});
|
|
192
|
-
const refundedAmount = input.amount ?? money(BigInt(Math.round(amountMinor)), currency);
|
|
193
|
-
return {
|
|
194
|
-
amount: refundedAmount,
|
|
195
|
-
capturedAmount: refundedAmount,
|
|
196
|
-
createdAt: Date.now(),
|
|
197
|
-
id: input.sessionId,
|
|
198
|
-
provider: "polar",
|
|
199
|
-
referenceId: "",
|
|
200
|
-
refundedAmount,
|
|
201
|
-
state: "refunded",
|
|
202
|
-
updatedAt: Date.now()
|
|
203
|
-
};
|
|
204
|
-
},
|
|
205
|
-
reportUsage: async (input) => {
|
|
206
|
-
await client.events.ingest({
|
|
207
|
-
events: [
|
|
208
|
-
{
|
|
209
|
-
externalCustomerId: input.referenceId,
|
|
210
|
-
metadata: { value: input.quantity },
|
|
211
|
-
name: input.featureId,
|
|
212
|
-
timestamp: input.timestamp === void 0 ? void 0 : new Date(input.timestamp)
|
|
213
|
-
}
|
|
214
|
-
]
|
|
215
|
-
});
|
|
216
|
-
},
|
|
217
|
-
resumeSubscription: async (subscriptionId) => {
|
|
218
|
-
const subscription = await client.subscriptions.update({ id: subscriptionId, subscriptionUpdate: { cancelAtPeriodEnd: false } });
|
|
219
|
-
return subscriptionFromPolar(subscription);
|
|
220
|
-
},
|
|
221
|
-
updateSubscription: async (subscriptionId, patch) => {
|
|
222
|
-
const subscription = await client.subscriptions.update({
|
|
223
|
-
id: subscriptionId,
|
|
224
|
-
subscriptionUpdate: patch.priceId ? { productId: patch.priceId } : {}
|
|
225
|
-
});
|
|
226
|
-
return subscriptionFromPolar(subscription);
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
export { createPolarAdapter };
|
|
1
|
+
import{a as p,r as n,f as i,e as w,p as I,b as s}from"../packem_shared/json-D85Nbd92.mjs";import{money as m,zeroMoney as f}from"../packem_shared/addMoney-BiiMn67T.mjs";import{verifyStandardWebhook as _}from"../packem_shared/constantTimeEqual-BpVBvmN-.mjs";import{e as h}from"../packem_shared/not-supported-DwGy4SQL.mjs";import{i as P}from"../packem_shared/subscription-event-C-71ye9J.mjs";const y=o=>o instanceof Date?o.getTime():I(typeof o=="string"?o:void 0),v={draft:"initiated",paid:"captured",partially_refunded:"partially_refunded",pending:"initiated",refunded:"refunded",void:"canceled"},g={active:"active",canceled:"canceled",incomplete:"past_due",incomplete_expired:"canceled",past_due:"past_due",trialing:"trialing",unpaid:"past_due"},b=h("polar (merchant-of-record)"),u=o=>{const a=p(o),t=Date.now();return{cancelAtPeriodEnd:w(a,"cancelAtPeriodEnd")??!1,createdAt:t,currentPeriodEnd:y(a.currentPeriodEnd),currentPeriodStart:y(a.currentPeriodStart),id:n(a,"id")??"",priceId:n(a,"productId")??"",provider:"polar",quantity:1,referenceId:i(a)??"",state:g[n(a,"status")??""]??"active",updatedAt:t}},A=o=>{const a=p(o),t=Date.now(),e=n(a,"currency")??"usd",r=m(BigInt(Math.round(s(a,"totalAmount")??s(a,"amount")??0)),e),d=v[n(a,"status")??""]??"initiated";return{amount:r,capturedAmount:d==="captured"||d==="partially_refunded"||d==="refunded"?r:f(e),createdAt:t,id:n(a,"id")??"",provider:"polar",referenceId:i(a)??"",refundedAmount:d==="refunded"?r:f(e),state:d,updatedAt:t}},S=(o,a,t)=>{const e={eventId:o,provider:"polar",raw:{object:t,type:a}},r=n(t,"currency")??"usd";switch(a){case"order.created":case"order.paid":return a==="order.created"&&v[n(t,"status")??""]!=="captured"?{...e,type:"unhandled"}:{...e,amount:m(BigInt(s(t,"total_amount")??s(t,"amount")??0),r),customerId:n(t,"customer_id"),referenceId:i(t),sessionId:n(t,"id"),subscriptionId:n(t,"subscription_id"),type:"payment.captured"};case"refund.created":return{...e,amount:m(BigInt(s(t,"amount")??0),r),referenceId:i(t),sessionId:n(t,"order_id")??n(t,"id"),type:"payment.refunded"};case"subscription.active":case"subscription.canceled":case"subscription.created":case"subscription.revoked":case"subscription.uncanceled":case"subscription.updated":{const d=a==="subscription.revoked"?"subscription.canceled":P(g[n(t,"status")??""]);return{...e,cancelAtPeriodEnd:w(t,"cancel_at_period_end"),currentPeriodEnd:I(n(t,"current_period_end")),currentPeriodStart:I(n(t,"current_period_start")),customerId:n(t,"customer_id"),priceId:n(t,"product_id"),referenceId:i(t),subscriptionId:n(t,"id"),type:d}}default:return{...e,type:"unhandled"}}},B=o=>{const{webhookSecret:a}=o,t=o.client;return{cancelPayment:()=>b("manual payment cancellation"),cancelSubscription:async(e,r)=>{const d=r?.atPeriodEnd?await t.subscriptions.update({id:e,subscriptionUpdate:{cancelAtPeriodEnd:!0}}):await t.subscriptions.revoke({id:e});return u(d)},capabilities:{merchantOfRecord:!0,portal:!0,usageMetering:!0},capturePayment:e=>b("manual capture"),createCheckout:async e=>{const r=await t.checkouts.create({customerEmail:e.customerId?void 0:e.email,customerId:e.customerId,externalCustomerId:e.referenceId,metadata:{...e.metadata,referenceId:e.referenceId},products:[e.priceId],returnUrl:e.cancelUrl,successUrl:e.successUrl});return{id:r.id,provider:"polar",url:r.url}},createPortalSession:async e=>({url:(await t.customerSessions.create({customerId:e.customerId})).customerPortalUrl}),getOrCreateCustomer:async e=>{const r=await t.customers.create({email:e.email??"",externalId:e.referenceId,metadata:{...e.metadata,referenceId:e.referenceId},type:"individual"});return{createdAt:Date.now(),email:r.email??void 0,id:r.id,provider:"polar",referenceId:e.referenceId}},getPaymentStatus:async e=>A(await t.orders.get({id:e})),getSubscriptionStatus:async e=>u(await t.subscriptions.get({id:e})),identifier:"polar",parseWebhook:async({headers:e,payload:r})=>{const d=e.get("webhook-id")??"";await _({payload:r,secret:a,toleranceSeconds:o.webhookToleranceSeconds,webhookId:d,webhookSignature:e.get("webhook-signature")??"",webhookTimestamp:e.get("webhook-timestamp")??""});const c=p(JSON.parse(r));return S(d,n(c,"type")??"",p(c.data))},refundPayment:async e=>{const r=e.amount?void 0:await t.orders.get({id:e.sessionId}),d=e.amount?.currency??r?.currency??"usd",c=e.amount?Number(e.amount.minorUnits):r?.totalAmount??0;await t.refunds.create({amount:c,orderId:e.sessionId,reason:e.reason??"customer_request"});const l=e.amount??m(BigInt(Math.round(c)),d);return{amount:l,capturedAmount:l,createdAt:Date.now(),id:e.sessionId,provider:"polar",referenceId:"",refundedAmount:l,state:"refunded",updatedAt:Date.now()}},reportUsage:async e=>{await t.events.ingest({events:[{externalCustomerId:e.referenceId,metadata:{value:e.quantity},name:e.featureId,timestamp:e.timestamp===void 0?void 0:new Date(e.timestamp)}]})},resumeSubscription:async e=>{const r=await t.subscriptions.update({id:e,subscriptionUpdate:{cancelAtPeriodEnd:!1}});return u(r)},updateSubscription:async(e,r)=>{const d=await t.subscriptions.update({id:e,subscriptionUpdate:r.priceId?{productId:r.priceId}:{}});return u(d)}}};export{B as createPolarAdapter};
|
|
@@ -1,271 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { idempotencyKey } from '../packem_shared/idempotencyKey-BzaHGdHD.mjs';
|
|
3
|
-
import { r as readString, e as readBoolean, b as readNumber, a as asRecord } from '../packem_shared/json-D0Cb5aMv.mjs';
|
|
4
|
-
import { compareMoney, money, zeroMoney } from '../packem_shared/addMoney-jSh_7TfF.mjs';
|
|
5
|
-
import { s as stateToEventType } from '../packem_shared/subscription-event-CwEsWRCK.mjs';
|
|
6
|
-
|
|
7
|
-
const PAYMENT_STATE_BY_STRIPE_STATUS = {
|
|
8
|
-
canceled: "canceled",
|
|
9
|
-
processing: "authorized",
|
|
10
|
-
requires_action: "authorized",
|
|
11
|
-
requires_capture: "authorized",
|
|
12
|
-
requires_confirmation: "initiated",
|
|
13
|
-
requires_payment_method: "initiated",
|
|
14
|
-
succeeded: "captured"
|
|
15
|
-
};
|
|
16
|
-
const SUBSCRIPTION_STATE_BY_STRIPE_STATUS = {
|
|
17
|
-
active: "active",
|
|
18
|
-
canceled: "canceled",
|
|
19
|
-
// SECURITY: `incomplete` means the FIRST payment has not succeeded (SCA
|
|
20
|
-
// `requires_action` or a failed initial charge). It must NOT map to an
|
|
21
|
-
// entitling state — Stripe's recommended `payment_behavior: "default_incomplete"`
|
|
22
|
-
// makes `incomplete` the initial status of every new subscription, so mapping
|
|
23
|
-
// it to `trialing` (which is in ACTIVE_STATES) would grant paid entitlements
|
|
24
|
-
// before any payment. Reserve `trialing` for a genuine Stripe trial (status
|
|
25
|
-
// `trialing`, mapped below); treat `incomplete` as non-entitling `past_due`.
|
|
26
|
-
incomplete: "past_due",
|
|
27
|
-
incomplete_expired: "canceled",
|
|
28
|
-
past_due: "past_due",
|
|
29
|
-
paused: "paused",
|
|
30
|
-
trialing: "trialing",
|
|
31
|
-
unpaid: "past_due"
|
|
32
|
-
};
|
|
33
|
-
const readReferenceId = (object) => readString(asRecord(object.metadata), "referenceId") ?? readString(object, "client_reference_id");
|
|
34
|
-
const firstItem = (object) => {
|
|
35
|
-
const items = asRecord(object.items);
|
|
36
|
-
const data = Array.isArray(items.data) ? items.data : [];
|
|
37
|
-
return asRecord(data[0]);
|
|
38
|
-
};
|
|
39
|
-
const firstPriceId = (object) => readString(asRecord(firstItem(object).price), "id");
|
|
40
|
-
const firstQuantity = (object) => readNumber(firstItem(object), "quantity");
|
|
41
|
-
const periodEndMs = (object) => {
|
|
42
|
-
const seconds = readNumber(firstItem(object), "current_period_end") ?? readNumber(object, "current_period_end");
|
|
43
|
-
return seconds === void 0 ? void 0 : seconds * 1e3;
|
|
44
|
-
};
|
|
45
|
-
const periodStartMs = (object) => {
|
|
46
|
-
const seconds = readNumber(firstItem(object), "current_period_start") ?? readNumber(object, "current_period_start");
|
|
47
|
-
return seconds === void 0 ? void 0 : seconds * 1e3;
|
|
48
|
-
};
|
|
49
|
-
const intentToSession = (input) => {
|
|
50
|
-
const intent = asRecord(input);
|
|
51
|
-
const currency = readString(intent, "currency") ?? "usd";
|
|
52
|
-
const amountValue = readNumber(intent, "amount") ?? 0;
|
|
53
|
-
const amount = money(BigInt(Math.round(amountValue)), currency);
|
|
54
|
-
const state = PAYMENT_STATE_BY_STRIPE_STATUS[readString(intent, "status") ?? ""] ?? "initiated";
|
|
55
|
-
const now = Date.now();
|
|
56
|
-
return {
|
|
57
|
-
amount,
|
|
58
|
-
capturedAmount: state === "captured" ? money(BigInt(Math.round(readNumber(intent, "amount_received") ?? amountValue)), currency) : zeroMoney(currency),
|
|
59
|
-
createdAt: now,
|
|
60
|
-
id: readString(intent, "id") ?? "",
|
|
61
|
-
provider: "stripe",
|
|
62
|
-
referenceId: readReferenceId(intent) ?? "",
|
|
63
|
-
refundedAmount: zeroMoney(currency),
|
|
64
|
-
state,
|
|
65
|
-
updatedAt: now
|
|
66
|
-
};
|
|
67
|
-
};
|
|
68
|
-
const subscriptionFromStripe = (input) => {
|
|
69
|
-
const subscription = asRecord(input);
|
|
70
|
-
const now = Date.now();
|
|
71
|
-
return {
|
|
72
|
-
cancelAtPeriodEnd: readBoolean(subscription, "cancel_at_period_end") ?? false,
|
|
73
|
-
createdAt: now,
|
|
74
|
-
currentPeriodEnd: periodEndMs(subscription),
|
|
75
|
-
currentPeriodStart: periodStartMs(subscription),
|
|
76
|
-
id: readString(subscription, "id") ?? "",
|
|
77
|
-
priceId: firstPriceId(subscription) ?? "",
|
|
78
|
-
provider: "stripe",
|
|
79
|
-
quantity: firstQuantity(subscription) ?? 1,
|
|
80
|
-
referenceId: readReferenceId(subscription) ?? "",
|
|
81
|
-
state: SUBSCRIPTION_STATE_BY_STRIPE_STATUS[readString(subscription, "status") ?? ""] ?? "active",
|
|
82
|
-
updatedAt: now
|
|
83
|
-
};
|
|
84
|
-
};
|
|
85
|
-
const mapEvent = (eventId, eventType, object) => {
|
|
86
|
-
const base = { eventId, provider: "stripe", raw: { object, type: eventType } };
|
|
87
|
-
const currency = readString(object, "currency") ?? "usd";
|
|
88
|
-
switch (eventType) {
|
|
89
|
-
case "charge.refunded": {
|
|
90
|
-
return {
|
|
91
|
-
...base,
|
|
92
|
-
amount: money(BigInt(Math.round(readNumber(object, "amount_refunded") ?? 0)), currency),
|
|
93
|
-
amountKind: "absolute",
|
|
94
|
-
referenceId: readReferenceId(object),
|
|
95
|
-
sessionId: readString(object, "payment_intent") ?? readString(object, "id"),
|
|
96
|
-
type: "payment.refunded"
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
case "checkout.session.completed": {
|
|
100
|
-
if (readString(object, "mode") === "subscription") {
|
|
101
|
-
const paymentStatus = readString(object, "payment_status");
|
|
102
|
-
const paid = paymentStatus === "paid" || paymentStatus === "no_payment_required";
|
|
103
|
-
return {
|
|
104
|
-
...base,
|
|
105
|
-
customerId: readString(object, "customer"),
|
|
106
|
-
referenceId: readReferenceId(object),
|
|
107
|
-
subscriptionId: readString(object, "subscription"),
|
|
108
|
-
type: paid ? "subscription.active" : "subscription.updated"
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
const amountTotal = readNumber(object, "amount_total");
|
|
112
|
-
return {
|
|
113
|
-
...base,
|
|
114
|
-
amount: amountTotal === void 0 ? void 0 : money(BigInt(Math.round(amountTotal)), currency),
|
|
115
|
-
customerId: readString(object, "customer"),
|
|
116
|
-
referenceId: readReferenceId(object),
|
|
117
|
-
sessionId: readString(object, "payment_intent") ?? readString(object, "id"),
|
|
118
|
-
type: "payment.captured"
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
case "customer.subscription.created":
|
|
122
|
-
case "customer.subscription.updated": {
|
|
123
|
-
return {
|
|
124
|
-
...base,
|
|
125
|
-
cancelAtPeriodEnd: readBoolean(object, "cancel_at_period_end"),
|
|
126
|
-
currentPeriodEnd: periodEndMs(object),
|
|
127
|
-
currentPeriodStart: periodStartMs(object),
|
|
128
|
-
customerId: readString(object, "customer"),
|
|
129
|
-
priceId: firstPriceId(object),
|
|
130
|
-
quantity: firstQuantity(object),
|
|
131
|
-
referenceId: readReferenceId(object),
|
|
132
|
-
subscriptionId: readString(object, "id"),
|
|
133
|
-
type: stateToEventType(SUBSCRIPTION_STATE_BY_STRIPE_STATUS[readString(object, "status") ?? ""])
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
case "customer.subscription.deleted": {
|
|
137
|
-
return { ...base, referenceId: readReferenceId(object), subscriptionId: readString(object, "id"), type: "subscription.canceled" };
|
|
138
|
-
}
|
|
139
|
-
case "payment_intent.amount_capturable_updated": {
|
|
140
|
-
return {
|
|
141
|
-
...base,
|
|
142
|
-
amount: money(BigInt(Math.round(readNumber(object, "amount") ?? 0)), currency),
|
|
143
|
-
referenceId: readReferenceId(object),
|
|
144
|
-
sessionId: readString(object, "id"),
|
|
145
|
-
type: "payment.authorized"
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
case "payment_intent.payment_failed": {
|
|
149
|
-
return { ...base, referenceId: readReferenceId(object), sessionId: readString(object, "id"), type: "payment.failed" };
|
|
150
|
-
}
|
|
151
|
-
case "payment_intent.succeeded": {
|
|
152
|
-
return {
|
|
153
|
-
...base,
|
|
154
|
-
amount: money(BigInt(Math.round(readNumber(object, "amount_received") ?? readNumber(object, "amount") ?? 0)), currency),
|
|
155
|
-
customerId: readString(object, "customer"),
|
|
156
|
-
referenceId: readReferenceId(object),
|
|
157
|
-
sessionId: readString(object, "id"),
|
|
158
|
-
type: "payment.captured"
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
default: {
|
|
162
|
-
return { ...base, type: "unhandled" };
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
const createStripeAdapter = (options) => {
|
|
167
|
-
const { webhookSecret } = options;
|
|
168
|
-
const client = options.client;
|
|
169
|
-
return {
|
|
170
|
-
cancelPayment: async (sessionId, paymentOptions) => {
|
|
171
|
-
const intent = await client.paymentIntents.cancel(sessionId, void 0, { idempotencyKey: paymentOptions?.idempotencyKey });
|
|
172
|
-
return intentToSession(intent);
|
|
173
|
-
},
|
|
174
|
-
cancelSubscription: async (subscriptionId, cancelOptions) => {
|
|
175
|
-
const subscription = cancelOptions?.atPeriodEnd ? await client.subscriptions.update(subscriptionId, { cancel_at_period_end: true }, { idempotencyKey: cancelOptions.idempotencyKey }) : await client.subscriptions.cancel(subscriptionId, void 0, { idempotencyKey: cancelOptions?.idempotencyKey });
|
|
176
|
-
return subscriptionFromStripe(subscription);
|
|
177
|
-
},
|
|
178
|
-
capabilities: { merchantOfRecord: false, portal: true, usageMetering: true },
|
|
179
|
-
capturePayment: async (input) => {
|
|
180
|
-
const intent = await client.paymentIntents.capture(
|
|
181
|
-
input.sessionId,
|
|
182
|
-
input.amount ? { amount_to_capture: Number(input.amount.minorUnits) } : void 0,
|
|
183
|
-
{ idempotencyKey: input.idempotencyKey }
|
|
184
|
-
);
|
|
185
|
-
return intentToSession(intent);
|
|
186
|
-
},
|
|
187
|
-
createCheckout: async (input) => {
|
|
188
|
-
const session = await client.checkout.sessions.create(
|
|
189
|
-
{
|
|
190
|
-
cancel_url: input.cancelUrl,
|
|
191
|
-
client_reference_id: input.referenceId,
|
|
192
|
-
customer: input.customerId,
|
|
193
|
-
line_items: [{ price: input.priceId, quantity: input.quantity ?? 1 }],
|
|
194
|
-
// Pin the framework-controlled `referenceId` LAST so caller metadata can never override it.
|
|
195
|
-
metadata: { ...input.metadata, referenceId: input.referenceId },
|
|
196
|
-
mode: input.mode,
|
|
197
|
-
subscription_data: input.mode === "subscription" ? { metadata: { referenceId: input.referenceId } } : void 0,
|
|
198
|
-
success_url: input.successUrl
|
|
199
|
-
},
|
|
200
|
-
{ idempotencyKey: input.idempotencyKey }
|
|
201
|
-
);
|
|
202
|
-
return { id: session.id, provider: "stripe", url: session.url ?? "" };
|
|
203
|
-
},
|
|
204
|
-
createPortalSession: async (input) => {
|
|
205
|
-
const session = await client.billingPortal.sessions.create({ customer: input.customerId, return_url: input.returnUrl });
|
|
206
|
-
return { url: session.url };
|
|
207
|
-
},
|
|
208
|
-
getOrCreateCustomer: async (ref) => {
|
|
209
|
-
const customer = await client.customers.create(
|
|
210
|
-
{ email: ref.email, metadata: { ...ref.metadata, referenceId: ref.referenceId } },
|
|
211
|
-
{ idempotencyKey: idempotencyKey("customer", "stripe", ref.referenceId) }
|
|
212
|
-
);
|
|
213
|
-
return { createdAt: Date.now(), email: customer.email ?? void 0, id: customer.id, provider: "stripe", referenceId: ref.referenceId };
|
|
214
|
-
},
|
|
215
|
-
getPaymentStatus: async (sessionId) => intentToSession(await client.paymentIntents.retrieve(sessionId)),
|
|
216
|
-
getSubscriptionStatus: async (subscriptionId) => subscriptionFromStripe(await client.subscriptions.retrieve(subscriptionId)),
|
|
217
|
-
identifier: "stripe",
|
|
218
|
-
parseWebhook: async ({ headers, payload }) => {
|
|
219
|
-
const signatureHeader = headers.get("stripe-signature") ?? "";
|
|
220
|
-
let event;
|
|
221
|
-
try {
|
|
222
|
-
event = await client.webhooks.constructEventAsync(payload, signatureHeader, webhookSecret, options.webhookToleranceSeconds);
|
|
223
|
-
} catch (error) {
|
|
224
|
-
throw new LunoraPaymentError("WEBHOOK_SIGNATURE_INVALID", error instanceof Error ? error.message : "invalid webhook signature");
|
|
225
|
-
}
|
|
226
|
-
const object = asRecord(asRecord(event.data).object);
|
|
227
|
-
return mapEvent(event.id, event.type, object);
|
|
228
|
-
},
|
|
229
|
-
refundPayment: async (input) => {
|
|
230
|
-
await client.refunds.create(
|
|
231
|
-
{
|
|
232
|
-
amount: input.amount ? Number(input.amount.minorUnits) : void 0,
|
|
233
|
-
payment_intent: input.sessionId,
|
|
234
|
-
reason: input.reason
|
|
235
|
-
},
|
|
236
|
-
{ idempotencyKey: input.idempotencyKey }
|
|
237
|
-
);
|
|
238
|
-
const intent = await client.paymentIntents.retrieve(input.sessionId);
|
|
239
|
-
const session = intentToSession(intent);
|
|
240
|
-
const refundedAmount = input.amount ?? session.capturedAmount;
|
|
241
|
-
const partial = input.amount !== void 0 && compareMoney(input.amount, session.capturedAmount) < 0;
|
|
242
|
-
return { ...session, refundedAmount, state: partial ? "partially_refunded" : "refunded" };
|
|
243
|
-
},
|
|
244
|
-
reportUsage: async (input) => {
|
|
245
|
-
await client.billing.meterEvents.create(
|
|
246
|
-
{
|
|
247
|
-
event_name: input.featureId,
|
|
248
|
-
identifier: input.idempotencyKey,
|
|
249
|
-
payload: { stripe_customer_id: input.customerId ?? "", value: String(input.quantity) },
|
|
250
|
-
timestamp: input.timestamp === void 0 ? void 0 : Math.floor(input.timestamp / 1e3)
|
|
251
|
-
},
|
|
252
|
-
{ idempotencyKey: input.idempotencyKey }
|
|
253
|
-
);
|
|
254
|
-
},
|
|
255
|
-
resumeSubscription: async (subscriptionId) => {
|
|
256
|
-
const subscription = await client.subscriptions.update(subscriptionId, { cancel_at_period_end: false });
|
|
257
|
-
return subscriptionFromStripe(subscription);
|
|
258
|
-
},
|
|
259
|
-
updateSubscription: async (subscriptionId, patch) => {
|
|
260
|
-
const parameters = {};
|
|
261
|
-
if (patch.priceId !== void 0 || patch.quantity !== void 0) {
|
|
262
|
-
const current = await client.subscriptions.retrieve(subscriptionId);
|
|
263
|
-
parameters.items = [{ id: current.items.data[0]?.id, price: patch.priceId, quantity: patch.quantity }];
|
|
264
|
-
}
|
|
265
|
-
const subscription = await client.subscriptions.update(subscriptionId, parameters);
|
|
266
|
-
return subscriptionFromStripe(subscription);
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
export { createStripeAdapter };
|
|
1
|
+
import{LunoraPaymentError as A}from"../packem_shared/LunoraPaymentError-DWQ-v_NC.mjs";import{idempotencyKey as P}from"../packem_shared/idempotencyKey-CfJKqcE3.mjs";import{r as a,e as b,b as o,a as u}from"../packem_shared/json-D85Nbd92.mjs";import{compareMoney as S,money as m,zeroMoney as f}from"../packem_shared/addMoney-BiiMn67T.mjs";import{i as q}from"../packem_shared/subscription-event-C-71ye9J.mjs";const E={canceled:"canceled",processing:"authorized",requires_action:"authorized",requires_capture:"authorized",requires_confirmation:"initiated",requires_payment_method:"initiated",succeeded:"captured"},v={active:"active",canceled:"canceled",incomplete:"past_due",incomplete_expired:"canceled",past_due:"past_due",paused:"paused",trialing:"trialing",unpaid:"past_due"},s=i=>a(u(i.metadata),"referenceId")??a(i,"client_reference_id"),_=i=>{const n=u(i.items),t=Array.isArray(n.data)?n.data:[];return u(t[0])},h=i=>a(u(_(i).price),"id"),w=i=>o(_(i),"quantity"),g=i=>{const n=o(_(i),"current_period_end")??o(i,"current_period_end");return n===void 0?void 0:n*1e3},K=i=>{const n=o(_(i),"current_period_start")??o(i,"current_period_start");return n===void 0?void 0:n*1e3},y=i=>{const n=u(i),t=a(n,"currency")??"usd",e=o(n,"amount")??0,r=m(BigInt(Math.round(e)),t),c=E[a(n,"status")??""]??"initiated",d=Date.now();return{amount:r,capturedAmount:c==="captured"?m(BigInt(Math.round(o(n,"amount_received")??e)),t):f(t),createdAt:d,id:a(n,"id")??"",provider:"stripe",referenceId:s(n)??"",refundedAmount:f(t),state:c,updatedAt:d}},l=i=>{const n=u(i),t=Date.now();return{cancelAtPeriodEnd:b(n,"cancel_at_period_end")??!1,createdAt:t,currentPeriodEnd:g(n),currentPeriodStart:K(n),id:a(n,"id")??"",priceId:h(n)??"",provider:"stripe",quantity:w(n)??1,referenceId:s(n)??"",state:v[a(n,"status")??""]??"active",updatedAt:t}},M=(i,n,t)=>{const e={eventId:i,provider:"stripe",raw:{object:t,type:n}},r=a(t,"currency")??"usd";switch(n){case"charge.refunded":return{...e,amount:m(BigInt(Math.round(o(t,"amount_refunded")??0)),r),amountKind:"absolute",referenceId:s(t),sessionId:a(t,"payment_intent")??a(t,"id"),type:"payment.refunded"};case"checkout.session.completed":{if(a(t,"mode")==="subscription"){const d=a(t,"payment_status"),p=d==="paid"||d==="no_payment_required";return{...e,customerId:a(t,"customer"),referenceId:s(t),subscriptionId:a(t,"subscription"),type:p?"subscription.active":"subscription.updated"}}const c=o(t,"amount_total");return{...e,amount:c===void 0?void 0:m(BigInt(Math.round(c)),r),customerId:a(t,"customer"),referenceId:s(t),sessionId:a(t,"payment_intent")??a(t,"id"),type:"payment.captured"}}case"customer.subscription.created":case"customer.subscription.updated":return{...e,cancelAtPeriodEnd:b(t,"cancel_at_period_end"),currentPeriodEnd:g(t),currentPeriodStart:K(t),customerId:a(t,"customer"),priceId:h(t),quantity:w(t),referenceId:s(t),subscriptionId:a(t,"id"),type:q(v[a(t,"status")??""])};case"customer.subscription.deleted":return{...e,referenceId:s(t),subscriptionId:a(t,"id"),type:"subscription.canceled"};case"payment_intent.amount_capturable_updated":return{...e,amount:m(BigInt(Math.round(o(t,"amount")??0)),r),referenceId:s(t),sessionId:a(t,"id"),type:"payment.authorized"};case"payment_intent.payment_failed":return{...e,referenceId:s(t),sessionId:a(t,"id"),type:"payment.failed"};case"payment_intent.succeeded":return{...e,amount:m(BigInt(Math.round(o(t,"amount_received")??o(t,"amount")??0)),r),customerId:a(t,"customer"),referenceId:s(t),sessionId:a(t,"id"),type:"payment.captured"};default:return{...e,type:"unhandled"}}},D=i=>{const{webhookSecret:n}=i,t=i.client;return{cancelPayment:async(e,r)=>{const c=await t.paymentIntents.cancel(e,void 0,{idempotencyKey:r?.idempotencyKey});return y(c)},cancelSubscription:async(e,r)=>{const c=r?.atPeriodEnd?await t.subscriptions.update(e,{cancel_at_period_end:!0},{idempotencyKey:r.idempotencyKey}):await t.subscriptions.cancel(e,void 0,{idempotencyKey:r?.idempotencyKey});return l(c)},capabilities:{merchantOfRecord:!1,portal:!0,usageMetering:!0},capturePayment:async e=>{const r=await t.paymentIntents.capture(e.sessionId,e.amount?{amount_to_capture:Number(e.amount.minorUnits)}:void 0,{idempotencyKey:e.idempotencyKey});return y(r)},createCheckout:async e=>{const r=await t.checkout.sessions.create({cancel_url:e.cancelUrl,client_reference_id:e.referenceId,customer:e.customerId,line_items:[{price:e.priceId,quantity:e.quantity??1}],metadata:{...e.metadata,referenceId:e.referenceId},mode:e.mode,subscription_data:e.mode==="subscription"?{metadata:{referenceId:e.referenceId}}:void 0,success_url:e.successUrl},{idempotencyKey:e.idempotencyKey});return{id:r.id,provider:"stripe",url:r.url??""}},createPortalSession:async e=>({url:(await t.billingPortal.sessions.create({customer:e.customerId,return_url:e.returnUrl})).url}),getOrCreateCustomer:async e=>{const r=await t.customers.create({email:e.email,metadata:{...e.metadata,referenceId:e.referenceId}},{idempotencyKey:P("customer","stripe",e.referenceId)});return{createdAt:Date.now(),email:r.email??void 0,id:r.id,provider:"stripe",referenceId:e.referenceId}},getPaymentStatus:async e=>y(await t.paymentIntents.retrieve(e)),getSubscriptionStatus:async e=>l(await t.subscriptions.retrieve(e)),identifier:"stripe",parseWebhook:async({headers:e,payload:r})=>{const c=e.get("stripe-signature")??"";let d;try{d=await t.webhooks.constructEventAsync(r,c,n,i.webhookToleranceSeconds)}catch(I){throw new A("WEBHOOK_SIGNATURE_INVALID",I instanceof Error?I.message:"invalid webhook signature")}const p=u(u(d.data).object);return M(d.id,d.type,p)},refundPayment:async e=>{await t.refunds.create({amount:e.amount?Number(e.amount.minorUnits):void 0,payment_intent:e.sessionId,reason:e.reason},{idempotencyKey:e.idempotencyKey});const r=await t.paymentIntents.retrieve(e.sessionId),c=y(r),d=e.amount??c.capturedAmount,p=e.amount!==void 0&&S(e.amount,c.capturedAmount)<0;return{...c,refundedAmount:d,state:p?"partially_refunded":"refunded"}},reportUsage:async e=>{await t.billing.meterEvents.create({event_name:e.featureId,identifier:e.idempotencyKey,payload:{stripe_customer_id:e.customerId??"",value:String(e.quantity)},timestamp:e.timestamp===void 0?void 0:Math.floor(e.timestamp/1e3)},{idempotencyKey:e.idempotencyKey})},resumeSubscription:async e=>{const r=await t.subscriptions.update(e,{cancel_at_period_end:!1});return l(r)},updateSubscription:async(e,r)=>{const c={};if(r.priceId!==void 0||r.quantity!==void 0){const p=await t.subscriptions.retrieve(e);c.items=[{id:p.items.data[0]?.id,price:r.priceId,quantity:r.quantity}]}const d=await t.subscriptions.update(e,c);return l(d)}}};export{D as createStripeAdapter};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/payment",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.32",
|
|
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.8",
|
|
75
|
-
"@lunora/server": "1.0.0-alpha.
|
|
75
|
+
"@lunora/server": "1.0.0-alpha.32",
|
|
76
76
|
"@lunora/values": "1.0.0-alpha.11",
|
|
77
77
|
"dinero.js": "2.0.2"
|
|
78
78
|
},
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
|
|
3
|
-
const STATUS_BY_CODE = {
|
|
4
|
-
CONFIG_INVALID: 500,
|
|
5
|
-
CURRENCY_MISMATCH: 400,
|
|
6
|
-
FORBIDDEN: 403,
|
|
7
|
-
INVALID_TRANSITION: 409,
|
|
8
|
-
NOT_FOUND: 404,
|
|
9
|
-
PROVIDER_ERROR: 502,
|
|
10
|
-
WEBHOOK_SIGNATURE_INVALID: 400,
|
|
11
|
-
WEBHOOK_TIMESTAMP_INVALID: 400
|
|
12
|
-
};
|
|
13
|
-
class LunoraPaymentError extends LunoraError {
|
|
14
|
-
constructor(code, message) {
|
|
15
|
-
super(code, message, { name: "LunoraPaymentError", status: STATUS_BY_CODE[code] });
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export { LunoraPaymentError };
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
const customerKey = (provider, referenceId) => `${provider}:${referenceId}`;
|
|
2
|
-
const recordKey = (provider, id) => `${provider}:${id}`;
|
|
3
|
-
class MemoryPaymentStore {
|
|
4
|
-
customers = /* @__PURE__ */ new Map();
|
|
5
|
-
processedEvents = /* @__PURE__ */ new Set();
|
|
6
|
-
sessions = /* @__PURE__ */ new Map();
|
|
7
|
-
subscriptions = /* @__PURE__ */ new Map();
|
|
8
|
-
usageEvents = /* @__PURE__ */ new Map();
|
|
9
|
-
getCustomerByReference(provider, referenceId) {
|
|
10
|
-
return Promise.resolve(this.customers.get(customerKey(provider, referenceId)));
|
|
11
|
-
}
|
|
12
|
-
getPaymentSession(provider, id) {
|
|
13
|
-
return Promise.resolve(this.sessions.get(recordKey(provider, id)));
|
|
14
|
-
}
|
|
15
|
-
getSubscription(provider, id) {
|
|
16
|
-
return Promise.resolve(this.subscriptions.get(recordKey(provider, id)));
|
|
17
|
-
}
|
|
18
|
-
listSubscriptionsByReference(referenceId) {
|
|
19
|
-
return Promise.resolve([...this.subscriptions.values()].filter((subscription) => subscription.referenceId === referenceId));
|
|
20
|
-
}
|
|
21
|
-
markEventProcessed(provider, eventId) {
|
|
22
|
-
const key = recordKey(provider, eventId);
|
|
23
|
-
if (this.processedEvents.has(key)) {
|
|
24
|
-
return Promise.resolve(false);
|
|
25
|
-
}
|
|
26
|
-
this.processedEvents.add(key);
|
|
27
|
-
return Promise.resolve(true);
|
|
28
|
-
}
|
|
29
|
-
releaseEvent(provider, eventId) {
|
|
30
|
-
this.processedEvents.delete(recordKey(provider, eventId));
|
|
31
|
-
return Promise.resolve();
|
|
32
|
-
}
|
|
33
|
-
markUsageReported(provider, idempotencyKey) {
|
|
34
|
-
const key = recordKey(provider, idempotencyKey);
|
|
35
|
-
const existing = this.usageEvents.get(key);
|
|
36
|
-
if (existing) {
|
|
37
|
-
this.usageEvents.set(key, { ...existing, reportedToProvider: true });
|
|
38
|
-
}
|
|
39
|
-
return Promise.resolve();
|
|
40
|
-
}
|
|
41
|
-
recordUsage(event) {
|
|
42
|
-
const key = recordKey(event.provider, event.idempotencyKey);
|
|
43
|
-
if (this.usageEvents.has(key)) {
|
|
44
|
-
return Promise.resolve(false);
|
|
45
|
-
}
|
|
46
|
-
this.usageEvents.set(key, event);
|
|
47
|
-
return Promise.resolve(true);
|
|
48
|
-
}
|
|
49
|
-
sumUsage(referenceId, featureId, since) {
|
|
50
|
-
let total = 0;
|
|
51
|
-
for (const event of this.usageEvents.values()) {
|
|
52
|
-
if (event.referenceId === referenceId && event.featureId === featureId && event.createdAt >= since) {
|
|
53
|
-
total += event.quantity;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return Promise.resolve(total);
|
|
57
|
-
}
|
|
58
|
-
upsertCustomer(customer) {
|
|
59
|
-
this.customers.set(customerKey(customer.provider, customer.referenceId), customer);
|
|
60
|
-
return Promise.resolve();
|
|
61
|
-
}
|
|
62
|
-
upsertPaymentSession(session) {
|
|
63
|
-
this.sessions.set(recordKey(session.provider, session.id), session);
|
|
64
|
-
return Promise.resolve();
|
|
65
|
-
}
|
|
66
|
-
upsertSubscription(subscription) {
|
|
67
|
-
this.subscriptions.set(recordKey(subscription.provider, subscription.id), subscription);
|
|
68
|
-
return Promise.resolve();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export { MemoryPaymentStore };
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
const PAYMENT_TRANSITIONS = {
|
|
2
|
-
authorized: { cancel: "canceled", capture: "captured", fail: "failed" },
|
|
3
|
-
canceled: {},
|
|
4
|
-
captured: { partial_refund: "partially_refunded", refund: "refunded" },
|
|
5
|
-
failed: {},
|
|
6
|
-
// A webhook can land before our local record exists, so "initiated" accepts the same
|
|
7
|
-
// outcomes a fresh intent could reach directly.
|
|
8
|
-
initiated: { authorize: "authorized", cancel: "canceled", capture: "captured", fail: "failed" },
|
|
9
|
-
partially_refunded: { partial_refund: "partially_refunded", refund: "refunded" },
|
|
10
|
-
refunded: {}
|
|
11
|
-
};
|
|
12
|
-
const SUBSCRIPTION_TRANSITIONS = {
|
|
13
|
-
active: { cancel: "canceled", mark_past_due: "past_due", pause: "paused", renew: "active" },
|
|
14
|
-
canceled: {},
|
|
15
|
-
past_due: { activate: "active", cancel: "canceled", pause: "paused", renew: "active" },
|
|
16
|
-
paused: { cancel: "canceled", resume: "active" },
|
|
17
|
-
trialing: { activate: "active", cancel: "canceled", mark_past_due: "past_due" }
|
|
18
|
-
};
|
|
19
|
-
const PAYMENT_TERMINAL_STATES = /* @__PURE__ */ new Set(["canceled", "failed", "refunded"]);
|
|
20
|
-
const SUBSCRIPTION_TERMINAL_STATES = /* @__PURE__ */ new Set(["canceled"]);
|
|
21
|
-
const nextPaymentState = (from, action) => PAYMENT_TRANSITIONS[from][action];
|
|
22
|
-
const canTransitionPayment = (from, action) => nextPaymentState(from, action) !== void 0;
|
|
23
|
-
const nextSubscriptionState = (from, action) => SUBSCRIPTION_TRANSITIONS[from][action];
|
|
24
|
-
const canTransitionSubscription = (from, action) => nextSubscriptionState(from, action) !== void 0;
|
|
25
|
-
|
|
26
|
-
export { PAYMENT_TERMINAL_STATES, SUBSCRIPTION_TERMINAL_STATES, canTransitionPayment, canTransitionSubscription, nextPaymentState, nextSubscriptionState };
|