@lunora/payment 1.0.0-alpha.30 → 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 +4 -4
- 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",
|
|
@@ -71,9 +71,9 @@
|
|
|
71
71
|
"access": "public"
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
75
|
-
"@lunora/server": "1.0.0-alpha.
|
|
76
|
-
"@lunora/values": "1.0.0-alpha.
|
|
74
|
+
"@lunora/errors": "1.0.0-alpha.8",
|
|
75
|
+
"@lunora/server": "1.0.0-alpha.32",
|
|
76
|
+
"@lunora/values": "1.0.0-alpha.11",
|
|
77
77
|
"dinero.js": "2.0.2"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
@@ -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 };
|