payment-kit 1.13.24 → 1.13.25

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 (38) hide show
  1. package/README.md +4 -0
  2. package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
  3. package/api/src/integrations/stripe/handlers/payment-intent.ts +2 -2
  4. package/api/src/integrations/stripe/handlers/setup-intent.ts +1 -1
  5. package/api/src/integrations/stripe/handlers/subscription.ts +2 -2
  6. package/api/src/routes/checkout-sessions.ts +3 -3
  7. package/api/src/routes/index.ts +2 -0
  8. package/api/src/routes/payment-links.ts +0 -1
  9. package/api/src/routes/pricing-table.ts +342 -0
  10. package/api/src/store/migrations/20231017-pricing-table.ts +10 -0
  11. package/api/src/store/models/index.ts +14 -1
  12. package/api/src/store/models/pricing-table.ts +107 -0
  13. package/api/src/store/models/types.ts +53 -0
  14. package/blocklet.yml +1 -1
  15. package/package.json +3 -3
  16. package/src/app.tsx +1 -1
  17. package/src/components/payment-link/actions.tsx +1 -1
  18. package/src/components/payment-link/chrome.tsx +5 -3
  19. package/src/components/payment-link/preview.tsx +8 -5
  20. package/src/components/payment-link/rename.tsx +3 -3
  21. package/src/components/pricing-table/actions.tsx +126 -0
  22. package/src/components/pricing-table/customer-settings.tsx +17 -0
  23. package/src/components/pricing-table/payment-settings.tsx +179 -0
  24. package/src/components/pricing-table/preview.tsx +34 -0
  25. package/src/components/pricing-table/price-item.tsx +64 -0
  26. package/src/components/pricing-table/product-item.tsx +86 -0
  27. package/src/components/pricing-table/product-settings.tsx +195 -0
  28. package/src/components/pricing-table/rename.tsx +67 -0
  29. package/src/libs/util.ts +43 -0
  30. package/src/locales/en.tsx +26 -0
  31. package/src/pages/admin/payments/links/create.tsx +1 -1
  32. package/src/pages/admin/products/index.tsx +8 -13
  33. package/src/pages/admin/products/pricing-tables/create.tsx +140 -0
  34. package/src/pages/admin/products/pricing-tables/detail.tsx +237 -0
  35. package/src/pages/admin/products/pricing-tables/index.tsx +154 -0
  36. package/src/pages/checkout/index.tsx +2 -1
  37. package/src/pages/checkout/pricing-table.tsx +195 -0
  38. package/src/pages/admin/products/pricing-tables.tsx +0 -3
package/README.md CHANGED
@@ -15,3 +15,7 @@ The decentralized stripe for blocklet platform.
15
15
  1. Install and login with instructions from: https://stripe.com/docs/stripe-cli
16
16
  2. Start your local payment-kit server, get it's port
17
17
  3. Run `stripe listen --forward-to http://127.0.0.1:8188/api/integrations/stripe/webhook --log-level=debug --latest`
18
+
19
+ ### Test Stripe
20
+
21
+ Invoices for subscriptions are not finalized automatically. You can use stripe postman collection to finalize it and then confirm the payment.
@@ -219,10 +219,10 @@ export async function handleInvoiceEvent(event: TEventExpanded, client: Stripe)
219
219
  try {
220
220
  await waitForStripeInvoiceMirrored(event.data.object.id);
221
221
  } catch (err) {
222
- logger.error('wait for stripe invoice mirror error', { localInvoiceId, error: err });
222
+ logger.error('wait for stripe invoice mirror error', { id: event.id, type: event.type, error: err });
223
223
  }
224
224
 
225
- logger.warn('local invoice id not found in strip event', { localInvoiceId });
225
+ logger.warn('local invoice id not found in strip event', { id: event.id, type: event.type });
226
226
  return;
227
227
  }
228
228
  }
@@ -123,10 +123,10 @@ export async function handlePaymentIntentEvent(event: TEventExpanded, client: St
123
123
  try {
124
124
  await waitForStripePaymentMirrored(event.data.object.id);
125
125
  } catch (err) {
126
- logger.error('wait for stripe payment intent mirror error', { localIntentId, error: err });
126
+ logger.error('wait for stripe payment intent mirror error', { id: event.id, type: event.type, error: err });
127
127
  }
128
128
 
129
- logger.warn('local payment intent id not found in strip event', { localIntentId });
129
+ logger.warn('local payment intent id not found in strip event', { id: event.id, type: event.type });
130
130
  return;
131
131
  }
