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.
Files changed (109) hide show
  1. package/README.md +14 -0
  2. package/api/src/index.ts +17 -6
  3. package/api/src/integrations/stripe/handlers/index.ts +53 -0
  4. package/api/src/integrations/stripe/handlers/invoice.ts +252 -0
  5. package/api/src/integrations/stripe/handlers/payment-intent.ts +172 -0
  6. package/api/src/integrations/stripe/handlers/setup-intent.ts +42 -0
  7. package/api/src/integrations/stripe/handlers/subscription.ts +61 -0
  8. package/api/src/integrations/stripe/resource.ts +317 -0
  9. package/api/src/integrations/stripe/setup.ts +50 -0
  10. package/api/src/jobs/invoice.ts +11 -0
  11. package/api/src/jobs/payment.ts +15 -7
  12. package/api/src/jobs/subscription.ts +18 -2
  13. package/api/src/libs/session.ts +104 -8
  14. package/api/src/libs/util.ts +47 -1
  15. package/api/src/routes/checkout-sessions.ts +134 -27
  16. package/api/src/routes/connect/collect.ts +12 -4
  17. package/api/src/routes/connect/pay.ts +30 -20
  18. package/api/src/routes/connect/setup.ts +12 -4
  19. package/api/src/routes/connect/shared.ts +28 -4
  20. package/api/src/routes/connect/subscribe.ts +12 -5
  21. package/api/src/routes/customers.ts +5 -5
  22. package/api/src/routes/events.ts +9 -6
  23. package/api/src/routes/index.ts +2 -0
  24. package/api/src/routes/integrations/stripe.ts +64 -0
  25. package/api/src/routes/invoices.ts +19 -9
  26. package/api/src/routes/payment-intents.ts +19 -9
  27. package/api/src/routes/payment-links.ts +57 -15
  28. package/api/src/routes/payment-methods.ts +98 -1
  29. package/api/src/routes/prices.ts +71 -14
  30. package/api/src/routes/products.ts +79 -22
  31. package/api/src/routes/settings.ts +10 -11
  32. package/api/src/routes/subscription-items.ts +5 -5
  33. package/api/src/routes/subscriptions.ts +61 -10
  34. package/api/src/routes/usage-records.ts +52 -18
  35. package/api/src/routes/webhook-attempts.ts +5 -5
  36. package/api/src/routes/webhook-endpoints.ts +5 -5
  37. package/api/src/store/migrations/20230905-genesis.ts +2 -2
  38. package/api/src/store/migrations/20230911-seeding.ts +4 -3
  39. package/api/src/store/models/checkout-session.ts +15 -7
  40. package/api/src/store/models/index.ts +31 -7
  41. package/api/src/store/models/invoice.ts +1 -1
  42. package/api/src/store/models/payment-intent.ts +2 -5
  43. package/api/src/store/models/payment-link.ts +1 -1
  44. package/api/src/store/models/payment-method.ts +54 -33
  45. package/api/src/store/models/price.ts +52 -17
  46. package/api/src/store/models/product.ts +0 -3
  47. package/api/src/store/models/subscription.ts +3 -5
  48. package/api/src/store/models/types.ts +56 -2
  49. package/api/third.d.ts +2 -0
  50. package/blocklet.yml +1 -1
  51. package/package.json +36 -29
  52. package/public/currencies/dai.png +0 -0
  53. package/public/currencies/dollar.png +0 -0
  54. package/public/currencies/usdc.png +0 -0
  55. package/public/currencies/usdt.png +0 -0
  56. package/public/methods/arcblock.png +0 -0
  57. package/public/methods/binance.png +0 -0
  58. package/public/methods/coinbase.png +0 -0
  59. package/public/methods/ethereum.jpg +0 -0
  60. package/public/methods/stripe.png +0 -0
  61. package/src/components/checkout/form/address.tsx +86 -10
  62. package/src/components/checkout/form/index.tsx +169 -83
  63. package/src/components/checkout/form/phone.tsx +96 -0
  64. package/src/components/checkout/form/stripe.tsx +195 -0
  65. package/src/components/checkout/pay.tsx +115 -34
  66. package/src/components/checkout/product-item.tsx +4 -3
  67. package/src/components/checkout/summary.tsx +5 -4
  68. package/src/components/drawer-form.tsx +4 -4
  69. package/src/components/input.tsx +22 -4
  70. package/src/components/invoice/table.tsx +8 -3
  71. package/src/components/payment-link/before-pay.tsx +11 -6
  72. package/src/components/payment-link/chrome.tsx +13 -0
  73. package/src/components/payment-link/preview.tsx +31 -0
  74. package/src/components/payment-link/product-select.tsx +8 -3
  75. package/src/components/payment-method/arcblock.tsx +53 -0
  76. package/src/components/payment-method/bitcoin.tsx +53 -0
  77. package/src/components/payment-method/ethereum.tsx +53 -0
  78. package/src/components/payment-method/form.tsx +54 -0
  79. package/src/components/payment-method/stripe.tsx +45 -0
  80. package/src/components/portal/invoice/list.tsx +1 -1
  81. package/src/components/portal/subscription/list.tsx +1 -1
  82. package/src/components/price/currency-select.tsx +53 -0
  83. package/src/components/price/form.tsx +118 -24
  84. package/src/components/product/add-price.tsx +1 -1
  85. package/src/components/product/edit-price.tsx +6 -2
  86. package/src/components/subscription/items/index.tsx +7 -6
  87. package/src/components/subscription/items/usage-records.tsx +98 -0
  88. package/src/components/subscription/list.tsx +3 -2
  89. package/src/components/subscription/status.tsx +68 -0
  90. package/src/contexts/settings.tsx +2 -2
  91. package/src/env.d.ts +2 -0
  92. package/src/libs/util.ts +116 -21
  93. package/src/locales/en.tsx +71 -3
  94. package/src/pages/admin/billing/invoices/detail.tsx +5 -2
  95. package/src/pages/admin/billing/subscriptions/detail.tsx +6 -6
  96. package/src/pages/admin/customers/customers/detail.tsx +13 -1
  97. package/src/pages/admin/payments/intents/detail.tsx +8 -3
  98. package/src/pages/admin/payments/links/create.tsx +23 -3
  99. package/src/pages/admin/payments/links/detail.tsx +13 -26
  100. package/src/pages/admin/products/prices/detail.tsx +55 -11
  101. package/src/pages/admin/products/prices/list.tsx +7 -1
  102. package/src/pages/admin/products/products/create.tsx +1 -1
  103. package/src/pages/admin/products/products/detail.tsx +14 -7
  104. package/src/pages/admin/settings/index.tsx +16 -6
  105. package/src/pages/admin/settings/payment-methods/create.tsx +81 -0
  106. package/src/pages/admin/settings/{payment-methods.tsx → payment-methods/index.tsx} +9 -6
  107. package/src/pages/checkout/pay.tsx +3 -1
  108. package/src/pages/customer/index.tsx +12 -1
  109. 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
