payment-kit 1.13.59 → 1.13.61

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.
@@ -33,7 +33,7 @@ export async function ensureStakedForGas() {
33
33
 
34
34
  const result = await client.getForgeState({});
35
35
  const { token, txConfig } = result.state;
36
- const holding = account.tokens.find((x: any) => x.address === token.address);
36
+ const holding = (account?.tokens || []).find((x: any) => x.address === token.address);
37
37
  if (toBN(holding?.value || '0').lte(toBN(txConfig.txGas.minStake))) {
38
38
  logger.info(`not enough balance to stake for gas on chain ${host}`);
39
39
  continue;
@@ -53,3 +53,24 @@ export async function ensureStakedForGas() {
53
53
  }
54
54
  }
55
55
  }
56
+
57
+ export async function hasStakedForGas(method: PaymentMethod, sender = wallet.address) {
58
+ if (method.type === 'arcblock') {
59
+ const client = method.getOcapClient();
60
+ const address = toStakeAddress(sender, sender);
61
+ const { state } = await client.getStakeState({ address });
62
+ return !!state;
63
+ }
64
+
65
+ return true;
66
+ }
67
+
68
+ export async function estimateMaxGasForTx(method: PaymentMethod, typeUrl = 'fg:t:delegate') {
69
+ if (method.type === 'arcblock') {
70
+ const client = method.getOcapClient();
71
+ const { estimate } = await client.estimateGas({ typeUrl });
72
+ return estimate.max;
73
+ }
74
+
75
+ return '0';
76
+ }
@@ -1,6 +1,8 @@
1
+ import { ensureStakedForGas } from '../integrations/blockchain/stake';
1
2
  import { wallet } from '../libs/auth';
2
3
  import dayjs from '../libs/dayjs';
3
4
  import CustomError from '../libs/error';
5
+ import { events } from '../libs/event';
4
6
  import logger from '../libs/logger';
5
7
  import { getGasPayerExtra, isDelegationSufficientForPayment } from '../libs/payment';
6
8
  import createQueue from '../libs/queue';
@@ -246,6 +248,15 @@ export const paymentQueue = createQueue<PaymentJob>({
246
248
  });
247
249
 
