@better-auth/stripe 1.4.16 → 1.4.18
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/.turbo/turbo-build.log +10 -8
- package/dist/client.d.mts +2 -1
- package/dist/client.mjs +2 -1
- package/dist/client.mjs.map +1 -0
- package/dist/index-BTvn0abC.d.mts +2 -1
- package/dist/index.mjs +77 -51
- package/dist/index.mjs.map +1 -0
- package/package.json +6 -6
- package/src/client.ts +1 -1
- package/src/hooks.ts +10 -5
- package/src/index.ts +10 -6
- package/src/metadata.ts +94 -0
- package/src/routes.ts +83 -95
- package/test/stripe.test.ts +398 -1
- package/tsdown.config.ts +1 -0
- package/CHANGELOG.md +0 -22
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/stripe",
|
|
3
3
|
"author": "Bereket Engida",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.18",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.mts",
|
|
@@ -46,19 +46,19 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"defu": "^6.1.4",
|
|
49
|
-
"zod": "^4.
|
|
49
|
+
"zod": "^4.3.5"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"stripe": "^18 || ^19 || ^20",
|
|
53
|
-
"@better-auth/core": "1.4.
|
|
54
|
-
"better-auth": "1.4.
|
|
53
|
+
"@better-auth/core": "1.4.18",
|
|
54
|
+
"better-auth": "1.4.18"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"better-call": "1.1.8",
|
|
58
58
|
"stripe": "^20.0.0",
|
|
59
59
|
"tsdown": "^0.17.2",
|
|
60
|
-
"@better-auth/core": "1.4.
|
|
61
|
-
"better-auth": "1.4.
|
|
60
|
+
"@better-auth/core": "1.4.18",
|
|
61
|
+
"better-auth": "1.4.18"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"test": "vitest",
|
package/src/client.ts
CHANGED
package/src/hooks.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { GenericEndpointContext } from "@better-auth/core";
|
|
|
2
2
|
import type { User } from "@better-auth/core/db";
|
|
3
3
|
import type { Organization } from "better-auth/plugins/organization";
|
|
4
4
|
import type Stripe from "stripe";
|
|
5
|
+
import { subscriptionMetadata } from "./metadata";
|
|
5
6
|
import type { CustomerType, StripeOptions, Subscription } from "./types";
|
|
6
7
|
import {
|
|
7
8
|
getPlanByPriceInfo,
|
|
@@ -66,10 +67,10 @@ export async function onCheckoutSessionCompleted(
|
|
|
66
67
|
priceLookupKey,
|
|
67
68
|
);
|
|
68
69
|
if (plan) {
|
|
70
|
+
const checkoutMeta = subscriptionMetadata.get(checkoutSession?.metadata);
|
|
69
71
|
const referenceId =
|
|
70
|
-
checkoutSession?.client_reference_id ||
|
|
71
|
-
|
|
72
|
-
const subscriptionId = checkoutSession?.metadata?.subscriptionId;
|
|
72
|
+
checkoutSession?.client_reference_id || checkoutMeta.referenceId;
|
|
73
|
+
const { subscriptionId } = checkoutMeta;
|
|
73
74
|
const seats = subscriptionItem.quantity;
|
|
74
75
|
if (referenceId && subscriptionId) {
|
|
75
76
|
const trial =
|
|
@@ -162,7 +163,9 @@ export async function onSubscriptionCreated(
|
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
// Check if subscription already exists in database
|
|
165
|
-
const subscriptionId =
|
|
166
|
+
const { subscriptionId } = subscriptionMetadata.get(
|
|
167
|
+
subscriptionCreated.metadata,
|
|
168
|
+
);
|
|
166
169
|
const existingSubscription =
|
|
167
170
|
await ctx.context.adapter.findOne<Subscription>({
|
|
168
171
|
model: "subscription",
|
|
@@ -275,7 +278,9 @@ export async function onSubscriptionUpdated(
|
|
|
275
278
|
const priceLookupKey = subscriptionItem.price.lookup_key;
|
|
276
279
|
const plan = await getPlanByPriceInfo(options, priceId, priceLookupKey);
|
|
277
280
|
|
|
278
|
-
const subscriptionId =
|
|
281
|
+
const { subscriptionId } = subscriptionMetadata.get(
|
|
282
|
+
subscriptionUpdated.metadata,
|
|
283
|
+
);
|
|
279
284
|
const customerId = subscriptionUpdated.customer?.toString();
|
|
280
285
|
let subscription = await ctx.context.adapter.findOne<Subscription>({
|
|
281
286
|
model: "subscription",
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
import { defu } from "defu";
|
|
9
9
|
import type Stripe from "stripe";
|
|
10
10
|
import { STRIPE_ERROR_CODES } from "./error-codes";
|
|
11
|
+
import { customerMetadata } from "./metadata";
|
|
11
12
|
import {
|
|
12
13
|
cancelSubscription,
|
|
13
14
|
cancelSubscriptionCallback,
|
|
@@ -178,7 +179,7 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|
|
178
179
|
try {
|
|
179
180
|
// Check if user customer already exists in Stripe by email
|
|
180
181
|
const existingCustomers = await client.customers.search({
|
|
181
|
-
query: `email:"${escapeStripeSearchValue(user.email)}" AND -metadata["customerType"]:"organization"`,
|
|
182
|
+
query: `email:"${escapeStripeSearchValue(user.email)}" AND -metadata["${customerMetadata.keys.customerType}"]:"organization"`,
|
|
182
183
|
limit: 1,
|
|
183
184
|
});
|
|
184
185
|
|
|
@@ -215,14 +216,17 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|
|
215
216
|
);
|
|
216
217
|
}
|
|
217
218
|
|
|
218
|
-
const params
|
|
219
|
+
const params = defu(
|
|
219
220
|
{
|
|
220
221
|
email: user.email,
|
|
221
222
|
name: user.name,
|
|
222
|
-
metadata:
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
metadata: customerMetadata.set(
|
|
224
|
+
{
|
|
225
|
+
userId: user.id,
|
|
226
|
+
customerType: "user",
|
|
227
|
+
},
|
|
228
|
+
extraCreateParams?.metadata,
|
|
229
|
+
),
|
|
226
230
|
},
|
|
227
231
|
extraCreateParams,
|
|
228
232
|
);
|
package/src/metadata.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { defu } from "defu";
|
|
2
|
+
import type Stripe from "stripe";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Internal metadata fields for Stripe Customer.
|
|
6
|
+
*/
|
|
7
|
+
type CustomerInternalMetadata =
|
|
8
|
+
| { customerType: "user"; userId: string }
|
|
9
|
+
| { customerType: "organization"; organizationId: string };
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Internal metadata fields for Stripe Subscription/Checkout.
|
|
13
|
+
*/
|
|
14
|
+
type SubscriptionInternalMetadata = {
|
|
15
|
+
userId: string;
|
|
16
|
+
subscriptionId: string;
|
|
17
|
+
referenceId: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Customer metadata - set internal fields and extract typed fields.
|
|
22
|
+
*/
|
|
23
|
+
export const customerMetadata = {
|
|
24
|
+
/**
|
|
25
|
+
* Internal metadata keys for type-safe access.
|
|
26
|
+
*/
|
|
27
|
+
keys: {
|
|
28
|
+
userId: "userId",
|
|
29
|
+
organizationId: "organizationId",
|
|
30
|
+
customerType: "customerType",
|
|
31
|
+
} as const,
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create metadata with internal fields that cannot be overridden by user metadata.
|
|
35
|
+
* Uses `defu` which prioritizes the first argument.
|
|
36
|
+
*/
|
|
37
|
+
set(
|
|
38
|
+
internalFields: CustomerInternalMetadata,
|
|
39
|
+
...userMetadata: (Stripe.Emptyable<Stripe.MetadataParam> | undefined)[]
|
|
40
|
+
): Stripe.MetadataParam {
|
|
41
|
+
return defu(internalFields, ...userMetadata.filter(Boolean));
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Extract internal fields from Stripe metadata.
|
|
46
|
+
* Provides type-safe access to internal metadata keys.
|
|
47
|
+
*/
|
|
48
|
+
get(metadata: Stripe.Metadata | null | undefined) {
|
|
49
|
+
return {
|
|
50
|
+
userId: metadata?.userId,
|
|
51
|
+
organizationId: metadata?.organizationId,
|
|
52
|
+
customerType: metadata?.customerType as
|
|
53
|
+
| CustomerInternalMetadata["customerType"]
|
|
54
|
+
| undefined,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Subscription/Checkout metadata - set internal fields and extract typed fields.
|
|
61
|
+
*/
|
|
62
|
+
export const subscriptionMetadata = {
|
|
63
|
+
/**
|
|
64
|
+
* Internal metadata keys for type-safe access.
|
|
65
|
+
*/
|
|
66
|
+
keys: {
|
|
67
|
+
userId: "userId",
|
|
68
|
+
subscriptionId: "subscriptionId",
|
|
69
|
+
referenceId: "referenceId",
|
|
70
|
+
} as const,
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create metadata with internal fields that cannot be overridden by user metadata.
|
|
74
|
+
* Uses `defu` which prioritizes the first argument.
|
|
75
|
+
*/
|
|
76
|
+
set(
|
|
77
|
+
internalFields: SubscriptionInternalMetadata,
|
|
78
|
+
...userMetadata: (Stripe.Emptyable<Stripe.MetadataParam> | undefined)[]
|
|
79
|
+
): Stripe.MetadataParam {
|
|
80
|
+
return defu(internalFields, ...userMetadata.filter(Boolean));
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Extract internal fields from Stripe metadata.
|
|
85
|
+
* Provides type-safe access to internal metadata keys.
|
|
86
|
+
*/
|
|
87
|
+
get(metadata: Stripe.Metadata | null | undefined) {
|
|
88
|
+
return {
|
|
89
|
+
userId: metadata?.userId,
|
|
90
|
+
subscriptionId: metadata?.subscriptionId,
|
|
91
|
+
referenceId: metadata?.referenceId,
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
};
|
package/src/routes.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
onSubscriptionDeleted,
|
|
15
15
|
onSubscriptionUpdated,
|
|
16
16
|
} from "./hooks";
|
|
17
|
+
import { customerMetadata, subscriptionMetadata } from "./metadata";
|
|
17
18
|
import { referenceMiddleware, stripeSessionMiddleware } from "./middleware";
|
|
18
19
|
import type {
|
|
19
20
|
CustomerType,
|
|
@@ -269,8 +270,9 @@ export const upgradeSubscription = (options: StripeOptions) => {
|
|
|
269
270
|
});
|
|
270
271
|
}
|
|
271
272
|
|
|
272
|
-
//
|
|
273
|
-
|
|
273
|
+
// If subscriptionId is provided, find that specific subscription.
|
|
274
|
+
// Otherwise, active subscription will be resolved by referenceId later.
|
|
275
|
+
const subscriptionToUpdate = ctx.body.subscriptionId
|
|
274
276
|
? await ctx.context.adapter.findOne<Subscription>({
|
|
275
277
|
model: "subscription",
|
|
276
278
|
where: [
|
|
@@ -280,26 +282,17 @@ export const upgradeSubscription = (options: StripeOptions) => {
|
|
|
280
282
|
},
|
|
281
283
|
],
|
|
282
284
|
})
|
|
283
|
-
:
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
value: referenceId,
|
|
290
|
-
},
|
|
291
|
-
],
|
|
292
|
-
})
|
|
293
|
-
: null;
|
|
294
|
-
|
|
285
|
+
: null;
|
|
286
|
+
if (ctx.body.subscriptionId && !subscriptionToUpdate) {
|
|
287
|
+
throw new APIError("BAD_REQUEST", {
|
|
288
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
295
291
|
if (
|
|
296
292
|
ctx.body.subscriptionId &&
|
|
297
293
|
subscriptionToUpdate &&
|
|
298
294
|
subscriptionToUpdate.referenceId !== referenceId
|
|
299
295
|
) {
|
|
300
|
-
subscriptionToUpdate = null;
|
|
301
|
-
}
|
|
302
|
-
if (ctx.body.subscriptionId && !subscriptionToUpdate) {
|
|
303
296
|
throw new APIError("BAD_REQUEST", {
|
|
304
297
|
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND,
|
|
305
298
|
});
|
|
@@ -334,7 +327,7 @@ export const upgradeSubscription = (options: StripeOptions) => {
|
|
|
334
327
|
try {
|
|
335
328
|
// First, search for existing organization customer by organizationId
|
|
336
329
|
const existingOrgCustomers = await client.customers.search({
|
|
337
|
-
query: `metadata["organizationId"]:"${org.id}"`,
|
|
330
|
+
query: `metadata["${customerMetadata.keys.organizationId}"]:"${org.id}"`,
|
|
338
331
|
limit: 1,
|
|
339
332
|
});
|
|
340
333
|
|
|
@@ -354,15 +347,17 @@ export const upgradeSubscription = (options: StripeOptions) => {
|
|
|
354
347
|
|
|
355
348
|
// Create Stripe customer for organization
|
|
356
349
|
// Email can be set via getCustomerCreateParams or updated in billing portal
|
|
357
|
-
// Use defu to
|
|
358
|
-
const customerParams
|
|
350
|
+
// Use defu to merge params (first argument takes priority)
|
|
351
|
+
const customerParams = defu(
|
|
359
352
|
{
|
|
360
353
|
name: org.name,
|
|
361
|
-
metadata:
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
354
|
+
metadata: customerMetadata.set(
|
|
355
|
+
{
|
|
356
|
+
organizationId: org.id,
|
|
357
|
+
customerType: "organization",
|
|
358
|
+
},
|
|
359
|
+
ctx.body.metadata,
|
|
360
|
+
),
|
|
366
361
|
},
|
|
367
362
|
extraCreateParams,
|
|
368
363
|
);
|
|
@@ -411,7 +406,7 @@ export const upgradeSubscription = (options: StripeOptions) => {
|
|
|
411
406
|
try {
|
|
412
407
|
// Try to find existing user Stripe customer by email
|
|
413
408
|
const existingCustomers = await client.customers.search({
|
|
414
|
-
query: `email:"${escapeStripeSearchValue(user.email)}" AND -metadata["customerType"]:"organization"`,
|
|
409
|
+
query: `email:"${escapeStripeSearchValue(user.email)}" AND -metadata["${customerMetadata.keys.customerType}"]:"organization"`,
|
|
415
410
|
limit: 1,
|
|
416
411
|
});
|
|
417
412
|
|
|
@@ -421,11 +416,13 @@ export const upgradeSubscription = (options: StripeOptions) => {
|
|
|
421
416
|
stripeCustomer = await client.customers.create({
|
|
422
417
|
email: user.email,
|
|
423
418
|
name: user.name,
|
|
424
|
-
metadata:
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
419
|
+
metadata: customerMetadata.set(
|
|
420
|
+
{
|
|
421
|
+
userId: user.id,
|
|
422
|
+
customerType: "user",
|
|
423
|
+
},
|
|
424
|
+
ctx.body.metadata,
|
|
425
|
+
),
|
|
429
426
|
});
|
|
430
427
|
}
|
|
431
428
|
|
|
@@ -493,17 +490,47 @@ export const upgradeSubscription = (options: StripeOptions) => {
|
|
|
493
490
|
return false;
|
|
494
491
|
});
|
|
495
492
|
|
|
493
|
+
// Get the current price ID from the active Stripe subscription
|
|
494
|
+
const stripeSubscriptionPriceId =
|
|
495
|
+
activeSubscription?.items.data[0]?.price.id;
|
|
496
|
+
|
|
496
497
|
// Also find any incomplete subscription that we can reuse
|
|
497
498
|
const incompleteSubscription = subscriptions.find(
|
|
498
499
|
(sub) => sub.status === "incomplete",
|
|
499
500
|
);
|
|
500
501
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
502
|
+
const priceId = ctx.body.annual
|
|
503
|
+
? plan.annualDiscountPriceId
|
|
504
|
+
: plan.priceId;
|
|
505
|
+
const lookupKey = ctx.body.annual
|
|
506
|
+
? plan.annualDiscountLookupKey
|
|
507
|
+
: plan.lookupKey;
|
|
508
|
+
const resolvedPriceId = lookupKey
|
|
509
|
+
? await resolvePriceIdFromLookupKey(client, lookupKey)
|
|
510
|
+
: undefined;
|
|
511
|
+
|
|
512
|
+
const priceIdToUse = priceId || resolvedPriceId;
|
|
513
|
+
if (!priceIdToUse) {
|
|
514
|
+
throw ctx.error("BAD_REQUEST", {
|
|
515
|
+
message: "Price ID not found for the selected plan",
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const isSamePlan = activeOrTrialingSubscription?.plan === ctx.body.plan;
|
|
520
|
+
const isSameSeats =
|
|
521
|
+
activeOrTrialingSubscription?.seats === (ctx.body.seats || 1);
|
|
522
|
+
const isSamePriceId = stripeSubscriptionPriceId === priceIdToUse;
|
|
523
|
+
const isSubscriptionStillValid =
|
|
524
|
+
!activeOrTrialingSubscription?.periodEnd ||
|
|
525
|
+
activeOrTrialingSubscription.periodEnd > new Date();
|
|
526
|
+
|
|
527
|
+
const isAlreadySubscribed =
|
|
528
|
+
activeOrTrialingSubscription?.status === "active" &&
|
|
529
|
+
isSamePlan &&
|
|
530
|
+
isSameSeats &&
|
|
531
|
+
isSamePriceId &&
|
|
532
|
+
isSubscriptionStillValid;
|
|
533
|
+
if (isAlreadySubscribed) {
|
|
507
534
|
throw new APIError("BAD_REQUEST", {
|
|
508
535
|
message: STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN,
|
|
509
536
|
});
|
|
@@ -539,32 +566,6 @@ export const upgradeSubscription = (options: StripeOptions) => {
|
|
|
539
566
|
dbSubscription = activeOrTrialingSubscription;
|
|
540
567
|
}
|
|
541
568
|
|
|
542
|
-
// Resolve price ID if using lookup keys
|
|
543
|
-
let priceIdToUse: string | undefined = undefined;
|
|
544
|
-
if (ctx.body.annual) {
|
|
545
|
-
priceIdToUse = plan.annualDiscountPriceId;
|
|
546
|
-
if (!priceIdToUse && plan.annualDiscountLookupKey) {
|
|
547
|
-
priceIdToUse = await resolvePriceIdFromLookupKey(
|
|
548
|
-
client,
|
|
549
|
-
plan.annualDiscountLookupKey,
|
|
550
|
-
);
|
|
551
|
-
}
|
|
552
|
-
} else {
|
|
553
|
-
priceIdToUse = plan.priceId;
|
|
554
|
-
if (!priceIdToUse && plan.lookupKey) {
|
|
555
|
-
priceIdToUse = await resolvePriceIdFromLookupKey(
|
|
556
|
-
client,
|
|
557
|
-
plan.lookupKey,
|
|
558
|
-
);
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
if (!priceIdToUse) {
|
|
563
|
-
throw ctx.error("BAD_REQUEST", {
|
|
564
|
-
message: "Price ID not found for the selected plan",
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
|
|
568
569
|
const { url } = await client.billingPortal.sessions
|
|
569
570
|
.create({
|
|
570
571
|
customer: customerId,
|
|
@@ -672,24 +673,6 @@ export const upgradeSubscription = (options: StripeOptions) => {
|
|
|
672
673
|
? { trial_period_days: plan.freeTrial.days }
|
|
673
674
|
: undefined;
|
|
674
675
|
|
|
675
|
-
let priceIdToUse: string | undefined = undefined;
|
|
676
|
-
if (ctx.body.annual) {
|
|
677
|
-
priceIdToUse = plan.annualDiscountPriceId;
|
|
678
|
-
if (!priceIdToUse && plan.annualDiscountLookupKey) {
|
|
679
|
-
priceIdToUse = await resolvePriceIdFromLookupKey(
|
|
680
|
-
client,
|
|
681
|
-
plan.annualDiscountLookupKey,
|
|
682
|
-
);
|
|
683
|
-
}
|
|
684
|
-
} else {
|
|
685
|
-
priceIdToUse = plan.priceId;
|
|
686
|
-
if (!priceIdToUse && plan.lookupKey) {
|
|
687
|
-
priceIdToUse = await resolvePriceIdFromLookupKey(
|
|
688
|
-
client,
|
|
689
|
-
plan.lookupKey,
|
|
690
|
-
);
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
676
|
const checkoutSession = await client.checkout.sessions
|
|
694
677
|
.create(
|
|
695
678
|
{
|
|
@@ -722,24 +705,29 @@ export const upgradeSubscription = (options: StripeOptions) => {
|
|
|
722
705
|
],
|
|
723
706
|
subscription_data: {
|
|
724
707
|
...freeTrial,
|
|
725
|
-
metadata:
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
708
|
+
metadata: subscriptionMetadata.set(
|
|
709
|
+
{
|
|
710
|
+
userId: user.id,
|
|
711
|
+
subscriptionId: subscription.id,
|
|
712
|
+
referenceId,
|
|
713
|
+
},
|
|
714
|
+
ctx.body.metadata,
|
|
715
|
+
params?.params?.subscription_data?.metadata,
|
|
716
|
+
),
|
|
732
717
|
},
|
|
733
718
|
mode: "subscription",
|
|
734
719
|
client_reference_id: referenceId,
|
|
735
720
|
...params?.params,
|
|
736
|
-
metadata
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
721
|
+
// metadata should come after spread to protect internal fields
|
|
722
|
+
metadata: subscriptionMetadata.set(
|
|
723
|
+
{
|
|
724
|
+
userId: user.id,
|
|
725
|
+
subscriptionId: subscription.id,
|
|
726
|
+
referenceId,
|
|
727
|
+
},
|
|
728
|
+
ctx.body.metadata,
|
|
729
|
+
params?.params?.metadata,
|
|
730
|
+
),
|
|
743
731
|
},
|
|
744
732
|
params?.options,
|
|
745
733
|
)
|