@lunora/payment 1.0.0-alpha.25 → 1.0.0-alpha.27

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.
@@ -4,6 +4,7 @@ import { verifyCreemSignature } from '../packem_shared/constantTimeEqual-BQjoW85
4
4
  import { m as makeNotSupported } from '../packem_shared/not-supported-C0onRyia.mjs';
5
5
  import { s as stateToEventType } from '../packem_shared/subscription-event-CwEsWRCK.mjs';
6
6
 
7
+ const ALREADY_EXISTS_PATTERN = /already exists/iu;
7
8
  const PAYMENT_STATE_BY_CREEM_STATUS = {
8
9
  canceled: "canceled",
9
10
  completed: "captured",
@@ -34,6 +35,16 @@ const notSupported = makeNotSupported("creem (merchant-of-record)");
34
35
  const idOf = (value) => typeof value === "string" ? value : readString(asRecord(value), "id");
35
36
  const readCheckoutUrl = (checkout) => readAny(checkout, "checkout_url", "checkoutUrl") ?? "";
36
37
  const isCanceling = (subscription) => readAny(subscription, "canceled_at", "canceledAt") !== void 0 || readString(subscription, "status") === "scheduled_cancel";
38
+ const isDuplicateCustomerError = (error) => {
39
+ if (!(error instanceof Error)) {
40
+ return false;
41
+ }
42
+ const { statusCode } = error;
43
+ if (typeof statusCode === "number" && statusCode !== 400 && statusCode !== 409) {
44
+ return false;
45
+ }
46
+ return ALREADY_EXISTS_PATTERN.test(error.message);
47
+ };
37
48
  const subscriptionFromCreem = (input) => {
38
49
  const subscription = asRecord(input);
39
50
  const now = Date.now();
@@ -172,10 +183,30 @@ const createCreemAdapter = (options) => {
172
183
  };
173
184
  };
174
185
  try {
175
- return toCustomer(asRecord(await client.customers.create({ email: ref.email ?? "", name: ref.metadata?.name ?? ref.referenceId })));
186
+ return toCustomer(
187
+ asRecord(
188
+ await client.customers.create({
189
+ email: ref.email ?? "",
190
+ // Pin the framework-controlled `referenceId` LAST so caller metadata can never
191
+ // override it (same pattern as `createCheckout`) — this is the only thing Creem
192
+ // lets us key a customer on (no `externalId`-style create, unlike Polar/Dodo), so
193
+ // the recovery path below can verify it before ever adopting a retrieved customer.
194
+ metadata: { ...ref.metadata, referenceId: ref.referenceId },
195
+ name: ref.metadata?.name ?? ref.referenceId
196
+ })
197
+ )
198
+ );
176
199
  } catch (error) {
177
- if (ref.email !== void 0) {
178
- return toCustomer(asRecord(await client.customers.retrieve(void 0, ref.email)));
200
+ if (ref.email !== void 0 && isDuplicateCustomerError(error)) {
201
+ const existing = asRecord(await client.customers.retrieve(void 0, ref.email));
202
+ const existingReferenceId = referenceFromMetadata(existing);
203
+ if (existingReferenceId !== ref.referenceId) {
204
+ throw new Error(
205
+ `Creem customer for email "${ref.email}" already belongs to a different reference; refusing to bind it to "${ref.referenceId}".`,
206
+ { cause: error }
207
+ );
208
+ }
209
+ return toCustomer(existing);
179
210
  }
180
211
  throw error;
181
212
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/payment",
3
- "version": "1.0.0-alpha.25",
3
+ "version": "1.0.0-alpha.27",
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.5",
75
- "@lunora/server": "1.0.0-alpha.25",
75
+ "@lunora/server": "1.0.0-alpha.27",
76
76
  "@lunora/values": "1.0.0-alpha.8",
77
77
  "dinero.js": "2.0.2"
78
78
  },