- size: number;
33
+ pageSize: number;
34
34
  livemode?: boolean;
35
35
  }>({
36
36
  page: Joi.number().integer().min(1).default(1),
37
- size: Joi.number().integer().min(1).max(100).default(20),
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, size, ...query } = await schema.validateAsync(req.query, { stripUnknown: true });
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) * size,
53
- limit: size,
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 = 'https://new.arcblock.io/.well-known/service/blocklet/logo?imageFilter=resize&w=80';
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: true,
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: true,
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?: string[];
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 & { payment_currencies: TPaymentCurrency[] };
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
+ };
@@ -386,7 +386,7 @@ export class Invoice extends Model<InferAttributes<Invoice>, InferCreationAttrib
386
386
  },
387
387
  statement_descriptor: {
388
388
  type: DataTypes.STRING(64),
389
- allowNull: false,
389
+ allowNull: true,
390
390
  },
391
391
  status_transitions: {
392
392
  type: DataTypes.JSON,
@@ -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(30),
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
- type StripeConfig = {
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 { CustomUnitAmount, LineItem, PriceRecurring, PriceTier, TransformQuantity } from './types';
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?: string;
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?: PriceRecurring;
49
+ declare recurring: PriceRecurring | null;
47
50
 
48
51
  // Defines if the tiering price should be graduated or volume based.
49
- declare tiers_mode?: LiteralUnion<'graduated' | 'volume', string>;
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?: PriceTier[];
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?: CustomUnitAmount;
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?: string;
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?: TransformQuantity;
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 = undefined;
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
- delete price.tiers;
226
+ price.tiers = null;
222
227
  }
223
228
 
224
229
  if (price.model !== 'package') {
225
- delete price.transform_quantity;
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