132
132
 
@@ -11,7 +11,7 @@ export async function handleSetupIntentEvent(event: TEventExpanded, _: Stripe) {
11
11
  });
12
12
 
13
13
  if (!subscription) {
14
- logger.warn('local subscription not found for setup intent', { stripeIntentId });
14
+ logger.warn('local subscription not found for setup intent', { id: event.id, type: event.type, stripeIntentId });
15
15
  return;
16
16
  }
17
17
 
@@ -26,12 +26,12 @@ export async function handleStripeSubscriptionSucceed(subscription: Subscription
26
26
  export async function handleSubscriptionEvent(event: TEventExpanded, _: Stripe) {
27
27
  const localSubscriptionId = event.data.object.metadata?.id;
28
28
  if (!localSubscriptionId) {
29
- logger.warn('local subscription id not found in strip event', { localSubscriptionId });
29
+ logger.warn('local subscription id not found in strip event', { id: event.id, type: event.type });
30
30
  return;
31
31
  }
32
32
  const subscription = await Subscription.findByPk(localSubscriptionId);
33
33
  if (!subscription) {
34
- logger.warn('local subscription not found', { localSubscriptionId });
34
+ logger.warn('local subscription not found', { id: event.id, type: event.type, localSubscriptionId });
35
35
  return;
36
36
  }
37
37
 
@@ -61,7 +61,7 @@ const getPaymentTypes = async (items: any[]) => {
61
61
  return methods.map((x) => x.type);
62
62
  };
63
63
 
64
- const formatBeforeSave = async (payload: any, throwOnEmptyItems = true) => {
64
+ export const formatCheckoutSession = async (payload: any, throwOnEmptyItems = true) => {
65
65
  const raw: Partial<CheckoutSession> = Object.assign(
66
66
  {
67
67
  allow_promotion_codes: false,
@@ -172,7 +172,7 @@ const formatBeforeSave = async (payload: any, throwOnEmptyItems = true) => {
172
172
 
173
173
  // create checkout session
174
174
  router.post('/', auth, async (req, res) => {
175
- const raw: Partial<CheckoutSession> = await formatBeforeSave(req.body);
175
+ const raw: Partial<CheckoutSession> = await formatCheckoutSession(req.body);
176
176
  raw.livemode = !!req.livemode;
177
177
  raw.created_via = req.user?.via as string;
178
178
 
@@ -197,7 +197,7 @@ router.post('/start/:id', user, async (req, res) => {
197
197
 
198
198
  const items = await Price.expand(link.line_items);
199
199
 
200
- const raw: Partial<CheckoutSession> = await formatBeforeSave(link, false);
200
+ const raw: Partial<CheckoutSession> = await formatCheckoutSession(link, false);
201
201
  raw.livemode = link.livemode;
202
202
  raw.created_via = 'portal';
203
203
  raw.currency_id = link.currency_id || req.currency.id;
@@ -11,6 +11,7 @@ import paymentIntents from './payment-intents';
11
11
  import paymentLinks from './payment-links';
12
12
  import paymentMethods from './payment-methods';
13
13
  import prices from './prices';
14
+ import pricingTables from './pricing-table';
14
15
  import products from './products';
15
16
  import settings from './settings';
16
17
  import subscriptionItems from './subscription-items';
@@ -50,6 +51,7 @@ router.use('/payment-links', paymentLinks);
50
51
  router.use('/payment-methods', paymentMethods);
51
52
  router.use('/payment-currencies', paymentCurrencies);
52
53
  router.use('/prices', prices);
54
+ router.use('/pricing-tables', pricingTables);
53
55
  router.use('/products', products);
54
56
  router.use('/settings', settings);
55
57
  router.use('/subscription-items', subscriptionItems);
@@ -244,7 +244,6 @@ router.post('/stash', auth, async (req, res) => {
244
244
  raw.active = true;
245
245
  raw.livemode = !!req.livemode;
246
246
  raw.created_via = req.user?.via;
247
- raw.created_via = 'portal';
248
247
  raw.currency_id = raw.currency_id || req.currency.id;
249
248
 
250
249
  let doc = await PaymentLink.findByPk(raw.id);
@@ -0,0 +1,342 @@
1
+ import { getUrl } from '@blocklet/sdk/lib/component';
2
+ import { Router } from 'express';
3
+ import Joi from 'joi';
4
+ import pick from 'lodash/pick';
5
+ import uniq from 'lodash/uniq';
6
+ import { Op, WhereOptions } from 'sequelize';
7
+
8
+ import { authenticate } from '../libs/security';
9
+ import { isLineItemCurrencyAligned } from '../libs/session';
10
+ import { formatMetadata } from '../libs/util';
11
+ import { CheckoutSession } from '../store/models/checkout-session';
12
+ import { PaymentCurrency } from '../store/models/payment-currency';
13
+ import { Price } from '../store/models/price';
14
+ import { PricingTable } from '../store/models/pricing-table';
15
+ import { Product } from '../store/models/product';
16
+ import { formatCheckoutSession } from './checkout-sessions';
17
+
18
+ const router = Router();
19
+ const auth = authenticate<PricingTable>({ component: true, roles: ['owner', 'admin'] });
20
+
21
+ const formatPricingTable = (payload: any) => {
22
+ const raw: Partial<PricingTable> = Object.assign(
23
+ {
24
+ branding_settings: {
25
+ background_color: '#ffffff',
26
+ border_style: 'default',
27
+ button_color: '#0074d4',
28
+ font_family: 'default',
29
+ },
30
+ },
31
+ pick(payload, ['name', 'items', 'metadata', 'brand_settings'])
32
+ );
33
+
34
+ raw.items = raw.items?.map((x) => {
35
+ const item = Object.assign(
36
+ {
37
+ adjustable_quantity: {
38
+ enabled: false,
39
+ maximum: 1,
40
+ minimum: 0,
41
+ },
42
+ after_completion: {
43
+ type: 'hosted_confirmation',
44
+ hosted_confirmation: {
45
+ custom_message: '',
46
+ },
47
+ },
48
+ allow_promotion_codes: false,
49
+ customer_creation: 'always',
50
+ consent_collection: {
51
+ promotions: 'none',
52
+ terms_of_service: 'none',
53
+ },
54
+ invoice_creation: {
55
+ enabled: true,
56
+ },
57
+ phone_number_collection: {
58
+ enabled: false,
59
+ },
60
+ billing_address_collection: 'auto',
61
+ subscription_data: {
62
+ description: '',
63
+ trial_period_days: 0,
64
+ },
65
+ submit_type: 'auto',
66
+ },
67
+ pick(x, [
68
+ 'product_id',
69
+ 'price_id',
70
+ 'is_highlight',
71
+ 'highlight_text',
72
+ 'adjustable_quantity',
73
+ 'after_completion',
74
+ 'allow_promotion_codes',
75
+ 'consent_collection',
76
+ 'custom_fields',
77
+ 'phone_number_collection',
78
+ 'billing_address_collection',
79
+ 'submit_type',
80
+ 'subscription_data',
81
+ ])
82
+ );
83
+
84
+ if (item.adjustable_quantity?.enabled) {
85
+ item.adjustable_quantity.minimum = Number(item.adjustable_quantity?.minimum);
86
+ item.adjustable_quantity.maximum = Number(item.adjustable_quantity?.maximum);
87
+ }
88
+ if (item.after_completion?.type === 'hosted_confirmation') {
89
+ // @ts-ignore
90
+ item.after_completion.redirect = null;
91
+ }
92
+ if (item.after_completion?.type === 'redirect') {
93
+ // @ts-ignore
94
+ item.after_completion.hosted_confirmation = null;
95
+ }
96
+
97
+ return item;
98
+ });
99
+
100
+ if (payload.highlight && payload.highlight_product_id) {
101
+ raw.items?.forEach((x) => {
102
+ if (x.product_id === payload.highlight_product_id) {
103
+ x.is_highlight = x.product_id === payload.highlight_product_id;
104
+ x.highlight_text = payload.highlight_text || 'popular';
105
+ } else {
106
+ x.is_highlight = false;
107
+ x.highlight_text = 'popular';
108
+ }
109
+ });
110
+ }
111
+
112
+ raw.metadata = formatMetadata(raw.metadata);
113
+
114
+ return raw;
115
+ };
116
+
117
+ // FIXME: @wangshijun use schema validation
118
+ // eslint-disable-next-line consistent-return
119
+ router.post('/', auth, async (req, res) => {
120
+ const raw: Partial<PricingTable> = formatPricingTable(req.body);
121
+ raw.active = true;
122
+ raw.locked = false;
123
+ raw.livemode = !!req.livemode;
124
+ raw.created_via = req.user?.via;
125
+
126
+ if (!raw.items?.length) {
127
+ return res.status(400).json({ error: 'items should not be empty for pricing table' });
128
+ }
129
+
130
+ // @ts-ignore
131
+ const items = await Price.expand(raw.items);
132
+ for (let i = 0; i < items.length; i++) {
133
+ if (isLineItemCurrencyAligned(items, i) === false) {
134
+ return res.status(400).json({ error: 'items should have same currency' });
135
+ }
136
+ }
137
+
138
+ const link = await PricingTable.create(raw as PricingTable);
139
+
140
+ res.json(link);
141
+ });
142
+
143
+ // list pricing tables
144
+ const paginationSchema = Joi.object<{
145
+ page: number;
146
+ pageSize: number;
147
+ active?: boolean;
148
+ livemode?: boolean;
149
+ }>({
150
+ page: Joi.number().integer().min(1).default(1),
151
+ pageSize: Joi.number().integer().min(1).max(100).default(20),
152
+ active: Joi.boolean().empty(''),
153
+ livemode: Joi.boolean().empty(''),
154
+ });
155
+ router.get('/', auth, async (req, res) => {
156
+ const { page, pageSize, ...query } = await paginationSchema.validateAsync(req.query, { stripUnknown: true });
157
+ const where: WhereOptions<PricingTable> = { id: { [Op.notIn]: [`prctbl_${req.user?.did}`] } };
158
+
159
+ if (typeof query.active === 'boolean') {
160
+ where.active = query.active;
161
+ }
162
+ if (typeof query.livemode === 'boolean') {
163
+ where.livemode = query.livemode;
164
+ }
165
+
166
+ try {
167
+ const { rows: list, count } = await PricingTable.findAndCountAll({
168
+ where,
169
+ order: [['created_at', 'DESC']],
170
+ offset: (page - 1) * pageSize,
171
+ limit: pageSize,
172
+ include: [],
173
+ });
174
+
175
+ const priceIds: string[] = uniq(list.reduce((acc: string[], x) => acc.concat(x.items.map((i) => i.price_id)), []));
176
+ const prices = await Price.findAll({ where: { id: priceIds }, include: [{ model: Product, as: 'product' }] });
177
+ const products = await Product.findAll({ where: { id: uniq(prices.map((x) => x.product_id)) } });
178
+
179
+ list.forEach((x) => {
180
+ x.items.forEach((i) => {
181
+ // @ts-ignore
182
+ i.price = prices.find((p) => p.id === i.price_id);
183
+ // @ts-ignore
184
+ i.product = products.find((p) => p.id === i.product_id);
185
+ });
186
+ });
187
+
188
+ res.json({ count, list });
189
+ } catch (err) {
190
+ console.error(err);
191
+ res.json({ count: 0, list: [] });
192
+ }
193
+ });
194
+
195
+ // eslint-disable-next-line consistent-return
196
+ router.get('/:id', async (req, res) => {
197
+ const doc = await PricingTable.findByPk(req.params.id);
198
+
199
+ if (!doc) {
200
+ return res.status(404).json({ error: 'pricing table not found' });
201
+ }
202
+
203
+ const prices = await Price.findAll({ where: { id: uniq(doc.items.map((x) => x.price_id)) } });
204
+ const products = await Product.findAll({ where: { id: uniq(doc.items.map((x) => x.product_id)) } });
205
+
206
+ doc.items.forEach((i) => {
207
+ // @ts-ignore
208
+ i.price = prices.find((p) => p.id === i.price_id);
209
+ // @ts-ignore
210
+ i.product = products.find((p) => p.id === i.product_id);
211
+ });
212
+
213
+ const currency = await PaymentCurrency.findOne({ where: { livemode: doc.livemode, is_base_currency: true } });
214
+
215
+ res.json({ ...doc.toJSON(), currency });
216
+ });
217
+
218
+ // update
219
+ // eslint-disable-next-line consistent-return
220
+ router.put('/:id', auth, async (req, res) => {
221
+ const doc = await PricingTable.findByPk(req.params.id);
222
+
223
+ if (!doc) {
224
+ return res.status(404).json({ error: 'pricing table not found' });
225
+ }
226
+ if (doc.active === false) {
227
+ return res.status(403).json({ error: 'pricing table archived' });
228
+ }
229
+ // if (doc.locked) {
230
+ // return res.status(403).json({ error: 'pricing table locked' });
231
+ // }
232
+
233
+ // FIXME: should only allow update some fields
234
+ await doc.update(formatPricingTable(req.body));
235
+
236
+ res.json(doc);
237
+ });
238
+
239
+ // archive
240
+ router.put('/:id/archive', auth, async (req, res) => {
241
+ const doc = await PricingTable.findByPk(req.params.id);
242
+
243
+ if (!doc) {
244
+ return res.status(404).json({ error: 'pricing table not found' });
245
+ }
246
+
247
+ if (doc.active === false) {
248
+ return res.status(403).json({ error: 'pricing table already archived' });
249
+ }
250
+
251
+ await doc.update({ active: false });
252
+ return res.json(doc);
253
+ });
254
+
255
+ // delete
256
+ router.delete('/:id', auth, async (req, res) => {
257
+ const doc = await PricingTable.findByPk(req.params.id);
258
+
259
+ if (!doc) {
260
+ return res.status(404).json({ error: 'pricing table not found' });
261
+ }
262
+
263
+ if (doc.active === false) {
264
+ return res.status(403).json({ error: 'pricing table archived' });
265
+ }
266
+
267
+ if (doc.locked) {
268
+ return res.status(403).json({ error: 'pricing table locked' });
269
+ }
270
+
271
+ await doc.destroy();
272
+ return res.json(doc);
273
+ });
274
+
275
+ router.post('/stash', auth, async (req, res) => {
276
+ try {
277
+ const raw: Partial<PricingTable> = req.body;
278
+ raw.id = `prctbl_${req.user?.did}`;
279
+ raw.active = true;
280
+ raw.locked = false;
281
+ raw.livemode = !!req.livemode;
282
+ raw.created_via = req.user?.via;
283
+
284
+ let doc = await PricingTable.findByPk(raw.id);
285
+ if (doc) {
286
+ await doc.update(formatPricingTable(req.body));
287
+ } else {
288
+ doc = await PricingTable.create(raw as PricingTable);
289
+ }
290
+ res.json(doc);
291
+ } catch (err) {
292
+ console.error(err);
293
+ res.status(500).json({ error: err.message });
294
+ }
295
+ });
296
+
297
+ // eslint-disable-next-line consistent-return
298
+ router.post('/:id/checkout/:priceId', async (req, res) => {
299
+ const doc = await PricingTable.findByPk(req.params.id);
300
+
301
+ if (!doc) {
302
+ return res.status(404).json({ error: 'pricing table not found' });
303
+ }
304
+ if (doc.active === false) {
305
+ return res.status(403).json({ error: 'pricing table archived' });
306
+ }
307
+ if (doc.locked) {
308
+ return res.status(403).json({ error: 'pricing table locked' });
309
+ }
310
+
311
+ const price = await doc.items.find((x) => x.price_id === req.params.priceId);
312
+ if (!price) {
313
+ return res.status(403).json({ error: 'pricing table item not valid' });
314
+ }
315
+
316
+ const raw: Partial<CheckoutSession> = await formatCheckoutSession({
317
+ line_items: [{ price_id: price.price_id, quantity: 1, adjustable_quantity: price.adjustable_quantity }],
318
+ ...pick(price, [
319
+ 'allow_promotion_codes',
320
+ 'consent_collection',
321
+ 'custom_fields',
322
+ 'customer_creation',
323
+ 'invoice_creation',
324
+ 'phone_number_collection',
325
+ 'billing_address_collection',
326
+ 'submit_type',
327
+ 'subscription_data',
328
+ ]),
329
+ metadata: {
330
+ pricing_table: doc.id,
331
+ },
332
+ });
333
+
334
+ raw.livemode = doc.livemode;
335
+ raw.created_via = 'portal';
336
+ raw.currency_id = req.currency.id;
337
+
338
+ const session = await CheckoutSession.create(raw as any);
339
+ res.json({ ...session.toJSON(), url: getUrl(`/checkout/pay/${session.id}`) });
340
+ });
341
+
342
+ export default router;
@@ -0,0 +1,10 @@
1
+ import type { Migration } from '../migrate';
2
+ import models from '../models';
3
+
4
+ export const up: Migration = async ({ context: queryInterface }) => {
5
+ await queryInterface.createTable('pricing_tables', models.PricingTable.GENESIS_ATTRIBUTES);
6
+ };
7
+
8
+ export const down: Migration = async ({ context: queryInterface }) => {
9
+ await queryInterface.dropTable('pricing_tables');
10
+ };
@@ -11,13 +11,14 @@ import { PaymentIntent, TPaymentIntent } from './payment-intent';
11
11
  import { PaymentLink, TPaymentLink } from './payment-link';
12
12
  import { PaymentMethod, TPaymentMethod } from './payment-method';
13
13
  import { Price, TPrice } from './price';
14
+ import { PricingTable, TPricingTable } from './pricing-table';
14
15
  import { Product, TProduct } from './product';
15
16
  import { PromotionCode } from './promotion-code';
16
17
  import { SetupIntent, TSetupIntent } from './setup-intent';
17
18
  import { Subscription, TSubscription } from './subscription';
18
19
  import { SubscriptionItem, TSubscriptionItem } from './subscription-item';
19
20
  import { SubscriptionSchedule } from './subscription-schedule';
20
- import type { LineItem } from './types';
21
+ import type { LineItem, PricingTableItem } from './types';
21
22
  import { TUsageRecord, UsageRecord } from './usage-record';
22
23
  import { TWebhookAttempt, WebhookAttempt } from './webhook-attempt';
23
24
  import { TWebhookEndpoint, WebhookEndpoint } from './webhook-endpoint';
@@ -35,6 +36,7 @@ const models = {
35
36
  PaymentLink,
36
37
  PaymentMethod,
37
38
  Price,
39
+ PricingTable,
38
40
  Product,
39
41
  PromotionCode,
40
42
  SetupIntent,
@@ -71,6 +73,7 @@ export * from './payment-intent';
71
73
  export * from './payment-link';
72
74
  export * from './payment-method';
73
75
  export * from './price';
76
+ export * from './pricing-table';
74
77
  export * from './product';
75
78
  export * from './promotion-code';
76
79
  export * from './setup-intent';
@@ -187,3 +190,13 @@ export type TUsageRecordSummary = {
187
190
  export type TSetupIntentExpanded = TSetupIntent & {
188
191
  object: 'setup_intent';
189
192
  };
193
+
194
+ export type TPricingTableItem = PricingTableItem & {
195
+ price: TPrice;
196
+ product: TProduct;
197
+ };
198
+
199
+ export type TPricingTableExpanded = TPricingTable & {
200
+ items: TPricingTableItem[];
201
+ currency: TPaymentCurrency;
202
+ };
@@ -0,0 +1,107 @@
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
+ import type { BrandSettings, PricingTableItem } from './types';
8
+
9
+ const nextId = createIdGenerator('prctbl', 24);
10
+
11
+ // @link https://stripe.com/docs/api/payment_links/payment_links
12
+ export class PricingTable extends Model<InferAttributes<PricingTable>, InferCreationAttributes<PricingTable>> {
13
+ // Unique identifier for the object.
14
+ declare id: CreationOptional<string>;
15
+
16
+ // Whether the PricingTable is currently available for purchase.
17
+ declare active: boolean;
18
+ declare livemode: boolean;
19
+ declare locked: boolean;
20
+
21
+ declare name: string;
22
+
23
+ // The items representing what is being sold. Each line item represents an item being sold. Up to 20 line items are supported.
24
+ declare items: PricingTableItem[];
25
+
26
+ declare branding_settings: BrandSettings;
27
+
28
+ declare metadata?: Record<string, any>;
29
+
30
+ declare created_at: CreationOptional<Date>;
31
+ declare created_via: LiteralUnion<'api' | 'dashboard' | 'portal', string>;
32
+ declare updated_at: CreationOptional<Date>;
33
+
34
+ public static readonly GENESIS_ATTRIBUTES = {
35
+ id: {
36
+ type: DataTypes.STRING(42),
37
+ primaryKey: true,
38
+ allowNull: false,
39
+ defaultValue: nextId,
40
+ },
41
+ active: {
42
+ type: DataTypes.BOOLEAN,
43
+ allowNull: false,
44
+ },
45
+ livemode: {
46
+ type: DataTypes.BOOLEAN,
47
+ allowNull: false,
48
+ },
49
+ locked: {
50
+ type: DataTypes.BOOLEAN,
51
+ allowNull: false,
52
+ },
53
+ name: {
54
+ type: DataTypes.STRING(255),
55
+ allowNull: true,
56
+ },
57
+ items: {
58
+ type: DataTypes.JSON,
59
+ defaultValue: [],
60
+ },
61
+ branding_settings: {
62
+ type: DataTypes.JSON,
63
+ allowNull: false,
64
+ },
65
+ metadata: {
66
+ type: DataTypes.JSON,
67
+ allowNull: true,
68
+ },
69
+ created_at: {
70
+ type: DataTypes.DATE,
71
+ defaultValue: DataTypes.NOW,
72
+ allowNull: false,
73
+ },
74
+ created_via: {
75
+ type: DataTypes.ENUM('api', 'dashboard', 'portal'),
76
+ },
77
+ updated_at: {
78
+ type: DataTypes.DATE,
79
+ defaultValue: DataTypes.NOW,
80
+ allowNull: false,
81
+ },
82
+ };
83
+
84
+ public static initialize(sequelize: any) {
85
+ this.init(PricingTable.GENESIS_ATTRIBUTES, {
86
+ sequelize,
87
+ modelName: 'PricingTable',
88
+ tableName: 'pricing_tables',
89
+ createdAt: 'created_at',
90
+ updatedAt: 'updated_at',
91
+ hooks: {
92
+ afterCreate: (model: PricingTable, options) =>
93
+ createEvent('PricingTable', 'pricing_table.created', model, options).catch(console.error),
94
+ afterUpdate: (model: PricingTable, options) =>
95
+ createEvent('PricingTable', 'pricing_table.updated', model, options).catch(console.error),
96
+ afterDestroy: (model: PricingTable, options) =>
97
+ createEvent('PricingTable', 'pricing_table.deleted', model, options).catch(console.error),
98
+ },
99
+ });
100
+ }
101
+
102
+ public static associate() {
103
+ // Do nothing
104
+ }
105
+ }
106
+
107
+ export type TPricingTable = InferAttributes<PricingTable>;
@@ -262,6 +262,59 @@ export type PaymentDetails = {
262
262
  };
263
263
  };
264
264
 
265
+ // Very similar to PaymentLink
266
+ export type PricingTableItem = {
267
+ price_id: string;
268
+ product_id: string;
269
+
270
+ adjustable_quantity: {
271
+ enabled: boolean;
272
+ maximum: number;
273
+ minimum: number;
274
+ };
275
+
276
+ // The specified behavior after the purchase is complete.
277
+ after_completion?: AfterPayment;
278
+
279
+ // Enables user redeemable promotion codes.
280
+ allow_promotion_codes: boolean;
281
+
282
+ // Configuration for collecting the customer’s billing address.
283
+ billing_address_collection?: LiteralUnion<'auto' | 'required', string>;
284
+
285
+ is_highlight: boolean;
286
+ highlight_text?: LiteralUnion<'deal' | 'popular' | 'recommended', string>;
287
+
288
+ consent_collection?: {
289
+ promotions?: LiteralUnion<'auto' | 'none', string>;
290
+ terms_of_service?: LiteralUnion<'required' | 'none', string>;
291
+ };
292
+
293
+ // Collect additional information from your customer using custom fields. Up to 2 fields are supported.
294
+ custom_fields: CustomField[];
295
+
296
+ // Controls phone number collection settings during checkout.
297
+ phone_number_collection?: {
298
+ enabled: boolean;
299
+ };
300
+
301
+ // Describes the type of transaction being performed in order to customize relevant text
302
+ submit_type: LiteralUnion<'auto' | 'book' | 'donate' | 'pay', string>;
303
+
304
+ // When creating a subscription, the specified configuration data will be used.
305
+ subscription_data?: {
306
+ description: string;
307
+ trial_period_days: number;
308
+ };
309
+ };
310
+
311
+ export type BrandSettings = {
312
+ background_color: string;
313
+ border_style: string;
314
+ button_color: string;
315
+ font_family: string;
316
+ };
317
+
265
318
  export type EventType = LiteralUnion<
266
319
  | 'account.application.authorized'
267
320
  | 'account.application.deauthorized'
package/blocklet.yml CHANGED
@@ -14,7 +14,7 @@ repository:
14
14
  type: git
15
15
  url: git+https://github.com/blocklet/payment-kit.git
16
16
  specVersion: 1.2.8
17
- version: 1.13.24
17
+ version: 1.13.25
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist