payment-kit 1.13.69 → 1.13.70
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/libs/notification/template/subscription-succeeded.ts +2 -1
- package/api/src/libs/payment.ts +1 -1
- package/api/src/libs/product.ts +13 -1
- package/api/src/schedule/base.ts +199 -0
- package/api/src/schedule/subscription-trail-will-end.ts +3 -170
- package/api/src/schedule/subscription-will-renew.ts +3 -170
- package/blocklet.yml +1 -1
- package/package.json +3 -3
|
@@ -80,7 +80,8 @@ export class SubscriptionSucceededEmailTemplate
|
|
|
80
80
|
]);
|
|
81
81
|
|
|
82
82
|
return Boolean(
|
|
83
|
-
['minted', 'sent', 'error'].includes(checkoutSession?.nft_mint_status as string) &&
|
|
83
|
+
['disabled', 'minted', 'sent', 'error'].includes(checkoutSession?.nft_mint_status as string) &&
|
|
84
|
+
invoice?.payment_intent_id
|
|
84
85
|
);
|
|
85
86
|
},
|
|
86
87
|
{ timeout: 1000 * 10, interval: 1000 }
|
package/api/src/libs/payment.ts
CHANGED
|
@@ -66,7 +66,7 @@ export async function isDelegationSufficientForPayment(args: {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// check token limits
|
|
69
|
-
if (grant.limit) {
|
|
69
|
+
if (grant.limit && Array.isArray(grant.limit.tokens) && grant.limit.tokens.length > 0) {
|
|
70
70
|
const tokenLimit = grant.limit.tokens.find((x: any) => x.address === tokenAddress);
|
|
71
71
|
if (!tokenLimit) {
|
|
72
72
|
return { sufficient: false, reason: 'NO_TOKEN_PERMISSION' };
|
package/api/src/libs/product.ts
CHANGED
|
@@ -9,11 +9,23 @@ export async function getMainProductName(subscriptionId: string): Promise<string
|
|
|
9
9
|
});
|
|
10
10
|
const priceId: string = checkoutSession?.line_items.find((x) => !x.cross_sell)?.price_id as string;
|
|
11
11
|
|
|
12
|
+
if (!priceId) {
|
|
13
|
+
throw new Error(`PriceId can't be found by subscriptionId(${subscriptionId})`);
|
|
14
|
+
}
|
|
15
|
+
|
|
12
16
|
const price = await Price.findByPk(priceId, {
|
|
13
17
|
attributes: ['product_id'],
|
|
14
18
|
});
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
if (!price) {
|
|
21
|
+
throw new Error(`Price can't be found by priceId(${priceId})`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const product = await Product.findByPk(price?.product_id);
|
|
25
|
+
|
|
26
|
+
if (!product) {
|
|
27
|
+
throw new Error(`Product can't be found by productId(${price?.product_id})`);
|
|
28
|
+
}
|
|
17
29
|
|
|
18
30
|
return product.name;
|
|
19
31
|
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import { clone } from 'lodash';
|
|
3
|
+
import pAll from 'p-all';
|
|
4
|
+
|
|
5
|
+
import { NotificationQueueJob, notificationQueue } from '../jobs/notification';
|
|
6
|
+
import { notificationCronConcurrency } from '../libs/env';
|
|
7
|
+
import logger from '../libs/logger';
|
|
8
|
+
import type { SubscriptionTrialWillEndEmailTemplateOptions } from '../libs/notification/template/subscription-trial-will-end';
|
|
9
|
+
import type { Subscription } from '../store/models';
|
|
10
|
+
import type { Diff } from './interface/diff';
|
|
11
|
+
|
|
12
|
+
export interface BaseSubscriptionScheduleNotificationSubscription extends Subscription {
|
|
13
|
+
diffs: Diff[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type BaseSubscriptionScheduleNotificationEventType =
|
|
17
|
+
| 'customer.subscription.trial_will_end'
|
|
18
|
+
| 'customer.subscription.will_renew';
|
|
19
|
+
|
|
20
|
+
type Task<Options> = {
|
|
21
|
+
id: string;
|
|
22
|
+
job: { type: NotificationQueueJob['type']; options: Options };
|
|
23
|
+
delay: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export abstract class BaseSubscriptionScheduleNotification {
|
|
27
|
+
readonly start: number;
|
|
28
|
+
readonly end: number;
|
|
29
|
+
abstract eventType: BaseSubscriptionScheduleNotificationEventType;
|
|
30
|
+
|
|
31
|
+
constructor() {
|
|
32
|
+
this.start = Date.now();
|
|
33
|
+
this.end = dayjs(this.start).add(1, 'M').add(1, 'd').toDate().getTime();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
abstract getSubscriptions(): Promise<Subscription[]>;
|
|
37
|
+
|
|
38
|
+
async run() {
|
|
39
|
+
const label: string = new Date().toISOString();
|
|
40
|
+
|
|
41
|
+
logger.info(`${label}: ${this.constructor.name}.run start`);
|
|
42
|
+
|
|
43
|
+
const subscriptions = await this.getSubscriptions();
|
|
44
|
+
const subscriptionForWillRenew = this.getSubscriptionsForWillRenew(subscriptions);
|
|
45
|
+
|
|
46
|
+
await this.addTaskToQueue(subscriptionForWillRenew);
|
|
47
|
+
|
|
48
|
+
logger.info(`${label}: ${this.constructor.name}.run end`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static DIFFS: Diff[] = [
|
|
52
|
+
{
|
|
53
|
+
value: 1,
|
|
54
|
+
unit: 'M',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
value: 7,
|
|
58
|
+
unit: 'd',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
value: 3,
|
|
62
|
+
unit: 'd',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
value: 1,
|
|
66
|
+
unit: 'd',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
value: 6,
|
|
70
|
+
unit: 'h',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
value: 30,
|
|
74
|
+
unit: 'm',
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
/**
|
|
78
|
+
* @see https://github.com/blocklet/payment-kit/issues/236#issuecomment-1824129965
|
|
79
|
+
* @description
|
|
80
|
+
* @param {Subscription[]} subscriptions
|
|
81
|
+
* @return {*} {SubscriptionForWillRenew[]}
|
|
82
|
+
* @memberof SubscriptionWillRenewSchedule
|
|
83
|
+
*/
|
|
84
|
+
getSubscriptionsForWillRenew(subscriptions: Subscription[]): BaseSubscriptionScheduleNotificationSubscription[] {
|
|
85
|
+
return subscriptions.map((subscription: Subscription): BaseSubscriptionScheduleNotificationSubscription => {
|
|
86
|
+
const s: BaseSubscriptionScheduleNotificationSubscription = clone(
|
|
87
|
+
subscription
|
|
88
|
+
) as BaseSubscriptionScheduleNotificationSubscription;
|
|
89
|
+
const currentPeriodStart: number = s.current_period_start * 1000;
|
|
90
|
+
const currentPeriodEnd: number = s.current_period_end * 1000;
|
|
91
|
+
|
|
92
|
+
if (
|
|
93
|
+
dayjs(currentPeriodEnd).diff(this.start, 'M') >= 1 &&
|
|
94
|
+
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'M') > 1
|
|
95
|
+
) {
|
|
96
|
+
s.diffs = BaseSubscriptionScheduleNotification.DIFFS.slice(0);
|
|
97
|
+
} else if (
|
|
98
|
+
dayjs(currentPeriodEnd).diff(this.start, 'd') >= 7 &&
|
|
99
|
+
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'd') > 7
|
|
100
|
+
) {
|
|
101
|
+
s.diffs = BaseSubscriptionScheduleNotification.DIFFS.slice(1);
|
|
102
|
+
} else if (
|
|
103
|
+
dayjs(currentPeriodEnd).diff(this.start, 'd') >= 3 &&
|
|
104
|
+
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'd') > 3
|
|
105
|
+
) {
|
|
106
|
+
s.diffs = BaseSubscriptionScheduleNotification.DIFFS.slice(2);
|
|
107
|
+
} else if (
|
|
108
|
+
dayjs(currentPeriodEnd).diff(this.start, 'd') >= 1 &&
|
|
109
|
+
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'd') > 1
|
|
110
|
+
) {
|
|
111
|
+
s.diffs = BaseSubscriptionScheduleNotification.DIFFS.slice(3);
|
|
112
|
+
} else if (
|
|
113
|
+
dayjs(currentPeriodEnd).diff(this.start, 'h') >= 6 &&
|
|
114
|
+
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'h') > 6
|
|
115
|
+
) {
|
|
116
|
+
s.diffs = BaseSubscriptionScheduleNotification.DIFFS.slice(4);
|
|
117
|
+
} else if (
|
|
118
|
+
dayjs(currentPeriodEnd).diff(this.start, 'm') >= 30 &&
|
|
119
|
+
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'm') > 30
|
|
120
|
+
) {
|
|
121
|
+
s.diffs = BaseSubscriptionScheduleNotification.DIFFS.slice(5);
|
|
122
|
+
} else {
|
|
123
|
+
s.diffs = [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 每一个周期都至少收到一封邮件
|
|
127
|
+
if (dayjs(currentPeriodEnd).diff(currentPeriodStart, 'M') > 1) {
|
|
128
|
+
s.diffs.forEach((x) => {
|
|
129
|
+
if (x.value === 7 && x.unit === 'd') {
|
|
130
|
+
x.required = true;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
} else if (dayjs(currentPeriodEnd).diff(currentPeriodStart, 'd') > 7) {
|
|
134
|
+
s.diffs.forEach((x) => {
|
|
135
|
+
if (x.value === 3 && x.unit === 'd') {
|
|
136
|
+
x.required = true;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
} else if (dayjs(currentPeriodEnd).diff(currentPeriodStart, 'h') >= 6) {
|
|
140
|
+
s.diffs.forEach((x) => {
|
|
141
|
+
if (x.value === 6 && x.unit === 'h') {
|
|
142
|
+
x.required = true;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
} else if (dayjs(currentPeriodEnd).diff(currentPeriodStart, 'm') >= 30) {
|
|
146
|
+
s.diffs.forEach((x) => {
|
|
147
|
+
if (x.value === 30 && x.unit === 'm') {
|
|
148
|
+
x.required = true;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return s;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async addTaskToQueue<Options extends SubscriptionTrialWillEndEmailTemplateOptions>(
|
|
158
|
+
subscriptions: BaseSubscriptionScheduleNotificationSubscription[]
|
|
159
|
+
): Promise<void> {
|
|
160
|
+
const type = this.eventType;
|
|
161
|
+
|
|
162
|
+
const tasks: Task<Options>[] = [];
|
|
163
|
+
for (const subscription of subscriptions) {
|
|
164
|
+
for (const diff of subscription.diffs) {
|
|
165
|
+
const task: Task<Options> = {
|
|
166
|
+
id: `${subscription.id}.${type}.${diff.value}.${diff.unit}`,
|
|
167
|
+
job: {
|
|
168
|
+
type,
|
|
169
|
+
options: {
|
|
170
|
+
subscriptionId: subscription.id,
|
|
171
|
+
willRenewValue: diff.value,
|
|
172
|
+
willRenewUnit: diff.unit,
|
|
173
|
+
required: !!diff.required,
|
|
174
|
+
} as Options,
|
|
175
|
+
},
|
|
176
|
+
delay: dayjs(subscription.current_period_end * 1000)
|
|
177
|
+
.subtract(diff.value, diff.unit)
|
|
178
|
+
.diff(this.start, 's'),
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
tasks.push(task);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
await pAll(
|
|
186
|
+
tasks.map((x) => {
|
|
187
|
+
return async () => {
|
|
188
|
+
const job = await notificationQueue.get(x.id);
|
|
189
|
+
if (job) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
notificationQueue.push(x);
|
|
194
|
+
};
|
|
195
|
+
}),
|
|
196
|
+
{ concurrency: notificationCronConcurrency }
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -1,42 +1,11 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
-
import dayjs from 'dayjs';
|
|
3
|
-
import { clone } from 'lodash';
|
|
4
|
-
import pAll from 'p-all';
|
|
5
2
|
import { Op } from 'sequelize';
|
|
6
3
|
|
|
7
|
-
import { NotificationQueueJob, notificationQueue } from '../jobs/notification';
|
|
8
|
-
import { notificationCronConcurrency } from '../libs/env';
|
|
9
|
-
import logger from '../libs/logger';
|
|
10
|
-
import type { SubscriptionTrialWillEndEmailTemplateOptions } from '../libs/notification/template/subscription-trial-will-end';
|
|
11
4
|
import { Subscription } from '../store/models';
|
|
12
|
-
import
|
|
5
|
+
import { BaseSubscriptionScheduleNotification, BaseSubscriptionScheduleNotificationEventType } from './base';
|
|
13
6
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class SubscriptionTrailWillEndSchedule {
|
|
19
|
-
private start: number;
|
|
20
|
-
|
|
21
|
-
private end: number;
|
|
22
|
-
|
|
23
|
-
constructor() {
|
|
24
|
-
this.start = Date.now();
|
|
25
|
-
this.end = dayjs(this.start).add(1, 'M').add(1, 'd').toDate().getTime();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async run() {
|
|
29
|
-
const label: string = new Date().toISOString();
|
|
30
|
-
|
|
31
|
-
logger.info(`${label}: SubscriptionTrailWillEndSchedule.run start`);
|
|
32
|
-
|
|
33
|
-
const subscriptions = await this.getSubscriptions();
|
|
34
|
-
const subscriptionForWillRenew = this.getSubscriptionsForWillRenew(subscriptions);
|
|
35
|
-
|
|
36
|
-
await this.addTaskToQueue(subscriptionForWillRenew);
|
|
37
|
-
|
|
38
|
-
logger.info(`${label}: SubscriptionTrailWillEndSchedule.run end`);
|
|
39
|
-
}
|
|
7
|
+
export class SubscriptionTrailWillEndSchedule extends BaseSubscriptionScheduleNotification {
|
|
8
|
+
override eventType: BaseSubscriptionScheduleNotificationEventType = 'customer.subscription.trial_will_end';
|
|
40
9
|
|
|
41
10
|
async getSubscriptions(): Promise<Subscription[]> {
|
|
42
11
|
const subscriptions = await Subscription.findAll({
|
|
@@ -58,140 +27,4 @@ export class SubscriptionTrailWillEndSchedule {
|
|
|
58
27
|
|
|
59
28
|
return subscriptions;
|
|
60
29
|
}
|
|
61
|
-
|
|
62
|
-
static DIFFS: Diff[] = [
|
|
63
|
-
{
|
|
64
|
-
value: 1,
|
|
65
|
-
unit: 'M',
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
value: 7,
|
|
69
|
-
unit: 'd',
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
value: 3,
|
|
73
|
-
unit: 'd',
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
value: 1,
|
|
77
|
-
unit: 'd',
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
value: 6,
|
|
81
|
-
unit: 'h',
|
|
82
|
-
},
|
|
83
|
-
];
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* @see https://github.com/blocklet/payment-kit/issues/236#issuecomment-1824129965
|
|
87
|
-
* @description
|
|
88
|
-
* @param {Subscription[]} subscriptions
|
|
89
|
-
* @return {*} {SubscriptionForWillRenew[]}
|
|
90
|
-
* @memberof SubscriptionWillRenewSchedule
|
|
91
|
-
*/
|
|
92
|
-
getSubscriptionsForWillRenew(subscriptions: Subscription[]): SubscriptionForTrailWillEnd[] {
|
|
93
|
-
return subscriptions.map((subscription: Subscription): SubscriptionForTrailWillEnd => {
|
|
94
|
-
const s: SubscriptionForTrailWillEnd = clone(subscription) as SubscriptionForTrailWillEnd;
|
|
95
|
-
const currentPeriodStart: number = s.current_period_start * 1000;
|
|
96
|
-
const currentPeriodEnd: number = s.current_period_end * 1000;
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
dayjs(currentPeriodEnd).diff(this.start, 'M') >= 1 &&
|
|
100
|
-
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'M') > 1
|
|
101
|
-
) {
|
|
102
|
-
s.diffs = SubscriptionTrailWillEndSchedule.DIFFS.slice(0);
|
|
103
|
-
} else if (
|
|
104
|
-
dayjs(currentPeriodEnd).diff(this.start, 'd') >= 7 &&
|
|
105
|
-
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'd') > 7
|
|
106
|
-
) {
|
|
107
|
-
s.diffs = SubscriptionTrailWillEndSchedule.DIFFS.slice(1);
|
|
108
|
-
} else if (
|
|
109
|
-
dayjs(currentPeriodEnd).diff(this.start, 'd') >= 3 &&
|
|
110
|
-
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'd') > 3
|
|
111
|
-
) {
|
|
112
|
-
s.diffs = SubscriptionTrailWillEndSchedule.DIFFS.slice(2);
|
|
113
|
-
} else if (
|
|
114
|
-
dayjs(currentPeriodEnd).diff(this.start, 'd') >= 1 &&
|
|
115
|
-
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'd') > 1
|
|
116
|
-
) {
|
|
117
|
-
s.diffs = SubscriptionTrailWillEndSchedule.DIFFS.slice(3);
|
|
118
|
-
} else if (
|
|
119
|
-
dayjs(currentPeriodEnd).diff(this.start, 'h') >= 6 &&
|
|
120
|
-
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'h') > 6
|
|
121
|
-
) {
|
|
122
|
-
s.diffs = SubscriptionTrailWillEndSchedule.DIFFS.slice(4);
|
|
123
|
-
} else {
|
|
124
|
-
s.diffs = [];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// 每一个周期都至少收到一封邮件
|
|
128
|
-
if (dayjs(currentPeriodEnd).diff(currentPeriodStart, 'M') > 1) {
|
|
129
|
-
s.diffs.forEach((x) => {
|
|
130
|
-
if (x.value === 7 && x.unit === 'd') {
|
|
131
|
-
x.required = true;
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
} else if (dayjs(currentPeriodEnd).diff(currentPeriodStart, 'd') > 7) {
|
|
135
|
-
s.diffs.forEach((x) => {
|
|
136
|
-
if (x.value === 3 && x.unit === 'd') {
|
|
137
|
-
x.required = true;
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
} else if (dayjs(currentPeriodEnd).diff(currentPeriodStart, 'h') >= 6) {
|
|
141
|
-
s.diffs.forEach((x) => {
|
|
142
|
-
if (x.value === 6 && x.unit === 'h') {
|
|
143
|
-
x.required = true;
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return s;
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async addTaskToQueue(subscriptions: SubscriptionForTrailWillEnd[]): Promise<void> {
|
|
153
|
-
const tasks: {
|
|
154
|
-
id: string;
|
|
155
|
-
job: { type: NotificationQueueJob['type']; options: SubscriptionTrialWillEndEmailTemplateOptions };
|
|
156
|
-
delay: number;
|
|
157
|
-
}[] = [];
|
|
158
|
-
|
|
159
|
-
for (const subscription of subscriptions) {
|
|
160
|
-
for (const diff of subscription.diffs) {
|
|
161
|
-
const id: string = `${subscription.id}.${diff.value}.${diff.unit}`;
|
|
162
|
-
const job: { type: NotificationQueueJob['type']; options: SubscriptionTrialWillEndEmailTemplateOptions } = {
|
|
163
|
-
type: 'customer.subscription.trial_will_end',
|
|
164
|
-
options: {
|
|
165
|
-
subscriptionId: subscription.id,
|
|
166
|
-
willRenewValue: diff.value,
|
|
167
|
-
willRenewUnit: diff.unit,
|
|
168
|
-
required: !!diff.required,
|
|
169
|
-
},
|
|
170
|
-
};
|
|
171
|
-
const delay: number = dayjs(subscription.current_period_end * 1000)
|
|
172
|
-
.subtract(diff.value, diff.unit)
|
|
173
|
-
.diff(this.start, 's');
|
|
174
|
-
|
|
175
|
-
tasks.push({
|
|
176
|
-
id,
|
|
177
|
-
job,
|
|
178
|
-
delay,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
await pAll(
|
|
184
|
-
tasks.map((x) => {
|
|
185
|
-
return async () => {
|
|
186
|
-
const job = await notificationQueue.get(x.id);
|
|
187
|
-
if (job) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
notificationQueue.push(x);
|
|
192
|
-
};
|
|
193
|
-
}),
|
|
194
|
-
{ concurrency: notificationCronConcurrency }
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
30
|
}
|
|
@@ -1,42 +1,11 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
-
import dayjs from 'dayjs';
|
|
3
|
-
import { clone } from 'lodash';
|
|
4
|
-
import pAll from 'p-all';
|
|
5
2
|
import { Op } from 'sequelize';
|
|
6
3
|
|
|
7
|
-
import { NotificationQueueJob, notificationQueue } from '../jobs/notification';
|
|
8
|
-
import { notificationCronConcurrency } from '../libs/env';
|
|
9
|
-
import logger from '../libs/logger';
|
|
10
|
-
import type { SubscriptionWillRenewEmailTemplateOptions } from '../libs/notification/template/subscription-will-renew';
|
|
11
4
|
import { Subscription } from '../store/models';
|
|
12
|
-
import
|
|
5
|
+
import { BaseSubscriptionScheduleNotification, BaseSubscriptionScheduleNotificationEventType } from './base';
|
|
13
6
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class SubscriptionWillRenewSchedule {
|
|
19
|
-
private start: number;
|
|
20
|
-
|
|
21
|
-
private end: number;
|
|
22
|
-
|
|
23
|
-
constructor() {
|
|
24
|
-
this.start = Date.now();
|
|
25
|
-
this.end = dayjs(this.start).add(1, 'M').add(1, 'd').toDate().getTime();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async run() {
|
|
29
|
-
const label: string = new Date().toISOString();
|
|
30
|
-
|
|
31
|
-
logger.info(`${label}: SubscriptionWillRenewSchedule.run start`);
|
|
32
|
-
|
|
33
|
-
const subscriptions = await this.getSubscriptions();
|
|
34
|
-
const subscriptionForWillRenew = this.getSubscriptionsForWillRenew(subscriptions);
|
|
35
|
-
|
|
36
|
-
await this.addTaskToQueue(subscriptionForWillRenew);
|
|
37
|
-
|
|
38
|
-
logger.info(`${label}: SubscriptionWillRenewSchedule.run end`);
|
|
39
|
-
}
|
|
7
|
+
export class SubscriptionWillRenewSchedule extends BaseSubscriptionScheduleNotification {
|
|
8
|
+
override eventType: BaseSubscriptionScheduleNotificationEventType = 'customer.subscription.will_renew';
|
|
40
9
|
|
|
41
10
|
async getSubscriptions(): Promise<Subscription[]> {
|
|
42
11
|
const subscriptions = await Subscription.findAll({
|
|
@@ -56,140 +25,4 @@ export class SubscriptionWillRenewSchedule {
|
|
|
56
25
|
|
|
57
26
|
return subscriptions;
|
|
58
27
|
}
|
|
59
|
-
|
|
60
|
-
static DIFFS: Diff[] = [
|
|
61
|
-
{
|
|
62
|
-
value: 1,
|
|
63
|
-
unit: 'M',
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
value: 7,
|
|
67
|
-
unit: 'd',
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
value: 3,
|
|
71
|
-
unit: 'd',
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
value: 1,
|
|
75
|
-
unit: 'd',
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
value: 6,
|
|
79
|
-
unit: 'h',
|
|
80
|
-
},
|
|
81
|
-
];
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* @see https://github.com/blocklet/payment-kit/issues/236#issuecomment-1824129965
|
|
85
|
-
* @description
|
|
86
|
-
* @param {Subscription[]} subscriptions
|
|
87
|
-
* @return {*} {SubscriptionForWillRenew[]}
|
|
88
|
-
* @memberof SubscriptionWillRenewSchedule
|
|
89
|
-
*/
|
|
90
|
-
getSubscriptionsForWillRenew(subscriptions: Subscription[]): SubscriptionForWillRenew[] {
|
|
91
|
-
return subscriptions.map((subscription: Subscription): SubscriptionForWillRenew => {
|
|
92
|
-
const s: SubscriptionForWillRenew = clone(subscription) as SubscriptionForWillRenew;
|
|
93
|
-
const currentPeriodStart: number = s.current_period_start * 1000;
|
|
94
|
-
const currentPeriodEnd: number = s.current_period_end * 1000;
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
dayjs(currentPeriodEnd).diff(this.start, 'M') >= 1 &&
|
|
98
|
-
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'M') > 1
|
|
99
|
-
) {
|
|
100
|
-
s.diffs = SubscriptionWillRenewSchedule.DIFFS.slice(0);
|
|
101
|
-
} else if (
|
|
102
|
-
dayjs(currentPeriodEnd).diff(this.start, 'd') >= 7 &&
|
|
103
|
-
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'd') > 7
|
|
104
|
-
) {
|
|
105
|
-
s.diffs = SubscriptionWillRenewSchedule.DIFFS.slice(1);
|
|
106
|
-
} else if (
|
|
107
|
-
dayjs(currentPeriodEnd).diff(this.start, 'd') >= 3 &&
|
|
108
|
-
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'd') > 3
|
|
109
|
-
) {
|
|
110
|
-
s.diffs = SubscriptionWillRenewSchedule.DIFFS.slice(2);
|
|
111
|
-
} else if (
|
|
112
|
-
dayjs(currentPeriodEnd).diff(this.start, 'd') >= 1 &&
|
|
113
|
-
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'd') > 1
|
|
114
|
-
) {
|
|
115
|
-
s.diffs = SubscriptionWillRenewSchedule.DIFFS.slice(3);
|
|
116
|
-
} else if (
|
|
117
|
-
dayjs(currentPeriodEnd).diff(this.start, 'h') >= 6 &&
|
|
118
|
-
dayjs(currentPeriodEnd).diff(currentPeriodStart, 'h') > 6
|
|
119
|
-
) {
|
|
120
|
-
s.diffs = SubscriptionWillRenewSchedule.DIFFS.slice(4);
|
|
121
|
-
} else {
|
|
122
|
-
s.diffs = [];
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// 每一个周期都至少收到一封邮件
|
|
126
|
-
if (dayjs(currentPeriodEnd).diff(currentPeriodStart, 'M') > 1) {
|
|
127
|
-
s.diffs.forEach((x) => {
|
|
128
|
-
if (x.value === 7 && x.unit === 'd') {
|
|
129
|
-
x.required = true;
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
} else if (dayjs(currentPeriodEnd).diff(currentPeriodStart, 'd') > 7) {
|
|
133
|
-
s.diffs.forEach((x) => {
|
|
134
|
-
if (x.value === 3 && x.unit === 'd') {
|
|
135
|
-
x.required = true;
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
} else if (dayjs(currentPeriodEnd).diff(currentPeriodStart, 'h') >= 6) {
|
|
139
|
-
s.diffs.forEach((x) => {
|
|
140
|
-
if (x.value === 6 && x.unit === 'h') {
|
|
141
|
-
x.required = true;
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return s;
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async addTaskToQueue(subscriptions: SubscriptionForWillRenew[]): Promise<void> {
|
|
151
|
-
const tasks: {
|
|
152
|
-
id: string;
|
|
153
|
-
job: { type: NotificationQueueJob['type']; options: SubscriptionWillRenewEmailTemplateOptions };
|
|
154
|
-
delay: number;
|
|
155
|
-
}[] = [];
|
|
156
|
-
|
|
157
|
-
for (const subscription of subscriptions) {
|
|
158
|
-
for (const diff of subscription.diffs) {
|
|
159
|
-
const id: string = `${subscription.id}.${diff.value}.${diff.unit}`;
|
|
160
|
-
const job: { type: NotificationQueueJob['type']; options: SubscriptionWillRenewEmailTemplateOptions } = {
|
|
161
|
-
type: 'customer.subscription.will_renew',
|
|
162
|
-
options: {
|
|
163
|
-
subscriptionId: subscription.id,
|
|
164
|
-
willRenewValue: diff.value,
|
|
165
|
-
willRenewUnit: diff.unit,
|
|
166
|
-
required: !!diff.required,
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
const delay: number = dayjs(subscription.current_period_end * 1000)
|
|
170
|
-
.subtract(diff.value, diff.unit)
|
|
171
|
-
.diff(this.start, 's');
|
|
172
|
-
|
|
173
|
-
tasks.push({
|
|
174
|
-
id,
|
|
175
|
-
job,
|
|
176
|
-
delay,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
await pAll(
|
|
182
|
-
tasks.map((x) => {
|
|
183
|
-
return async () => {
|
|
184
|
-
const job = await notificationQueue.get(x.id);
|
|
185
|
-
if (job) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
notificationQueue.push(x);
|
|
190
|
-
};
|
|
191
|
-
}),
|
|
192
|
-
{ concurrency: notificationCronConcurrency }
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
28
|
}
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.70",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "COMPONENT_STORE_URL=https://test.store.blocklet.dev blocklet dev",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"@abtnode/types": "^1.16.19",
|
|
107
107
|
"@arcblock/eslint-config": "^0.2.4",
|
|
108
108
|
"@arcblock/eslint-config-ts": "^0.2.4",
|
|
109
|
-
"@did-pay/types": "1.13.
|
|
109
|
+
"@did-pay/types": "1.13.70",
|
|
110
110
|
"@types/cookie-parser": "^1.4.6",
|
|
111
111
|
"@types/cors": "^2.8.17",
|
|
112
112
|
"@types/dotenv-flow": "^3.3.3",
|
|
@@ -143,5 +143,5 @@
|
|
|
143
143
|
"parser": "typescript"
|
|
144
144
|
}
|
|
145
145
|
},
|
|
146
|
-
"gitHead": "
|
|
146
|
+
"gitHead": "f5fbe3d9d64d7ccdcdcc9dc2df791ab7665e64a3"
|
|
147
147
|
}
|