payment-kit 1.13.17 → 1.13.19
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/README.md +14 -0
- package/api/src/index.ts +17 -6
- package/api/src/integrations/stripe/handlers/index.ts +53 -0
- package/api/src/integrations/stripe/handlers/invoice.ts +252 -0
- package/api/src/integrations/stripe/handlers/payment-intent.ts +172 -0
- package/api/src/integrations/stripe/handlers/setup-intent.ts +42 -0
- package/api/src/integrations/stripe/handlers/subscription.ts +61 -0
- package/api/src/integrations/stripe/resource.ts +317 -0
- package/api/src/integrations/stripe/setup.ts +50 -0
- package/api/src/jobs/invoice.ts +11 -0
- package/api/src/jobs/payment.ts +15 -7
- package/api/src/jobs/subscription.ts +18 -2
- package/api/src/libs/session.ts +104 -8
- package/api/src/libs/util.ts +47 -1
- package/api/src/routes/checkout-sessions.ts +134 -27
- package/api/src/routes/connect/collect.ts +12 -4
- package/api/src/routes/connect/pay.ts +30 -20
- package/api/src/routes/connect/setup.ts +12 -4
- package/api/src/routes/connect/shared.ts +28 -4
- package/api/src/routes/connect/subscribe.ts +12 -5
- package/api/src/routes/customers.ts +5 -5
- package/api/src/routes/events.ts +9 -6
- package/api/src/routes/index.ts +2 -0
- package/api/src/routes/integrations/stripe.ts +64 -0
- package/api/src/routes/invoices.ts +19 -9
- package/api/src/routes/payment-intents.ts +19 -9
- package/api/src/routes/payment-links.ts +57 -15
- package/api/src/routes/payment-methods.ts +98 -1
- package/api/src/routes/prices.ts +71 -14
- package/api/src/routes/products.ts +79 -22
- package/api/src/routes/settings.ts +10 -11
- package/api/src/routes/subscription-items.ts +5 -5
- package/api/src/routes/subscriptions.ts +61 -10
- package/api/src/routes/usage-records.ts +52 -18
- package/api/src/routes/webhook-attempts.ts +5 -5
- package/api/src/routes/webhook-endpoints.ts +5 -5
- package/api/src/store/migrations/20230905-genesis.ts +2 -2
- package/api/src/store/migrations/20230911-seeding.ts +4 -3
- package/api/src/store/models/checkout-session.ts +15 -7
- package/api/src/store/models/index.ts +31 -7
- package/api/src/store/models/invoice.ts +1 -1
- package/api/src/store/models/payment-intent.ts +2 -5
- package/api/src/store/models/payment-link.ts +1 -1
- package/api/src/store/models/payment-method.ts +54 -33
- package/api/src/store/models/price.ts +52 -17
- package/api/src/store/models/product.ts +0 -3
- package/api/src/store/models/subscription.ts +3 -5
- package/api/src/store/models/types.ts +56 -2
- package/api/third.d.ts +2 -0
- package/blocklet.yml +1 -1
- package/package.json +36 -29
- package/public/currencies/dai.png +0 -0
- package/public/currencies/dollar.png +0 -0
- package/public/currencies/usdc.png +0 -0
- package/public/currencies/usdt.png +0 -0
- package/public/methods/arcblock.png +0 -0
- package/public/methods/binance.png +0 -0
- package/public/methods/coinbase.png +0 -0
- package/public/methods/ethereum.jpg +0 -0
- package/public/methods/stripe.png +0 -0
- package/src/components/checkout/form/address.tsx +86 -10
- package/src/components/checkout/form/index.tsx +169 -83
- package/src/components/checkout/form/phone.tsx +96 -0
- package/src/components/checkout/form/stripe.tsx +195 -0
- package/src/components/checkout/pay.tsx +115 -34
- package/src/components/checkout/product-item.tsx +4 -3
- package/src/components/checkout/summary.tsx +5 -4
- package/src/components/drawer-form.tsx +4 -4
- package/src/components/input.tsx +22 -4
- package/src/components/invoice/table.tsx +8 -3
- package/src/components/payment-link/before-pay.tsx +11 -6
- package/src/components/payment-link/chrome.tsx +13 -0
- package/src/components/payment-link/preview.tsx +31 -0
- package/src/components/payment-link/product-select.tsx +8 -3
- package/src/components/payment-method/arcblock.tsx +53 -0
- package/src/components/payment-method/bitcoin.tsx +53 -0
- package/src/components/payment-method/ethereum.tsx +53 -0
- package/src/components/payment-method/form.tsx +54 -0
- package/src/components/payment-method/stripe.tsx +45 -0
- package/src/components/portal/invoice/list.tsx +1 -1
- package/src/components/portal/subscription/list.tsx +1 -1
- package/src/components/price/currency-select.tsx +53 -0
- package/src/components/price/form.tsx +118 -24
- package/src/components/product/add-price.tsx +1 -1
- package/src/components/product/edit-price.tsx +6 -2
- package/src/components/subscription/items/index.tsx +7 -6
- package/src/components/subscription/items/usage-records.tsx +98 -0
- package/src/components/subscription/list.tsx +3 -2
- package/src/components/subscription/status.tsx +68 -0
- package/src/contexts/settings.tsx +2 -2
- package/src/env.d.ts +2 -0
- package/src/libs/util.ts +116 -21
- package/src/locales/en.tsx +71 -3
- package/src/pages/admin/billing/invoices/detail.tsx +5 -2
- package/src/pages/admin/billing/subscriptions/detail.tsx +6 -6
- package/src/pages/admin/customers/customers/detail.tsx +13 -1
- package/src/pages/admin/payments/intents/detail.tsx +8 -3
- package/src/pages/admin/payments/links/create.tsx +23 -3
- package/src/pages/admin/payments/links/detail.tsx +13 -26
- package/src/pages/admin/products/prices/detail.tsx +55 -11
- package/src/pages/admin/products/prices/list.tsx +7 -1
- package/src/pages/admin/products/products/create.tsx +1 -1
- package/src/pages/admin/products/products/detail.tsx +14 -7
- package/src/pages/admin/settings/index.tsx +16 -6
- package/src/pages/admin/settings/payment-methods/create.tsx +81 -0
- package/src/pages/admin/settings/{payment-methods.tsx → payment-methods/index.tsx} +9 -6
- package/src/pages/checkout/pay.tsx +3 -1
- package/src/pages/customer/index.tsx +12 -1
- package/public/.gitkeep +0 -0
|
@@ -30,15 +30,15 @@ router.post('/', auth, async (req, res) => {
|
|
|
30
30
|
|
|
31
31
|
const schema = Joi.object<{
|
|
32
32
|
page: number;
|
|
33
|
-
|
|
33
|
+
pageSize: number;
|
|
34
34
|
livemode?: boolean;
|
|
35
35
|
}>({
|
|
36
36
|
page: Joi.number().integer().min(1).default(1),
|
|
37
|
-
|
|
37
|
+
pageSize: Joi.number().integer().min(1).max(100).default(20),
|
|
38
38
|
livemode: Joi.boolean().empty(''),
|
|
39
39
|
});
|
|
40
40
|
router.get('/', auth, async (req, res) => {
|
|
41
|
-
const { page,
|
|
41
|
+
const { page, pageSize, ...query } = await schema.validateAsync(req.query, { stripUnknown: true });
|
|
42
42
|
const where: WhereOptions<WebhookEndpoint> = {};
|
|
43
43
|
|
|
44
44
|
if (typeof query.livemode === 'boolean') {
|
|
@@ -49,8 +49,8 @@ router.get('/', auth, async (req, res) => {
|
|
|
49
49
|
const { rows: list, count } = await WebhookEndpoint.findAndCountAll({
|
|
50
50
|
where,
|
|
51
51
|
order: [['created_at', 'DESC']],
|
|
52
|
-
offset: (page - 1) *
|
|
53
|
-
limit:
|
|
52
|
+
offset: (page - 1) * pageSize,
|
|
53
|
+
limit: pageSize,
|
|
54
54
|
include: [],
|
|
55
55
|
});
|
|
56
56
|
|
|
@@ -7,9 +7,9 @@ export const up: Migration = async ({ context: queryInterface }) => {
|
|
|
7
7
|
await queryInterface.createTable('customers', models.Customer.GENESIS_ATTRIBUTES);
|
|
8
8
|
await queryInterface.createTable('discounts', models.Discount.GENESIS_ATTRIBUTES);
|
|
9
9
|
await queryInterface.createTable('events', models.Event.GENESIS_ATTRIBUTES);
|
|
10
|
-
await queryInterface.createTable('jobs', models.Job.GENESIS_ATTRIBUTES);
|
|
11
10
|
await queryInterface.createTable('invoices', models.Invoice.GENESIS_ATTRIBUTES);
|
|
12
11
|
await queryInterface.createTable('invoice_items', models.InvoiceItem.GENESIS_ATTRIBUTES);
|
|
12
|
+
await queryInterface.createTable('jobs', models.Job.GENESIS_ATTRIBUTES);
|
|
13
13
|
await queryInterface.createTable('payment_currencies', models.PaymentCurrency.GENESIS_ATTRIBUTES);
|
|
14
14
|
await queryInterface.createTable('payment_intents', models.PaymentIntent.GENESIS_ATTRIBUTES);
|
|
15
15
|
await queryInterface.createTable('payment_links', models.PaymentLink.GENESIS_ATTRIBUTES);
|
|
@@ -32,9 +32,9 @@ export const down: Migration = async ({ context: queryInterface }) => {
|
|
|
32
32
|
await queryInterface.dropTable('customers');
|
|
33
33
|
await queryInterface.dropTable('discounts');
|
|
34
34
|
await queryInterface.dropTable('events');
|
|
35
|
-
await queryInterface.dropTable('jobs');
|
|
36
35
|
await queryInterface.dropTable('invoices');
|
|
37
36
|
await queryInterface.dropTable('invoice_items');
|
|
37
|
+
await queryInterface.dropTable('jobs');
|
|
38
38
|
await queryInterface.dropTable('payment_currencies');
|
|
39
39
|
await queryInterface.dropTable('payment_intents');
|
|
40
40
|
await queryInterface.dropTable('payment_links');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getUrl } from '@blocklet/sdk/lib/component';
|
|
1
2
|
import { fromTokenToUnit } from '@ocap/util';
|
|
2
3
|
import Sequelize from 'sequelize';
|
|
3
4
|
|
|
@@ -15,7 +16,7 @@ const mainId = genPaymentMethodId();
|
|
|
15
16
|
const betaId = genPaymentMethodId();
|
|
16
17
|
const now = new Date();
|
|
17
18
|
|
|
18
|
-
const logo = '
|
|
19
|
+
const logo = getUrl('/methods/arcblock.png');
|
|
19
20
|
|
|
20
21
|
const paymentCurrencies = [
|
|
21
22
|
{
|
|
@@ -60,7 +61,7 @@ const paymentCurrencies = [
|
|
|
60
61
|
id: marsId,
|
|
61
62
|
active: true,
|
|
62
63
|
livemode: false,
|
|
63
|
-
locked:
|
|
64
|
+
locked: false,
|
|
64
65
|
is_base_currency: false,
|
|
65
66
|
payment_method_id: betaId,
|
|
66
67
|
name: 'RollupTestToken',
|
|
@@ -79,7 +80,7 @@ const paymentCurrencies = [
|
|
|
79
80
|
id: play3Id,
|
|
80
81
|
active: true,
|
|
81
82
|
livemode: false,
|
|
82
|
-
locked:
|
|
83
|
+
locked: false,
|
|
83
84
|
is_base_currency: false,
|
|
84
85
|
payment_method_id: betaId,
|
|
85
86
|
name: 'Playground Token',
|
|
@@ -13,7 +13,7 @@ import type { LiteralUnion } from 'type-fest';
|
|
|
13
13
|
|
|
14
14
|
import { createStatusEvent } from '../../libs/audit';
|
|
15
15
|
import { createIdGenerator } from '../../libs/util';
|
|
16
|
-
import type { CurrencyConversion, CustomField, CustomerDetail, InvoiceData, LineItem } from './types';
|
|
16
|
+
import type { CurrencyConversion, CustomField, CustomerDetail, InvoiceData, LineItem, PaymentDetails } from './types';
|
|
17
17
|
|
|
18
18
|
const nextId = createIdGenerator('cs', 58);
|
|
19
19
|
|
|
@@ -31,6 +31,10 @@ export class CheckoutSession extends Model<InferAttributes<CheckoutSession>, Inf
|
|
|
31
31
|
// ID of the invoice created by the Checkout Session, if it exists.
|
|
32
32
|
declare invoice_id?: string;
|
|
33
33
|
|
|
34
|
+
// customer id for the Checkout Session, if it exists.
|
|
35
|
+
declare customer_id?: string;
|
|
36
|
+
declare customer_did?: string;
|
|
37
|
+
|
|
34
38
|
// The ID of the Payment Link that created this Session.
|
|
35
39
|
declare payment_link_id?: string;
|
|
36
40
|
|
|
@@ -125,7 +129,7 @@ export class CheckoutSession extends Model<InferAttributes<CheckoutSession>, Inf
|
|
|
125
129
|
};
|
|
126
130
|
|
|
127
131
|
// The list of payment method types that customers can use
|
|
128
|
-
declare payment_method_types
|
|
132
|
+
declare payment_method_types: string[];
|
|
129
133
|
|
|
130
134
|
// Controls phone number collection settings during checkout.
|
|
131
135
|
declare phone_number_collection?: {
|
|
@@ -156,10 +160,7 @@ export class CheckoutSession extends Model<InferAttributes<CheckoutSession>, Inf
|
|
|
156
160
|
};
|
|
157
161
|
|
|
158
162
|
// 3rd party payment tx hash
|
|
159
|
-
declare payment_details?:
|
|
160
|
-
tx_hash?: string;
|
|
161
|
-
payer?: string;
|
|
162
|
-
};
|
|
163
|
+
declare payment_details?: PaymentDetails;
|
|
163
164
|
|
|
164
165
|
// FIXME: Only exist on creation
|
|
165
166
|
// declare discounts?: {
|
|
@@ -209,6 +210,14 @@ export class CheckoutSession extends Model<InferAttributes<CheckoutSession>, Inf
|
|
|
209
210
|
type: DataTypes.STRING(30),
|
|
210
211
|
allowNull: true,
|
|
211
212
|
},
|
|
213
|
+
customer_id: {
|
|
214
|
+
type: DataTypes.STRING(30),
|
|
215
|
+
allowNull: true,
|
|
216
|
+
},
|
|
217
|
+
customer_did: {
|
|
218
|
+
type: DataTypes.STRING(40),
|
|
219
|
+
allowNull: true,
|
|
220
|
+
},
|
|
212
221
|
payment_link_id: {
|
|
213
222
|
type: DataTypes.STRING(30),
|
|
214
223
|
allowNull: true,
|
|
@@ -375,7 +384,6 @@ export class CheckoutSession extends Model<InferAttributes<CheckoutSession>, Inf
|
|
|
375
384
|
});
|
|
376
385
|
}
|
|
377
386
|
|
|
378
|
-
// FIXME:
|
|
379
387
|
public static associate(models: any) {
|
|
380
388
|
this.hasOne(models.PaymentCurrency, {
|
|
381
389
|
sourceKey: 'currency_id',
|
|
@@ -18,7 +18,7 @@ import { Subscription, TSubscription } from './subscription';
|
|
|
18
18
|
import { SubscriptionItem, TSubscriptionItem } from './subscription-item';
|
|
19
19
|
import { SubscriptionSchedule } from './subscription-schedule';
|
|
20
20
|
import type { LineItem } from './types';
|
|
21
|
-
import { UsageRecord } from './usage-record';
|
|
21
|
+
import { TUsageRecord, UsageRecord } from './usage-record';
|
|
22
22
|
import { TWebhookAttempt, WebhookAttempt } from './webhook-attempt';
|
|
23
23
|
import { TWebhookEndpoint, WebhookEndpoint } from './webhook-endpoint';
|
|
24
24
|
|
|
@@ -73,6 +73,7 @@ export * from './payment-method';
|
|
|
73
73
|
export * from './price';
|
|
74
74
|
export * from './product';
|
|
75
75
|
export * from './promotion-code';
|
|
76
|
+
export * from './setup-intent';
|
|
76
77
|
export * from './subscription';
|
|
77
78
|
export * from './subscription-item';
|
|
78
79
|
export * from './subscription-schedule';
|
|
@@ -81,25 +82,33 @@ export * from './webhook-attempt';
|
|
|
81
82
|
export * from './webhook-endpoint';
|
|
82
83
|
export * from './types';
|
|
83
84
|
|
|
84
|
-
export type TPriceExpanded = TPrice & { product: TProduct; currency: TPaymentCurrency };
|
|
85
|
+
export type TPriceExpanded = TPrice & { object: 'price'; product: TProduct; currency: TPaymentCurrency };
|
|
85
86
|
|
|
86
87
|
export type TLineItemExpanded = LineItem & { price: TPriceExpanded };
|
|
87
88
|
|
|
88
|
-
export type TProductExpanded = TProduct & { prices: TPrice[]; default_price: TPrice };
|
|
89
|
+
export type TProductExpanded = TProduct & { object: 'price'; prices: TPrice[]; default_price: TPrice };
|
|
89
90
|
|
|
90
|
-
export type TPaymentLinkExpanded = TPaymentLink & { line_items: TLineItemExpanded[] };
|
|
91
|
+
export type TPaymentLinkExpanded = TPaymentLink & { object: 'payment_link'; line_items: TLineItemExpanded[] };
|
|
91
92
|
|
|
92
|
-
export type TPaymentMethodExpanded = TPaymentMethod & {
|
|
93
|
+
export type TPaymentMethodExpanded = TPaymentMethod & {
|
|
94
|
+
object: 'payment_method';
|
|
95
|
+
payment_currencies: TPaymentCurrency[];
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export type TPaymentCurrencyExpanded = TPaymentCurrency & {
|
|
99
|
+
object: 'payment_currency';
|
|
100
|
+
};
|
|
93
101
|
|
|
94
102
|
export type TCheckoutSessionExpanded = TCheckoutSession & {
|
|
103
|
+
object: 'checkout_session';
|
|
95
104
|
line_items: TLineItemExpanded[];
|
|
96
105
|
payment_link?: TPaymentLink;
|
|
97
106
|
payment_intent?: TPaymentIntent;
|
|
98
107
|
subscription?: TSubscription;
|
|
99
|
-
currency: TPaymentCurrency;
|
|
100
108
|
};
|
|
101
109
|
|
|
102
110
|
export type TPaymentIntentExpanded = TPaymentIntent & {
|
|
111
|
+
object: 'payment_intent';
|
|
103
112
|
customer: TCustomer;
|
|
104
113
|
paymentCurrency: TPaymentCurrency;
|
|
105
114
|
paymentMethod: TPaymentMethod;
|
|
@@ -109,6 +118,7 @@ export type TPaymentIntentExpanded = TPaymentIntent & {
|
|
|
109
118
|
};
|
|
110
119
|
|
|
111
120
|
export type TSubscriptionExpanded = TSubscription & {
|
|
121
|
+
object: 'subscription';
|
|
112
122
|
customer: TCustomer;
|
|
113
123
|
paymentCurrency: TPaymentCurrency;
|
|
114
124
|
paymentMethod: TPaymentMethod;
|
|
@@ -116,16 +126,19 @@ export type TSubscriptionExpanded = TSubscription & {
|
|
|
116
126
|
};
|
|
117
127
|
|
|
118
128
|
export type TSubscriptionItemExpanded = TSubscriptionItem & {
|
|
129
|
+
object: 'subscription';
|
|
119
130
|
price: TPriceExpanded;
|
|
120
131
|
};
|
|
121
132
|
|
|
122
133
|
export type TCustomerExpanded = TCustomer & {
|
|
134
|
+
object: 'subscription';
|
|
123
135
|
payments: TPaymentIntent[];
|
|
124
136
|
subscriptions: TSubscription[];
|
|
125
137
|
invoices: TInvoice[];
|
|
126
138
|
};
|
|
127
139
|
|
|
128
140
|
export type TInvoiceExpanded = TInvoice & {
|
|
141
|
+
object: 'invoice';
|
|
129
142
|
customer: TCustomer;
|
|
130
143
|
paymentCurrency: TPaymentCurrency;
|
|
131
144
|
paymentMethod: TPaymentMethod;
|
|
@@ -135,23 +148,32 @@ export type TInvoiceExpanded = TInvoice & {
|
|
|
135
148
|
};
|
|
136
149
|
|
|
137
150
|
export type TInvoiceItemExpanded = TInvoiceItem & {
|
|
151
|
+
object: 'invoice_item';
|
|
138
152
|
price: TPriceExpanded;
|
|
139
153
|
};
|
|
140
154
|
|
|
141
155
|
export type TEventExpanded = TEvent & {
|
|
156
|
+
object: 'event';
|
|
142
157
|
webhooks: TWebhookEndpointExpanded[];
|
|
143
158
|
};
|
|
144
159
|
|
|
145
160
|
export type TWebhookAttemptExpanded = TWebhookAttempt & {
|
|
161
|
+
object: 'webhook_attempt';
|
|
146
162
|
event: TEvent;
|
|
147
163
|
endpoint: TWebhookEndpoint;
|
|
148
164
|
};
|
|
149
165
|
|
|
150
166
|
export type TWebhookEndpointExpanded = TWebhookEndpoint & {
|
|
167
|
+
object: 'webhook_endpoint';
|
|
151
168
|
attempts: TWebhookAttemptExpanded[];
|
|
152
169
|
};
|
|
153
170
|
|
|
171
|
+
export type TUsageRecordExpanded = TUsageRecord & {
|
|
172
|
+
object: 'usage_record';
|
|
173
|
+
};
|
|
174
|
+
|
|
154
175
|
export type TUsageRecordSummary = {
|
|
176
|
+
object: 'usage_record_summary';
|
|
155
177
|
livemode: boolean;
|
|
156
178
|
invoice_id: string;
|
|
157
179
|
subscription_item_id: string;
|
|
@@ -162,4 +184,6 @@ export type TUsageRecordSummary = {
|
|
|
162
184
|
};
|
|
163
185
|
};
|
|
164
186
|
|
|
165
|
-
export type TSetupIntentExpanded = TSetupIntent & {
|
|
187
|
+
export type TSetupIntentExpanded = TSetupIntent & {
|
|
188
|
+
object: 'setup_intent';
|
|
189
|
+
};
|
|
@@ -5,7 +5,7 @@ import type { LiteralUnion } from 'type-fest';
|
|
|
5
5
|
|
|
6
6
|
import { createEvent, createStatusEvent } from '../../libs/audit';
|
|
7
7
|
import { createIdGenerator } from '../../libs/util';
|
|
8
|
-
import type { PaymentError } from './types';
|
|
8
|
+
import type { PaymentDetails, PaymentError } from './types';
|
|
9
9
|
|
|
10
10
|
const nextId = createIdGenerator('pi', 24);
|
|
11
11
|
|
|
@@ -75,10 +75,7 @@ export class PaymentIntent extends Model<InferAttributes<PaymentIntent>, InferCr
|
|
|
75
75
|
declare review?: string;
|
|
76
76
|
|
|
77
77
|
// 3rd party payment tx hash
|
|
78
|
-
declare payment_details?:
|
|
79
|
-
tx_hash?: string;
|
|
80
|
-
payer?: string;
|
|
81
|
-
};
|
|
78
|
+
declare payment_details?: PaymentDetails;
|
|
82
79
|
|
|
83
80
|
declare setup_future_usage: LiteralUnion<'off_session' | 'on_session', string>;
|
|
84
81
|
|
|
@@ -94,7 +94,7 @@ export class PaymentLink extends Model<InferAttributes<PaymentLink>, InferCreati
|
|
|
94
94
|
|
|
95
95
|
public static readonly GENESIS_ATTRIBUTES = {
|
|
96
96
|
id: {
|
|
97
|
-
type: DataTypes.STRING(
|
|
97
|
+
type: DataTypes.STRING(40),
|
|
98
98
|
primaryKey: true,
|
|
99
99
|
allowNull: false,
|
|
100
100
|
defaultValue: nextId,
|
|
@@ -1,34 +1,17 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/lines-between-class-members */
|
|
2
|
+
import security from '@blocklet/sdk/lib/security';
|
|
3
|
+
import cloneDeep from 'lodash/cloneDeep';
|
|
2
4
|
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model } from 'sequelize';
|
|
5
|
+
import Stripe from 'stripe';
|
|
3
6
|
import type { LiteralUnion } from 'type-fest';
|
|
4
7
|
|
|
5
|
-
import { createIdGenerator } from '../../libs/util';
|
|
8
|
+
import { STRIPE_API_VERSION, createIdGenerator } from '../../libs/util';
|
|
6
9
|
import { sequelize } from '../sequelize';
|
|
10
|
+
import type { PaymentMethodSettings } from './types';
|
|
7
11
|
|
|
8
12
|
const nextId = createIdGenerator('pm', 24);
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
publishable_key: string;
|
|
12
|
-
secret_key: string;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
type ArcblockConfig = {
|
|
16
|
-
chain_id: string;
|
|
17
|
-
api_host: string;
|
|
18
|
-
explorer_host: string;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
type EthereumConfig = {
|
|
22
|
-
chain_id: number;
|
|
23
|
-
api_host: string;
|
|
24
|
-
explorer_host: string;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
type BitcoinConfig = {
|
|
28
|
-
chain_id: number;
|
|
29
|
-
api_host: string;
|
|
30
|
-
explorer_host: string;
|
|
31
|
-
};
|
|
14
|
+
const stripeClients = new Map<string, Stripe>();
|
|
32
15
|
|
|
33
16
|
// eslint-disable-next-line prettier/prettier
|
|
34
17
|
export class PaymentMethod extends Model<InferAttributes<PaymentMethod>, InferCreationAttributes<PaymentMethod>> {
|
|
@@ -56,12 +39,7 @@ export class PaymentMethod extends Model<InferAttributes<PaymentMethod>, InferCr
|
|
|
56
39
|
};
|
|
57
40
|
|
|
58
41
|
// Extra settings to make the payment method work
|
|
59
|
-
declare settings:
|
|
60
|
-
stripe?: StripeConfig;
|
|
61
|
-
arcblock?: ArcblockConfig;
|
|
62
|
-
ethereum?: EthereumConfig;
|
|
63
|
-
bitcoin?: BitcoinConfig;
|
|
64
|
-
};
|
|
42
|
+
declare settings: PaymentMethodSettings;
|
|
65
43
|
|
|
66
44
|
// What features are supported
|
|
67
45
|
declare features: {
|
|
@@ -73,7 +51,6 @@ export class PaymentMethod extends Model<InferAttributes<PaymentMethod>, InferCr
|
|
|
73
51
|
declare metadata: Record<string, any>;
|
|
74
52
|
|
|
75
53
|
declare created_at: CreationOptional<Date>;
|
|
76
|
-
|
|
77
54
|
declare updated_at: CreationOptional<Date>;
|
|
78
55
|
|
|
79
56
|
public static readonly GENESIS_ATTRIBUTES = {
|
|
@@ -157,12 +134,56 @@ export class PaymentMethod extends Model<InferAttributes<PaymentMethod>, InferCr
|
|
|
157
134
|
});
|
|
158
135
|
}
|
|
159
136
|
|
|
160
|
-
public static expand(livemode?: boolean) {
|
|
137
|
+
public static expand(livemode?: boolean, conditions: Record<string, any> = {}) {
|
|
161
138
|
return PaymentMethod.findAll({
|
|
162
|
-
where: { livemode: !!livemode },
|
|
139
|
+
where: { livemode: !!livemode, active: true, ...conditions },
|
|
163
140
|
order: [['created_at', 'DESC']],
|
|
164
141
|
include: [{ model: sequelize.models.PaymentCurrency, as: 'payment_currencies' }],
|
|
165
|
-
});
|
|
142
|
+
}).then((methods) => methods.map((x) => x.toJSON()));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public static encryptSettings(settings: PaymentMethodSettings) {
|
|
146
|
+
const tmp = cloneDeep(settings);
|
|
147
|
+
if (tmp.stripe) {
|
|
148
|
+
tmp.stripe.secret_key = security.encrypt(tmp.stripe.secret_key);
|
|
149
|
+
tmp.stripe.webhook_signing_secret = security.encrypt(tmp.stripe.webhook_signing_secret);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return tmp;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public static decryptSettings(settings: PaymentMethodSettings) {
|
|
156
|
+
const tmp = cloneDeep(settings);
|
|
157
|
+
if (tmp.stripe) {
|
|
158
|
+
tmp.stripe.secret_key = security.decrypt(tmp.stripe.secret_key);
|
|
159
|
+
tmp.stripe.webhook_signing_secret = security.decrypt(tmp.stripe.webhook_signing_secret);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return tmp;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
getStripe() {
|
|
166
|
+
if (this.type !== 'stripe') {
|
|
167
|
+
throw new Error('payment method is not stripe');
|
|
168
|
+
}
|
|
169
|
+
if (!this.settings.stripe) {
|
|
170
|
+
throw new Error('payment method config insufficient');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (stripeClients.has(this.id)) {
|
|
174
|
+
return stripeClients.get(this.id) as Stripe;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const settings = PaymentMethod.decryptSettings(this.settings);
|
|
178
|
+
const client = new Stripe(settings.stripe?.secret_key as string, { apiVersion: STRIPE_API_VERSION });
|
|
179
|
+
stripeClients.set(this.id, client);
|
|
180
|
+
|
|
181
|
+
return client as Stripe;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public static async supportAutoCharge(id: string) {
|
|
185
|
+
const method = await PaymentMethod.findByPk(id);
|
|
186
|
+
return method && ['arcblock', 'ethereum'].includes(method.type);
|
|
166
187
|
}
|
|
167
188
|
}
|
|
168
189
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { fromTokenToUnit } from '@ocap/util';
|
|
1
2
|
import isEmpty from 'lodash/isEmpty';
|
|
3
|
+
import omit from 'lodash/omit';
|
|
2
4
|
import {
|
|
3
5
|
CreationOptional,
|
|
4
6
|
DataTypes,
|
|
@@ -13,7 +15,8 @@ import type { LiteralUnion } from 'type-fest';
|
|
|
13
15
|
import { createEvent } from '../../libs/audit';
|
|
14
16
|
import { createIdGenerator, formatMetadata } from '../../libs/util';
|
|
15
17
|
import { sequelize } from '../sequelize';
|
|
16
|
-
import type {
|
|
18
|
+
import type { TPaymentCurrency } from './payment-currency';
|
|
19
|
+
import type { CustomUnitAmount, LineItem, PriceCurrency, PriceRecurring, PriceTier, TransformQuantity } from './types';
|
|
17
20
|
|
|
18
21
|
const nextId = createIdGenerator('price', 24);
|
|
19
22
|
|
|
@@ -26,7 +29,7 @@ export class Price extends Model<InferAttributes<Price>, InferCreationAttributes
|
|
|
26
29
|
declare product_id: string;
|
|
27
30
|
|
|
28
31
|
// A brief description of the price, hidden from customers.
|
|
29
|
-
declare nickname
|
|
32
|
+
declare nickname: string | null;
|
|
30
33
|
|
|
31
34
|
// Whether the price can be used for new purchases.
|
|
32
35
|
declare active: boolean;
|
|
@@ -43,31 +46,30 @@ export class Price extends Model<InferAttributes<Price>, InferCreationAttributes
|
|
|
43
46
|
declare unit_amount: string;
|
|
44
47
|
|
|
45
48
|
// The recurring components of a price such as interval and usage_type.
|
|
46
|
-
declare recurring
|
|
49
|
+
declare recurring: PriceRecurring | null;
|
|
47
50
|
|
|
48
51
|
// Defines if the tiering price should be graduated or volume based.
|
|
49
|
-
declare tiers_mode
|
|
52
|
+
declare tiers_mode: LiteralUnion<'graduated' | 'volume', string> | null;
|
|
50
53
|
|
|
51
54
|
// Each element represents a pricing tier. This parameter requires billing_scheme to be set to tiered
|
|
52
|
-
declare tiers
|
|
55
|
+
declare tiers: PriceTier[] | null;
|
|
53
56
|
|
|
54
57
|
// When set, provides configuration for the amount to be adjusted by the customer during Checkout Sessions and Payment Links.
|
|
55
|
-
declare custom_unit_amount
|
|
58
|
+
declare custom_unit_amount: CustomUnitAmount | null;
|
|
56
59
|
|
|
57
60
|
// A lookup key used to retrieve prices dynamically from a static string
|
|
58
|
-
declare lookup_key
|
|
61
|
+
declare lookup_key: string | null;
|
|
59
62
|
|
|
60
63
|
// Set of key-value pairs that you can attach to an object.
|
|
61
64
|
declare metadata: Record<string, any>;
|
|
62
65
|
|
|
63
|
-
declare transform_quantity
|
|
66
|
+
declare transform_quantity: TransformQuantity | null;
|
|
64
67
|
|
|
65
68
|
// Payment currency id
|
|
66
69
|
declare currency_id: string;
|
|
70
|
+
declare currency_options: PriceCurrency[];
|
|
67
71
|
|
|
68
|
-
declare upsell?: {
|
|
69
|
-
upsells_to_id: string;
|
|
70
|
-
};
|
|
72
|
+
declare upsell?: { upsells_to_id: string } | null;
|
|
71
73
|
|
|
72
74
|
declare created_at: CreationOptional<Date>;
|
|
73
75
|
declare created_via: LiteralUnion<'api' | 'dashboard' | 'portal', string>;
|
|
@@ -140,6 +142,10 @@ export class Price extends Model<InferAttributes<Price>, InferCreationAttributes
|
|
|
140
142
|
currency_id: {
|
|
141
143
|
type: DataTypes.STRING(16),
|
|
142
144
|
},
|
|
145
|
+
currency_options: {
|
|
146
|
+
type: DataTypes.JSON,
|
|
147
|
+
defaultValue: [],
|
|
148
|
+
},
|
|
143
149
|
created_at: {
|
|
144
150
|
type: DataTypes.DATE,
|
|
145
151
|
defaultValue: DataTypes.NOW,
|
|
@@ -201,13 +207,12 @@ export class Price extends Model<InferAttributes<Price>, InferCreationAttributes
|
|
|
201
207
|
|
|
202
208
|
public static format(price: Partial<TPrice & { model: string }>) {
|
|
203
209
|
if (price.type === 'recurring') {
|
|
204
|
-
if (price.recurring) {
|
|
205
|
-
price.recurring.usage_type = price.recurring?.metered ? 'metered' : 'licensed';
|
|
206
|
-
} else {
|
|
210
|
+
if (!price.recurring) {
|
|
207
211
|
throw new Error('recurring config is required for recurring prices');
|
|
208
212
|
}
|
|
213
|
+
price.recurring.interval_count = Number(price.recurring.interval_count);
|
|
209
214
|
} else {
|
|
210
|
-
price.recurring =
|
|
215
|
+
price.recurring = null;
|
|
211
216
|
}
|
|
212
217
|
|
|
213
218
|
if (price.model && ['graduated', 'volume'].includes(price.model)) {
|
|
@@ -218,11 +223,11 @@ export class Price extends Model<InferAttributes<Price>, InferCreationAttributes
|
|
|
218
223
|
}
|
|
219
224
|
} else {
|
|
220
225
|
price.billing_scheme = 'per_unit';
|
|
221
|
-
|
|
226
|
+
price.tiers = null;
|
|
222
227
|
}
|
|
223
228
|
|
|
224
229
|
if (price.model !== 'package') {
|
|
225
|
-
|
|
230
|
+
price.transform_quantity = null;
|
|
226
231
|
}
|
|
227
232
|
|
|
228
233
|
price.metadata = formatMetadata(price.metadata);
|
|
@@ -230,6 +235,23 @@ export class Price extends Model<InferAttributes<Price>, InferCreationAttributes
|
|
|
230
235
|
return price;
|
|
231
236
|
}
|
|
232
237
|
|
|
238
|
+
public static formatCurrencies(options: PriceCurrency[], currencies: TPaymentCurrency[]) {
|
|
239
|
+
return options.map((x) => {
|
|
240
|
+
const currency = currencies.find((c) => c.id === x.currency_id);
|
|
241
|
+
if (!currency) {
|
|
242
|
+
throw new Error(`currency ${x.currency_id} used in price not found or inactive`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return omit(
|
|
246
|
+
{
|
|
247
|
+
...x,
|
|
248
|
+
unit_amount: fromTokenToUnit(x.unit_amount, currency.decimal).toString(),
|
|
249
|
+
},
|
|
250
|
+
['currency']
|
|
251
|
+
) as any;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
233
255
|
public static async expand(items: LineItem[], deep: boolean = true): Promise<(LineItem & { price: TPrice })[]> {
|
|
234
256
|
const priceIds: string[] = items.map((i) => i.price_id);
|
|
235
257
|
const prices = await Price.findAll({
|
|
@@ -237,6 +259,19 @@ export class Price extends Model<InferAttributes<Price>, InferCreationAttributes
|
|
|
237
259
|
include: deep ? [{ model: sequelize.models.Product, as: 'product' }] : [],
|
|
238
260
|
});
|
|
239
261
|
|
|
262
|
+
prices.forEach((x) => {
|
|
263
|
+
if (!x.currency_options || x.currency_options?.length === 0) {
|
|
264
|
+
x.currency_options = [
|
|
265
|
+
{
|
|
266
|
+
currency_id: x.currency_id,
|
|
267
|
+
unit_amount: x.unit_amount,
|
|
268
|
+
tiers: null,
|
|
269
|
+
custom_unit_amount: null,
|
|
270
|
+
},
|
|
271
|
+
];
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
240
275
|
return items.map((x) => ({
|
|
241
276
|
...x,
|
|
242
277
|
price: prices.find((p) => p.id === x.price_id),
|
|
@@ -9,7 +9,6 @@ const nextId = createIdGenerator('prod', 14);
|
|
|
9
9
|
|
|
10
10
|
export type ProductFeature = { name: string };
|
|
11
11
|
|
|
12
|
-
// FIXME: default_price_id is not implemented
|
|
13
12
|
// @link https://stripe.com/docs/api/products
|
|
14
13
|
export class Product extends Model<InferAttributes<Product>, InferCreationAttributes<Product>> {
|
|
15
14
|
// Unique identifier for the object.
|
|
@@ -52,9 +51,7 @@ export class Product extends Model<InferAttributes<Product>, InferCreationAttrib
|
|
|
52
51
|
// declare shippable?: boolean;
|
|
53
52
|
|
|
54
53
|
declare created_at: CreationOptional<Date>;
|
|
55
|
-
|
|
56
54
|
declare created_via: LiteralUnion<'api' | 'dashboard' | 'portal', string>;
|
|
57
|
-
|
|
58
55
|
declare updated_at: CreationOptional<Date>;
|
|
59
56
|
|
|
60
57
|
public static readonly GENESIS_ATTRIBUTES = {
|
|
@@ -5,10 +5,11 @@ import type { LiteralUnion } from 'type-fest';
|
|
|
5
5
|
|
|
6
6
|
import { createEvent, createStatusEvent } from '../../libs/audit';
|
|
7
7
|
import { createIdGenerator } from '../../libs/util';
|
|
8
|
-
import type { PaymentSettings, PriceRecurring } from './types';
|
|
8
|
+
import type { PaymentDetails, PaymentSettings, PriceRecurring } from './types';
|
|
9
9
|
|
|
10
10
|
const nextId = createIdGenerator('sub', 24);
|
|
11
11
|
|
|
12
|
+
// FIXME: subscription settings https://dashboard.stripe.com/settings/billing/automatic
|
|
12
13
|
// eslint-disable-next-line prettier/prettier
|
|
13
14
|
export class Subscription extends Model<InferAttributes<Subscription>, InferCreationAttributes<Subscription>> {
|
|
14
15
|
declare id: CreationOptional<string>;
|
|
@@ -97,10 +98,7 @@ export class Subscription extends Model<InferAttributes<Subscription>, InferCrea
|
|
|
97
98
|
};
|
|
98
99
|
|
|
99
100
|
// 3rd party payment tx hash
|
|
100
|
-
declare payment_details?:
|
|
101
|
-
tx_hash?: string;
|
|
102
|
-
payer?: string;
|
|
103
|
-
};
|
|
101
|
+
declare payment_details?: PaymentDetails;
|
|
104
102
|
|
|
105
103
|
// TODO: following fields not supported
|
|
106
104
|
// application
|