payment-kit 1.13.30 → 1.13.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/api/src/integrations/blockchain/nft.ts +0 -1
- package/api/src/integrations/blocklet/passport.ts +1 -1
- package/api/src/integrations/stripe/handlers/invoice.ts +44 -2
- package/api/src/integrations/stripe/handlers/payment-intent.ts +32 -8
- package/api/src/integrations/stripe/resource.ts +7 -4
- package/api/src/jobs/subscription.ts +1 -1
- package/api/src/libs/payment.ts +6 -1
- package/api/src/libs/session.ts +78 -27
- package/api/src/libs/util.ts +15 -0
- package/api/src/routes/checkout-sessions.ts +161 -20
- package/api/src/routes/connect/collect.ts +5 -9
- package/api/src/routes/connect/pay.ts +5 -9
- package/api/src/routes/connect/setup.ts +22 -10
- package/api/src/routes/connect/shared.ts +13 -10
- package/api/src/routes/connect/subscribe.ts +29 -20
- package/api/src/routes/invoices.ts +5 -1
- package/api/src/routes/payment-intents.ts +5 -1
- package/api/src/routes/payment-links.ts +3 -2
- package/api/src/routes/prices.ts +32 -21
- package/api/src/store/migrations/20231023-upsell.ts +11 -0
- package/api/src/store/models/index.ts +10 -2
- package/api/src/store/models/price.ts +89 -23
- package/api/src/store/models/types.ts +1 -0
- package/blocklet.yml +1 -1
- package/package.json +17 -17
- package/src/components/blockchain/tx.tsx +3 -1
- package/src/components/checkout/pay.tsx +39 -19
- package/src/components/checkout/product-card.tsx +2 -6
- package/src/components/checkout/product-item.tsx +84 -21
- package/src/components/checkout/summary.tsx +11 -2
- package/src/components/info-row.tsx +3 -1
- package/src/components/invoice/table.tsx +1 -1
- package/src/components/price/upsell-select.tsx +83 -0
- package/src/components/price/upsell.tsx +74 -0
- package/src/components/status.tsx +1 -1
- package/src/components/subscription/actions/cancel.tsx +25 -27
- package/src/components/subscription/items/index.tsx +1 -1
- package/src/libs/util.ts +51 -31
- package/src/locales/en.tsx +23 -2
- package/src/locales/zh.tsx +52 -40
- package/src/pages/admin/billing/index.tsx +3 -3
- package/src/pages/admin/customers/customers/detail.tsx +1 -0
- package/src/pages/admin/index.tsx +1 -0
- package/src/pages/admin/products/prices/detail.tsx +7 -0
- package/src/pages/customer/invoice.tsx +7 -6
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
|
|
16
16
|
export async function checkPassportForPaymentLink(doc: PaymentLink) {
|
|
17
17
|
// @ts-ignore
|
|
18
|
-
const items: TLineItemExpanded[] = await Price.expand(doc.line_items
|
|
18
|
+
const items: TLineItemExpanded[] = await Price.expand(doc.line_items);
|
|
19
19
|
const item = items.find((x) => x.price.product?.metadata?.passport);
|
|
20
20
|
return item?.price.product?.metadata?.passport;
|
|
21
21
|
}
|
|
@@ -20,16 +20,57 @@ export async function handleStripeInvoicePaid(invoice: Invoice, event: TEventExp
|
|
|
20
20
|
await invoice.update({
|
|
21
21
|
status: 'paid',
|
|
22
22
|
...pick(event.data.object, [
|
|
23
|
-
'paid',
|
|
24
|
-
'paid_out_of_band',
|
|
25
23
|
'amount_due',
|
|
26
24
|
'amount_paid',
|
|
27
25
|
'amount_remaining',
|
|
26
|
+
'last_finalization_error',
|
|
27
|
+
'paid_out_of_band',
|
|
28
|
+
'paid',
|
|
28
29
|
'status_transitions',
|
|
30
|
+
'subtotal_excluding_tax',
|
|
31
|
+
'subtotal',
|
|
32
|
+
'tax',
|
|
33
|
+
'total_discount_amounts',
|
|
34
|
+
'total',
|
|
29
35
|
]),
|
|
30
36
|
});
|
|
31
37
|
}
|
|
32
38
|
|
|
39
|
+
export async function syncStripeInvoice(invoice: Invoice) {
|
|
40
|
+
if (!invoice.metadata?.stripe_id) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const method = await PaymentMethod.findByPk(invoice.default_payment_method_id);
|
|
45
|
+
if (!method) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const client = await method.getStripeClient();
|
|
50
|
+
const stripeInvoice = await client.invoices.retrieve(invoice.metadata.stripe_id);
|
|
51
|
+
if (stripeInvoice) {
|
|
52
|
+
await invoice.update(
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
pick(stripeInvoice, [
|
|
55
|
+
'amount_due',
|
|
56
|
+
'amount_paid',
|
|
57
|
+
'amount_remaining',
|
|
58
|
+
'last_finalization_error',
|
|
59
|
+
'paid_out_of_band',
|
|
60
|
+
'paid',
|
|
61
|
+
'status_transitions',
|
|
62
|
+
'status',
|
|
63
|
+
'subtotal_excluding_tax',
|
|
64
|
+
'subtotal',
|
|
65
|
+
'tax',
|
|
66
|
+
'total_discount_amounts',
|
|
67
|
+
'total',
|
|
68
|
+
])
|
|
69
|
+
);
|
|
70
|
+
logger.info('stripe invoice synced', { locale: invoice.id, remote: stripeInvoice.id });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
33
74
|
export async function ensureStripeInvoice(stripeInvoice: any, subscription: Subscription, client: Stripe) {
|
|
34
75
|
const customer = await Customer.findByPk(subscription.customer_id);
|
|
35
76
|
const checkoutSession = await CheckoutSession.findOne({ where: { subscription_id: subscription.id } });
|
|
@@ -76,6 +117,7 @@ export async function ensureStripeInvoice(stripeInvoice: any, subscription: Subs
|
|
|
76
117
|
'tax',
|
|
77
118
|
'total_discount_amounts',
|
|
78
119
|
'total',
|
|
120
|
+
'last_finalization_error',
|
|
79
121
|
]),
|
|
80
122
|
|
|
81
123
|
currency_id: subscription.currency_id,
|
|
@@ -6,7 +6,7 @@ import type Stripe from 'stripe';
|
|
|
6
6
|
|
|
7
7
|
import dayjs from '../../../libs/dayjs';
|
|
8
8
|
import logger from '../../../libs/logger';
|
|
9
|
-
import { CheckoutSession, Invoice, PaymentIntent, TEventExpanded } from '../../../store/models';
|
|
9
|
+
import { CheckoutSession, Invoice, PaymentIntent, PaymentMethod, TEventExpanded } from '../../../store/models';
|
|
10
10
|
import { handleStripeInvoiceCreated } from './invoice';
|
|
11
11
|
|
|
12
12
|
export async function handleStripePaymentSucceed(paymentIntent: PaymentIntent, event?: TEventExpanded) {
|
|
@@ -46,6 +46,31 @@ export async function handleStripePaymentSucceed(paymentIntent: PaymentIntent, e
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
export async function syncStripPayment(paymentIntent: PaymentIntent) {
|
|
50
|
+
if (!paymentIntent.metadata?.stripe_id) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const method = await PaymentMethod.findByPk(paymentIntent.payment_method_id);
|
|
55
|
+
if (!method) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const client = await method.getStripeClient();
|
|
60
|
+
const stripeIntent = await client.paymentIntents.retrieve(paymentIntent.metadata.stripe_id);
|
|
61
|
+
if (stripeIntent) {
|
|
62
|
+
// @ts-ignore
|
|
63
|
+
await paymentIntent.update({
|
|
64
|
+
amount: String(stripeIntent.amount),
|
|
65
|
+
amount_received: String(stripeIntent.amount_received),
|
|
66
|
+
amount_capturable: String(stripeIntent.amount_capturable),
|
|
67
|
+
amount_details: stripeIntent.amount_details as any,
|
|
68
|
+
...pick(stripeIntent, ['status', 'confirmation_method', 'capture_method', 'last_payment_error']),
|
|
69
|
+
});
|
|
70
|
+
logger.info('stripe payment intent synced', { locale: paymentIntent.id, remote: stripeIntent.id });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
49
74
|
export async function handleStripePaymentCreated(event: TEventExpanded, client: Stripe) {
|
|
50
75
|
logger.info('possible payment intent from subscription', { id: event.id, type: event.type });
|
|
51
76
|
|
|
@@ -69,9 +94,9 @@ export async function handleStripePaymentCreated(event: TEventExpanded, client:
|
|
|
69
94
|
payment_method_types: ['stripe'],
|
|
70
95
|
|
|
71
96
|
amount: String(stripeIntent.amount),
|
|
72
|
-
amount_received:
|
|
97
|
+
amount_received: String(stripeIntent.amount_received),
|
|
73
98
|
amount_capturable: String(stripeIntent.amount_capturable),
|
|
74
|
-
amount_details:
|
|
99
|
+
amount_details: stripeIntent.amount_details,
|
|
75
100
|
|
|
76
101
|
...pick(stripeIntent, [
|
|
77
102
|
'livemode',
|
|
@@ -83,6 +108,7 @@ export async function handleStripePaymentCreated(event: TEventExpanded, client:
|
|
|
83
108
|
'statement_descriptor',
|
|
84
109
|
'statement_descriptor_suffix',
|
|
85
110
|
'setup_future_usage',
|
|
111
|
+
'last_payment_error',
|
|
86
112
|
]),
|
|
87
113
|
|
|
88
114
|
metadata: {
|
|
@@ -113,11 +139,9 @@ export async function handlePaymentIntentEvent(event: TEventExpanded, client: St
|
|
|
113
139
|
const localIntentId = event.data.object.metadata?.id;
|
|
114
140
|
if (!localIntentId) {
|
|
115
141
|
// We only handle payment_intents created from subscriptions
|
|
116
|
-
if (event.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
142
|
+
if (event.data.object.invoice) {
|
|
143
|
+
await handleStripePaymentCreated(event, client);
|
|
144
|
+
return;
|
|
121
145
|
}
|
|
122
146
|
|
|
123
147
|
try {
|
|
@@ -205,7 +205,8 @@ export async function ensureStripeSubscription(
|
|
|
205
205
|
const customer = await ensureStripePaymentCustomer(internal, method);
|
|
206
206
|
const prices = await Promise.all(
|
|
207
207
|
items.map(async (x: any) => {
|
|
208
|
-
x.
|
|
208
|
+
const price = x.upsell_price || x.price;
|
|
209
|
+
x.stripePrice = await ensureStripePrice(price as any, method, currency);
|
|
209
210
|
return x;
|
|
210
211
|
})
|
|
211
212
|
);
|
|
@@ -213,7 +214,8 @@ export async function ensureStripeSubscription(
|
|
|
213
214
|
const recurringItems = prices
|
|
214
215
|
.filter((x) => x.price.type === 'recurring')
|
|
215
216
|
.map((x) => {
|
|
216
|
-
|
|
217
|
+
const price = x.upsell_price || x.price;
|
|
218
|
+
if (price.recurring?.usage_type === 'metered') {
|
|
217
219
|
return { price: x.stripePrice.id };
|
|
218
220
|
}
|
|
219
221
|
return { price: x.stripePrice.id, quantity: x.quantity };
|
|
@@ -242,8 +244,9 @@ export async function ensureStripeSubscription(
|
|
|
242
244
|
await Promise.all(
|
|
243
245
|
stripeSubscription.items.data.map(async (x: any) => {
|
|
244
246
|
const item = prices.find((y) => y.stripePrice.id === x.price.id);
|
|
247
|
+
const price = item.upsell_price || item.price; // local
|
|
245
248
|
let exist = await SubscriptionItem.findOne({
|
|
246
|
-
where: { price_id:
|
|
249
|
+
where: { price_id: price.id, subscription_id: internal.id },
|
|
247
250
|
});
|
|
248
251
|
if (exist) {
|
|
249
252
|
await exist.update({ metadata: { stripe_id: x.id, stripe_subscription_id: stripeSubscription.id } });
|
|
@@ -252,7 +255,7 @@ export async function ensureStripeSubscription(
|
|
|
252
255
|
} else {
|
|
253
256
|
exist = await SubscriptionItem.create({
|
|
254
257
|
livemode: stripeSubscription.livemode,
|
|
255
|
-
price_id:
|
|
258
|
+
price_id: price.id,
|
|
256
259
|
quantity: x.quantity,
|
|
257
260
|
subscription_id: internal.id,
|
|
258
261
|
billing_thresholds: x.billing_threshold,
|
|
@@ -134,7 +134,7 @@ export const handleSubscription = async (job: SubscriptionJob) => {
|
|
|
134
134
|
const subscriptionItems = await SubscriptionItem.findAll({ where: { subscription_id: subscription.id } });
|
|
135
135
|
let expandedItems = await Price.expand(
|
|
136
136
|
subscriptionItems.map((x) => ({ id: x.id, price_id: x.price_id, quantity: x.quantity })),
|
|
137
|
-
true
|
|
137
|
+
{ product: true }
|
|
138
138
|
);
|
|
139
139
|
|
|
140
140
|
// get usage summaries for this billing cycle
|
package/api/src/libs/payment.ts
CHANGED
|
@@ -31,13 +31,18 @@ export async function isDelegationSufficientForPayment(args: {
|
|
|
31
31
|
return { sufficient: false, reason: 'NO_DELEGATION' };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// have enough permissions
|
|
35
|
+
if (state.ops.some((x: any) => x.key === 'fg:t:transfer_v2') === false) {
|
|
36
|
+
return { sufficient: false, reason: 'NO_TRANSFER_PERMISSION' };
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
// balance enough token for payment?
|
|
35
40
|
const { tokens } = await client.getAccountTokens({ address: delegator, token: paymentCurrency.contract as string });
|
|
36
41
|
const [token] = tokens;
|
|
37
42
|
if (!token) {
|
|
38
43
|
return { sufficient: false, reason: 'NO_TOKEN' };
|
|
39
44
|
}
|
|
40
|
-
if (new BN(
|
|
45
|
+
if (new BN(token.balance).lte(new BN(amount))) {
|
|
41
46
|
return { sufficient: false, reason: 'NO_ENOUGH_TOKEN' };
|
|
42
47
|
}
|
|
43
48
|
|
package/api/src/libs/session.ts
CHANGED
|
@@ -3,14 +3,12 @@ import { BN } from '@ocap/util';
|
|
|
3
3
|
import cloneDeep from 'lodash/cloneDeep';
|
|
4
4
|
import isEqual from 'lodash/isEqual';
|
|
5
5
|
|
|
6
|
-
import type { TPaymentCurrency, TPaymentMethodExpanded } from '../store/models';
|
|
6
|
+
import type { TLineItemExpanded, TPaymentCurrency, TPaymentMethodExpanded } from '../store/models';
|
|
7
7
|
import type { Price, TPrice } from '../store/models/price';
|
|
8
8
|
import type { Product } from '../store/models/product';
|
|
9
|
-
import type {
|
|
9
|
+
import type { PriceCurrency, PriceRecurring } from '../store/models/types';
|
|
10
10
|
import dayjs from './dayjs';
|
|
11
11
|
|
|
12
|
-
export type TLineItemExpanded = LineItem & { price: TPrice };
|
|
13
|
-
|
|
14
12
|
export function getStatementDescriptor(items: any[]) {
|
|
15
13
|
for (const item of items) {
|
|
16
14
|
if (item.price?.product?.statement_descriptor) {
|
|
@@ -58,35 +56,26 @@ export function getPriceCurrencyOptions(price: TPrice): PriceCurrency[] {
|
|
|
58
56
|
|
|
59
57
|
// FIXME: apply coupon for discounts
|
|
60
58
|
export function getCheckoutAmount(items: TLineItemExpanded[], currency: TPaymentCurrency, includeFreeTrial = false) {
|
|
61
|
-
|
|
62
|
-
.reduce((acc, x) => {
|
|
63
|
-
if (x.price.type === 'recurring') {
|
|
64
|
-
if (includeFreeTrial) {
|
|
65
|
-
return acc;
|
|
66
|
-
}
|
|
67
|
-
if (x.price.recurring?.usage_type === 'metered') {
|
|
68
|
-
return acc;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return acc.add(new BN(getPriceUintAmountByCurrency(x.price, currency)).mul(new BN(x.quantity)));
|
|
72
|
-
}, new BN(0))
|
|
73
|
-
.toString();
|
|
59
|
+
let renew = new BN(0);
|
|
74
60
|
|
|
75
61
|
const total = items
|
|
76
62
|
.reduce((acc, x) => {
|
|
77
|
-
|
|
63
|
+
const price = x.upsell_price || x.price;
|
|
64
|
+
if (price.type === 'recurring') {
|
|
65
|
+
renew = renew.add(new BN(getPriceUintAmountByCurrency(price, currency)).mul(new BN(x.quantity)));
|
|
66
|
+
|
|
78
67
|
if (includeFreeTrial) {
|
|
79
68
|
return acc;
|
|
80
69
|
}
|
|
81
|
-
if (
|
|
70
|
+
if (price.recurring?.usage_type === 'metered') {
|
|
82
71
|
return acc;
|
|
83
72
|
}
|
|
84
73
|
}
|
|
85
|
-
return acc.add(new BN(getPriceUintAmountByCurrency(
|
|
74
|
+
return acc.add(new BN(getPriceUintAmountByCurrency(price, currency)).mul(new BN(x.quantity)));
|
|
86
75
|
}, new BN(0))
|
|
87
76
|
.toString();
|
|
88
77
|
|
|
89
|
-
return { subtotal, total, discount: '0', shipping: '0', tax: '0' };
|
|
78
|
+
return { subtotal: total, total, renew: renew.toString(), discount: '0', shipping: '0', tax: '0' };
|
|
90
79
|
}
|
|
91
80
|
|
|
92
81
|
export function getRecurringPeriod(recurring: PriceRecurring) {
|
|
@@ -115,15 +104,17 @@ export function getSubscriptionCreateSetup(items: TLineItemExpanded[], currency:
|
|
|
115
104
|
let subscription = new BN(0);
|
|
116
105
|
|
|
117
106
|
items.forEach((x) => {
|
|
118
|
-
|
|
119
|
-
|
|
107
|
+
const price = x.upsell_price || x.price;
|
|
108
|
+
setup = setup.add(new BN(price.unit_amount).mul(new BN(x.quantity)));
|
|
109
|
+
if (price.type === 'recurring') {
|
|
120
110
|
if (trialInDays === 0) {
|
|
121
|
-
subscription = setup.add(new BN(getPriceUintAmountByCurrency(
|
|
111
|
+
subscription = setup.add(new BN(getPriceUintAmountByCurrency(price, currency)).mul(new BN(x.quantity)));
|
|
122
112
|
}
|
|
123
113
|
}
|
|
124
114
|
});
|
|
125
115
|
|
|
126
|
-
const
|
|
116
|
+
const item = items.find((x) => x.price.type === 'recurring');
|
|
117
|
+
const recurring = (item?.upsell_price || item?.price)?.recurring as PriceRecurring;
|
|
127
118
|
const cycle = getRecurringPeriod(recurring);
|
|
128
119
|
const trial = trialInDays ? trialInDays * 24 * 60 * 60 * 1000 : 0;
|
|
129
120
|
|
|
@@ -205,7 +196,7 @@ export function getSupportedPaymentCurrencies(items: TLineItemExpanded[]) {
|
|
|
205
196
|
}
|
|
206
197
|
|
|
207
198
|
export function isLineItemCurrencyAligned(list: TLineItemExpanded[], index: number) {
|
|
208
|
-
const prices = list.map((x) => x.price);
|
|
199
|
+
const prices = list.map((x) => x.upsell_price || x.price);
|
|
209
200
|
|
|
210
201
|
const current = getPriceCurrencyOptions(prices[index] as TPrice)
|
|
211
202
|
.map((x) => x.currency_id)
|
|
@@ -224,7 +215,7 @@ export function isLineItemCurrencyAligned(list: TLineItemExpanded[], index: numb
|
|
|
224
215
|
}
|
|
225
216
|
|
|
226
217
|
export function isLineItemRecurringAligned(list: TLineItemExpanded[], index: number) {
|
|
227
|
-
const prices = list.map((x) => x.price);
|
|
218
|
+
const prices = list.map((x) => x.upsell_price || x.price);
|
|
228
219
|
|
|
229
220
|
if (prices[index]?.type !== 'recurring') {
|
|
230
221
|
return true;
|
|
@@ -258,3 +249,63 @@ export function isLineItemAligned(list: TLineItemExpanded[], index: number) {
|
|
|
258
249
|
aligned: currency && recurring,
|
|
259
250
|
};
|
|
260
251
|
}
|
|
252
|
+
|
|
253
|
+
// FIXME: upsell validate https://stripe.com/docs/payments/checkout/upsells
|
|
254
|
+
export function canUpsell(from: TPrice, to: TPrice) {
|
|
255
|
+
if (from.id === to.id) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
if (from.product_id !== to.product_id) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
if (to.active === false) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
if (to.type !== 'recurring') {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// longer periods
|
|
269
|
+
const fromPeriod = getRecurringPeriod(from.recurring as PriceRecurring);
|
|
270
|
+
const toPeriod = getRecurringPeriod(to.recurring as PriceRecurring);
|
|
271
|
+
if (fromPeriod >= toPeriod) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// and lower prices
|
|
276
|
+
const fromPrice = new BN(from.unit_amount).div(new BN(fromPeriod));
|
|
277
|
+
const toPrice = new BN(to.unit_amount).div(new BN(toPeriod));
|
|
278
|
+
if (fromPrice.lt(toPrice)) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function getFastCheckoutAmount(
|
|
286
|
+
items: TLineItemExpanded[],
|
|
287
|
+
mode: string,
|
|
288
|
+
currency: TPaymentCurrency,
|
|
289
|
+
includeFreeTrial = false,
|
|
290
|
+
minimumCycle = 2
|
|
291
|
+
) {
|
|
292
|
+
if (minimumCycle < 1) {
|
|
293
|
+
// eslint-disable-next-line no-param-reassign
|
|
294
|
+
minimumCycle = 1;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const { total, renew } = getCheckoutAmount(items, currency, includeFreeTrial);
|
|
298
|
+
if (mode === 'payment') {
|
|
299
|
+
return total;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (mode === 'setup') {
|
|
303
|
+
return new BN(renew).mul(new BN(minimumCycle)).toString();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (mode === 'subscription') {
|
|
307
|
+
return new BN(total).add(new BN(renew).mul(new BN(minimumCycle - 1))).toString();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return '0';
|
|
311
|
+
}
|
package/api/src/libs/util.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
|
|
3
3
|
import { getUrl } from '@blocklet/sdk/lib/component';
|
|
4
|
+
import env from '@blocklet/sdk/lib/env';
|
|
4
5
|
import { customAlphabet } from 'nanoid';
|
|
5
6
|
|
|
6
7
|
import dayjs from './dayjs';
|
|
@@ -142,3 +143,17 @@ export const getNextRetry = (retryCount: number) => {
|
|
|
142
143
|
export const getWebhookJobId = (eventId: string, webhookId: string) => {
|
|
143
144
|
return md5([eventId, webhookId].join('-'));
|
|
144
145
|
};
|
|
146
|
+
|
|
147
|
+
export function getTxMetadata(extra: Record<string, any> = {}): any {
|
|
148
|
+
return {
|
|
149
|
+
type: 'json',
|
|
150
|
+
value: {
|
|
151
|
+
paymentKit: {
|
|
152
|
+
// FIXME: this should be customizable
|
|
153
|
+
description: `Manage subscriptions and payments on ${env.appName}`,
|
|
154
|
+
customerPortal: getUrl('/customer'),
|
|
155
|
+
...extra,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|