payment-kit 1.13.174 → 1.13.176
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/src/crons/index.ts +20 -1
- package/api/src/integrations/blockchain/stake.ts +99 -1
- package/api/src/integrations/stripe/handlers/invoice.ts +4 -0
- package/api/src/integrations/stripe/handlers/payment-intent.ts +1 -1
- package/api/src/integrations/stripe/resource.ts +63 -12
- package/api/src/libs/audit.ts +3 -3
- package/api/src/libs/env.ts +2 -0
- package/api/src/libs/notification/template/subscription-will-renew.ts +1 -1
- package/api/src/libs/subscription.ts +35 -2
- package/api/src/queues/checkout-session.ts +98 -94
- package/api/src/queues/invoice.ts +23 -13
- package/api/src/queues/notification.ts +13 -11
- package/api/src/queues/payment.ts +27 -21
- package/api/src/queues/refund.ts +5 -5
- package/api/src/queues/subscription.ts +210 -49
- package/api/src/routes/checkout-sessions.ts +8 -40
- package/api/src/routes/connect/change-payment.ts +52 -38
- package/api/src/routes/connect/change-plan.ts +51 -39
- package/api/src/routes/connect/collect-batch.ts +1 -0
- package/api/src/routes/connect/collect.ts +2 -1
- package/api/src/routes/connect/pay.ts +1 -0
- package/api/src/routes/connect/setup.ts +70 -56
- package/api/src/routes/connect/shared.ts +162 -17
- package/api/src/routes/connect/subscribe.ts +60 -54
- package/api/src/routes/invoices.ts +5 -0
- package/api/src/routes/payment-intents.ts +6 -2
- package/api/src/store/models/subscription.ts +1 -6
- package/api/src/store/models/types.ts +5 -1
- package/api/tests/libs/subscription.spec.ts +85 -3
- package/blocklet.yml +1 -1
- package/package.json +4 -4
- package/src/app.tsx +2 -1
- package/src/components/customer/link.tsx +22 -8
- package/src/components/event/list.tsx +1 -3
- package/src/pages/admin/billing/subscriptions/detail.tsx +12 -1
- package/src/pages/admin/payments/intents/detail.tsx +1 -1
- package/src/pages/admin/products/products/index.tsx +3 -0
- package/src/pages/customer/invoice/detail.tsx +7 -3
- package/src/pages/customer/subscription/detail.tsx +14 -5
- /package/api/src/libs/notification/template/{subscription-cacceled.ts → subscription-canceled.ts} +0 -0
|
@@ -41,7 +41,6 @@ import {
|
|
|
41
41
|
import { CHECKOUT_SESSION_TTL, createCodeGenerator, formatMetadata, getDataObjectFromQuery } from '../libs/util';
|
|
42
42
|
import { invoiceQueue } from '../queues/invoice';
|
|
43
43
|
import { paymentQueue } from '../queues/payment';
|
|
44
|
-
import { addSubscriptionJob } from '../queues/subscription';
|
|
45
44
|
import type { TPriceExpanded, TProductExpanded } from '../store/models';
|
|
46
45
|
import { CheckoutSession } from '../store/models/checkout-session';
|
|
47
46
|
import { Customer } from '../store/models/customer';
|
|
@@ -54,7 +53,7 @@ import { Product } from '../store/models/product';
|
|
|
54
53
|
import { SetupIntent } from '../store/models/setup-intent';
|
|
55
54
|
import { Subscription } from '../store/models/subscription';
|
|
56
55
|
import { SubscriptionItem } from '../store/models/subscription-item';
|
|
57
|
-
import { ensureInvoiceForCheckout
|
|
56
|
+
import { ensureInvoiceForCheckout } from './connect/shared';
|
|
58
57
|
|
|
59
58
|
const getInvoicePrefix = createCodeGenerator('', 8);
|
|
60
59
|
|
|
@@ -464,6 +463,7 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
464
463
|
const lineItems = await Price.expand(checkoutSession.line_items, { product: true, upsell: true });
|
|
465
464
|
const trialInDays = Number(checkoutSession.subscription_data?.trial_period_days || 0);
|
|
466
465
|
const trialEnds = Number(checkoutSession.subscription_data?.trial_end || 0);
|
|
466
|
+
const billingThreshold = Number(checkoutSession.subscription_data?.billing_threshold_amount || 0);
|
|
467
467
|
const amount = getCheckoutAmount(lineItems, paymentCurrency.id, !!trialInDays);
|
|
468
468
|
await checkoutSession.update({
|
|
469
469
|
amount_subtotal: amount.subtotal,
|
|
@@ -675,8 +675,8 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
675
675
|
},
|
|
676
676
|
},
|
|
677
677
|
// @ts-ignore
|
|
678
|
-
billing_thresholds:
|
|
679
|
-
? { amount_gte:
|
|
678
|
+
billing_thresholds: billingThreshold
|
|
679
|
+
? { amount_gte: billingThreshold, reset_billing_cycle_anchor: false } // prettier-ignore
|
|
680
680
|
: null,
|
|
681
681
|
pending_invoice_item_interval: setup.recurring,
|
|
682
682
|
pending_setup_intent: setupIntent?.id,
|
|
@@ -688,9 +688,9 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
688
688
|
formatSubscriptionProduct(lineItems.filter((x) => x.price.type === 'recurring')),
|
|
689
689
|
proration_behavior: checkoutSession.subscription_data?.proration_behavior || 'none',
|
|
690
690
|
payment_behavior: 'default_incomplete',
|
|
691
|
-
days_until_due: checkoutSession.subscription_data?.days_until_due
|
|
691
|
+
days_until_due: checkoutSession.subscription_data?.days_until_due ?? checkoutSession.metadata?.days_until_due,
|
|
692
692
|
days_until_cancel:
|
|
693
|
-
checkoutSession.subscription_data?.days_until_cancel
|
|
693
|
+
checkoutSession.subscription_data?.days_until_cancel ?? checkoutSession.metadata?.days_until_cancel,
|
|
694
694
|
metadata: checkoutSession.metadata as any,
|
|
695
695
|
});
|
|
696
696
|
|
|
@@ -775,38 +775,6 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
775
775
|
}
|
|
776
776
|
}
|
|
777
777
|
|
|
778
|
-
// all subscription payments are done after delegation
|
|
779
|
-
if (checkoutSession.mode === 'subscription' && subscription) {
|
|
780
|
-
if (
|
|
781
|
-
delegation.sufficient || // we can pay from delegation
|
|
782
|
-
(balance.sufficient && ['NO_TOKEN', 'NO_ENOUGH_TOKEN'].includes(delegation.reason as string)) // we can pay from balance
|
|
783
|
-
) {
|
|
784
|
-
await subscription.update({ payment_settings: paymentSettings });
|
|
785
|
-
|
|
786
|
-
const { invoice } = await ensureInvoiceForCheckout({ checkoutSession, customer, subscription });
|
|
787
|
-
if (invoice) {
|
|
788
|
-
invoiceQueue.push({ id: invoice.id, job: { invoiceId: invoice.id, retryOnError: false } });
|
|
789
|
-
}
|
|
790
|
-
addSubscriptionJob(subscription, 'cycle', false, subscription.trial_end);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
if (checkoutSession.mode === 'setup' && setupIntent && subscription) {
|
|
795
|
-
if (delegation.sufficient) {
|
|
796
|
-
const { invoice } = await ensureSetupIntent(checkoutSession.id, customer.did);
|
|
797
|
-
if (invoice) {
|
|
798
|
-
await invoiceQueue.pushAndWait({ id: invoice.id, job: { invoiceId: invoice.id, retryOnError: false } });
|
|
799
|
-
}
|
|
800
|
-
await setupIntent.update({ status: 'succeeded', ...paymentSettings });
|
|
801
|
-
await subscription.update({
|
|
802
|
-
status: subscription.trial_end ? 'trialing' : 'active',
|
|
803
|
-
payment_settings: paymentSettings,
|
|
804
|
-
});
|
|
805
|
-
await checkoutSession.update({ status: 'complete', payment_status: 'no_payment_required' });
|
|
806
|
-
logger.info(`CheckoutSession ${checkoutSession.id} updated on setup done ${setupIntent.id}`);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
|
|
810
778
|
let stripeContext: any = null;
|
|
811
779
|
if (paymentMethod.type === 'stripe') {
|
|
812
780
|
if (paymentIntent) {
|
|
@@ -858,8 +826,8 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
858
826
|
subscription,
|
|
859
827
|
checkoutSession,
|
|
860
828
|
customer,
|
|
861
|
-
delegation,
|
|
862
|
-
balance,
|
|
829
|
+
delegation: checkoutSession.mode === 'payment' ? delegation : null,
|
|
830
|
+
balance: checkoutSession.mode === 'payment' ? balance : null,
|
|
863
831
|
});
|
|
864
832
|
} catch (err) {
|
|
865
833
|
console.error(err);
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { toTypeInfo } from '@arcblock/did';
|
|
2
|
-
import type { Transaction } from '@ocap/client';
|
|
3
|
-
import { fromPublicKey } from '@ocap/wallet';
|
|
4
|
-
|
|
5
1
|
import type { CallbackArgs } from '../../libs/auth';
|
|
6
|
-
import {
|
|
2
|
+
import { isDelegationSufficientForPayment } from '../../libs/payment';
|
|
3
|
+
import { getFastCheckoutAmount } from '../../libs/session';
|
|
7
4
|
import { getTxMetadata } from '../../libs/util';
|
|
8
5
|
import type { TLineItemExpanded } from '../../store/models';
|
|
9
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
ensureChangePaymentContext,
|
|
8
|
+
executeOcapTransactions,
|
|
9
|
+
getAuthPrincipalClaim,
|
|
10
|
+
getDelegationTxClaim,
|
|
11
|
+
getStakeTxClaim,
|
|
12
|
+
} from './shared';
|
|
10
13
|
|
|
11
14
|
export default {
|
|
12
15
|
action: 'change-payment',
|
|
@@ -19,25 +22,56 @@ export default {
|
|
|
19
22
|
},
|
|
20
23
|
onConnect: async ({ userDid, userPk, extraParams }: CallbackArgs) => {
|
|
21
24
|
const { subscriptionId } = extraParams;
|
|
22
|
-
const { subscription, paymentMethod, paymentCurrency } = await ensureChangePaymentContext(subscriptionId);
|
|
25
|
+
const { subscription, customer, paymentMethod, paymentCurrency } = await ensureChangePaymentContext(subscriptionId);
|
|
23
26
|
|
|
24
27
|
if (paymentMethod.type === 'arcblock') {
|
|
28
|
+
const claims: { [type: string]: [string, object] } = {};
|
|
29
|
+
|
|
25
30
|
// @ts-ignore
|
|
26
31
|
const items = subscription!.items as TLineItemExpanded[];
|
|
32
|
+
const trialInDays = 0;
|
|
33
|
+
const billingThreshold = Number(subscription.billing_thresholds?.amount_gte || 0);
|
|
34
|
+
const fastCheckoutAmount = getFastCheckoutAmount(items, 'setup', paymentCurrency.id, !!trialInDays);
|
|
35
|
+
const delegation = await isDelegationSufficientForPayment({
|
|
36
|
+
paymentMethod,
|
|
37
|
+
paymentCurrency,
|
|
38
|
+
userDid: customer!.did,
|
|
39
|
+
amount: fastCheckoutAmount,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// if we can complete purchase without any wallet interaction
|
|
43
|
+
if (delegation.sufficient === false) {
|
|
44
|
+
claims.delegation = [
|
|
45
|
+
'signature',
|
|
46
|
+
await getDelegationTxClaim({
|
|
47
|
+
mode: 'setup',
|
|
48
|
+
userDid,
|
|
49
|
+
userPk,
|
|
50
|
+
nonce: `change-method-${subscription.id}`,
|
|
51
|
+
data: getTxMetadata({ subscriptionId: subscription.id }),
|
|
52
|
+
paymentCurrency,
|
|
53
|
+
paymentMethod,
|
|
54
|
+
trialInDays,
|
|
55
|
+
billingThreshold,
|
|
56
|
+
items,
|
|
57
|
+
}),
|
|
58
|
+
];
|
|
59
|
+
}
|
|
27
60
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
61
|
+
// we always need to stake for the subscription
|
|
62
|
+
claims.staking = [
|
|
63
|
+
'prepareTx',
|
|
64
|
+
await getStakeTxClaim({
|
|
31
65
|
userDid,
|
|
32
66
|
userPk,
|
|
33
|
-
nonce: subscription!.id,
|
|
34
|
-
data: getTxMetadata({ subscriptionId: subscription!.id }),
|
|
35
67
|
paymentCurrency,
|
|
36
68
|
paymentMethod,
|
|
37
|
-
trial: false,
|
|
38
69
|
items,
|
|
70
|
+
subscription,
|
|
39
71
|
}),
|
|
40
|
-
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
return claims;
|
|
41
75
|
}
|
|
42
76
|
|
|
43
77
|
throw new Error(`Payment method ${paymentMethod.type} not supported`);
|
|
@@ -52,27 +86,14 @@ export default {
|
|
|
52
86
|
if (paymentMethod.type === 'arcblock') {
|
|
53
87
|
await subscription?.update({
|
|
54
88
|
payment_settings: {
|
|
55
|
-
payment_method_types: [
|
|
89
|
+
payment_method_types: [paymentMethod.type],
|
|
56
90
|
payment_method_options: {
|
|
57
91
|
arcblock: { payer: userDid },
|
|
58
92
|
},
|
|
59
93
|
},
|
|
60
94
|
});
|
|
61
95
|
|
|
62
|
-
const
|
|
63
|
-
const claim = claims.find((x) => x.type === 'signature');
|
|
64
|
-
|
|
65
|
-
// execute the delegate tx
|
|
66
|
-
const tx: Partial<Transaction> = client.decodeTx(claim.origin);
|
|
67
|
-
tx.signature = claim.sig;
|
|
68
|
-
|
|
69
|
-
// @ts-ignore
|
|
70
|
-
const { buffer } = await client.encodeDelegateTx({ tx });
|
|
71
|
-
const txHash = await client.sendDelegateTx(
|
|
72
|
-
// @ts-ignore
|
|
73
|
-
{ tx, wallet: fromPublicKey(userPk, toTypeInfo(userDid)) },
|
|
74
|
-
getGasPayerExtra(buffer)
|
|
75
|
-
);
|
|
96
|
+
const paymentDetails = await executeOcapTransactions(userDid, userPk, claims, paymentMethod);
|
|
76
97
|
|
|
77
98
|
await setupIntent.update({
|
|
78
99
|
status: 'succeeded',
|
|
@@ -82,23 +103,16 @@ export default {
|
|
|
82
103
|
arcblock: { payer: userDid },
|
|
83
104
|
},
|
|
84
105
|
setup_details: {
|
|
85
|
-
arcblock:
|
|
86
|
-
tx_hash: txHash,
|
|
87
|
-
payer: userDid,
|
|
88
|
-
},
|
|
106
|
+
arcblock: paymentDetails,
|
|
89
107
|
},
|
|
90
108
|
});
|
|
91
109
|
|
|
92
110
|
await subscription?.update({
|
|
93
111
|
currency_id: paymentCurrency.id,
|
|
94
112
|
default_payment_method_id: paymentMethod.id,
|
|
95
|
-
payment_settings: {
|
|
96
|
-
payment_method_types: [paymentMethod.type],
|
|
97
|
-
payment_method_options: {},
|
|
98
|
-
},
|
|
99
113
|
});
|
|
100
114
|
|
|
101
|
-
return { hash:
|
|
115
|
+
return { hash: paymentDetails.tx_hash };
|
|
102
116
|
}
|
|
103
117
|
|
|
104
118
|
throw new Error(`Payment method ${paymentMethod.type} not supported`);
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { toTypeInfo } from '@arcblock/did';
|
|
2
|
-
import type { Transaction } from '@ocap/client';
|
|
3
|
-
import { fromPublicKey } from '@ocap/wallet';
|
|
4
|
-
|
|
5
1
|
import type { CallbackArgs } from '../../libs/auth';
|
|
6
|
-
import {
|
|
2
|
+
import { isDelegationSufficientForPayment } from '../../libs/payment';
|
|
3
|
+
import { getFastCheckoutAmount } from '../../libs/session';
|
|
7
4
|
import { getTxMetadata } from '../../libs/util';
|
|
8
5
|
import { invoiceQueue } from '../../queues/invoice';
|
|
9
6
|
import { addSubscriptionJob } from '../../queues/subscription';
|
|
10
7
|
import type { TLineItemExpanded } from '../../store/models';
|
|
11
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
ensureSubscription,
|
|
10
|
+
executeOcapTransactions,
|
|
11
|
+
getAuthPrincipalClaim,
|
|
12
|
+
getDelegationTxClaim,
|
|
13
|
+
getStakeTxClaim,
|
|
14
|
+
} from './shared';
|
|
12
15
|
|
|
13
16
|
export default {
|
|
14
17
|
action: 'change-plan',
|
|
@@ -21,25 +24,56 @@ export default {
|
|
|
21
24
|
},
|
|
22
25
|
onConnect: async ({ userDid, userPk, extraParams }: CallbackArgs) => {
|
|
23
26
|
const { subscriptionId } = extraParams;
|
|
24
|
-
const { paymentMethod, paymentCurrency, subscription } = await ensureSubscription(subscriptionId);
|
|
27
|
+
const { paymentMethod, paymentCurrency, subscription, customer } = await ensureSubscription(subscriptionId);
|
|
25
28
|
|
|
26
29
|
if (paymentMethod.type === 'arcblock') {
|
|
30
|
+
const claims: { [type: string]: [string, object] } = {};
|
|
31
|
+
|
|
27
32
|
// @ts-ignore
|
|
28
33
|
const items = subscription!.items as TLineItemExpanded[];
|
|
34
|
+
const trialInDays = 0;
|
|
35
|
+
const billingThreshold = Number(subscription!.billing_thresholds?.amount_gte || 0);
|
|
36
|
+
const fastCheckoutAmount = getFastCheckoutAmount(items, 'subscription', paymentCurrency.id, !!trialInDays);
|
|
37
|
+
const delegation = await isDelegationSufficientForPayment({
|
|
38
|
+
paymentMethod,
|
|
39
|
+
paymentCurrency,
|
|
40
|
+
userDid: customer!.did,
|
|
41
|
+
amount: fastCheckoutAmount,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// if we can complete purchase without any wallet interaction
|
|
45
|
+
if (delegation.sufficient === false) {
|
|
46
|
+
claims.delegation = [
|
|
47
|
+
'signature',
|
|
48
|
+
await getDelegationTxClaim({
|
|
49
|
+
mode: 'subscription',
|
|
50
|
+
userDid,
|
|
51
|
+
userPk,
|
|
52
|
+
nonce: `change-plan-${subscription!.id}`,
|
|
53
|
+
data: getTxMetadata({ subscriptionId: subscription!.id }),
|
|
54
|
+
paymentCurrency,
|
|
55
|
+
paymentMethod,
|
|
56
|
+
trialInDays,
|
|
57
|
+
billingThreshold,
|
|
58
|
+
items,
|
|
59
|
+
}),
|
|
60
|
+
];
|
|
61
|
+
}
|
|
29
62
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
63
|
+
// we always need to stake for the subscription
|
|
64
|
+
claims.staking = [
|
|
65
|
+
'prepareTx',
|
|
66
|
+
await getStakeTxClaim({
|
|
33
67
|
userDid,
|
|
34
68
|
userPk,
|
|
35
|
-
nonce: subscription!.id,
|
|
36
|
-
data: getTxMetadata({ subscriptionId: subscription!.id }),
|
|
37
69
|
paymentCurrency,
|
|
38
70
|
paymentMethod,
|
|
39
|
-
trial: false,
|
|
40
71
|
items,
|
|
72
|
+
subscription: subscription!,
|
|
41
73
|
}),
|
|
42
|
-
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
return claims;
|
|
43
77
|
}
|
|
44
78
|
|
|
45
79
|
throw new Error(`Payment method ${paymentMethod.type} not supported`);
|
|
@@ -59,35 +93,13 @@ export default {
|
|
|
59
93
|
},
|
|
60
94
|
});
|
|
61
95
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// execute the delegate tx
|
|
66
|
-
const tx: Partial<Transaction> = client.decodeTx(claim.origin);
|
|
67
|
-
tx.signature = claim.sig;
|
|
68
|
-
|
|
69
|
-
// @ts-ignore
|
|
70
|
-
const { buffer } = await client.encodeDelegateTx({ tx });
|
|
71
|
-
const txHash = await client.sendDelegateTx(
|
|
72
|
-
// @ts-ignore
|
|
73
|
-
{ tx, wallet: fromPublicKey(userPk, toTypeInfo(userDid)) },
|
|
74
|
-
getGasPayerExtra(buffer)
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
await subscription?.update({
|
|
78
|
-
payment_details: {
|
|
79
|
-
arcblock: {
|
|
80
|
-
tx_hash: txHash,
|
|
81
|
-
payer: userDid,
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
});
|
|
96
|
+
const paymentDetails = await executeOcapTransactions(userDid, userPk, claims, paymentMethod);
|
|
97
|
+
await subscription?.update({ payment_details: { arcblock: paymentDetails } });
|
|
85
98
|
|
|
86
99
|
if (invoice) {
|
|
87
100
|
if (invoice.status === 'uncollectible') {
|
|
88
101
|
await invoice.update({ status: 'open' });
|
|
89
102
|
}
|
|
90
|
-
|
|
91
103
|
await invoiceQueue.pushAndWait({
|
|
92
104
|
id: invoice.id,
|
|
93
105
|
job: { invoiceId: invoice.id, retryOnError: false, waitForPayment: true },
|
|
@@ -97,7 +109,7 @@ export default {
|
|
|
97
109
|
await addSubscriptionJob(subscription, 'cycle', false, subscription.trial_end);
|
|
98
110
|
}
|
|
99
111
|
|
|
100
|
-
return { hash:
|
|
112
|
+
return { hash: paymentDetails.tx_hash };
|
|
101
113
|
}
|
|
102
114
|
|
|
103
115
|
throw new Error(`Payment method ${paymentMethod.type} not supported`);
|
|
@@ -58,7 +58,7 @@ export default {
|
|
|
58
58
|
userPk,
|
|
59
59
|
nonce: invoice.id,
|
|
60
60
|
data: getTxMetadata({ subscriptionId: subscription!.id, invoiceId: invoice.id }),
|
|
61
|
-
|
|
61
|
+
trialInDays: 0,
|
|
62
62
|
paymentCurrency,
|
|
63
63
|
paymentMethod,
|
|
64
64
|
items,
|
|
@@ -102,6 +102,7 @@ export default {
|
|
|
102
102
|
arcblock: {
|
|
103
103
|
tx_hash: txHash,
|
|
104
104
|
payer: userDid,
|
|
105
|
+
type: 'transfer',
|
|
105
106
|
},
|
|
106
107
|
},
|
|
107
108
|
});
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { toTypeInfo } from '@arcblock/did';
|
|
2
|
-
import type { Transaction } from '@ocap/client';
|
|
3
|
-
import { fromPublicKey } from '@ocap/wallet';
|
|
4
|
-
|
|
5
1
|
import type { CallbackArgs } from '../../libs/auth';
|
|
6
|
-
import
|
|
2
|
+
import logger from '../../libs/logger';
|
|
3
|
+
import { isDelegationSufficientForPayment } from '../../libs/payment';
|
|
4
|
+
import { getFastCheckoutAmount } from '../../libs/session';
|
|
7
5
|
import { getTxMetadata } from '../../libs/util';
|
|
8
6
|
import { invoiceQueue } from '../../queues/invoice';
|
|
9
7
|
import { addSubscriptionJob } from '../../queues/subscription';
|
|
10
8
|
import type { TLineItemExpanded } from '../../store/models';
|
|
11
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
ensureSetupIntent,
|
|
11
|
+
executeOcapTransactions,
|
|
12
|
+
getAuthPrincipalClaim,
|
|
13
|
+
getDelegationTxClaim,
|
|
14
|
+
getStakeTxClaim,
|
|
15
|
+
} from './shared';
|
|
12
16
|
|
|
13
17
|
export default {
|
|
14
18
|
action: 'setup',
|
|
@@ -21,7 +25,7 @@ export default {
|
|
|
21
25
|
},
|
|
22
26
|
onConnect: async ({ userDid, userPk, extraParams }: CallbackArgs) => {
|
|
23
27
|
const { checkoutSessionId } = extraParams;
|
|
24
|
-
const { paymentMethod, paymentCurrency, checkoutSession, subscription } = await ensureSetupIntent(
|
|
28
|
+
const { paymentMethod, paymentCurrency, checkoutSession, subscription, customer } = await ensureSetupIntent(
|
|
25
29
|
checkoutSessionId,
|
|
26
30
|
userDid
|
|
27
31
|
);
|
|
@@ -30,20 +34,51 @@ export default {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
if (paymentMethod.type === 'arcblock') {
|
|
37
|
+
const claims: { [type: string]: [string, object] } = {};
|
|
38
|
+
|
|
39
|
+
// if we can complete purchase without any wallet interaction
|
|
33
40
|
const items = checkoutSession.line_items as TLineItemExpanded[];
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
const trialInDays = Number(checkoutSession.subscription_data?.trial_period_days || 0);
|
|
42
|
+
const billingThreshold = Number(checkoutSession.subscription_data?.billing_threshold_amount || 0);
|
|
43
|
+
const fastCheckoutAmount = getFastCheckoutAmount(items, checkoutSession.mode, paymentCurrency.id, !!trialInDays);
|
|
44
|
+
const delegation = await isDelegationSufficientForPayment({
|
|
45
|
+
paymentMethod,
|
|
46
|
+
paymentCurrency,
|
|
47
|
+
userDid: customer.did,
|
|
48
|
+
amount: fastCheckoutAmount,
|
|
49
|
+
});
|
|
50
|
+
if (delegation.sufficient === false) {
|
|
51
|
+
claims.delegation = [
|
|
52
|
+
'signature',
|
|
53
|
+
await getDelegationTxClaim({
|
|
54
|
+
mode: checkoutSession.mode,
|
|
55
|
+
userDid,
|
|
56
|
+
userPk,
|
|
57
|
+
nonce: checkoutSession.id,
|
|
58
|
+
data: getTxMetadata({ subscriptionId: subscription.id, checkoutSessionId }),
|
|
59
|
+
paymentCurrency,
|
|
60
|
+
paymentMethod,
|
|
61
|
+
trialInDays,
|
|
62
|
+
billingThreshold,
|
|
63
|
+
items,
|
|
64
|
+
}),
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// we always need to stake for the subscription
|
|
69
|
+
claims.staking = [
|
|
70
|
+
'prepareTx',
|
|
71
|
+
await getStakeTxClaim({
|
|
37
72
|
userDid,
|
|
38
73
|
userPk,
|
|
39
|
-
nonce: checkoutSession.id,
|
|
40
|
-
data: getTxMetadata({ subscriptionId: subscription.id, checkoutSessionId }),
|
|
41
74
|
paymentCurrency,
|
|
42
75
|
paymentMethod,
|
|
43
|
-
trial: !!checkoutSession.subscription_data?.trial_period_days,
|
|
44
76
|
items,
|
|
77
|
+
subscription,
|
|
45
78
|
}),
|
|
46
|
-
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
return claims;
|
|
47
82
|
}
|
|
48
83
|
|
|
49
84
|
throw new Error(`Payment method ${paymentMethod.type} not supported`);
|
|
@@ -60,63 +95,42 @@ export default {
|
|
|
60
95
|
}
|
|
61
96
|
|
|
62
97
|
if (paymentMethod.type === 'arcblock') {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
payment_method_options: {
|
|
68
|
-
arcblock: { payer: userDid },
|
|
69
|
-
},
|
|
98
|
+
const paymentSettings = {
|
|
99
|
+
payment_method_types: ['arcblock'],
|
|
100
|
+
payment_method_options: {
|
|
101
|
+
arcblock: { payer: userDid },
|
|
70
102
|
},
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const tx: Partial<Transaction> = client.decodeTx(claim.origin);
|
|
78
|
-
tx.signature = claim.sig;
|
|
79
|
-
|
|
80
|
-
// @ts-ignore
|
|
81
|
-
const { buffer } = await client.encodeDelegateTx({ tx });
|
|
82
|
-
const txHash = await client.sendDelegateTx(
|
|
83
|
-
// @ts-ignore
|
|
84
|
-
{ tx, wallet: fromPublicKey(userPk, toTypeInfo(userDid)) },
|
|
85
|
-
getGasPayerExtra(buffer)
|
|
86
|
-
);
|
|
103
|
+
};
|
|
104
|
+
await setupIntent.update({ status: 'processing' });
|
|
105
|
+
await subscription.update({ payment_settings: paymentSettings });
|
|
106
|
+
if (invoice) {
|
|
107
|
+
await invoice.update({ payment_settings: paymentSettings });
|
|
108
|
+
}
|
|
87
109
|
|
|
110
|
+
const paymentDetails = await executeOcapTransactions(userDid, userPk, claims, paymentMethod);
|
|
88
111
|
await setupIntent.update({
|
|
89
112
|
status: 'succeeded',
|
|
90
113
|
last_setup_error: null,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
arcblock: { payer: userDid },
|
|
94
|
-
},
|
|
95
|
-
setup_details: {
|
|
96
|
-
arcblock: {
|
|
97
|
-
tx_hash: txHash,
|
|
98
|
-
payer: userDid,
|
|
99
|
-
},
|
|
100
|
-
},
|
|
114
|
+
setup_details: { arcblock: paymentDetails },
|
|
115
|
+
...paymentSettings,
|
|
101
116
|
});
|
|
102
|
-
|
|
103
117
|
await subscription.update({
|
|
104
118
|
status: subscription.trial_end ? 'trialing' : 'active',
|
|
105
|
-
payment_details: {
|
|
106
|
-
arcblock: {
|
|
107
|
-
tx_hash: txHash,
|
|
108
|
-
payer: userDid,
|
|
109
|
-
},
|
|
110
|
-
},
|
|
119
|
+
payment_details: { arcblock: paymentDetails },
|
|
111
120
|
});
|
|
112
121
|
|
|
113
122
|
await checkoutSession.update({ status: 'complete', payment_status: 'paid' });
|
|
114
123
|
if (invoice) {
|
|
115
|
-
invoiceQueue.
|
|
124
|
+
invoiceQueue.pushAndWait({ id: invoice.id, job: { invoiceId: invoice.id, retryOnError: false } });
|
|
116
125
|
}
|
|
117
126
|
await addSubscriptionJob(subscription, 'cycle', false, subscription.trial_end);
|
|
127
|
+
logger.info('CheckoutSession updated on setup done', {
|
|
128
|
+
checkoutSession: checkoutSession.id,
|
|
129
|
+
setupIntent: setupIntent.id,
|
|
130
|
+
paymentDetails,
|
|
131
|
+
});
|
|
118
132
|
|
|
119
|
-
return { hash:
|
|
133
|
+
return { hash: paymentDetails.tx_hash };
|
|
120
134
|
}
|
|
121
135
|
|
|
122
136
|
throw new Error(`Payment method ${paymentMethod.type} not supported`);
|