payment-kit 1.13.15

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 (222) hide show
  1. package/.eslintrc.js +15 -0
  2. package/README.md +3 -0
  3. package/api/dev.ts +6 -0
  4. package/api/hooks/pre-start.js +12 -0
  5. package/api/src/hooks/pre-start.ts +21 -0
  6. package/api/src/index.ts +92 -0
  7. package/api/src/jobs/event.ts +72 -0
  8. package/api/src/jobs/invoice.ts +148 -0
  9. package/api/src/jobs/payment.ts +208 -0
  10. package/api/src/jobs/subscription.ts +301 -0
  11. package/api/src/jobs/webhook.ts +113 -0
  12. package/api/src/libs/audit.ts +73 -0
  13. package/api/src/libs/auth.ts +40 -0
  14. package/api/src/libs/chain/arcblock.ts +13 -0
  15. package/api/src/libs/dayjs.ts +17 -0
  16. package/api/src/libs/env.ts +5 -0
  17. package/api/src/libs/hooks.ts +42 -0
  18. package/api/src/libs/logger.ts +27 -0
  19. package/api/src/libs/middleware.ts +12 -0
  20. package/api/src/libs/payment.ts +53 -0
  21. package/api/src/libs/queue/index.ts +263 -0
  22. package/api/src/libs/queue/store.ts +47 -0
  23. package/api/src/libs/security.ts +95 -0
  24. package/api/src/libs/session.ts +164 -0
  25. package/api/src/libs/util.ts +93 -0
  26. package/api/src/locales/en.ts +3 -0
  27. package/api/src/locales/index.ts +37 -0
  28. package/api/src/locales/zh.ts +3 -0
  29. package/api/src/routes/checkout-sessions.ts +536 -0
  30. package/api/src/routes/connect/collect.ts +109 -0
  31. package/api/src/routes/connect/pay.ts +116 -0
  32. package/api/src/routes/connect/setup.ts +121 -0
  33. package/api/src/routes/connect/shared.ts +410 -0
  34. package/api/src/routes/connect/subscribe.ts +128 -0
  35. package/api/src/routes/customers.ts +70 -0
  36. package/api/src/routes/events.ts +76 -0
  37. package/api/src/routes/index.ts +59 -0
  38. package/api/src/routes/invoices.ts +126 -0
  39. package/api/src/routes/payment-currencies.ts +38 -0
  40. package/api/src/routes/payment-intents.ts +122 -0
  41. package/api/src/routes/payment-links.ts +221 -0
  42. package/api/src/routes/payment-methods.ts +39 -0
  43. package/api/src/routes/prices.ts +134 -0
  44. package/api/src/routes/products.ts +191 -0
  45. package/api/src/routes/settings.ts +33 -0
  46. package/api/src/routes/subscription-items.ts +148 -0
  47. package/api/src/routes/subscriptions.ts +254 -0
  48. package/api/src/routes/usage-records.ts +120 -0
  49. package/api/src/routes/webhook-attempts.ts +57 -0
  50. package/api/src/routes/webhook-endpoints.ts +105 -0
  51. package/api/src/store/migrate.ts +16 -0
  52. package/api/src/store/migrations/20230905-genesis.ts +52 -0
  53. package/api/src/store/migrations/20230911-seeding.ts +145 -0
  54. package/api/src/store/models/checkout-session.ts +395 -0
  55. package/api/src/store/models/coupon.ts +137 -0
  56. package/api/src/store/models/customer.ts +199 -0
  57. package/api/src/store/models/discount.ts +116 -0
  58. package/api/src/store/models/event.ts +111 -0
  59. package/api/src/store/models/index.ts +165 -0
  60. package/api/src/store/models/invoice-item.ts +185 -0
  61. package/api/src/store/models/invoice.ts +492 -0
  62. package/api/src/store/models/job.ts +75 -0
  63. package/api/src/store/models/payment-currency.ts +139 -0
  64. package/api/src/store/models/payment-intent.ts +282 -0
  65. package/api/src/store/models/payment-link.ts +219 -0
  66. package/api/src/store/models/payment-method.ts +169 -0
  67. package/api/src/store/models/price.ts +266 -0
  68. package/api/src/store/models/product.ts +162 -0
  69. package/api/src/store/models/promotion-code.ts +112 -0
  70. package/api/src/store/models/setup-intent.ts +206 -0
  71. package/api/src/store/models/subscription-item.ts +103 -0
  72. package/api/src/store/models/subscription-schedule.ts +157 -0
  73. package/api/src/store/models/subscription.ts +307 -0
  74. package/api/src/store/models/types.ts +406 -0
  75. package/api/src/store/models/usage-record.ts +132 -0
  76. package/api/src/store/models/webhook-attempt.ts +96 -0
  77. package/api/src/store/models/webhook-endpoint.ts +96 -0
  78. package/api/src/store/sequelize.ts +15 -0
  79. package/api/third.d.ts +28 -0
  80. package/blocklet.md +3 -0
  81. package/blocklet.yml +89 -0
  82. package/index.html +14 -0
  83. package/logo.png +0 -0
  84. package/package.json +133 -0
  85. package/public/.gitkeep +0 -0
  86. package/screenshots/.gitkeep +0 -0
  87. package/screenshots/1-subscription.png +0 -0
  88. package/screenshots/2-customer-1.png +0 -0
  89. package/screenshots/3-customer-2.png +0 -0
  90. package/screenshots/4-admin-3.png +0 -0
  91. package/screenshots/5-admin-4.png +0 -0
  92. package/scripts/build-clean.js +6 -0
  93. package/scripts/bump-version.mjs +35 -0
  94. package/src/app.tsx +68 -0
  95. package/src/components/actions.tsx +85 -0
  96. package/src/components/blockchain/tx.tsx +29 -0
  97. package/src/components/checkout/amount.tsx +24 -0
  98. package/src/components/checkout/error.tsx +30 -0
  99. package/src/components/checkout/footer.tsx +12 -0
  100. package/src/components/checkout/form/address.tsx +38 -0
  101. package/src/components/checkout/form/index.tsx +295 -0
  102. package/src/components/checkout/header.tsx +23 -0
  103. package/src/components/checkout/pay.tsx +222 -0
  104. package/src/components/checkout/product-card.tsx +56 -0
  105. package/src/components/checkout/product-item.tsx +37 -0
  106. package/src/components/checkout/skeleton/overview.tsx +21 -0
  107. package/src/components/checkout/skeleton/payment.tsx +35 -0
  108. package/src/components/checkout/success.tsx +183 -0
  109. package/src/components/checkout/summary.tsx +34 -0
  110. package/src/components/collapse.tsx +50 -0
  111. package/src/components/confirm.tsx +55 -0
  112. package/src/components/copyable.tsx +38 -0
  113. package/src/components/currency.tsx +15 -0
  114. package/src/components/customer/actions.tsx +73 -0
  115. package/src/components/data.tsx +20 -0
  116. package/src/components/drawer-form.tsx +77 -0
  117. package/src/components/error-fallback.tsx +7 -0
  118. package/src/components/error.tsx +39 -0
  119. package/src/components/event/list.tsx +217 -0
  120. package/src/components/info-card.tsx +40 -0
  121. package/src/components/info-metric.tsx +35 -0
  122. package/src/components/info-row.tsx +28 -0
  123. package/src/components/input.tsx +40 -0
  124. package/src/components/invoice/action.tsx +94 -0
  125. package/src/components/invoice/list.tsx +225 -0
  126. package/src/components/invoice/table.tsx +110 -0
  127. package/src/components/layout.tsx +70 -0
  128. package/src/components/livemode.tsx +23 -0
  129. package/src/components/metadata/editor.tsx +57 -0
  130. package/src/components/metadata/form.tsx +45 -0
  131. package/src/components/payment-intent/actions.tsx +81 -0
  132. package/src/components/payment-intent/list.tsx +204 -0
  133. package/src/components/payment-link/actions.tsx +114 -0
  134. package/src/components/payment-link/after-pay.tsx +87 -0
  135. package/src/components/payment-link/before-pay.tsx +175 -0
  136. package/src/components/payment-link/item.tsx +135 -0
  137. package/src/components/payment-link/product-select.tsx +66 -0
  138. package/src/components/payment-link/rename.tsx +64 -0
  139. package/src/components/portal/invoice/list.tsx +110 -0
  140. package/src/components/portal/subscription/cancel.tsx +83 -0
  141. package/src/components/portal/subscription/list.tsx +232 -0
  142. package/src/components/price/actions.tsx +21 -0
  143. package/src/components/price/form.tsx +292 -0
  144. package/src/components/product/actions.tsx +125 -0
  145. package/src/components/product/add-price.tsx +59 -0
  146. package/src/components/product/create.tsx +97 -0
  147. package/src/components/product/edit-price.tsx +75 -0
  148. package/src/components/product/edit.tsx +67 -0
  149. package/src/components/product/features.tsx +32 -0
  150. package/src/components/product/form.tsx +76 -0
  151. package/src/components/relative-time.tsx +41 -0
  152. package/src/components/section/header.tsx +29 -0
  153. package/src/components/status.tsx +12 -0
  154. package/src/components/subscription/actions/cancel.tsx +66 -0
  155. package/src/components/subscription/actions/index.tsx +172 -0
  156. package/src/components/subscription/actions/pause.tsx +83 -0
  157. package/src/components/subscription/items/actions.tsx +31 -0
  158. package/src/components/subscription/items/index.tsx +107 -0
  159. package/src/components/subscription/list.tsx +200 -0
  160. package/src/components/switch.tsx +48 -0
  161. package/src/components/table.tsx +66 -0
  162. package/src/components/uploader.tsx +81 -0
  163. package/src/components/webhook/attempts.tsx +149 -0
  164. package/src/contexts/products.tsx +42 -0
  165. package/src/contexts/session.ts +10 -0
  166. package/src/contexts/settings.tsx +54 -0
  167. package/src/env.d.ts +17 -0
  168. package/src/global.css +97 -0
  169. package/src/hooks/mobile.ts +15 -0
  170. package/src/index.tsx +6 -0
  171. package/src/libs/api.ts +19 -0
  172. package/src/libs/dayjs.ts +17 -0
  173. package/src/libs/util.ts +474 -0
  174. package/src/locales/en.tsx +395 -0
  175. package/src/locales/index.tsx +8 -0
  176. package/src/locales/zh.tsx +389 -0
  177. package/src/pages/admin/billing/index.tsx +56 -0
  178. package/src/pages/admin/billing/invoices/detail.tsx +215 -0
  179. package/src/pages/admin/billing/invoices/index.tsx +5 -0
  180. package/src/pages/admin/billing/subscriptions/detail.tsx +237 -0
  181. package/src/pages/admin/billing/subscriptions/index.tsx +5 -0
  182. package/src/pages/admin/customers/customers/detail.tsx +209 -0
  183. package/src/pages/admin/customers/customers/index.tsx +109 -0
  184. package/src/pages/admin/customers/index.tsx +47 -0
  185. package/src/pages/admin/developers/events/detail.tsx +77 -0
  186. package/src/pages/admin/developers/events/index.tsx +5 -0
  187. package/src/pages/admin/developers/index.tsx +60 -0
  188. package/src/pages/admin/developers/logs.tsx +3 -0
  189. package/src/pages/admin/developers/overview.tsx +3 -0
  190. package/src/pages/admin/developers/webhooks/detail.tsx +109 -0
  191. package/src/pages/admin/developers/webhooks/index.tsx +102 -0
  192. package/src/pages/admin/index.tsx +120 -0
  193. package/src/pages/admin/overview.tsx +3 -0
  194. package/src/pages/admin/payments/index.tsx +65 -0
  195. package/src/pages/admin/payments/intents/detail.tsx +205 -0
  196. package/src/pages/admin/payments/intents/index.tsx +5 -0
  197. package/src/pages/admin/payments/links/create.tsx +141 -0
  198. package/src/pages/admin/payments/links/detail.tsx +318 -0
  199. package/src/pages/admin/payments/links/index.tsx +167 -0
  200. package/src/pages/admin/products/coupons/index.tsx +3 -0
  201. package/src/pages/admin/products/index.tsx +81 -0
  202. package/src/pages/admin/products/prices/actions.tsx +151 -0
  203. package/src/pages/admin/products/prices/detail.tsx +203 -0
  204. package/src/pages/admin/products/prices/list.tsx +95 -0
  205. package/src/pages/admin/products/pricing-tables.tsx +3 -0
  206. package/src/pages/admin/products/products/create.tsx +105 -0
  207. package/src/pages/admin/products/products/detail.tsx +246 -0
  208. package/src/pages/admin/products/products/index.tsx +154 -0
  209. package/src/pages/admin/settings/branding.tsx +3 -0
  210. package/src/pages/admin/settings/business.tsx +3 -0
  211. package/src/pages/admin/settings/index.tsx +47 -0
  212. package/src/pages/admin/settings/payment-methods.tsx +80 -0
  213. package/src/pages/checkout/index.tsx +38 -0
  214. package/src/pages/checkout/pay.tsx +89 -0
  215. package/src/pages/customer/index.tsx +93 -0
  216. package/src/pages/customer/invoice.tsx +147 -0
  217. package/src/pages/home.tsx +9 -0
  218. package/tsconfig.api.json +9 -0
  219. package/tsconfig.eslint.json +7 -0
  220. package/tsconfig.json +99 -0
  221. package/tsconfig.types.json +11 -0
  222. package/vite.config.ts +19 -0
@@ -0,0 +1,169 @@
1
+ /* eslint-disable @typescript-eslint/lines-between-class-members */
2
+ import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model } from 'sequelize';
3
+ import type { LiteralUnion } from 'type-fest';
4
+
5
+ import { createIdGenerator } from '../../libs/util';
6
+ import { sequelize } from '../sequelize';
7
+
8
+ const nextId = createIdGenerator('pm', 24);
9
+
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
+ };
32
+
33
+ // eslint-disable-next-line prettier/prettier
34
+ export class PaymentMethod extends Model<InferAttributes<PaymentMethod>, InferCreationAttributes<PaymentMethod>> {
35
+ // Unique identifier for the object.
36
+ declare id: CreationOptional<string>;
37
+
38
+ declare active: boolean;
39
+ declare livemode: boolean;
40
+ declare locked: boolean;
41
+
42
+ declare type: LiteralUnion<'stripe' | 'arcblock' | 'ethereum' | 'bitcoin', string>;
43
+
44
+ declare name: string;
45
+
46
+ declare description: string;
47
+
48
+ declare logo: string;
49
+
50
+ declare default_currency_id?: string;
51
+
52
+ // How do we confirm transactions
53
+ declare confirmation: {
54
+ type: LiteralUnion<'immediate' | 'callback' | 'block', string>;
55
+ block?: number;
56
+ };
57
+
58
+ // Extra settings to make the payment method work
59
+ declare settings: {
60
+ stripe?: StripeConfig;
61
+ arcblock?: ArcblockConfig;
62
+ ethereum?: EthereumConfig;
63
+ bitcoin?: BitcoinConfig;
64
+ };
65
+
66
+ // What features are supported
67
+ declare features: {
68
+ recurring: boolean;
69
+ refund: boolean;
70
+ dispute: boolean;
71
+ };
72
+
73
+ declare metadata: Record<string, any>;
74
+
75
+ declare created_at: CreationOptional<Date>;
76
+
77
+ declare updated_at: CreationOptional<Date>;
78
+
79
+ public static readonly GENESIS_ATTRIBUTES = {
80
+ id: {
81
+ type: DataTypes.STRING(30),
82
+ primaryKey: true,
83
+ allowNull: false,
84
+ defaultValue: nextId,
85
+ },
86
+ active: {
87
+ type: DataTypes.BOOLEAN,
88
+ allowNull: false,
89
+ },
90
+ livemode: {
91
+ type: DataTypes.BOOLEAN,
92
+ allowNull: false,
93
+ },
94
+ locked: {
95
+ type: DataTypes.BOOLEAN,
96
+ defaultValue: false,
97
+ },
98
+ type: {
99
+ type: DataTypes.ENUM('stripe', 'arcblock', 'ethereum', 'bitcoin'),
100
+ },
101
+ name: {
102
+ type: DataTypes.STRING(64),
103
+ },
104
+ description: {
105
+ type: DataTypes.STRING(512),
106
+ },
107
+ logo: {
108
+ type: DataTypes.STRING(512),
109
+ },
110
+ default_currency_id: {
111
+ type: DataTypes.STRING(15),
112
+ allowNull: true,
113
+ },
114
+ confirmation: {
115
+ type: DataTypes.JSON,
116
+ allowNull: false,
117
+ },
118
+ settings: {
119
+ type: DataTypes.JSON,
120
+ allowNull: false,
121
+ },
122
+ features: {
123
+ type: DataTypes.JSON,
124
+ allowNull: false,
125
+ },
126
+ metadata: {
127
+ type: DataTypes.JSON,
128
+ allowNull: true,
129
+ },
130
+ created_at: {
131
+ type: DataTypes.DATE,
132
+ defaultValue: DataTypes.NOW,
133
+ allowNull: false,
134
+ },
135
+ updated_at: {
136
+ type: DataTypes.DATE,
137
+ defaultValue: DataTypes.NOW,
138
+ allowNull: false,
139
+ },
140
+ };
141
+
142
+ // eslint-disable-next-line @typescript-eslint/no-shadow
143
+ public static initialize(sequelize: any) {
144
+ this.init(PaymentMethod.GENESIS_ATTRIBUTES, {
145
+ sequelize,
146
+ modelName: 'PaymentMethod',
147
+ tableName: 'payment_methods',
148
+ createdAt: 'created_at',
149
+ updatedAt: 'updated_at',
150
+ });
151
+ }
152
+
153
+ public static associate(models: any) {
154
+ this.hasMany(models.PaymentCurrency, {
155
+ foreignKey: 'payment_method_id',
156
+ as: 'payment_currencies',
157
+ });
158
+ }
159
+
160
+ public static expand(livemode?: boolean) {
161
+ return PaymentMethod.findAll({
162
+ where: { livemode: !!livemode },
163
+ order: [['created_at', 'DESC']],
164
+ include: [{ model: sequelize.models.PaymentCurrency, as: 'payment_currencies' }],
165
+ });
166
+ }
167
+ }
168
+
169
+ export type TPaymentMethod = InferAttributes<PaymentMethod>;
@@ -0,0 +1,266 @@
1
+ import isEmpty from 'lodash/isEmpty';
2
+ import {
3
+ CreationOptional,
4
+ DataTypes,
5
+ FindOptions,
6
+ InferAttributes,
7
+ InferCreationAttributes,
8
+ Model,
9
+ Op,
10
+ } from 'sequelize';
11
+ import type { LiteralUnion } from 'type-fest';
12
+
13
+ import { createEvent } from '../../libs/audit';
14
+ import { createIdGenerator, formatMetadata } from '../../libs/util';
15
+ import { sequelize } from '../sequelize';
16
+ import type { CustomUnitAmount, LineItem, PriceRecurring, PriceTier, TransformQuantity } from './types';
17
+
18
+ const nextId = createIdGenerator('price', 24);
19
+
20
+ // @link https://stripe.com/docs/api/prices
21
+ export class Price extends Model<InferAttributes<Price>, InferCreationAttributes<Price>> {
22
+ // Unique identifier for the object.
23
+ declare id: CreationOptional<string>;
24
+
25
+ // the product id
26
+ declare product_id: string;
27
+
28
+ // A brief description of the price, hidden from customers.
29
+ declare nickname?: string;
30
+
31
+ // Whether the price can be used for new purchases.
32
+ declare active: boolean;
33
+ declare livemode: boolean;
34
+ declare locked: CreationOptional<boolean>;
35
+
36
+ // One of one_time or recurring depending on whether the price is for a one-time purchase or a recurring (subscription) purchase.
37
+ declare type: LiteralUnion<'one_time' | 'recurring', string>;
38
+
39
+ // Describes how to compute the price per period.
40
+ declare billing_scheme: LiteralUnion<'per_unit' | 'tiered', string>;
41
+
42
+ // The unit amount in big number to be charged, represented as a whole integer if possible
43
+ declare unit_amount: string;
44
+
45
+ // The recurring components of a price such as interval and usage_type.
46
+ declare recurring?: PriceRecurring;
47
+
48
+ // Defines if the tiering price should be graduated or volume based.
49
+ declare tiers_mode?: LiteralUnion<'graduated' | 'volume', string>;
50
+
51
+ // Each element represents a pricing tier. This parameter requires billing_scheme to be set to tiered
52
+ declare tiers?: PriceTier[];
53
+
54
+ // 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;
56
+
57
+ // A lookup key used to retrieve prices dynamically from a static string
58
+ declare lookup_key?: string;
59
+
60
+ // Set of key-value pairs that you can attach to an object.
61
+ declare metadata: Record<string, any>;
62
+
63
+ declare transform_quantity?: TransformQuantity;
64
+
65
+ // Payment currency id
66
+ declare currency_id: string;
67
+
68
+ declare upsell?: {
69
+ upsells_to_id: string;
70
+ };
71
+
72
+ declare created_at: CreationOptional<Date>;
73
+ declare created_via: LiteralUnion<'api' | 'dashboard' | 'portal', string>;
74
+ declare updated_at: CreationOptional<Date>;
75
+
76
+ public static readonly GENESIS_ATTRIBUTES = {
77
+ id: {
78
+ type: DataTypes.STRING(32),
79
+ primaryKey: true,
80
+ allowNull: false,
81
+ defaultValue: nextId,
82
+ },
83
+ product_id: {
84
+ type: DataTypes.STRING(32),
85
+ allowNull: false,
86
+ },
87
+ nickname: {
88
+ type: DataTypes.STRING(512),
89
+ },
90
+ active: {
91
+ type: DataTypes.BOOLEAN,
92
+ allowNull: false,
93
+ },
94
+ livemode: {
95
+ type: DataTypes.BOOLEAN,
96
+ allowNull: false,
97
+ },
98
+ locked: {
99
+ type: DataTypes.BOOLEAN,
100
+ defaultValue: false,
101
+ },
102
+ type: {
103
+ type: DataTypes.ENUM('one_time', 'recurring'),
104
+ },
105
+ billing_scheme: {
106
+ type: DataTypes.ENUM('per_unit', 'tiered'),
107
+ },
108
+ unit_amount: {
109
+ type: DataTypes.STRING(32),
110
+ allowNull: false,
111
+ },
112
+ recurring: {
113
+ type: DataTypes.JSON,
114
+ allowNull: true,
115
+ },
116
+ tiers_mode: {
117
+ type: DataTypes.ENUM('graduated', 'volume'),
118
+ allowNull: true,
119
+ },
120
+ tiers: {
121
+ type: DataTypes.JSON,
122
+ allowNull: true,
123
+ },
124
+ custom_unit_amount: {
125
+ type: DataTypes.JSON,
126
+ allowNull: true,
127
+ },
128
+ lookup_key: {
129
+ type: DataTypes.STRING(128),
130
+ allowNull: true,
131
+ },
132
+ metadata: {
133
+ type: DataTypes.JSON,
134
+ allowNull: true,
135
+ },
136
+ transform_quantity: {
137
+ type: DataTypes.JSON,
138
+ allowNull: true,
139
+ },
140
+ currency_id: {
141
+ type: DataTypes.STRING(16),
142
+ },
143
+ created_at: {
144
+ type: DataTypes.DATE,
145
+ defaultValue: DataTypes.NOW,
146
+ allowNull: false,
147
+ },
148
+ created_via: {
149
+ type: DataTypes.ENUM('api', 'dashboard', 'portal'),
150
+ },
151
+ updated_at: {
152
+ type: DataTypes.DATE,
153
+ defaultValue: DataTypes.NOW,
154
+ allowNull: false,
155
+ },
156
+ };
157
+
158
+ // eslint-disable-next-line @typescript-eslint/no-shadow
159
+ public static initialize(sequelize: any) {
160
+ this.init(Price.GENESIS_ATTRIBUTES, {
161
+ sequelize,
162
+ modelName: 'Price',
163
+ tableName: 'prices',
164
+ createdAt: 'created_at',
165
+ updatedAt: 'updated_at',
166
+ hooks: {
167
+ afterCreate: (model: Price, options) =>
168
+ createEvent('Price', 'price.created', model, options).catch(console.error),
169
+ afterUpdate: (model: Price, options) =>
170
+ createEvent('Price', 'price.updated', model, options).catch(console.error),
171
+ afterDestroy: (model: Price, options) =>
172
+ createEvent('Price', 'price.deleted', model, options).catch(console.error),
173
+ },
174
+ });
175
+ }
176
+
177
+ public static associate(models: any) {
178
+ this.hasOne(models.Product, {
179
+ sourceKey: 'product_id',
180
+ foreignKey: 'id',
181
+ as: 'product',
182
+ });
183
+ this.hasOne(models.PaymentCurrency, {
184
+ sourceKey: 'currency_id',
185
+ foreignKey: 'id',
186
+ as: 'currency',
187
+ });
188
+ }
189
+
190
+ public static getModel(price: TPrice) {
191
+ if (price.billing_scheme === 'tiered') {
192
+ return price.tiers_mode;
193
+ }
194
+
195
+ if (price.transform_quantity) {
196
+ return 'package';
197
+ }
198
+
199
+ return 'standard';
200
+ }
201
+
202
+ public static format(price: Partial<TPrice & { model: string }>) {
203
+ if (price.type === 'recurring') {
204
+ if (price.recurring) {
205
+ price.recurring.usage_type = price.recurring?.metered ? 'metered' : 'licensed';
206
+ } else {
207
+ throw new Error('recurring config is required for recurring prices');
208
+ }
209
+ } else {
210
+ price.recurring = undefined;
211
+ }
212
+
213
+ if (price.model && ['graduated', 'volume'].includes(price.model)) {
214
+ price.billing_scheme = 'tiered';
215
+ price.tiers_mode = price.model;
216
+ if (isEmpty(price.tiers)) {
217
+ throw new Error('tiers is required for graduated and volume prices');
218
+ }
219
+ } else {
220
+ price.billing_scheme = 'per_unit';
221
+ delete price.tiers;
222
+ }
223
+
224
+ if (price.model !== 'package') {
225
+ delete price.transform_quantity;
226
+ }
227
+
228
+ price.metadata = formatMetadata(price.metadata);
229
+
230
+ return price;
231
+ }
232
+
233
+ public static async expand(items: LineItem[], deep: boolean = true): Promise<(LineItem & { price: TPrice })[]> {
234
+ const priceIds: string[] = items.map((i) => i.price_id);
235
+ const prices = await Price.findAll({
236
+ where: { id: priceIds },
237
+ include: deep ? [{ model: sequelize.models.Product, as: 'product' }] : [],
238
+ });
239
+
240
+ return items.map((x) => ({
241
+ ...x,
242
+ price: prices.find((p) => p.id === x.price_id),
243
+ })) as (LineItem & { price: TPrice })[];
244
+ }
245
+
246
+ public static async insert(price: TPrice & { model: string }) {
247
+ if (price.lookup_key) {
248
+ const exist = await this.count({ where: { lookup_key: price.lookup_key } });
249
+ if (exist) {
250
+ throw new Error('lookup_key already exists');
251
+ }
252
+ }
253
+
254
+ // @ts-ignore
255
+ return this.create(this.format(price));
256
+ }
257
+
258
+ public static findByPkOrLookupKey(id: string, options: FindOptions<Price> = {}) {
259
+ return this.findOne({
260
+ where: { [Op.or]: [{ id }, { lookup_key: id }] },
261
+ ...options,
262
+ });
263
+ }
264
+ }
265
+
266
+ export type TPrice = InferAttributes<Price>;
@@ -0,0 +1,162 @@
1
+ /* eslint-disable @typescript-eslint/lines-between-class-members */
2
+ import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model } from 'sequelize';
3
+ import type { LiteralUnion } from 'type-fest';
4
+
5
+ import { createEvent } from '../../libs/audit';
6
+ import { createIdGenerator } from '../../libs/util';
7
+
8
+ const nextId = createIdGenerator('prod', 14);
9
+
10
+ export type ProductFeature = { name: string };
11
+
12
+ // FIXME: default_price_id is not implemented
13
+ // @link https://stripe.com/docs/api/products
14
+ export class Product extends Model<InferAttributes<Product>, InferCreationAttributes<Product>> {
15
+ // Unique identifier for the object.
16
+ declare id: CreationOptional<string>;
17
+
18
+ // Whether the product is currently available for purchase.
19
+ declare active: boolean;
20
+ declare livemode: boolean;
21
+ declare locked: CreationOptional<boolean>;
22
+
23
+ declare type: LiteralUnion<'service' | 'good', string>;
24
+
25
+ // The product’s name, meant to be displayable to the customer.
26
+ declare name: string;
27
+
28
+ // The product’s description, meant to be displayable to the customer.
29
+ declare description: string;
30
+
31
+ // A list of up to 8 URLs of images for this product, meant to be displayable to the customer.
32
+ declare images: any[];
33
+
34
+ // A list of up to 15 features for this product. These are displayed in pricing tables.
35
+ declare features: ProductFeature[];
36
+
37
+ // A label that represents units of this product.
38
+ declare unit_label?: string;
39
+
40
+ // The ID of the Price object that is the default price for this product.
41
+ declare default_price_id?: string;
42
+
43
+ declare metadata?: Record<string, any>;
44
+
45
+ declare statement_descriptor?: string;
46
+
47
+ // If set, we will mint an nft to consumer on purchase
48
+ declare nft_factory?: string;
49
+
50
+ // TODO: goods related props are not supported
51
+ // declare package_dimensions?: any;
52
+ // declare shippable?: boolean;
53
+
54
+ declare created_at: CreationOptional<Date>;
55
+
56
+ declare created_via: LiteralUnion<'api' | 'dashboard' | 'portal', string>;
57
+
58
+ declare updated_at: CreationOptional<Date>;
59
+
60
+ public static readonly GENESIS_ATTRIBUTES = {
61
+ id: {
62
+ type: DataTypes.STRING(18),
63
+ primaryKey: true,
64
+ allowNull: false,
65
+ defaultValue: nextId,
66
+ },
67
+ active: {
68
+ type: DataTypes.BOOLEAN,
69
+ allowNull: false,
70
+ },
71
+ livemode: {
72
+ type: DataTypes.BOOLEAN,
73
+ allowNull: false,
74
+ },
75
+ locked: {
76
+ type: DataTypes.BOOLEAN,
77
+ defaultValue: false,
78
+ },
79
+ type: {
80
+ type: DataTypes.ENUM('service', 'good'),
81
+ },
82
+ name: {
83
+ type: DataTypes.STRING(512),
84
+ },
85
+ description: {
86
+ type: DataTypes.STRING(2048),
87
+ },
88
+ images: {
89
+ type: DataTypes.JSON,
90
+ defaultValue: [],
91
+ },
92
+ features: {
93
+ type: DataTypes.JSON,
94
+ defaultValue: [],
95
+ },
96
+ unit_label: {
97
+ type: DataTypes.STRING(32),
98
+ allowNull: true,
99
+ },
100
+ default_price_id: {
101
+ type: DataTypes.STRING(32),
102
+ allowNull: true,
103
+ },
104
+ metadata: {
105
+ type: DataTypes.JSON,
106
+ allowNull: true,
107
+ },
108
+ statement_descriptor: {
109
+ type: DataTypes.STRING(32),
110
+ allowNull: true,
111
+ },
112
+ nft_factory: {
113
+ type: DataTypes.STRING(40),
114
+ allowNull: true,
115
+ },
116
+ created_at: {
117
+ type: DataTypes.DATE,
118
+ defaultValue: DataTypes.NOW,
119
+ allowNull: false,
120
+ },
121
+ created_via: {
122
+ type: DataTypes.ENUM('api', 'dashboard', 'portal'),
123
+ },
124
+ updated_at: {
125
+ type: DataTypes.DATE,
126
+ defaultValue: DataTypes.NOW,
127
+ allowNull: false,
128
+ },
129
+ };
130
+
131
+ public static initialize(sequelize: any) {
132
+ this.init(Product.GENESIS_ATTRIBUTES, {
133
+ sequelize,
134
+ modelName: 'Product',
135
+ tableName: 'products',
136
+ createdAt: 'created_at',
137
+ updatedAt: 'updated_at',
138
+ hooks: {
139
+ afterCreate: (model: Product, options) =>
140
+ createEvent('Product', 'product.created', model, options).catch(console.error),
141
+ afterUpdate: (model: Product, options) =>
142
+ createEvent('Product', 'product.updated', model, options).catch(console.error),
143
+ afterDestroy: (model: Product, options) =>
144
+ createEvent('Product', 'product.deleted', model, options).catch(console.error),
145
+ },
146
+ });
147
+ }
148
+
149
+ public static associate(models: any) {
150
+ this.hasMany(models.Price, {
151
+ foreignKey: 'product_id',
152
+ as: 'prices',
153
+ });
154
+ this.hasOne(models.Price, {
155
+ sourceKey: 'default_price_id',
156
+ foreignKey: 'id',
157
+ as: 'default_price',
158
+ });
159
+ }
160
+ }
161
+
162
+ export type TProduct = InferAttributes<Product>;
@@ -0,0 +1,112 @@
1
+ /* eslint-disable @typescript-eslint/lines-between-class-members */
2
+ import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model } from 'sequelize';
3
+
4
+ import { createIdGenerator } from '../../libs/util';
5
+
6
+ const nextId = createIdGenerator('promo', 24);
7
+
8
+ // @link https://stripe.com/docs/api/promotion_codes
9
+ // eslint-disable-next-line prettier/prettier
10
+ export class PromotionCode extends Model<InferAttributes<PromotionCode>, InferCreationAttributes<PromotionCode>> {
11
+ declare id: CreationOptional<string>;
12
+ declare livemode: boolean;
13
+ declare active: boolean;
14
+
15
+ declare code: string;
16
+
17
+ declare coupon_id: string;
18
+
19
+ // redeem conditions
20
+ declare max_redemptions?: number;
21
+ declare restrictions?: {
22
+ currency_options?: Record<string, number>;
23
+ first_time_transaction?: boolean;
24
+ minimum_amount?: number;
25
+ minimum_amount_currency?: string;
26
+ };
27
+ declare customer_id?: string;
28
+
29
+ declare times_redeemed?: number;
30
+
31
+ declare expires_at: CreationOptional<Date>;
32
+ declare created_at: CreationOptional<Date>;
33
+ declare updated_at: CreationOptional<Date>;
34
+
35
+ public static readonly GENESIS_ATTRIBUTES = {
36
+ id: {
37
+ type: DataTypes.STRING(30),
38
+ primaryKey: true,
39
+ allowNull: false,
40
+ defaultValue: nextId,
41
+ },
42
+ livemode: {
43
+ type: DataTypes.BOOLEAN,
44
+ allowNull: false,
45
+ },
46
+ active: {
47
+ type: DataTypes.BOOLEAN,
48
+ defaultValue: true,
49
+ },
50
+ code: {
51
+ type: DataTypes.STRING(16),
52
+ allowNull: false,
53
+ },
54
+ coupon_id: {
55
+ type: DataTypes.STRING(30),
56
+ allowNull: true,
57
+ },
58
+ max_redemptions: {
59
+ type: DataTypes.INTEGER,
60
+ allowNull: true,
61
+ },
62
+ restrictions: {
63
+ type: DataTypes.JSON,
64
+ defaultValue: {},
65
+ },
66
+ customer_id: {
67
+ type: DataTypes.STRING(30),
68
+ allowNull: true,
69
+ },
70
+ times_redeemed: {
71
+ type: DataTypes.INTEGER,
72
+ defaultValue: 0,
73
+ },
74
+ metadata: {
75
+ type: DataTypes.JSON,
76
+ allowNull: true,
77
+ },
78
+ expires_at: {
79
+ type: DataTypes.DATE,
80
+ allowNull: true,
81
+ },
82
+ created_at: {
83
+ type: DataTypes.DATE,
84
+ defaultValue: DataTypes.NOW,
85
+ allowNull: false,
86
+ },
87
+ updated_at: {
88
+ type: DataTypes.DATE,
89
+ defaultValue: DataTypes.NOW,
90
+ allowNull: false,
91
+ },
92
+ };
93
+
94
+ public static initialize(sequelize: any) {
95
+ this.init(PromotionCode.GENESIS_ATTRIBUTES, {
96
+ sequelize,
97
+ modelName: 'PromotionCode',
98
+ tableName: 'promotion_codes',
99
+ createdAt: 'created_at',
100
+ updatedAt: 'updated_at',
101
+ });
102
+ }
103
+
104
+ public static associate(models: any) {
105
+ this.belongsTo(models.Coupon, {
106
+ foreignKey: 'coupon_id',
107
+ as: 'coupon',
108
+ });
109
+ }
110
+ }
111
+
112
+ export type TPromotionCode = InferAttributes<PromotionCode>;