@lunora/payment 1.0.0-alpha.22 → 1.0.0-alpha.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/index.d.mts +2 -9
  2. package/dist/index.d.ts +2 -9
  3. package/dist/index.mjs +5 -5
  4. package/dist/packem_shared/{adapter.d-iFA55DzU.d.mts → adapter.d-SULGy2QF.d.mts} +11 -2
  5. package/dist/packem_shared/{adapter.d-iFA55DzU.d.ts → adapter.d-SULGy2QF.d.ts} +11 -2
  6. package/dist/packem_shared/{createPayment-DzX-ji34.mjs → createPayment-CKQAznOL.mjs} +10 -12
  7. package/dist/packem_shared/idempotencyKey-BzaHGdHD.mjs +9 -0
  8. package/dist/packem_shared/{json-DhcPm8EO.mjs → json-D0Cb5aMv.mjs} +19 -1
  9. package/dist/packem_shared/{lunoraDatabaseToPaymentDatabase-CL2vdAXq.mjs → lunoraDatabaseToPaymentDatabase-DTsgPvfO.mjs} +1 -1
  10. package/dist/packem_shared/paymentTables-D4TbhaIk.mjs +59 -0
  11. package/dist/packem_shared/{reconcile-Dtn_ErbJ.mjs → reconcile-qE3ERi3D.mjs} +13 -1
  12. package/dist/providers/autumn-features.mjs +1 -1
  13. package/dist/providers/autumn.d.mts +1 -1
  14. package/dist/providers/autumn.d.ts +1 -1
  15. package/dist/providers/autumn.mjs +3 -22
  16. package/dist/providers/creem.d.mts +1 -1
  17. package/dist/providers/creem.d.ts +1 -1
  18. package/dist/providers/creem.mjs +11 -11
  19. package/dist/providers/dodopayments.d.mts +1 -1
  20. package/dist/providers/dodopayments.d.ts +1 -1
  21. package/dist/providers/dodopayments.mjs +2 -2
  22. package/dist/providers/polar.d.mts +1 -1
  23. package/dist/providers/polar.d.ts +1 -1
  24. package/dist/providers/polar.mjs +13 -3
  25. package/dist/providers/stripe.d.mts +1 -1
  26. package/dist/providers/stripe.d.ts +1 -1
  27. package/dist/providers/stripe.mjs +2 -2
  28. package/package.json +4 -4
  29. package/dist/packem_shared/idempotencyKey-BFzDCA7g.mjs +0 -3
  30. package/dist/packem_shared/paymentTables-DccHwWr_.mjs +0 -127
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as ProviderId, C as Customer, b as PaymentSession, S as Subscription, U as UsageEvent, W as WebhookActionType, A as ApplyResult, P as PaymentAdapter, c as AttachInput, d as CheckoutResult, e as CancelSubscriptionOptions, f as CheckInput, g as CheckResult, h as CheckoutInput, F as FeatureBalance, T as TrackInput, i as TrackResult, j as CurrencyCode, M as Money, k as PaymentState, l as SubscriptionState, m as WebhookAction } from "./packem_shared/adapter.d-iFA55DzU.mjs";
2
- export { type n as AdapterRegistry, type o as CaptureInput, type p as CustomerRef, type q as PortalInput, type r as ProviderCapabilities, type R as RefundAmountKind, type s as RefundInput, type t as ReportUsageInput, type u as SubscriptionPatch, type v as WebhookHeaders, type w as WebhookInput, x as createAdapterRegistry } from "./packem_shared/adapter.d-iFA55DzU.mjs";
1
+ import { a as ProviderId, C as Customer, b as PaymentSession, S as Subscription, U as UsageEvent, W as WebhookActionType, A as ApplyResult, P as PaymentAdapter, c as AttachInput, d as CheckoutResult, e as CancelSubscriptionOptions, f as CheckInput, g as CheckResult, h as CheckoutInput, F as FeatureBalance, T as TrackInput, i as TrackResult, j as CurrencyCode, M as Money, k as PaymentState, l as SubscriptionState, m as WebhookAction } from "./packem_shared/adapter.d-SULGy2QF.mjs";
2
+ export { type n as AdapterRegistry, type o as CaptureInput, type p as CustomerRef, type q as PortalInput, type r as ProviderCapabilities, type R as RefundAmountKind, type s as RefundInput, type t as ReportUsageInput, type u as SubscriptionPatch, type v as WebhookHeaders, type w as WebhookInput, x as createAdapterRegistry } from "./packem_shared/adapter.d-SULGy2QF.mjs";
3
3
  import { LunoraError } from '@lunora/errors';
4
4
  import { TableDefinition } from '@lunora/server';
