payment-kit 1.19.0 → 1.19.1
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/crons/index.ts +8 -0
- package/api/src/index.ts +4 -0
- package/api/src/libs/credit-grant.ts +146 -0
- package/api/src/libs/env.ts +1 -0
- package/api/src/libs/invoice.ts +4 -3
- package/api/src/libs/notification/template/base.ts +388 -2
- package/api/src/libs/notification/template/customer-credit-grant-granted.ts +149 -0
- package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +151 -0
- package/api/src/libs/notification/template/customer-credit-insufficient.ts +254 -0
- package/api/src/libs/notification/template/subscription-canceled.ts +193 -202
- package/api/src/libs/notification/template/subscription-refund-succeeded.ts +215 -237
- package/api/src/libs/notification/template/subscription-renewed.ts +130 -200
- package/api/src/libs/notification/template/subscription-succeeded.ts +100 -202
- package/api/src/libs/notification/template/subscription-trial-start.ts +142 -188
- package/api/src/libs/notification/template/subscription-trial-will-end.ts +146 -174
- package/api/src/libs/notification/template/subscription-upgraded.ts +96 -192
- package/api/src/libs/notification/template/subscription-will-canceled.ts +94 -135
- package/api/src/libs/notification/template/subscription-will-renew.ts +220 -245
- package/api/src/libs/payment.ts +69 -0
- package/api/src/libs/queue/index.ts +3 -2
- package/api/src/libs/session.ts +8 -0
- package/api/src/libs/subscription.ts +74 -3
- package/api/src/libs/ws.ts +23 -1
- package/api/src/locales/en.ts +33 -0
- package/api/src/locales/zh.ts +31 -0
- package/api/src/queues/credit-consume.ts +715 -0
- package/api/src/queues/credit-grant.ts +572 -0
- package/api/src/queues/notification.ts +173 -128
- package/api/src/queues/payment.ts +210 -122
- package/api/src/queues/subscription.ts +179 -0
- package/api/src/routes/checkout-sessions.ts +157 -9
- package/api/src/routes/connect/shared.ts +3 -2
- package/api/src/routes/credit-grants.ts +241 -0
- package/api/src/routes/credit-transactions.ts +208 -0
- package/api/src/routes/index.ts +8 -0
- package/api/src/routes/meter-events.ts +347 -0
- package/api/src/routes/meters.ts +219 -0
- package/api/src/routes/payment-currencies.ts +14 -2
- package/api/src/routes/payment-links.ts +1 -1
- package/api/src/routes/payment-methods.ts +14 -2
- package/api/src/routes/prices.ts +43 -0
- package/api/src/routes/pricing-table.ts +13 -7
- package/api/src/routes/products.ts +63 -4
- package/api/src/routes/settings.ts +1 -1
- package/api/src/routes/subscriptions.ts +4 -0
- package/api/src/store/migrations/20250610-billing-credit.ts +43 -0
- package/api/src/store/models/credit-grant.ts +486 -0
- package/api/src/store/models/credit-transaction.ts +268 -0
- package/api/src/store/models/customer.ts +8 -0
- package/api/src/store/models/index.ts +52 -1
- package/api/src/store/models/meter-event.ts +423 -0
- package/api/src/store/models/meter.ts +176 -0
- package/api/src/store/models/payment-currency.ts +66 -14
- package/api/src/store/models/price.ts +6 -0
- package/api/src/store/models/product.ts +2 -2
- package/api/src/store/models/subscription.ts +24 -0
- package/api/src/store/models/types.ts +28 -2
- package/api/tests/libs/subscription.spec.ts +53 -0
- package/blocklet.yml +9 -1
- package/package.json +4 -4
- package/scripts/sdk.js +233 -1
- package/src/app.tsx +10 -0
- package/src/components/collapse.tsx +11 -1
- package/src/components/customer/credit-grant-item-list.tsx +99 -0
- package/src/components/customer/credit-overview.tsx +233 -0
- package/src/components/customer/form.tsx +5 -2
- package/src/components/invoice/list.tsx +19 -1
- package/src/components/metadata/form.tsx +286 -90
- package/src/components/meter/actions.tsx +101 -0
- package/src/components/meter/add-usage-dialog.tsx +239 -0
- package/src/components/meter/events-list.tsx +657 -0
- package/src/components/meter/form.tsx +245 -0
- package/src/components/meter/products.tsx +264 -0
- package/src/components/meter/usage-guide.tsx +174 -0
- package/src/components/payment-currency/form.tsx +2 -0
- package/src/components/payment-intent/list.tsx +19 -1
- package/src/components/payment-link/preview.tsx +1 -1
- package/src/components/payment-link/product-select.tsx +52 -12
- package/src/components/payment-method/arcblock.tsx +2 -0
- package/src/components/payment-method/base.tsx +2 -0
- package/src/components/payment-method/bitcoin.tsx +2 -0
- package/src/components/payment-method/ethereum.tsx +2 -0
- package/src/components/payment-method/stripe.tsx +2 -0
- package/src/components/payouts/list.tsx +19 -1
- package/src/components/price/currency-select.tsx +51 -31
- package/src/components/price/form.tsx +881 -407
- package/src/components/pricing-table/preview.tsx +1 -1
- package/src/components/product/add-price.tsx +9 -7
- package/src/components/product/create.tsx +7 -4
- package/src/components/product/edit-price.tsx +21 -12
- package/src/components/product/features.tsx +17 -7
- package/src/components/product/form.tsx +104 -89
- package/src/components/refund/list.tsx +19 -1
- package/src/components/section/header.tsx +5 -18
- package/src/components/subscription/items/index.tsx +1 -1
- package/src/components/subscription/metrics.tsx +37 -5
- package/src/components/subscription/portal/actions.tsx +2 -1
- package/src/contexts/products.tsx +26 -9
- package/src/hooks/subscription.ts +34 -0
- package/src/libs/meter-utils.ts +196 -0
- package/src/libs/util.ts +4 -0
- package/src/locales/en.tsx +385 -4
- package/src/locales/zh.tsx +364 -0
- package/src/pages/admin/billing/index.tsx +61 -33
- package/src/pages/admin/billing/invoices/detail.tsx +1 -1
- package/src/pages/admin/billing/meters/create.tsx +60 -0
- package/src/pages/admin/billing/meters/detail.tsx +435 -0
- package/src/pages/admin/billing/meters/index.tsx +210 -0
- package/src/pages/admin/billing/meters/meter-event.tsx +346 -0
- package/src/pages/admin/billing/subscriptions/detail.tsx +47 -14
- package/src/pages/admin/customers/customers/credit-grant/detail.tsx +391 -0
- package/src/pages/admin/customers/customers/detail.tsx +22 -10
- package/src/pages/admin/customers/index.tsx +5 -0
- package/src/pages/admin/developers/events/detail.tsx +1 -1
- package/src/pages/admin/developers/index.tsx +1 -1
- package/src/pages/admin/payments/intents/detail.tsx +1 -1
- package/src/pages/admin/payments/payouts/detail.tsx +1 -1
- package/src/pages/admin/payments/refunds/detail.tsx +1 -1
- package/src/pages/admin/products/index.tsx +3 -2
- package/src/pages/admin/products/links/detail.tsx +1 -1
- package/src/pages/admin/products/prices/actions.tsx +16 -4
- package/src/pages/admin/products/prices/detail.tsx +30 -3
- package/src/pages/admin/products/prices/list.tsx +8 -1
- package/src/pages/admin/products/pricing-tables/detail.tsx +1 -1
- package/src/pages/admin/products/products/create.tsx +233 -57
- package/src/pages/admin/products/products/detail.tsx +2 -1
- package/src/pages/admin/settings/payment-methods/index.tsx +3 -0
- package/src/pages/customer/credit-grant/detail.tsx +308 -0
- package/src/pages/customer/index.tsx +35 -2
- package/src/pages/customer/recharge/account.tsx +5 -5
- package/src/pages/customer/subscription/change-payment.tsx +4 -2
- package/src/pages/customer/subscription/detail.tsx +48 -14
- package/src/pages/customer/subscription/embed.tsx +1 -1
|
@@ -4,17 +4,10 @@ import { fromUnitToToken } from '@ocap/util';
|
|
|
4
4
|
import type { ManipulateType } from 'dayjs';
|
|
5
5
|
|
|
6
6
|
import dayjs from '../../dayjs';
|
|
7
|
-
import { getUserLocale } from '../../../integrations/blocklet/notification';
|
|
8
7
|
import { translate } from '../../../locales';
|
|
9
|
-
import {
|
|
10
|
-
import { PaymentCurrency } from '../../../store/models/payment-currency';
|
|
11
|
-
import { getCustomerInvoicePageUrl } from '../../invoice';
|
|
12
|
-
import logger from '../../logger';
|
|
13
|
-
import { getMainProductName } from '../../product';
|
|
14
|
-
import { getCustomerSubscriptionPageUrl } from '../../subscription';
|
|
8
|
+
import { Invoice, PaymentCurrency, PaymentMethod } from '../../../store/models';
|
|
15
9
|
import { formatTime, getSimplifyDuration } from '../../time';
|
|
16
|
-
import
|
|
17
|
-
import { getSubscriptionNotificationCustomActions } from '../../util';
|
|
10
|
+
import { BaseSubscriptionEmailTemplate, BaseEmailTemplateType } from './base';
|
|
18
11
|
|
|
19
12
|
export interface SubscriptionWillCanceledEmailTemplateOptions {
|
|
20
13
|
subscriptionId: string;
|
|
@@ -29,35 +22,32 @@ interface SubscriptionWillCanceledEmailTemplateContext {
|
|
|
29
22
|
at: string;
|
|
30
23
|
cancelReason: string;
|
|
31
24
|
body: string;
|
|
32
|
-
|
|
33
25
|
userDid: string;
|
|
34
26
|
paymentInfo: string;
|
|
35
|
-
|
|
36
27
|
viewSubscriptionLink: string;
|
|
37
|
-
viewInvoiceLink
|
|
28
|
+
viewInvoiceLink?: string;
|
|
38
29
|
customActions: any[];
|
|
39
30
|
needRenew: boolean;
|
|
31
|
+
isCreditSubscription: boolean;
|
|
40
32
|
}
|
|
41
33
|
|
|
42
|
-
export class SubscriptionWillCanceledEmailTemplate
|
|
43
|
-
implements BaseEmailTemplate<SubscriptionWillCanceledEmailTemplateContext>
|
|
44
|
-
{
|
|
34
|
+
export class SubscriptionWillCanceledEmailTemplate extends BaseSubscriptionEmailTemplate<SubscriptionWillCanceledEmailTemplateContext> {
|
|
45
35
|
options: SubscriptionWillCanceledEmailTemplateOptions;
|
|
46
36
|
|
|
47
37
|
constructor(options: SubscriptionWillCanceledEmailTemplateOptions) {
|
|
38
|
+
super();
|
|
48
39
|
this.options = options;
|
|
49
40
|
}
|
|
50
41
|
|
|
51
42
|
async getContext(): Promise<SubscriptionWillCanceledEmailTemplateContext> {
|
|
52
43
|
if (!this.options.required) {
|
|
53
|
-
logger.error('SubscriptionWillCanceledEmailTemplate: This execution will be skipped', this.options);
|
|
54
44
|
throw new Error('SubscriptionWillCanceledEmailTemplate: This execution will be skipped');
|
|
55
45
|
}
|
|
56
46
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
47
|
+
// 获取基础订阅数据
|
|
48
|
+
const basicData = await this.getSubscriptionBasicData(this.options.subscriptionId);
|
|
49
|
+
const { subscription, userDid, locale, productName, isCreditSubscription } = basicData;
|
|
50
|
+
|
|
61
51
|
if (subscription.isImmutable()) {
|
|
62
52
|
throw new Error(`Subscription(${this.options.subscriptionId}) is immutable, no need to send notification`);
|
|
63
53
|
}
|
|
@@ -73,15 +63,9 @@ export class SubscriptionWillCanceledEmailTemplate
|
|
|
73
63
|
throw new Error(`Subscription(${this.options.subscriptionId}) is already canceled, no need to send notification`);
|
|
74
64
|
}
|
|
75
65
|
|
|
76
|
-
|
|
77
|
-
if (!customer) {
|
|
78
|
-
throw new Error(`Customer not found: ${subscription.customer_id}`);
|
|
79
|
-
}
|
|
80
|
-
|
|
66
|
+
// 获取发票和支付信息
|
|
81
67
|
const invoice = await Invoice.findOne({
|
|
82
|
-
where: {
|
|
83
|
-
id: subscription.latest_invoice_id,
|
|
84
|
-
},
|
|
68
|
+
where: { id: subscription.latest_invoice_id },
|
|
85
69
|
include: [
|
|
86
70
|
{ model: PaymentCurrency, as: 'paymentCurrency' },
|
|
87
71
|
{ model: PaymentMethod, as: 'paymentMethod' },
|
|
@@ -91,15 +75,24 @@ export class SubscriptionWillCanceledEmailTemplate
|
|
|
91
75
|
throw new Error(`Invoice(${subscription.latest_invoice_id}) not found`);
|
|
92
76
|
}
|
|
93
77
|
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
78
|
+
// 获取链接
|
|
79
|
+
const links = this.generateSubscriptionLinks(
|
|
80
|
+
subscription,
|
|
81
|
+
locale,
|
|
82
|
+
userDid,
|
|
83
|
+
'customer.subscription.will_canceled',
|
|
84
|
+
subscription.latest_invoice_id,
|
|
85
|
+
isCreditSubscription
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// 计算时间和取消原因
|
|
89
|
+
const at = formatTime(cancelAt * 1000);
|
|
90
|
+
const willCancelDuration = getSimplifyDuration((cancelAt - now) * 1000, locale);
|
|
91
|
+
|
|
99
92
|
// @ts-ignore
|
|
100
|
-
const paymentInfo
|
|
93
|
+
const paymentInfo = `${fromUnitToToken(+invoice.total, invoice?.paymentCurrency?.decimal)} ${invoice?.paymentCurrency?.symbol}${invoice?.paymentMethod ? `(${invoice?.paymentMethod.name})` : ''}`;
|
|
101
94
|
|
|
102
|
-
let body
|
|
95
|
+
let body = translate('notification.subscriptWillCanceled.body', locale, {
|
|
103
96
|
productName,
|
|
104
97
|
willCancelDuration,
|
|
105
98
|
at,
|
|
@@ -119,6 +112,7 @@ export class SubscriptionWillCanceledEmailTemplate
|
|
|
119
112
|
canceled_at: formatTime(subscription.canceled_at ? subscription.canceled_at * 1000 : dayjs().unix()),
|
|
120
113
|
}
|
|
121
114
|
);
|
|
115
|
+
|
|
122
116
|
if (subscription.status === 'past_due' || subscription.cancelation_details?.reason === 'payment_failed') {
|
|
123
117
|
body = translate('notification.subscriptWillCanceled.pastDue', locale, {
|
|
124
118
|
productName,
|
|
@@ -128,41 +122,24 @@ export class SubscriptionWillCanceledEmailTemplate
|
|
|
128
122
|
needRenew = true;
|
|
129
123
|
}
|
|
130
124
|
|
|
131
|
-
const viewSubscriptionLink = getCustomerSubscriptionPageUrl({
|
|
132
|
-
subscriptionId: subscription.id,
|
|
133
|
-
locale,
|
|
134
|
-
userDid,
|
|
135
|
-
});
|
|
136
|
-
const viewInvoiceLink = getCustomerInvoicePageUrl({
|
|
137
|
-
invoiceId: invoice.id,
|
|
138
|
-
userDid,
|
|
139
|
-
locale,
|
|
140
|
-
action: 'pay',
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
const customActions = getSubscriptionNotificationCustomActions(
|
|
144
|
-
subscription,
|
|
145
|
-
'customer.subscription.will_canceled',
|
|
146
|
-
locale
|
|
147
|
-
);
|
|
148
125
|
return {
|
|
149
126
|
locale,
|
|
150
127
|
productName,
|
|
151
128
|
at,
|
|
152
129
|
body,
|
|
153
130
|
cancelReason,
|
|
154
|
-
|
|
155
131
|
userDid,
|
|
156
132
|
paymentInfo,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
customActions,
|
|
133
|
+
viewSubscriptionLink: links.viewSubscriptionLink,
|
|
134
|
+
viewInvoiceLink: links.viewInvoiceLink,
|
|
135
|
+
customActions: links.customActions,
|
|
161
136
|
needRenew,
|
|
137
|
+
isCreditSubscription,
|
|
162
138
|
};
|
|
163
139
|
}
|
|
164
140
|
|
|
165
141
|
async getTemplate(): Promise<BaseEmailTemplateType | null> {
|
|
142
|
+
const context = await this.getContext();
|
|
166
143
|
const {
|
|
167
144
|
locale,
|
|
168
145
|
productName,
|
|
@@ -172,109 +149,91 @@ export class SubscriptionWillCanceledEmailTemplate
|
|
|
172
149
|
needRenew,
|
|
173
150
|
userDid,
|
|
174
151
|
paymentInfo,
|
|
175
|
-
|
|
176
152
|
viewSubscriptionLink,
|
|
177
153
|
viewInvoiceLink,
|
|
178
154
|
customActions,
|
|
179
|
-
|
|
155
|
+
isCreditSubscription,
|
|
156
|
+
} = context;
|
|
180
157
|
|
|
181
158
|
// 如果当前时间大于订阅终止时间,那么不发送通知
|
|
182
159
|
if (dayjs().utc().isAfter(dayjs.utc(at))) {
|
|
183
160
|
return null;
|
|
184
161
|
}
|
|
185
162
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
{
|
|
194
|
-
type: 'section',
|
|
195
|
-
fields: [
|
|
196
|
-
{
|
|
197
|
-
type: 'text',
|
|
198
|
-
data: {
|
|
199
|
-
type: 'plain',
|
|
200
|
-
color: '#9397A1',
|
|
201
|
-
text: translate('notification.common.account', locale),
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
type: 'text',
|
|
206
|
-
data: {
|
|
207
|
-
type: 'plain',
|
|
208
|
-
text: userDid,
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
{
|
|
212
|
-
type: 'text',
|
|
213
|
-
data: {
|
|
214
|
-
type: 'plain',
|
|
215
|
-
color: '#9397A1',
|
|
216
|
-
text: translate('notification.common.product', locale),
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
type: 'text',
|
|
221
|
-
data: {
|
|
222
|
-
type: 'plain',
|
|
223
|
-
text: productName,
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
...(needRenew
|
|
227
|
-
? [
|
|
228
|
-
{
|
|
229
|
-
type: 'text',
|
|
230
|
-
data: {
|
|
231
|
-
type: 'plain',
|
|
232
|
-
color: '#9397A1',
|
|
233
|
-
text: translate('notification.common.paymentAmount', locale),
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
type: 'text',
|
|
238
|
-
data: {
|
|
239
|
-
type: 'plain',
|
|
240
|
-
text: paymentInfo,
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
]
|
|
244
|
-
: []),
|
|
163
|
+
// 构建字段
|
|
164
|
+
const commonFields = this.buildCommonFields(userDid, productName, locale);
|
|
165
|
+
|
|
166
|
+
// 支付金额字段(仅在需要续费时显示)
|
|
167
|
+
const paymentAmountFields =
|
|
168
|
+
needRenew && !isCreditSubscription
|
|
169
|
+
? [
|
|
245
170
|
{
|
|
246
171
|
type: 'text',
|
|
247
172
|
data: {
|
|
248
173
|
type: 'plain',
|
|
249
174
|
color: '#9397A1',
|
|
250
|
-
text: translate('notification.
|
|
175
|
+
text: translate('notification.common.paymentAmount', locale),
|
|
251
176
|
},
|
|
252
177
|
},
|
|
253
178
|
{
|
|
254
179
|
type: 'text',
|
|
255
180
|
data: {
|
|
256
181
|
type: 'plain',
|
|
257
|
-
text:
|
|
182
|
+
text: paymentInfo,
|
|
258
183
|
},
|
|
259
184
|
},
|
|
260
|
-
]
|
|
185
|
+
]
|
|
186
|
+
: [];
|
|
187
|
+
|
|
188
|
+
// 取消原因字段
|
|
189
|
+
const cancelReasonFields = [
|
|
190
|
+
{
|
|
191
|
+
type: 'text',
|
|
192
|
+
data: {
|
|
193
|
+
type: 'plain',
|
|
194
|
+
color: '#9397A1',
|
|
195
|
+
text: translate('notification.subscriptWillCanceled.cancelReason', locale),
|
|
261
196
|
},
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
type: 'text',
|
|
200
|
+
data: {
|
|
201
|
+
type: 'plain',
|
|
202
|
+
text: cancelReason,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
// 构建操作按钮
|
|
208
|
+
const actions = [
|
|
209
|
+
viewSubscriptionLink && {
|
|
210
|
+
name: translate('notification.common.viewSubscription', locale),
|
|
211
|
+
title: translate('notification.common.viewSubscription', locale),
|
|
212
|
+
link: viewSubscriptionLink,
|
|
213
|
+
},
|
|
214
|
+
viewInvoiceLink &&
|
|
215
|
+
needRenew && {
|
|
216
|
+
name: translate('notification.common.renewNow', locale),
|
|
217
|
+
title: translate('notification.common.renewNow', locale),
|
|
218
|
+
link: viewInvoiceLink,
|
|
219
|
+
},
|
|
220
|
+
...customActions,
|
|
221
|
+
].filter(Boolean);
|
|
222
|
+
|
|
223
|
+
const template: BaseEmailTemplateType = {
|
|
224
|
+
title: translate('notification.subscriptWillCanceled.title', locale, {
|
|
225
|
+
productName,
|
|
226
|
+
}),
|
|
227
|
+
body,
|
|
228
|
+
// @ts-expect-error
|
|
229
|
+
attachments: [
|
|
230
|
+
{
|
|
231
|
+
type: 'section',
|
|
232
|
+
fields: [...commonFields, ...paymentAmountFields, ...cancelReasonFields].filter(Boolean),
|
|
269
233
|
},
|
|
270
|
-
viewInvoiceLink &&
|
|
271
|
-
needRenew && {
|
|
272
|
-
name: translate('notification.common.renewNow', locale),
|
|
273
|
-
title: translate('notification.common.renewNow', locale),
|
|
274
|
-
link: viewInvoiceLink,
|
|
275
|
-
},
|
|
276
|
-
...customActions,
|
|
277
234
|
].filter(Boolean),
|
|
235
|
+
// @ts-ignore
|
|
236
|
+
actions,
|
|
278
237
|
};
|
|
279
238
|
|
|
280
239
|
return template;
|