248
250
  export const startPaymentQueue = async () => {
251
+ // Do gas stake as soon as possible after payment succeed
252
+ events.on('payment_intent.succeeded', async (paymentIntent: PaymentIntent) => {
253
+ const paymentMethod = await PaymentMethod.findByPk(paymentIntent.payment_method_id);
254
+ if (paymentMethod && paymentMethod.type === 'arcblock') {
255
+ ensureStakedForGas();
256
+ }
257
+ });
258
+
259
+ // Restore previous payments
249
260
  const payments = await PaymentIntent.findAll({
250
261
  where: {
251
262
  status: ['requires_capture', 'processing'],
@@ -20,7 +20,7 @@ const init = (label: string): Logger => {
20
20
  return instance;
21
21
  };
22
22
 
23
- const logger = process.env.NODE_ENV === 'production' ? consoleLogger : init('app');
23
+ const logger = process.env.BLOCKLET_ENV === 'production' ? consoleLogger : init('app');
24
24
 
25
25
  export default logger;
26
26
 
@@ -721,6 +721,7 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
721
721
  await invoice.update({ auto_advance: true, payment_settings: paymentSettings });
722
722
  invoiceQueue.push({ id: invoice.id, job: { invoiceId: invoice.id, retryOnError: false } });
723
723
  } else {
724
+ await paymentIntent.update({ status: 'requires_capture' });
724
725
  paymentQueue.push({
725
726
  id: paymentIntent.id,
726
727
  job: { paymentIntentId: paymentIntent.id, paymentSettings, retryOnError: false },
@@ -7,10 +7,8 @@ import { subscriptionQueue } from '../../jobs/subscription';
7
7
  import type { CallbackArgs } from '../../libs/auth';
8
8
  import { wallet } from '../../libs/auth';
9
9
  import { getGasPayerExtra } from '../../libs/payment';
10
- import { getFastCheckoutAmount } from '../../libs/session';
11
10
  import { getTxMetadata } from '../../libs/util';
12
- import type { TLineItemExpanded } from '../../store/models';
13
- import { ensureSetupIntent, getAuthPrincipalClaim } from './shared';
11
+ import { ensureSetupIntent, getAuthPrincipalClaim, getTokenRequirements } from './shared';
14
12
 
15
13
  export default {
16
14
  action: 'setup',
@@ -32,12 +30,7 @@ export default {
32
30
  }
33
31
 
34
32
  if (paymentMethod.type === 'arcblock') {
35
- const amount = getFastCheckoutAmount(
36
- checkoutSession.line_items as TLineItemExpanded[],
37
- checkoutSession.mode,
38
- paymentCurrency,
39
- !!checkoutSession.subscription_data?.trial_period_days
40
- );
33
+ const tokenRequirements = await getTokenRequirements(checkoutSession, paymentMethod, paymentCurrency);
41
34
 
42
35
  return {
43
36
  signature: {
@@ -57,7 +50,7 @@ export default {
57
50
  },
58
51
  nonce: checkoutSessionId,
59
52
  requirement: {
60
- tokens: amount === '0' ? [] : [{ address: paymentCurrency.contract as string, value: amount }],
53
+ tokens: tokenRequirements,
61
54
  },
62
55
  chainInfo: {
63
56
  host: paymentMethod.settings?.arcblock?.api_host as string,
@@ -1,9 +1,10 @@
1
1
  import { BN } from '@ocap/util';
2
2
 
3
+ import { estimateMaxGasForTx, hasStakedForGas } from '../../integrations/blockchain/stake';
3
4
  import { blocklet } from '../../libs/auth';
4
5
  import dayjs from '../../libs/dayjs';
5
6
  import logger from '../../libs/logger';
6
- import { getStatementDescriptor } from '../../libs/session';
7
+ import { getFastCheckoutAmount, getStatementDescriptor } from '../../libs/session';
7
8
  import type { TLineItemExpanded } from '../../store/models';
8
9
  import { CheckoutSession } from '../../store/models/checkout-session';
9
10
  import { Customer } from '../../store/models/customer';
@@ -450,3 +451,41 @@ export function getAuthPrincipalClaim(method: PaymentMethod, action: string) {
450
451
  chainInfo,
451
452
  };
452
453
  }
454
+
455
+ export async function getTokenRequirements(
456
+ checkoutSession: CheckoutSession,
457
+ paymentMethod: PaymentMethod,
458
+ paymentCurrency: PaymentCurrency
459
+ ) {
460
+ const tokenRequirements = [];
461
+ let amount = getFastCheckoutAmount(
462
+ checkoutSession.line_items as TLineItemExpanded[],
463
+ checkoutSession.mode,
464
+ paymentCurrency,
465
+ !!checkoutSession.subscription_data?.trial_period_days
466
+ );
467
+
468
+ // If the app has not staked, we need to add the gas fee to the amount
469
+ if ((await hasStakedForGas(paymentMethod)) === false) {
470
+ const maxGas = await estimateMaxGasForTx(paymentMethod);
471
+ // pay with the base currency? just add gas to it
472
+ if (paymentCurrency.is_base_currency) {
473
+ amount = new BN(amount).add(new BN(maxGas)).toString();
474
+ tokenRequirements.push({ address: paymentCurrency.contract as string, value: amount });
475
+ } else {
476
+ tokenRequirements.push({ address: paymentCurrency.contract as string, value: amount });
477
+ const baseCurrency = await PaymentCurrency.findOne({
478
+ where: { active: true, is_base_currency: true, payment_method_id: paymentMethod.id },
479
+ });
480
+ if (baseCurrency) {
481
+ tokenRequirements.push({ address: baseCurrency.contract as string, value: maxGas });
482
+ } else {
483
+ throw new Error('Base currency not found since app has not staked for gas');
484
+ }
485
+ }
486
+ } else {
487
+ tokenRequirements.push({ address: paymentCurrency.contract as string, value: amount });
488
+ }
489
+
490
+ return tokenRequirements;
491
+ }
@@ -9,10 +9,8 @@ import { subscriptionQueue } from '../../jobs/subscription';
9
9
  import type { CallbackArgs } from '../../libs/auth';
10
10
  import { wallet } from '../../libs/auth';
11
11
  import { getGasPayerExtra } from '../../libs/payment';
12
- import { getFastCheckoutAmount } from '../../libs/session';
13
12
  import { getTxMetadata } from '../../libs/util';
14
- import type { TLineItemExpanded } from '../../store/models';
15
- import { ensureInvoiceForCheckout, ensurePaymentIntent, getAuthPrincipalClaim } from './shared';
13
+ import { ensureInvoiceForCheckout, ensurePaymentIntent, getAuthPrincipalClaim, getTokenRequirements } from './shared';
16
14
 
17
15
  export default {
18
16
  action: 'subscription',
@@ -34,12 +32,7 @@ export default {
34
32
  }
35
33
 
36
34
  if (paymentMethod.type === 'arcblock') {
37
- const amount = getFastCheckoutAmount(
38
- checkoutSession.line_items as TLineItemExpanded[],
39
- checkoutSession.mode,
40
- paymentCurrency,
41
- !!checkoutSession.subscription_data?.trial_period_days
42
- );
35
+ const tokenRequirements = await getTokenRequirements(checkoutSession, paymentMethod, paymentCurrency);
43
36
 
44
37
  return {
45
38
  signature: {
@@ -59,7 +52,7 @@ export default {
59
52
  },
60
53
  nonce: checkoutSessionId,
61
54
  requirement: {
62
- tokens: amount === '0' ? [] : [{ address: paymentCurrency.contract as string, value: amount }],
55
+ tokens: tokenRequirements,
63
56
  },
64
57
  chainInfo: {
65
58
  host: paymentMethod.settings?.arcblock?.api_host as string,
@@ -1,14 +1,16 @@
1
+ /* eslint-disable no-console */
1
2
  /* eslint-disable no-await-in-loop */
2
3
  import type { QueryInterface } from 'sequelize';
3
4
  import { SequelizeStorage, Umzug } from 'umzug';
4
5
 
6
+ import logger from '../libs/logger';
5
7
  import { sequelize } from './sequelize';
6
8
 
7
9
  const umzug = new Umzug({
8
10
  migrations: { glob: ['migrations/*.{ts,js}', { cwd: __dirname }] },
9
11
  context: sequelize.getQueryInterface(),
10
12
  storage: new SequelizeStorage({ sequelize }),
11
- logger: console,
13
+ logger,
12
14
  });
13
15
 
14
16
  export default function migrate() {
@@ -17,14 +19,18 @@ export default function migrate() {
17
19
 
18
20
  type ColumnChanges = Record<string, { name: string; field: any }[]>;
19
21
  export async function safeApplyColumnChanges(context: QueryInterface, changes: ColumnChanges) {
22
+ console.info('safeApplyColumnChanges', changes);
20
23
  for (const [table, columns] of Object.entries(changes)) {
21
24
  const schema = await context.describeTable(table);
22
25
  for (const { name, field } of columns) {
23
26
  if (!schema[name]) {
24
27
  await context.addColumn(table, name, field);
28
+ console.info('safeApplyColumnChanges.addColumn', { table, name, field });
25
29
  if (field.defaultValue) {
26
30
  await context.bulkUpdate(table, { [name]: field.defaultValue }, {});
27
31
  }
32
+ } else {
33
+ console.info('safeApplyColumnChanges.skip', { table, name, field });
28
34
  }
29
35
  }
30
36
  }
@@ -1,52 +1,52 @@
1
1
  import type { Migration } from '../migrate';
2
2
  import models from '../models';
3
3
 
4
- export const up: Migration = async ({ context: queryInterface }) => {
5
- await queryInterface.createTable('checkout_sessions', models.CheckoutSession.GENESIS_ATTRIBUTES);
6
- await queryInterface.createTable('coupons', models.Coupon.GENESIS_ATTRIBUTES);
7
- await queryInterface.createTable('customers', models.Customer.GENESIS_ATTRIBUTES);
8
- await queryInterface.createTable('discounts', models.Discount.GENESIS_ATTRIBUTES);
9
- await queryInterface.createTable('events', models.Event.GENESIS_ATTRIBUTES);
10
- await queryInterface.createTable('invoices', models.Invoice.GENESIS_ATTRIBUTES);
11
- await queryInterface.createTable('invoice_items', models.InvoiceItem.GENESIS_ATTRIBUTES);
12
- await queryInterface.createTable('jobs', models.Job.GENESIS_ATTRIBUTES);
13
- await queryInterface.createTable('payment_currencies', models.PaymentCurrency.GENESIS_ATTRIBUTES);
14
- await queryInterface.createTable('payment_intents', models.PaymentIntent.GENESIS_ATTRIBUTES);
15
- await queryInterface.createTable('payment_links', models.PaymentLink.GENESIS_ATTRIBUTES);
16
- await queryInterface.createTable('payment_methods', models.PaymentMethod.GENESIS_ATTRIBUTES);
17
- await queryInterface.createTable('prices', models.Price.GENESIS_ATTRIBUTES);
18
- await queryInterface.createTable('products', models.Product.GENESIS_ATTRIBUTES);
19
- await queryInterface.createTable('promotion_codes', models.PromotionCode.GENESIS_ATTRIBUTES);
20
- await queryInterface.createTable('setup_intents', models.SetupIntent.GENESIS_ATTRIBUTES);
21
- await queryInterface.createTable('subscription_items', models.SubscriptionItem.GENESIS_ATTRIBUTES);
22
- await queryInterface.createTable('subscription_schedules', models.SubscriptionSchedule.GENESIS_ATTRIBUTES);
23
- await queryInterface.createTable('subscriptions', models.Subscription.GENESIS_ATTRIBUTES);
24
- await queryInterface.createTable('usage_records', models.UsageRecord.GENESIS_ATTRIBUTES);
25
- await queryInterface.createTable('webhook_attempts', models.WebhookAttempt.GENESIS_ATTRIBUTES);
26
- await queryInterface.createTable('webhook_endpoints', models.WebhookEndpoint.GENESIS_ATTRIBUTES);
4
+ export const up: Migration = async ({ context }) => {
5
+ await context.createTable('checkout_sessions', models.CheckoutSession.GENESIS_ATTRIBUTES);
6
+ await context.createTable('coupons', models.Coupon.GENESIS_ATTRIBUTES);
7
+ await context.createTable('customers', models.Customer.GENESIS_ATTRIBUTES);
8
+ await context.createTable('discounts', models.Discount.GENESIS_ATTRIBUTES);
9
+ await context.createTable('events', models.Event.GENESIS_ATTRIBUTES);
10
+ await context.createTable('invoices', models.Invoice.GENESIS_ATTRIBUTES);
11
+ await context.createTable('invoice_items', models.InvoiceItem.GENESIS_ATTRIBUTES);
12
+ await context.createTable('jobs', models.Job.GENESIS_ATTRIBUTES);
13
+ await context.createTable('payment_currencies', models.PaymentCurrency.GENESIS_ATTRIBUTES);
14
+ await context.createTable('payment_intents', models.PaymentIntent.GENESIS_ATTRIBUTES);
15
+ await context.createTable('payment_links', models.PaymentLink.GENESIS_ATTRIBUTES);
16
+ await context.createTable('payment_methods', models.PaymentMethod.GENESIS_ATTRIBUTES);
17
+ await context.createTable('prices', models.Price.GENESIS_ATTRIBUTES);
18
+ await context.createTable('products', models.Product.GENESIS_ATTRIBUTES);
19
+ await context.createTable('promotion_codes', models.PromotionCode.GENESIS_ATTRIBUTES);
20
+ await context.createTable('setup_intents', models.SetupIntent.GENESIS_ATTRIBUTES);
21
+ await context.createTable('subscription_items', models.SubscriptionItem.GENESIS_ATTRIBUTES);
22
+ await context.createTable('subscription_schedules', models.SubscriptionSchedule.GENESIS_ATTRIBUTES);
23
+ await context.createTable('subscriptions', models.Subscription.GENESIS_ATTRIBUTES);
24
+ await context.createTable('usage_records', models.UsageRecord.GENESIS_ATTRIBUTES);
25
+ await context.createTable('webhook_attempts', models.WebhookAttempt.GENESIS_ATTRIBUTES);
26
+ await context.createTable('webhook_endpoints', models.WebhookEndpoint.GENESIS_ATTRIBUTES);
27
27
  };
28
28
 
29
- export const down: Migration = async ({ context: queryInterface }) => {
30
- await queryInterface.dropTable('checkout_sessions');
31
- await queryInterface.dropTable('coupons');
32
- await queryInterface.dropTable('customers');
33
- await queryInterface.dropTable('discounts');
34
- await queryInterface.dropTable('events');
35
- await queryInterface.dropTable('invoices');
36
- await queryInterface.dropTable('invoice_items');
37
- await queryInterface.dropTable('jobs');
38
- await queryInterface.dropTable('payment_currencies');
39
- await queryInterface.dropTable('payment_intents');
40
- await queryInterface.dropTable('payment_links');
41
- await queryInterface.dropTable('payment_methods');
42
- await queryInterface.dropTable('prices');
43
- await queryInterface.dropTable('products');
44
- await queryInterface.dropTable('promotion_codes');
45
- await queryInterface.dropTable('setup_intents');
46
- await queryInterface.dropTable('subscription_items');
47
- await queryInterface.dropTable('subscription_schedules');
48
- await queryInterface.dropTable('subscriptions');
49
- await queryInterface.dropTable('usage_records');
50
- await queryInterface.dropTable('webhook_endpoints');
51
- await queryInterface.dropTable('webhook_attempts');
29
+ export const down: Migration = async ({ context }) => {
30
+ await context.dropTable('checkout_sessions');
31
+ await context.dropTable('coupons');
32
+ await context.dropTable('customers');
33
+ await context.dropTable('discounts');
34
+ await context.dropTable('events');
35
+ await context.dropTable('invoices');
36
+ await context.dropTable('invoice_items');
37
+ await context.dropTable('jobs');
38
+ await context.dropTable('payment_currencies');
39
+ await context.dropTable('payment_intents');
40
+ await context.dropTable('payment_links');
41
+ await context.dropTable('payment_methods');
42
+ await context.dropTable('prices');
43
+ await context.dropTable('products');
44
+ await context.dropTable('promotion_codes');
45
+ await context.dropTable('setup_intents');
46
+ await context.dropTable('subscription_items');
47
+ await context.dropTable('subscription_schedules');
48
+ await context.dropTable('subscriptions');
49
+ await context.dropTable('usage_records');
50
+ await context.dropTable('webhook_endpoints');
51
+ await context.dropTable('webhook_attempts');
52
52
  };
@@ -158,8 +158,8 @@ const paymentMethods = [
158
158
  },
159
159
  ];
160
160
 
161
- export const up: Migration = async ({ context: queryInterface }) => {
162
- await queryInterface.bulkInsert(
161
+ export const up: Migration = async ({ context }) => {
162
+ await context.bulkInsert(
163
163
  'payment_methods',
164
164
  paymentMethods,
165
165
  {},
@@ -170,7 +170,7 @@ export const up: Migration = async ({ context: queryInterface }) => {
170
170
  metadata: { type: new Sequelize.JSON() },
171
171
  }
172
172
  );
173
- await queryInterface.bulkInsert(
173
+ await context.bulkInsert(
174
174
  'payment_currencies',
175
175
  paymentCurrencies,
176
176
  {},
@@ -180,7 +180,7 @@ export const up: Migration = async ({ context: queryInterface }) => {
180
180
  );
181
181
  };
182
182
 
183
- export const down: Migration = async ({ context: queryInterface }) => {
184
- await queryInterface.bulkDelete('payment_methods', {});
185
- await queryInterface.bulkDelete('payment_currencies', {});
183
+ export const down: Migration = async ({ context }) => {
184
+ await context.bulkDelete('payment_methods', {});
185
+ await context.bulkDelete('payment_currencies', {});
186
186
  };
@@ -1,10 +1,10 @@
1
1
  import type { Migration } from '../migrate';
2
2
  import models from '../models';
3
3
 
4
- export const up: Migration = async ({ context: queryInterface }) => {
5
- await queryInterface.createTable('pricing_tables', models.PricingTable.GENESIS_ATTRIBUTES);
4
+ export const up: Migration = async ({ context }) => {
5
+ await context.createTable('pricing_tables', models.PricingTable.GENESIS_ATTRIBUTES);
6
6
  };
7
7
 
8
- export const down: Migration = async ({ context: queryInterface }) => {
9
- await queryInterface.dropTable('pricing_tables');
8
+ export const down: Migration = async ({ context }) => {
9
+ await context.dropTable('pricing_tables');
10
10
  };
@@ -9,7 +9,6 @@ export const up: Migration = async ({ context }) => {
9
9
  checkout_sessions: [
10
10
  { name: 'nft_mint_settings', field: { type: DataTypes.JSON, allowNull: true } },
11
11
  { name: 'payment_intent_data', field: { type: DataTypes.JSON, allowNull: true } },
12
- { name: 'nft_mint_settings', field: { type: DataTypes.JSON, allowNull: true } },
13
12
  { name: 'nft_mint_details', field: { type: DataTypes.JSON, allowNull: true } },
14
13
  {
15
14
  name: 'nft_mint_status',
@@ -10,6 +10,6 @@ import env from '../libs/env';
10
10
  // eslint-disable-next-line import/prefer-default-export
11
11
  export const sequelize = new Sequelize({
12
12
  dialect: 'sqlite',
13
- logging: false,
13
+ logging: process.env.SQL_LOG === '1',
14
14
  storage: join(env.dataDir, 'payment-kit.db'),
15
15
  });
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.59
17
+ version: 1.13.61
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
@@ -57,7 +57,6 @@ screenshots:
57
57
  - 5-admin-4.png
58
58
  components:
59
59
  - name: image-bin
60
- required: true
61
60
  source:
62
61
  store: https://test.store.blocklet.dev
63
62
  name: image-bin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.13.59",
3
+ "version": "1.13.61",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev",
6
6
  "eject": "vite eject",
@@ -104,7 +104,7 @@
104
104
  "@abtnode/types": "1.16.18",
105
105
  "@arcblock/eslint-config": "^0.2.4",
106
106
  "@arcblock/eslint-config-ts": "^0.2.4",
107
- "@did-pay/types": "1.13.59",
107
+ "@did-pay/types": "1.13.61",
108
108
  "@types/cookie-parser": "^1.4.6",
109
109
  "@types/cors": "^2.8.17",
110
110
  "@types/dotenv-flow": "^3.3.3",
@@ -141,5 +141,5 @@
141
141
  "parser": "typescript"
142
142
  }
143
143
  },
144
- "gitHead": "b07b1b9616fd429159569c18d9971cc654037da7"
144
+ "gitHead": "d765c5289d482e4265d25b82db551c695eada1e0"
145
145
  }