5
5
  interface PaymentStore {
@@ -233,13 +233,6 @@ declare class LunoraPaymentError extends LunoraError {
233
233
  readonly code: PaymentErrorCode;
234
234
  constructor(code: PaymentErrorCode, message: string);
235
235
  }
236
- /**
237
- * Outbound idempotency keys.
238
- *
239
- * Every mutating provider call carries a stable key derived from our own operation + inputs, so
240
- * a Worker retry can never double-charge. Distinct from inbound webhook dedupe (keyed on the
241
- * provider event id).
242
- */
243
236
  /** Build a deterministic idempotency key from an operation name and stable parts. */
244
237
  declare const idempotencyKey: (operation: string, ...parts: ReadonlyArray<number | string>) => string;
245
238
  /** True when the currency has no minor unit (e.g. JPY). */
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as ProviderId, C as Customer, b as PaymentSession, S as Subscription, U as UsageEvent, W as WebhookActionType, A as ApplyResult, P as PaymentAdapter, c as AttachInput, d as CheckoutResult, e as CancelSubscriptionOptions, f as CheckInput, g as CheckResult, h as CheckoutInput, F as FeatureBalance, T as TrackInput, i as TrackResult, j as CurrencyCode, M as Money, k as PaymentState, l as SubscriptionState, m as WebhookAction } from "./packem_shared/adapter.d-iFA55DzU.js";
2
- export { type n as AdapterRegistry, type o as CaptureInput, type p as CustomerRef, type q as PortalInput, type r as ProviderCapabilities, type R as RefundAmountKind, type s as RefundInput, type t as ReportUsageInput, type u as SubscriptionPatch, type v as WebhookHeaders, type w as WebhookInput, x as createAdapterRegistry } from "./packem_shared/adapter.d-iFA55DzU.js";
1
+ import { a as ProviderId, C as Customer, b as PaymentSession, S as Subscription, U as UsageEvent, W as WebhookActionType, A as ApplyResult, P as PaymentAdapter, c as AttachInput, d as CheckoutResult, e as CancelSubscriptionOptions, f as CheckInput, g as CheckResult, h as CheckoutInput, F as FeatureBalance, T as TrackInput, i as TrackResult, j as CurrencyCode, M as Money, k as PaymentState, l as SubscriptionState, m as WebhookAction } from "./packem_shared/adapter.d-SULGy2QF.js";
2
+ export { type n as AdapterRegistry, type o as CaptureInput, type p as CustomerRef, type q as PortalInput, type r as ProviderCapabilities, type R as RefundAmountKind, type s as RefundInput, type t as ReportUsageInput, type u as SubscriptionPatch, type v as WebhookHeaders, type w as WebhookInput, x as createAdapterRegistry } from "./packem_shared/adapter.d-SULGy2QF.js";
3
3
  import { LunoraError } from '@lunora/errors';
4
4
  import { TableDefinition } from '@lunora/server';
5
5
  interface PaymentStore {
@@ -233,13 +233,6 @@ declare class LunoraPaymentError extends LunoraError {
233
233
  readonly code: PaymentErrorCode;
234
234
  constructor(code: PaymentErrorCode, message: string);
235
235
  }
236
- /**
237
- * Outbound idempotency keys.
238
- *
239
- * Every mutating provider call carries a stable key derived from our own operation + inputs, so
240
- * a Worker retry can never double-charge. Distinct from inbound webhook dedupe (keyed on the
241
- * provider event id).
242
- */
243
236
  /** Build a deterministic idempotency key from an operation name and stable parts. */
244
237
  declare const idempotencyKey: (operation: string, ...parts: ReadonlyArray<number | string>) => string;
245
238
  /** True when the currency has no minor unit (e.g. JPY). */
package/dist/index.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  export { createAdapterRegistry } from './packem_shared/createAdapterRegistry-Ds7bx_TK.mjs';
2
- export { lunoraDatabaseToPaymentDatabase, paymentsFromContext } from './packem_shared/lunoraDatabaseToPaymentDatabase-CL2vdAXq.mjs';
3
- export { createPayment } from './packem_shared/createPayment-DzX-ji34.mjs';
2
+ export { lunoraDatabaseToPaymentDatabase, paymentsFromContext } from './packem_shared/lunoraDatabaseToPaymentDatabase-DTsgPvfO.mjs';
3
+ export { createPayment } from './packem_shared/createPayment-CKQAznOL.mjs';
4
4
  export { createDatabasePaymentStore } from './packem_shared/createDatabasePaymentStore-C0NUCj1H.mjs';
5
5
  export { entitlementsForReference, featureNames, hasActivePrice, resolveEntitlements, usagePeriodStart } from './packem_shared/entitlementsForReference-CzZGXPoZ.mjs';
6
6
  export { LunoraPaymentError } from './packem_shared/LunoraPaymentError-BeQlhkZj.mjs';
7
- export { default as idempotencyKey } from './packem_shared/idempotencyKey-BFzDCA7g.mjs';
7
+ export { idempotencyKey } from './packem_shared/idempotencyKey-BzaHGdHD.mjs';
8
8
  export { addMoney, allocateMoney, compareMoney, formatMoney, fromMoneyJSON, isZeroDecimalCurrency, isZeroMoney, money, subtractMoney, toMoneyJSON, zeroMoney } from './packem_shared/addMoney-jSh_7TfF.mjs';
9
- export { reconcile } from './packem_shared/reconcile-Dtn_ErbJ.mjs';
10
- export { default as paymentTables } from './packem_shared/paymentTables-DccHwWr_.mjs';
9
+ export { reconcile } from './packem_shared/reconcile-qE3ERi3D.mjs';
10
+ export { default as paymentTables } from './packem_shared/paymentTables-D4TbhaIk.mjs';
11
11
  export { PAYMENT_TERMINAL_STATES, SUBSCRIPTION_TERMINAL_STATES, canTransitionPayment, canTransitionSubscription, nextPaymentState, nextSubscriptionState } from './packem_shared/PAYMENT_TERMINAL_STATES-DrxV0clv.mjs';
12
12
  export { MemoryPaymentStore } from './packem_shared/MemoryPaymentStore-DvgdWa3C.mjs';
13
13
  export { default as applyWebhookAction } from './packem_shared/applyWebhookAction-CLAx4svt.mjs';
@@ -73,7 +73,11 @@ interface CustomerRef {
73
73
  }
74
74
  interface CheckoutInput {
75
75
  readonly cancelUrl: string;
76
- /** Existing provider customer id, if known. */
76
+ /**
77
+ * Ignored at runtime (kept for backward-compat). The provider customer is always derived from the store for the
78
+ * authorized `referenceId` (never caller-supplied) to prevent cross-tenant checkout attachment (IDOR).
79
+ * Retained on the type only for backward compatibility; setting it has no effect.
80
+ */
77
81
  readonly customerId?: string;
78
82
  /**
79
83
  * Customer email, used when the reference has no provider customer yet. Some Merchant-of-Record
@@ -123,7 +127,12 @@ interface TrackInput {
123
127
  readonly featureId: string;
124
128
  /** Caller-supplied dedupe key; a fresh one is generated when omitted (so each call records). */
125
129
  readonly idempotencyKey?: string;
126
- /** `"add"` (default) increments usage by `quantity`; `"set"` reconciles the period total to `quantity`. */
130
+ /**
131
+ * `"add"` (default) increments usage by `quantity`; `"set"` reconciles the period total to `quantity`.
132
+ * `"set"` is a non-atomic read-modify-write (it reads the current total, then appends the delta), so
133
+ * concurrent `"set"` calls for the same reference can over- or under-count — call it only from a
134
+ * serialized context (a single Durable Object or per-reference lock). `"add"` is always safe.
135
+ */
127
136
  readonly mode?: "add" | "set";
128
137
  /** Usage amount to add, or the absolute period total when `mode` is `"set"` (defaults to `1`). */
129
138
  readonly quantity?: number;
@@ -73,7 +73,11 @@ interface CustomerRef {
73
73
  }
74
74
  interface CheckoutInput {
75
75
  readonly cancelUrl: string;
76
- /** Existing provider customer id, if known. */
76
+ /**
77
+ * Ignored at runtime (kept for backward-compat). The provider customer is always derived from the store for the
78
+ * authorized `referenceId` (never caller-supplied) to prevent cross-tenant checkout attachment (IDOR).
79
+ * Retained on the type only for backward compatibility; setting it has no effect.
80
+ */
77
81
  readonly customerId?: string;
78
82
  /**
79
83
  * Customer email, used when the reference has no provider customer yet. Some Merchant-of-Record
@@ -123,7 +127,12 @@ interface TrackInput {
123
127
  readonly featureId: string;
124
128
  /** Caller-supplied dedupe key; a fresh one is generated when omitted (so each call records). */
125
129
  readonly idempotencyKey?: string;
126
- /** `"add"` (default) increments usage by `quantity`; `"set"` reconciles the period total to `quantity`. */
130
+ /**
131
+ * `"add"` (default) increments usage by `quantity`; `"set"` reconciles the period total to `quantity`.
132
+ * `"set"` is a non-atomic read-modify-write (it reads the current total, then appends the delta), so
133
+ * concurrent `"set"` calls for the same reference can over- or under-count — call it only from a
134
+ * serialized context (a single Durable Object or per-reference lock). `"add"` is always safe.
135
+ */
127
136
  readonly mode?: "add" | "set";
128
137
  /** Usage amount to add, or the absolute period total when `mode` is `"set"` (defaults to `1`). */
129
138
  readonly quantity?: number;
@@ -1,7 +1,7 @@
1
1
  import { toErrorBody } from '@lunora/errors';
2
2
  import { usagePeriodStart, resolveEntitlements, featureNames, hasActivePrice } from './entitlementsForReference-CzZGXPoZ.mjs';
3
3
  import { LunoraPaymentError } from './LunoraPaymentError-BeQlhkZj.mjs';
4
- import idempotencyKey from './idempotencyKey-BFzDCA7g.mjs';
4
+ import { idempotencyKey, derivedIdempotencyKey } from './idempotencyKey-BzaHGdHD.mjs';
5
5
  import { n as notifyObserver } from './observability-CvhJ205g.mjs';
6
6
  import applyWebhookAction from './applyWebhookAction-CLAx4svt.mjs';
7
7
 
@@ -27,18 +27,16 @@ const createPayment = (options) => {
27
27
  const startCheckout = async (input) => {
28
28
  await ensureAuthorized(input.referenceId);
29
29
  const metadata = stripReferenceId(input.metadata);
30
- let { customerId } = input;
31
- if (!customerId) {
32
- const existing = await store.getCustomerByReference(adapter.identifier, input.referenceId);
33
- if (existing) {
34
- customerId = existing.id;
35
- } else {
36
- const customer = await adapter.getOrCreateCustomer({ email: input.email, referenceId: input.referenceId });
37
- customerId = customer.id;
38
- await store.upsertCustomer(customer);
39
- }
30
+ let customerId;
31
+ const existing = await store.getCustomerByReference(adapter.identifier, input.referenceId);
32
+ if (existing) {
33
+ customerId = existing.id;
34
+ } else {
35
+ const customer = await adapter.getOrCreateCustomer({ email: input.email, referenceId: input.referenceId });
36
+ customerId = customer.id;
37
+ await store.upsertCustomer(customer);
40
38
  }
41
- const key = input.idempotencyKey ?? idempotencyKey(
39
+ const key = input.idempotencyKey ?? await derivedIdempotencyKey(
42
40
  "checkout",
43
41
  adapter.identifier,
44
42
  input.referenceId,
@@ -0,0 +1,9 @@
1
+ const encoder = new TextEncoder();
2
+ const sha256Hex = async (value) => [...new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(value)))].map((byte) => byte.toString(16).padStart(2, "0")).join("");
3
+ const idempotencyKey = (operation, ...parts) => [operation, ...parts.map(String)].join(":");
4
+ const derivedIdempotencyKey = async (operation, provider, ...parts) => {
5
+ const digest = await sha256Hex(JSON.stringify([provider, ...parts.map(String)]));
6
+ return `${operation}:${provider}:${digest}`;
7
+ };
8
+
9
+ export { derivedIdempotencyKey, idempotencyKey };
@@ -2,10 +2,28 @@ const asRecord = (value) => typeof value === "object" && value !== null ? value
2
2
  const readString = (object, key) => typeof object[key] === "string" ? object[key] : void 0;
3
3
  const readNumber = (object, key) => typeof object[key] === "number" ? object[key] : void 0;
4
4
  const readBoolean = (object, key) => typeof object[key] === "boolean" ? object[key] : void 0;
5
+ const readAny = (object, ...keys) => {
6
+ for (const key of keys) {
7
+ const value = readString(object, key);
8
+ if (value !== void 0) {
9
+ return value;
10
+ }
11
+ }
12
+ return void 0;
13
+ };
14
+ const readAnyNumber = (object, ...keys) => {
15
+ for (const key of keys) {
16
+ const value = readNumber(object, key);
17
+ if (value !== void 0) {
18
+ return value;
19
+ }
20
+ }
21
+ return void 0;
22
+ };
5
23
  const referenceFromMetadata = (object) => readString(asRecord(object.metadata), "referenceId");
6
24
  const parseTimestamp = (value) => {
7
25
  const parsed = typeof value === "string" ? Date.parse(value) : Number.NaN;
8
26
  return Number.isNaN(parsed) ? void 0 : parsed;
9
27
  };
10
28
 
11
- export { asRecord as a, readNumber as b, readBoolean as c, referenceFromMetadata as d, parseTimestamp as p, readString as r };
29
+ export { asRecord as a, readNumber as b, readAny as c, readAnyNumber as d, readBoolean as e, referenceFromMetadata as f, parseTimestamp as p, readString as r };
@@ -1,4 +1,4 @@
1
- import { createPayment } from './createPayment-DzX-ji34.mjs';
1
+ import { createPayment } from './createPayment-CKQAznOL.mjs';
2
2
  import { createDatabasePaymentStore } from './createDatabasePaymentStore-C0NUCj1H.mjs';
3
3
 
4
4
  const lunoraDatabaseToPaymentDatabase = (database) => {
@@ -0,0 +1,59 @@
1
+ import { defineTable } from '@lunora/server';
2
+ import { v } from '@lunora/values';
3
+
4
+ const customers = defineTable({
5
+ createdAt: v.number(),
6
+ email: v.optional(v.string()),
7
+ provider: v.string(),
8
+ providerCustomerId: v.string(),
9
+ referenceId: v.string()
10
+ }).index("by_provider_customer", ["provider", "providerCustomerId"], { unique: true }).index("by_reference", ["referenceId"]);
11
+ const subscriptions = defineTable({
12
+ cancelAtPeriodEnd: v.boolean(),
13
+ createdAt: v.number(),
14
+ currentPeriodEnd: v.optional(v.number()),
15
+ currentPeriodStart: v.optional(v.number()),
16
+ priceId: v.string(),
17
+ provider: v.string(),
18
+ providerSubscriptionId: v.string(),
19
+ quantity: v.number(),
20
+ referenceId: v.string(),
21
+ state: v.string(),
22
+ updatedAt: v.number()
23
+ }).index("by_provider_subscription", ["provider", "providerSubscriptionId"], { unique: true }).index("by_reference", ["referenceId"]);
24
+ const paymentSessions = defineTable({
25
+ amountMinor: v.bigint(),
26
+ capturedMinor: v.bigint(),
27
+ createdAt: v.number(),
28
+ currency: v.string(),
29
+ provider: v.string(),
30
+ providerSessionId: v.string(),
31
+ referenceId: v.string(),
32
+ refundedMinor: v.bigint(),
33
+ state: v.string(),
34
+ updatedAt: v.number()
35
+ }).index("by_provider_session", ["provider", "providerSessionId"], { unique: true }).index("by_reference", ["referenceId"]);
36
+ const events = defineTable({
37
+ processedAt: v.number(),
38
+ provider: v.string(),
39
+ providerEventId: v.string(),
40
+ type: v.string()
41
+ }).index("by_provider_event", ["provider", "providerEventId"], { unique: true });
42
+ const usageEvents = defineTable({
43
+ createdAt: v.number(),
44
+ featureId: v.string(),
45
+ idempotencyKey: v.string(),
46
+ provider: v.string(),
47
+ quantity: v.number(),
48
+ referenceId: v.string(),
49
+ reportedToProvider: v.boolean()
50
+ }).index("by_idempotency", ["provider", "idempotencyKey"], { unique: true }).index("by_reference_feature", ["referenceId", "featureId"]);
51
+ const paymentTables = {
52
+ customers,
53
+ events,
54
+ paymentSessions,
55
+ subscriptions,
56
+ usageEvents
57
+ };
58
+
59
+ export { paymentTables as default };
@@ -4,6 +4,17 @@ import { n as notifyObserver } from './observability-CvhJ205g.mjs';
4
4
  const sameCurrencyAmount = (a, b) => a.currency === b.currency && compareMoney(a, b) === 0;
5
5
  const subscriptionDrifted = (existing, current) => existing?.state !== current.state || existing.cancelAtPeriodEnd !== current.cancelAtPeriodEnd || existing.currentPeriodEnd !== current.currentPeriodEnd || existing.priceId !== current.priceId || existing.quantity !== current.quantity;
6
6
  const paymentDrifted = (existing, current) => existing?.state !== current.state || !sameCurrencyAmount(existing.capturedAmount, current.capturedAmount) || !sameCurrencyAmount(existing.refundedAmount, current.refundedAmount);
7
+ const REFUND_STATES = /* @__PURE__ */ new Set(["partially_refunded", "refunded"]);
8
+ const mergePaymentTruth = (existing, current) => {
9
+ if (!existing) {
10
+ return current;
11
+ }
12
+ const sameRefundCurrency = existing.refundedAmount.currency === current.refundedAmount.currency;
13
+ const refundedAmount = sameRefundCurrency && compareMoney(existing.refundedAmount, current.refundedAmount) > 0 ? existing.refundedAmount : current.refundedAmount;
14
+ const state = current.state === "captured" && REFUND_STATES.has(existing.state) ? existing.state : current.state;
15
+ const referenceId = current.referenceId === "" ? existing.referenceId : current.referenceId;
16
+ return { ...current, referenceId, refundedAmount, state };
17
+ };
7
18
  const reconcileSubscription = async (adapter, store, id, observer) => {
8
19
  const current = await adapter.getSubscriptionStatus(id);
9
20
  const existing = await store.getSubscription(adapter.identifier, id);
@@ -15,8 +26,9 @@ const reconcileSubscription = async (adapter, store, id, observer) => {
15
26
  return true;
16
27
  };
17
28
  const reconcilePayment = async (adapter, store, id, observer) => {
18
- const current = await adapter.getPaymentStatus(id);
29
+ const providerTruth = await adapter.getPaymentStatus(id);
19
30
  const existing = await store.getPaymentSession(adapter.identifier, id);
31
+ const current = mergePaymentTruth(existing, providerTruth);
20
32
  if (!paymentDrifted(existing, current)) {
21
33
  return false;
22
34
  }
@@ -1,4 +1,4 @@
1
- import { a as asRecord, r as readString, b as readNumber } from '../packem_shared/json-DhcPm8EO.mjs';
1
+ import { a as asRecord, r as readString, b as readNumber } from '../packem_shared/json-D0Cb5aMv.mjs';
2
2
 
3
3
  const toFeatureId = (featureId) => typeof featureId === "string" ? featureId : [...featureId];
4
4
  const entityFrom = (record) => {
@@ -1,4 +1,4 @@
1
- import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.mjs";
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-SULGy2QF.mjs";
2
2
  /**
3
3
  * The `autumn-js` SDK surface the adapter uses, as a structural type — a real `Autumn` instance
4
4
  * satisfies it without a cast. Resources/methods are `unknown` (the adapter re-types the client as
@@ -1,4 +1,4 @@
1
- import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.js";
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-SULGy2QF.js";
2
2
  /**
3
3
  * The `autumn-js` SDK surface the adapter uses, as a structural type — a real `Autumn` instance
4
4
  * satisfies it without a cast. Resources/methods are `unknown` (the adapter re-types the client as
@@ -1,7 +1,8 @@
1
1
  import { LunoraPaymentError } from '../packem_shared/LunoraPaymentError-BeQlhkZj.mjs';
2
- import { a as asRecord, c as readBoolean, r as readString, b as readNumber } from '../packem_shared/json-DhcPm8EO.mjs';
2
+ import { a as asRecord, c as readAny, d as readAnyNumber, e as readBoolean, r as readString } from '../packem_shared/json-D0Cb5aMv.mjs';
3
3
  import { money } from '../packem_shared/addMoney-jSh_7TfF.mjs';
4
4
  import { verifyStandardWebhook } from '../packem_shared/constantTimeEqual-BQjoW85H.mjs';
5
+ import { m as makeNotSupported } from '../packem_shared/not-supported-C0onRyia.mjs';
5
6
  import { s as stateToEventType } from '../packem_shared/subscription-event-CwEsWRCK.mjs';
6
7
 
7
8
  const SUBSCRIPTION_STATE_BY_AUTUMN_STATUS = {
@@ -21,9 +22,7 @@ const SUBSCRIPTION_STATE_BY_AUTUMN_ACTION = {
21
22
  scheduled: "paused"
22
23
  };
23
24
  const SUBSCRIPTION_ID_SEPARATOR = "::";
24
- const notSupported = (operation) => {
25
- throw new LunoraPaymentError("PROVIDER_ERROR", `autumn manages billing through the underlying processor and does not support ${operation}`);
26
- };
25
+ const notSupported = makeNotSupported("autumn");
27
26
  const autumnSubscriptionId = (customerId, productId) => `${customerId}${SUBSCRIPTION_ID_SEPARATOR}${productId}`;
28
27
  const parseAutumnSubscriptionId = (subscriptionId) => {
29
28
  const index = subscriptionId.lastIndexOf(SUBSCRIPTION_ID_SEPARATOR);
@@ -32,24 +31,6 @@ const parseAutumnSubscriptionId = (subscriptionId) => {
32
31
  }
33
32
  return { customerId: subscriptionId.slice(0, index), productId: subscriptionId.slice(index + SUBSCRIPTION_ID_SEPARATOR.length) };
34
33
  };
35
- const readAny = (object, ...keys) => {
36
- for (const key of keys) {
37
- const value = readString(object, key);
38
- if (value !== void 0) {
39
- return value;
40
- }
41
- }
42
- return void 0;
43
- };
44
- const readAnyNumber = (object, ...keys) => {
45
- for (const key of keys) {
46
- const value = readNumber(object, key);
47
- if (value !== void 0) {
48
- return value;
49
- }
50
- }
51
- return void 0;
52
- };
53
34
  const isCanceling = (product) => readAnyNumber(product, "canceled_at", "canceledAt") !== void 0 || readAny(product, "status") === "scheduled";
54
35
  const productToSubscription = (customerId, product) => {
55
36
  const now = Date.now();
@@ -1,4 +1,4 @@
1
- import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.mjs";
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-SULGy2QF.mjs";
2
2
  /**
3
3
  * The `creem` SDK surface the adapter uses, as a structural type — a real `Creem` instance satisfies
4
4
  * it without a cast. Resources are `unknown` (the adapter re-types the client as the real `Creem`
@@ -1,4 +1,4 @@
1
- import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.js";
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-SULGy2QF.js";
2
2
  /**
3
3
  * The `creem` SDK surface the adapter uses, as a structural type — a real `Creem` instance satisfies
4
4
  * it without a cast. Resources are `unknown` (the adapter re-types the client as the real `Creem`
@@ -1,4 +1,4 @@
1
- import { a as asRecord, r as readString, d as referenceFromMetadata, b as readNumber, p as parseTimestamp, c as readBoolean } from '../packem_shared/json-DhcPm8EO.mjs';
1
+ import { a as asRecord, c as readAny, r as readString, f as referenceFromMetadata, b as readNumber, p as parseTimestamp, e as readBoolean, d as readAnyNumber } from '../packem_shared/json-D0Cb5aMv.mjs';
2
2
  import { money, zeroMoney } from '../packem_shared/addMoney-jSh_7TfF.mjs';
3
3
  import { verifyCreemSignature } from '../packem_shared/constantTimeEqual-BQjoW85H.mjs';
4
4
  import { m as makeNotSupported } from '../packem_shared/not-supported-C0onRyia.mjs';
@@ -32,8 +32,8 @@ const SUBSCRIPTION_STATE_BY_CREEM_STATUS = {
32
32
  };
33
33
  const notSupported = makeNotSupported("creem (merchant-of-record)");
34
34
  const idOf = (value) => typeof value === "string" ? value : readString(asRecord(value), "id");
35
- const readCheckoutUrl = (checkout) => readString(checkout, "checkout_url") ?? readString(checkout, "checkoutUrl") ?? "";
36
- const isCanceling = (subscription) => readString(subscription, "canceled_at") !== void 0 || readString(subscription, "canceledAt") !== void 0 || readString(subscription, "status") === "scheduled_cancel";
35
+ const readCheckoutUrl = (checkout) => readAny(checkout, "checkout_url", "checkoutUrl") ?? "";
36
+ const isCanceling = (subscription) => readAny(subscription, "canceled_at", "canceledAt") !== void 0 || readString(subscription, "status") === "scheduled_cancel";
37
37
  const subscriptionFromCreem = (input) => {
38
38
  const subscription = asRecord(input);
39
39
  const now = Date.now();
@@ -41,8 +41,8 @@ const subscriptionFromCreem = (input) => {
41
41
  return {
42
42
  cancelAtPeriodEnd: isCanceling(subscription),
43
43
  createdAt: now,
44
- currentPeriodEnd: parseTimestamp(readString(subscription, "current_period_end_date") ?? readString(subscription, "currentPeriodEndDate")),
45
- currentPeriodStart: parseTimestamp(readString(subscription, "current_period_start_date") ?? readString(subscription, "currentPeriodStartDate")),
44
+ currentPeriodEnd: parseTimestamp(readAny(subscription, "current_period_end_date", "currentPeriodEndDate")),
45
+ currentPeriodStart: parseTimestamp(readAny(subscription, "current_period_start_date", "currentPeriodStartDate")),
46
46
  id: readString(subscription, "id") ?? "",
47
47
  priceId: idOf(subscription.product) ?? "",
48
48
  provider: "creem",
@@ -93,8 +93,8 @@ const mapEvent = (eventId, eventType, object) => {
93
93
  };
94
94
  }
95
95
  case "refund.created": {
96
- const amount = readNumber(object, "refund_amount") ?? readNumber(object, "refundAmount") ?? readNumber(object, "amount") ?? readNumber(order, "amount");
97
- const refundCurrency = readString(object, "refund_currency") ?? readString(object, "refundCurrency") ?? currency;
96
+ const amount = readAnyNumber(object, "refund_amount", "refundAmount", "amount") ?? readNumber(order, "amount");
97
+ const refundCurrency = readAny(object, "refund_currency", "refundCurrency") ?? currency;
98
98
  return {
99
99
  ...base,
100
100
  amount: amount === void 0 ? void 0 : money(BigInt(Math.round(amount)), refundCurrency),
@@ -117,8 +117,8 @@ const mapEvent = (eventId, eventType, object) => {
117
117
  return {
118
118
  ...base,
119
119
  cancelAtPeriodEnd: readBoolean(object, "cancel_at_period_end") ?? isCanceling(object),
120
- currentPeriodEnd: parseTimestamp(readString(object, "current_period_end_date") ?? readString(object, "currentPeriodEndDate")),
121
- currentPeriodStart: parseTimestamp(readString(object, "current_period_start_date") ?? readString(object, "currentPeriodStartDate")),
120
+ currentPeriodEnd: parseTimestamp(readAny(object, "current_period_end_date", "currentPeriodEndDate")),
121
+ currentPeriodStart: parseTimestamp(readAny(object, "current_period_start_date", "currentPeriodStartDate")),
122
122
  customerId: idOf(object.customer),
123
123
  priceId: idOf(object.product),
124
124
  referenceId: referenceFromMetadata(object) ?? idOf(object.customer),
@@ -159,7 +159,7 @@ const createCreemAdapter = (options) => {
159
159
  },
160
160
  createPortalSession: async (input) => {
161
161
  const link = await client.customers.generateBillingLinks({ customerId: input.customerId });
162
- return { url: readString(link, "customer_portal_link") ?? readString(link, "customerPortalLink") ?? "" };
162
+ return { url: readAny(link, "customer_portal_link", "customerPortalLink") ?? "" };
163
163
  },
164
164
  getOrCreateCustomer: async (ref) => {
165
165
  const toCustomer = (record) => {
@@ -186,7 +186,7 @@ const createCreemAdapter = (options) => {
186
186
  parseWebhook: async ({ headers, payload }) => {
187
187
  await verifyCreemSignature({ payload, secret: webhookSecret, signature: headers.get("creem-signature") ?? "" });
188
188
  const event = asRecord(JSON.parse(payload));
189
- return mapEvent(readString(event, "id") ?? "", readString(event, "eventType") ?? readString(event, "type") ?? "", asRecord(event.object));
189
+ return mapEvent(readString(event, "id") ?? "", readAny(event, "eventType", "type") ?? "", asRecord(event.object));
190
190
  },
191
191
  // Creem refunds are issued from the dashboard; there is no SDK endpoint to initiate one.
192
192
  refundPayment: () => notSupported("programmatic refunds"),
@@ -1,4 +1,4 @@
1
- import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.mjs";
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-SULGy2QF.mjs";
2
2
  /**
3
3
  * The `dodopayments` SDK surface the adapter uses, as a structural type — a real `DodoPayments`
4
4
  * instance satisfies it without a cast. Resources are `unknown` (the adapter re-types the client as
@@ -1,4 +1,4 @@
1
- import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.js";
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-SULGy2QF.js";
2
2
  /**
3
3
  * The `dodopayments` SDK surface the adapter uses, as a structural type — a real `DodoPayments`
4
4
  * instance satisfies it without a cast. Resources are `unknown` (the adapter re-types the client as
@@ -1,6 +1,6 @@
1
1
  import { LunoraPaymentError } from '../packem_shared/LunoraPaymentError-BeQlhkZj.mjs';
2
- import idempotencyKey from '../packem_shared/idempotencyKey-BFzDCA7g.mjs';
3
- import { a as asRecord, b as readNumber, r as readString, d as referenceFromMetadata, p as parseTimestamp, c as readBoolean } from '../packem_shared/json-DhcPm8EO.mjs';
2
+ import { idempotencyKey } from '../packem_shared/idempotencyKey-BzaHGdHD.mjs';
3
+ import { a as asRecord, b as readNumber, r as readString, f as referenceFromMetadata, p as parseTimestamp, e as readBoolean } from '../packem_shared/json-D0Cb5aMv.mjs';
4
4
  import { money, zeroMoney } from '../packem_shared/addMoney-jSh_7TfF.mjs';
5
5
  import { verifyStandardWebhook } from '../packem_shared/constantTimeEqual-BQjoW85H.mjs';
6
6
  import { m as makeNotSupported } from '../packem_shared/not-supported-C0onRyia.mjs';
@@ -1,4 +1,4 @@
1
- import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.mjs";
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-SULGy2QF.mjs";
2
2
  /**
3
3
  * The `@polar-sh/sdk` surface the adapter uses, as a structural type — a real `Polar` instance
4
4
  * satisfies it without a cast. Resources are `unknown` (the adapter re-types the client as the real
@@ -1,4 +1,4 @@
1
- import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.js";
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-SULGy2QF.js";
2
2
  /**
3
3
  * The `@polar-sh/sdk` surface the adapter uses, as a structural type — a real `Polar` instance
4
4
  * satisfies it without a cast. Resources are `unknown` (the adapter re-types the client as the real
@@ -1,4 +1,4 @@
1
- import { a as asRecord, r as readString, d as referenceFromMetadata, c as readBoolean, p as parseTimestamp, b as readNumber } from '../packem_shared/json-DhcPm8EO.mjs';
1
+ import { a as asRecord, r as readString, f as referenceFromMetadata, e as readBoolean, p as parseTimestamp, b as readNumber } from '../packem_shared/json-D0Cb5aMv.mjs';
2
2
  import { money, zeroMoney } from '../packem_shared/addMoney-jSh_7TfF.mjs';
3
3
  import { verifyStandardWebhook } from '../packem_shared/constantTimeEqual-BQjoW85H.mjs';
4
4
  import { m as makeNotSupported } from '../packem_shared/not-supported-C0onRyia.mjs';
@@ -61,7 +61,9 @@ const orderToSession = (input) => {
61
61
  createdAt: now,
62
62
  id: readString(order, "id") ?? "",
63
63
  provider: "polar",
64
- referenceId: "",
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) ?? "",
65
67
  refundedAmount: state === "refunded" ? amount : zeroMoney(currency),
66
68
  state,
67
69
  updatedAt: now
@@ -132,10 +134,18 @@ const createPolarAdapter = (options) => {
132
134
  capturePayment: (_input) => notSupported("manual capture"),
133
135
  createCheckout: async (input) => {
134
136
  const checkout = await client.checkouts.create({
135
- customerEmail: void 0,
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,
136
144
  // Pin the framework-controlled `referenceId` LAST so caller metadata can never override it.
137
145
  metadata: { ...input.metadata, referenceId: input.referenceId },
138
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,
139
149
  successUrl: input.successUrl
140
150
  });
141
151
  return { id: checkout.id, provider: "polar", url: checkout.url };
@@ -1,4 +1,4 @@
1
- import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.mjs";
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-SULGy2QF.mjs";
2
2
  /**
3
3
  * The Stripe SDK surface the adapter uses, as a structural type — a real `Stripe` instance satisfies
4
4
  * it without a cast. Resources are `unknown` here (the adapter re-types the client as the real
@@ -1,4 +1,4 @@
1
- import { P as PaymentAdapter } from "../packem_shared/adapter.d-iFA55DzU.js";
1
+ import { P as PaymentAdapter } from "../packem_shared/adapter.d-SULGy2QF.js";
2
2
  /**
3
3
  * The Stripe SDK surface the adapter uses, as a structural type — a real `Stripe` instance satisfies
4
4
  * it without a cast. Resources are `unknown` here (the adapter re-types the client as the real
@@ -1,6 +1,6 @@
1
1
  import { LunoraPaymentError } from '../packem_shared/LunoraPaymentError-BeQlhkZj.mjs';
2
- import idempotencyKey from '../packem_shared/idempotencyKey-BFzDCA7g.mjs';
3
- import { r as readString, c as readBoolean, b as readNumber, a as asRecord } from '../packem_shared/json-DhcPm8EO.mjs';
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
4
  import { compareMoney, money, zeroMoney } from '../packem_shared/addMoney-jSh_7TfF.mjs';
5
5
  import { s as stateToEventType } from '../packem_shared/subscription-event-CwEsWRCK.mjs';
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/payment",
3
- "version": "1.0.0-alpha.22",
3
+ "version": "1.0.0-alpha.23",
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.3",
75
- "@lunora/server": "1.0.0-alpha.22",
76
- "@lunora/values": "1.0.0-alpha.6",
74
+ "@lunora/errors": "1.0.0-alpha.4",
75
+ "@lunora/server": "1.0.0-alpha.23",
76
+ "@lunora/values": "1.0.0-alpha.7",
77
77
  "dinero.js": "2.0.2"
78
78
  },
79
79
  "peerDependencies": {
@@ -1,3 +0,0 @@
1
- const idempotencyKey = (operation, ...parts) => [operation, ...parts.map(String)].join(":");
2
-
3
- export { idempotencyKey as default };
@@ -1,127 +0,0 @@
1
- import { defineTable } from '@lunora/server';
2
- import { v } from '@lunora/values';
3
-
4
- const products = defineTable({
5
- description: v.optional(v.string()),
6
- name: v.string(),
7
- provider: v.string(),
8
- providerProductId: v.string()
9
- }).index("by_provider_product", ["provider", "providerProductId"], { unique: true });
10
- const prices = defineTable({
11
- active: v.boolean(),
12
- amountMinor: v.bigint(),
13
- currency: v.string(),
14
- interval: v.optional(v.string()),
15
- provider: v.string(),
16
- providerPriceId: v.string(),
17
- providerProductId: v.string()
18
- }).index("by_provider_price", ["provider", "providerPriceId"], { unique: true });
19
- const customers = defineTable({
20
- createdAt: v.number(),
21
- email: v.optional(v.string()),
22
- provider: v.string(),
23
- providerCustomerId: v.string(),
24
- referenceId: v.string()
25
- }).index("by_provider_customer", ["provider", "providerCustomerId"], { unique: true }).index("by_reference", ["referenceId"]);
26
- const subscriptions = defineTable({
27
- cancelAtPeriodEnd: v.boolean(),
28
- createdAt: v.number(),
29
- currentPeriodEnd: v.optional(v.number()),
30
- currentPeriodStart: v.optional(v.number()),
31
- priceId: v.string(),
32
- provider: v.string(),
33
- providerSubscriptionId: v.string(),
34
- quantity: v.number(),
35
- referenceId: v.string(),
36
- state: v.string(),
37
- updatedAt: v.number()
38
- }).index("by_provider_subscription", ["provider", "providerSubscriptionId"], { unique: true }).index("by_reference", ["referenceId"]);
39
- const checkouts = defineTable({
40
- createdAt: v.number(),
41
- mode: v.string(),
42
- priceId: v.string(),
43
- provider: v.string(),
44
- providerCheckoutId: v.string(),
45
- referenceId: v.string(),
46
- url: v.string()
47
- }).index("by_provider_checkout", ["provider", "providerCheckoutId"], { unique: true });
48
- const paymentSessions = defineTable({
49
- amountMinor: v.bigint(),
50
- capturedMinor: v.bigint(),
51
- createdAt: v.number(),
52
- currency: v.string(),
53
- provider: v.string(),
54
- providerSessionId: v.string(),
55
- referenceId: v.string(),
56
- refundedMinor: v.bigint(),
57
- state: v.string(),
58
- updatedAt: v.number()
59
- }).index("by_provider_session", ["provider", "providerSessionId"], { unique: true }).index("by_reference", ["referenceId"]);
60
- const payments = defineTable({
61
- amountMinor: v.bigint(),
62
- createdAt: v.number(),
63
- currency: v.string(),
64
- provider: v.string(),
65
- providerPaymentId: v.string(),
66
- referenceId: v.string(),
67
- sessionId: v.string(),
68
- status: v.string()
69
- }).index("by_provider_payment", ["provider", "providerPaymentId"], { unique: true });
70
- const captures = defineTable({
71
- amountMinor: v.bigint(),
72
- createdAt: v.number(),
73
- currency: v.string(),
74
- provider: v.string(),
75
- providerCaptureId: v.string(),
76
- sessionId: v.string()
77
- }).index("by_session", ["sessionId"]);
78
- const refunds = defineTable({
79
- amountMinor: v.bigint(),
80
- createdAt: v.number(),
81
- currency: v.string(),
82
- provider: v.string(),
83
- providerRefundId: v.string(),
84
- reason: v.optional(v.string()),
85
- sessionId: v.string()
86
- }).index("by_session", ["sessionId"]);
87
- const invoices = defineTable({
88
- amountMinor: v.bigint(),
89
- createdAt: v.number(),
90
- currency: v.string(),
91
- provider: v.string(),
92
- providerInvoiceId: v.string(),
93
- referenceId: v.string(),
94
- status: v.string(),
95
- subscriptionId: v.optional(v.string())
96
- }).index("by_provider_invoice", ["provider", "providerInvoiceId"], { unique: true });
97
- const events = defineTable({
98
- processedAt: v.number(),
99
- provider: v.string(),
100
- providerEventId: v.string(),
101
- type: v.string()
102
- }).index("by_provider_event", ["provider", "providerEventId"], { unique: true });
103
- const usageEvents = defineTable({
104
- createdAt: v.number(),
105
- featureId: v.string(),
106
- idempotencyKey: v.string(),
107
- provider: v.string(),
108
- quantity: v.number(),
109
- referenceId: v.string(),
110
- reportedToProvider: v.boolean()
111
- }).index("by_idempotency", ["provider", "idempotencyKey"], { unique: true }).index("by_reference_feature", ["referenceId", "featureId"]);
112
- const paymentTables = {
113
- captures,
114
- checkouts,
115
- customers,
116
- events,
117
- invoices,
118
- paymentSessions,
119
- payments,
120
- prices,
121
- products,
122
- refunds,
123
- subscriptions,
124
- usageEvents
125
- };
126
-
127
- export { paymentTables as default };