payment-kit 1.18.49 → 1.18.51
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/currency.ts +25 -4
- package/api/src/libs/invoice.ts +231 -0
- package/api/src/libs/payment.ts +92 -0
- package/api/src/queues/payment.ts +0 -2
- package/api/src/queues/subscription.ts +24 -1
- package/api/src/routes/connect/change-payment.ts +62 -42
- package/api/src/routes/connect/shared.ts +5 -3
- package/api/src/routes/connect/subscribe.ts +36 -5
- package/api/src/routes/invoices.ts +75 -2
- package/api/src/routes/subscriptions.ts +37 -6
- package/blocklet.yml +1 -1
- package/package.json +18 -18
- package/public/currencies/0xBTC.png +0 -0
- package/public/currencies/1INCH.svg +1 -0
- package/public/currencies/AAVE.svg +1 -0
- package/public/currencies/ABT.svg +16 -0
- package/public/currencies/ACX.png +0 -0
- package/public/currencies/ADX.svg +12 -0
- package/public/currencies/AIKEK.png +0 -0
- package/public/currencies/AJNA.svg +12 -0
- package/public/currencies/AKIRA.png +0 -0
- package/public/currencies/ALEX.png +0 -0
- package/public/currencies/ALI.png +0 -0
- package/public/currencies/ALT.svg +12 -0
- package/public/currencies/AMKT.svg +5 -0
- package/public/currencies/AMP.svg +7 -0
- package/public/currencies/APU.png +0 -0
- package/public/currencies/APW.svg +21 -0
- package/public/currencies/APX.png +0 -0
- package/public/currencies/ARC.png +0 -0
- package/public/currencies/ARIA20.svg +1 -0
- package/public/currencies/ARPA.svg +1 -0
- package/public/currencies/ATH.svg +1 -0
- package/public/currencies/AVC.png +0 -0
- package/public/currencies/AVRK.png +0 -0
- package/public/currencies/AXGT.svg +1 -0
- package/public/currencies/AZUR.svg +20 -0
- package/public/currencies/BAL.png +0 -0
- package/public/currencies/BANK.png +0 -0
- package/public/currencies/BAXA.svg +3 -0
- package/public/currencies/BCAT.png +0 -0
- package/public/currencies/BEPRO.svg +1 -0
- package/public/currencies/BERRY.svg +1 -0
- package/public/currencies/BICO.svg +11 -0
- package/public/currencies/BITBOT.jpg +0 -0
- package/public/currencies/BITCOIN.png +0 -0
- package/public/currencies/BKN.svg +1 -0
- package/public/currencies/BLUE.png +0 -0
- package/public/currencies/BNB.png +0 -0
- package/public/currencies/BOBA.svg +16 -0
- package/public/currencies/BOBO.png +0 -0
- package/public/currencies/BOND.svg +10 -0
- package/public/currencies/BOTTO.svg +4 -0
- package/public/currencies/BRIGHT.svg +83 -0
- package/public/currencies/BTRST.svg +1 -0
- package/public/currencies/BURN.png +0 -0
- package/public/currencies/BitANT.png +0 -0
- package/public/currencies/BitBTC.png +0 -0
- package/public/currencies/CARD.svg +1 -0
- package/public/currencies/CBX.svg +1 -0
- package/public/currencies/CHI.svg +1 -0
- package/public/currencies/CIG.svg +21 -0
- package/public/currencies/CNG.svg +9 -0
- package/public/currencies/COC.svg +8 -0
- package/public/currencies/COLLAB.svg +8 -0
- package/public/currencies/COMP.svg +1 -0
- package/public/currencies/COW.svg +1 -0
- package/public/currencies/CRV.png +0 -0
- package/public/currencies/CRYO.svg +1 -0
- package/public/currencies/CTRAVL.svg +1 -0
- package/public/currencies/CTSI.svg +1 -0
- package/public/currencies/CTX.svg +1 -0
- package/public/currencies/CUSD.png +0 -0
- package/public/currencies/CXT.svg +11 -0
- package/public/currencies/D2D.svg +6 -0
- package/public/currencies/DAI.svg +13 -0
- package/public/currencies/DCN.svg +47 -0
- package/public/currencies/DF.svg +15 -0
- package/public/currencies/DHT.svg +1 -0
- package/public/currencies/DIMO.svg +1 -0
- package/public/currencies/DMS.png +0 -0
- package/public/currencies/DODO.svg +6 -0
- package/public/currencies/DOGEGF.png +0 -0
- package/public/currencies/DOLA.svg +1 -0
- package/public/currencies/DOSE.svg +1 -0
- package/public/currencies/DUCK.png +0 -0
- package/public/currencies/DYP.png +0 -0
- package/public/currencies/EIGEN.svg +13 -0
- package/public/currencies/ENS.png +0 -0
- package/public/currencies/EPOCH.svg +1 -0
- package/public/currencies/EQB.svg +4 -0
- package/public/currencies/EQZ.png +0 -0
- package/public/currencies/ERN.png +0 -0
- package/public/currencies/ERNTST.png +0 -0
- package/public/currencies/EST.png +0 -0
- package/public/currencies/ETH.svg +13 -0
- package/public/currencies/ETHIX.svg +1 -0
- package/public/currencies/EVERY.svg +13 -0
- package/public/currencies/F.svg +1 -0
- package/public/currencies/FACTR.png +0 -0
- package/public/currencies/FANS.svg +11 -0
- package/public/currencies/FARM.png +0 -0
- package/public/currencies/FCR.png +0 -0
- package/public/currencies/FET.svg +21 -0
- package/public/currencies/FIS.svg +22 -0
- package/public/currencies/FLASH.svg +22 -0
- package/public/currencies/FLy.png +0 -0
- package/public/currencies/FOAM.svg +23 -0
- package/public/currencies/FORTH.svg +1 -0
- package/public/currencies/FOX.svg +8 -0
- package/public/currencies/FRAX.png +0 -0
- package/public/currencies/FU.svg +17 -0
- package/public/currencies/FVT.svg +1 -0
- package/public/currencies/FXN.svg +25 -0
- package/public/currencies/FXS.png +0 -0
- package/public/currencies/Froglic.png +0 -0
- package/public/currencies/GALAXIS.svg +1 -0
- package/public/currencies/GCH.png +0 -0
- package/public/currencies/GENOME.svg +23 -0
- package/public/currencies/GGTK.svg +1 -0
- package/public/currencies/GHST.svg +15 -0
- package/public/currencies/GIGACHAD.png +0 -0
- package/public/currencies/GIV.svg +6 -0
- package/public/currencies/GLS.svg +22 -0
- package/public/currencies/GOLD.png +0 -0
- package/public/currencies/GRG.png +0 -0
- package/public/currencies/GROW.svg +1 -0
- package/public/currencies/GRT.svg +11 -0
- package/public/currencies/GTC.svg +5 -0
- package/public/currencies/GURU.svg +1 -0
- package/public/currencies/GYEN.svg +1 -0
- package/public/currencies/GYSR.png +0 -0
- package/public/currencies/HAIR.svg +1 -0
- package/public/currencies/HAN.svg +25 -0
- package/public/currencies/HANeP.svg +1 -0
- package/public/currencies/HAUS.png +0 -0
- package/public/currencies/HEU.svg +5 -0
- package/public/currencies/HOBA.png +0 -0
- package/public/currencies/HUNT.svg +1 -0
- package/public/currencies/IBEX.png +0 -0
- package/public/currencies/IDRISS.svg +4 -0
- package/public/currencies/IOTX.png +0 -0
- package/public/currencies/IPOR.svg +1 -0
- package/public/currencies/IPT.svg +7 -0
- package/public/currencies/IRARA.svg +1 -0
- package/public/currencies/ISK.png +0 -0
- package/public/currencies/IYKYK.svg +14 -0
- package/public/currencies/JAM.svg +20 -0
- package/public/currencies/JRT.svg +8 -0
- package/public/currencies/KAGE.png +0 -0
- package/public/currencies/KAI.svg +23 -0
- package/public/currencies/KIBSHI.png +0 -0
- package/public/currencies/KNC.png +0 -0
- package/public/currencies/KOMPETE.png +0 -0
- package/public/currencies/KROM.png +0 -0
- package/public/currencies/LCX.svg +6 -0
- package/public/currencies/LDO.svg +5 -0
- package/public/currencies/LIF3.svg +27 -0
- package/public/currencies/LINK.png +0 -0
- package/public/currencies/LIZ.png +0 -0
- package/public/currencies/LMEOW.png +0 -0
- package/public/currencies/LOCG.png +0 -0
- package/public/currencies/LORDS.png +0 -0
- package/public/currencies/LPF.svg +18 -0
- package/public/currencies/LQTY.svg +1 -0
- package/public/currencies/LRC.png +0 -0
- package/public/currencies/LRDS.svg +44 -0
- package/public/currencies/LSK.png +0 -0
- package/public/currencies/LUSD.svg +1 -0
- package/public/currencies/LYRA.png +0 -0
- package/public/currencies/MASK.svg +4 -0
- package/public/currencies/MASQ.png +0 -0
- package/public/currencies/MBS.png +0 -0
- package/public/currencies/MCADE.svg +26 -0
- package/public/currencies/MET.svg +15 -0
- package/public/currencies/MINDS.svg +32 -0
- package/public/currencies/MKR.png +0 -0
- package/public/currencies/MLN.svg +1 -0
- package/public/currencies/MOCHI.svg +1 -0
- package/public/currencies/MOM.svg +5 -0
- package/public/currencies/MONKE.png +0 -0
- package/public/currencies/MOOV.svg +5 -0
- package/public/currencies/MORPHO.svg +14 -0
- package/public/currencies/MPWR.svg +5 -0
- package/public/currencies/MTA.svg +15 -0
- package/public/currencies/MTL.png +0 -0
- package/public/currencies/MUGLOO.png +0 -0
- package/public/currencies/Mog.png +0 -0
- package/public/currencies/NAVI.svg +1 -0
- package/public/currencies/NCT.svg +49 -0
- package/public/currencies/NET.svg +11 -0
- package/public/currencies/NEURON.svg +38 -0
- package/public/currencies/NFD.png +0 -0
- package/public/currencies/NOGS.png +0 -0
- package/public/currencies/NOUNS.png +0 -0
- package/public/currencies/NOVA.png +0 -0
- package/public/currencies/NSTR.svg +4 -0
- package/public/currencies/Neiro.png +0 -0
- package/public/currencies/OCEAN.png +0 -0
- package/public/currencies/OGN.svg +8 -0
- package/public/currencies/OL.svg +21 -0
- package/public/currencies/OLAS.svg +7 -0
- package/public/currencies/OM.png +0 -0
- package/public/currencies/OMETA.svg +7 -0
- package/public/currencies/OMI.svg +42 -0
- package/public/currencies/OPENLOOT.svg +21 -0
- package/public/currencies/OPN.svg +1 -0
- package/public/currencies/OS.svg +1 -0
- package/public/currencies/OUTb.svg +4 -0
- package/public/currencies/OVR.png +0 -0
- package/public/currencies/PAGE.svg +1 -0
- package/public/currencies/PAL.svg +1 -0
- package/public/currencies/PAPER.svg +4 -0
- package/public/currencies/PENDLE.png +0 -0
- package/public/currencies/PEPE.png +0 -0
- package/public/currencies/PERP.png +0 -0
- package/public/currencies/PHTK.svg +14 -0
- package/public/currencies/PIP.svg +11 -0
- package/public/currencies/POGS.png +0 -0
- package/public/currencies/POOL.svg +22 -0
- package/public/currencies/POP.svg +8 -0
- package/public/currencies/PORK.png +0 -0
- package/public/currencies/PRE.svg +66 -0
- package/public/currencies/PREMIA.svg +4 -0
- package/public/currencies/PRF.png +0 -0
- package/public/currencies/PRO.svg +1 -0
- package/public/currencies/PSP.svg +35 -0
- package/public/currencies/PSTAKE.png +0 -0
- package/public/currencies/PSY.svg +26 -0
- package/public/currencies/RAC.png +0 -0
- package/public/currencies/RAI.svg +1 -0
- package/public/currencies/RAZOR.svg +1 -0
- package/public/currencies/REKT.png +0 -0
- package/public/currencies/RFKJ.png +0 -0
- package/public/currencies/RFWSTETH.svg +36 -0
- package/public/currencies/RGT.png +0 -0
- package/public/currencies/RICE.svg +22 -0
- package/public/currencies/RIZE.svg +21 -0
- package/public/currencies/RPL.svg +1 -0
- package/public/currencies/RSC.svg +6 -0
- package/public/currencies/RSR.png +0 -0
- package/public/currencies/RSS3.png +0 -0
- package/public/currencies/Reach.svg +17 -0
- package/public/currencies/SAIL.png +0 -0
- package/public/currencies/SALD.png +0 -0
- package/public/currencies/SARCO.png +0 -0
- package/public/currencies/SCM.svg +1 -0
- package/public/currencies/SCRY.svg +13 -0
- package/public/currencies/SDL.svg +1 -0
- package/public/currencies/SEAM.svg +5 -0
- package/public/currencies/SEXY.svg +1 -0
- package/public/currencies/SHU.png +0 -0
- package/public/currencies/SIPHER.png +0 -0
- package/public/currencies/SKAI.svg +1 -0
- package/public/currencies/SLN.png +0 -0
- package/public/currencies/SMT.svg +1 -0
- package/public/currencies/SMURFCAT.png +0 -0
- package/public/currencies/SNT.svg +14 -0
- package/public/currencies/SNX.svg +20 -0
- package/public/currencies/SOFI.png +0 -0
- package/public/currencies/SOFT.svg +1 -0
- package/public/currencies/SOVRN.svg +13 -0
- package/public/currencies/SPANK.png +0 -0
- package/public/currencies/SPC.png +0 -0
- package/public/currencies/SPOT.svg +1 -0
- package/public/currencies/SQD.png +0 -0
- package/public/currencies/SQT.svg +14 -0
- package/public/currencies/STNT.svg +38 -0
- package/public/currencies/STPT.png +0 -0
- package/public/currencies/SUDO.svg +6 -0
- package/public/currencies/SUKU.png +0 -0
- package/public/currencies/SUPER.svg +6 -0
- package/public/currencies/SUSHI.svg +19 -0
- package/public/currencies/SYNTH.svg +1 -0
- package/public/currencies/ShibDoge.png +0 -0
- package/public/currencies/Silo.svg +4 -0
- package/public/currencies/StaFi.svg +65 -0
- package/public/currencies/T.svg +4 -0
- package/public/currencies/TEN.svg +1 -0
- package/public/currencies/TETU.svg +6 -0
- package/public/currencies/THALES.png +0 -0
- package/public/currencies/THING.png +0 -0
- package/public/currencies/THX.svg +22 -0
- package/public/currencies/TKAI.svg +1 -0
- package/public/currencies/TKN.png +0 -0
- package/public/currencies/TODL.png +0 -0
- package/public/currencies/TOWER.svg +1 -0
- package/public/currencies/TRAC.png +0 -0
- package/public/currencies/TRB.png +0 -0
- package/public/currencies/TRUE.svg +11 -0
- package/public/currencies/TRX.svg +4 -0
- package/public/currencies/TSC.png +0 -0
- package/public/currencies/TT.png +0 -0
- package/public/currencies/TUSD.png +0 -0
- package/public/currencies/TYBENG.png +0 -0
- package/public/currencies/TheDAO.svg +1 -0
- package/public/currencies/U.png +0 -0
- package/public/currencies/UBI.svg +5 -0
- package/public/currencies/UCASH.svg +1 -0
- package/public/currencies/UDT.svg +4 -0
- package/public/currencies/UMA.png +0 -0
- package/public/currencies/UNI.png +0 -0
- package/public/currencies/USA.png +0 -0
- package/public/currencies/USC.svg +23 -0
- package/public/currencies/USD3.svg +13 -0
- package/public/currencies/USDD.png +0 -0
- package/public/currencies/USDS.png +0 -0
- package/public/currencies/USDbC.png +0 -0
- package/public/currencies/UST.png +0 -0
- package/public/currencies/VALX.png +0 -0
- package/public/currencies/VIDYA.png +0 -0
- package/public/currencies/VITA.svg +10 -0
- package/public/currencies/VSP.svg +15 -0
- package/public/currencies/VUSD.svg +1 -0
- package/public/currencies/WAAC.png +0 -0
- package/public/currencies/WAD.png +0 -0
- package/public/currencies/WALLET.svg +1 -0
- package/public/currencies/WAMPL.svg +53 -0
- package/public/currencies/WBTC.png +0 -0
- package/public/currencies/WCT.svg +5 -0
- package/public/currencies/WEXO.png +0 -0
- package/public/currencies/WLD.jpeg +0 -0
- package/public/currencies/WMC.png +0 -0
- package/public/currencies/WOO.png +0 -0
- package/public/currencies/Wildfire.svg +1 -0
- package/public/currencies/XCHF.png +0 -0
- package/public/currencies/XCN.svg +1 -0
- package/public/currencies/XMT.svg +1 -0
- package/public/currencies/XYO.svg +1 -0
- package/public/currencies/YFI.svg +4 -0
- package/public/currencies/YFX.svg +11 -0
- package/public/currencies/YGG.svg +11 -0
- package/public/currencies/YOKAI.png +0 -0
- package/public/currencies/YYY.svg +1 -0
- package/public/currencies/ZENT.svg +12 -0
- package/public/currencies/ZRX.png +0 -0
- package/public/currencies/ZUN.svg +24 -0
- package/public/currencies/ZUSD.svg +1 -0
- package/public/currencies/ZYN.png +0 -0
- package/public/currencies/aCRV.png +0 -0
- package/public/currencies/btc.png +0 -0
- package/public/currencies/bwAJNA.svg +1 -0
- package/public/currencies/cSTONE.svg +23 -0
- package/public/currencies/cbETH.svg +9 -0
- package/public/currencies/crvUSD.png +0 -0
- package/public/currencies/cvxFXS.png +0 -0
- package/public/currencies/dollar.png +0 -0
- package/public/currencies/ePendle.png +0 -0
- package/public/currencies/frxETH.png +0 -0
- package/public/currencies/fxUSD.svg +5 -0
- package/public/currencies/iFARM.png +0 -0
- package/public/currencies/iZi.png +0 -0
- package/public/currencies/imgnAI.svg +1 -0
- package/public/currencies/mpETH.svg +4 -0
- package/public/currencies/nETH.svg +11 -0
- package/public/currencies/rETH.svg +1 -0
- package/public/currencies/rgUSD.svg +5 -0
- package/public/currencies/rnETH.svg +11 -0
- package/public/currencies/sDAI.svg +13 -0
- package/public/currencies/sFRAX.png +0 -0
- package/public/currencies/sUSDS.png +0 -0
- package/public/currencies/scWETHv2.svg +1 -0
- package/public/currencies/send.svg +12 -0
- package/public/currencies/stETH.png +1 -0
- package/public/currencies/stZENT.svg +12 -0
- package/public/currencies/stkLYRA.svg +26 -0
- package/public/currencies/tGS.png +0 -0
- package/public/currencies/tSTAR.png +0 -0
- package/public/currencies/usdc.png +0 -0
- package/public/currencies/usdt.png +0 -0
- package/public/currencies/veKWENTA.svg +24 -0
- package/public/currencies/wHOGE.svg +14 -0
- package/public/currencies/wOpenX.svg +140 -0
- package/public/currencies/wPOKT.svg +17 -0
- package/public/currencies/wTBT.svg +1 -0
- package/public/currencies/weETH.svg +23 -0
- package/public/currencies/wstETH.svg +11 -0
- package/public/currencies/yETH.svg +8 -0
- package/public/currencies/zunETH.svg +46 -0
- package/public/currencies/zunUSD.svg +33 -0
- package/src/components/customer/overdraft-protection.tsx +37 -9
- package/src/components/invoice/action.tsx +47 -9
- package/src/components/payment-currency/add.tsx +12 -3
- package/src/components/payment-currency/edit.tsx +89 -3
- package/src/components/payment-currency/tokenList.json +8847 -8156
- package/src/components/subscription/portal/actions.tsx +6 -2
- package/src/libs/util.ts +3 -1
- package/src/locales/en.tsx +10 -2
- package/src/locales/zh.tsx +10 -2
- package/src/pages/admin/settings/vault-config/edit-form.tsx +13 -0
- package/src/pages/customer/recharge/account.tsx +8 -1
- package/src/pages/customer/recharge/subscription.tsx +8 -1
- package/src/pages/customer/subscription/change-payment.tsx +7 -4
- package/src/pages/customer/subscription/detail.tsx +7 -6
|
@@ -6,7 +6,12 @@ import logger from '../libs/logger';
|
|
|
6
6
|
export async function syncCurrencyLogo() {
|
|
7
7
|
const where = {
|
|
8
8
|
logo: {
|
|
9
|
-
[Op.
|
|
9
|
+
[Op.or]: [
|
|
10
|
+
{ [Op.like]: '%/methods/%.png' },
|
|
11
|
+
{ [Op.like]: '%/currencies/%.png' },
|
|
12
|
+
{ [Op.like]: '%/methods/%.svg' },
|
|
13
|
+
{ [Op.like]: '%/currencies/%.svg' },
|
|
14
|
+
],
|
|
10
15
|
},
|
|
11
16
|
};
|
|
12
17
|
|
|
@@ -19,9 +24,24 @@ export async function syncCurrencyLogo() {
|
|
|
19
24
|
|
|
20
25
|
const updateLogo = (item: PaymentMethod | PaymentCurrency) => {
|
|
21
26
|
const { logo } = item;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
|
|
28
|
+
// 匹配 /methods/ 和 /currencies/ 路径下的 logo
|
|
29
|
+
const methodsMatch = /\/methods\/(stripe|arcblock|ethereum|base)\.(png|svg)$/.test(logo);
|
|
30
|
+
const currenciesMatch = /\/currencies\/[^/]+\.(png|svg)$/.test(logo);
|
|
31
|
+
|
|
32
|
+
if (methodsMatch || currenciesMatch) {
|
|
33
|
+
let newLogo: string;
|
|
34
|
+
|
|
35
|
+
if (methodsMatch) {
|
|
36
|
+
// 处理 /methods/ 路径
|
|
37
|
+
newLogo = getUrl(logo.replace(/^.*?(\/methods\/.*)$/, '$1'));
|
|
38
|
+
} else {
|
|
39
|
+
newLogo = getUrl(logo.replace(/^.*?(\/currencies\/.*)$/, '$1'));
|
|
40
|
+
}
|
|
41
|
+
if (newLogo !== logo) {
|
|
42
|
+
promises.push((item as any).update({ logo: newLogo }));
|
|
43
|
+
logger.info(`Updating logo: ${logo} -> ${newLogo}`);
|
|
44
|
+
}
|
|
25
45
|
}
|
|
26
46
|
};
|
|
27
47
|
|
|
@@ -30,6 +50,7 @@ export async function syncCurrencyLogo() {
|
|
|
30
50
|
|
|
31
51
|
try {
|
|
32
52
|
await Promise.all(promises);
|
|
53
|
+
logger.info(`Updated ${promises.length} logo(s)`);
|
|
33
54
|
} catch (error) {
|
|
34
55
|
logger.error('syncCurrency error', error);
|
|
35
56
|
}
|
package/api/src/libs/invoice.ts
CHANGED
|
@@ -1119,3 +1119,234 @@ export async function retryUncollectibleInvoices(options: {
|
|
|
1119
1119
|
logger.info('Released retry uncollectible lock', { lockKey });
|
|
1120
1120
|
}
|
|
1121
1121
|
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Migrate billing when a subscription's payment method changes.
|
|
1125
|
+
* Steps:
|
|
1126
|
+
* 1. Check if all subscription items are prepaid (licensed recurring).
|
|
1127
|
+
* 2. If so, check if there is an unpaid invoice for the current period with the old payment method.
|
|
1128
|
+
* 3. If such an invoice exists, void the old invoice and cancel its payment intent if present.
|
|
1129
|
+
* 4. Create a new invoice for the current period with the new payment method.
|
|
1130
|
+
* 5. If any step fails, throw an error and log details.
|
|
1131
|
+
*
|
|
1132
|
+
* @param subscription Subscription instance
|
|
1133
|
+
* @param oldCurrencyId Old payment currency ID
|
|
1134
|
+
* @param newCurrencyId New payment currency ID
|
|
1135
|
+
* @returns Migration result: { migrated, oldInvoice, newInvoice }
|
|
1136
|
+
*/
|
|
1137
|
+
export const migrateSubscriptionPaymentMethodInvoice = async (
|
|
1138
|
+
subscription: Subscription,
|
|
1139
|
+
oldCurrencyId: string,
|
|
1140
|
+
newCurrencyId: string
|
|
1141
|
+
) => {
|
|
1142
|
+
// 1. Check if all subscription items are prepaid (licensed recurring)
|
|
1143
|
+
const subscriptionItems = await SubscriptionItem.findAll({
|
|
1144
|
+
where: { subscription_id: subscription.id },
|
|
1145
|
+
include: [{ model: Price, as: 'price' }],
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
const subscriptionItemsExpanded = await Price.expand(subscriptionItems.map((x) => x.toJSON()));
|
|
1149
|
+
const isPrepaid = subscriptionItemsExpanded.every((item: TLineItemExpanded) => {
|
|
1150
|
+
const price = getSubscriptionItemPrice(item);
|
|
1151
|
+
return price.type === 'recurring' && price.recurring?.usage_type === 'licensed';
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
if (!isPrepaid) {
|
|
1155
|
+
logger.info('Skip billing migration for non-prepaid items', {
|
|
1156
|
+
subscriptionId: subscription.id,
|
|
1157
|
+
});
|
|
1158
|
+
return { migrated: false, reason: 'has_non_prepaid_items' };
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// 2. Find unpaid invoice for the current period with the old payment method
|
|
1162
|
+
const currentPeriodInvoice = await Invoice.findOne({
|
|
1163
|
+
where: {
|
|
1164
|
+
subscription_id: subscription.id,
|
|
1165
|
+
billing_reason: 'subscription_cycle',
|
|
1166
|
+
status: ['open', 'uncollectible'],
|
|
1167
|
+
currency_id: oldCurrencyId,
|
|
1168
|
+
period_start: {
|
|
1169
|
+
[Op.gte]: subscription.current_period_start,
|
|
1170
|
+
[Op.lt]: subscription.current_period_end,
|
|
1171
|
+
},
|
|
1172
|
+
},
|
|
1173
|
+
order: [['created_at', 'DESC']],
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
let voidedInvoice: Invoice | null = null;
|
|
1177
|
+
|
|
1178
|
+
if (!currentPeriodInvoice) {
|
|
1179
|
+
voidedInvoice = await Invoice.findOne({
|
|
1180
|
+
where: {
|
|
1181
|
+
subscription_id: subscription.id,
|
|
1182
|
+
billing_reason: 'subscription_cycle',
|
|
1183
|
+
status: 'void',
|
|
1184
|
+
currency_id: oldCurrencyId,
|
|
1185
|
+
period_start: {
|
|
1186
|
+
[Op.gte]: subscription.current_period_start,
|
|
1187
|
+
[Op.lt]: subscription.current_period_end,
|
|
1188
|
+
},
|
|
1189
|
+
},
|
|
1190
|
+
order: [['created_at', 'DESC']],
|
|
1191
|
+
});
|
|
1192
|
+
if (!voidedInvoice) {
|
|
1193
|
+
logger.info('Skip billing migration for no unpaid invoice', {
|
|
1194
|
+
subscriptionId: subscription.id,
|
|
1195
|
+
periodStart: subscription.current_period_start,
|
|
1196
|
+
periodEnd: subscription.current_period_end,
|
|
1197
|
+
oldCurrencyId,
|
|
1198
|
+
});
|
|
1199
|
+
return { migrated: false, reason: 'no_unpaid_invoice' };
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// 3. Get old and new payment method/currency
|
|
1204
|
+
const oldPaymentCurrency = await PaymentCurrency.findByPk(oldCurrencyId);
|
|
1205
|
+
if (!oldPaymentCurrency) {
|
|
1206
|
+
throw new Error(`Payment currency ${oldCurrencyId} not found`);
|
|
1207
|
+
}
|
|
1208
|
+
const oldPaymentMethod = await PaymentMethod.findByPk(oldPaymentCurrency.payment_method_id);
|
|
1209
|
+
if (!oldPaymentMethod) {
|
|
1210
|
+
throw new Error(`Payment method for currency ${oldCurrencyId} not found`);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const newPaymentCurrency = await PaymentCurrency.findByPk(newCurrencyId);
|
|
1214
|
+
if (!newPaymentCurrency) {
|
|
1215
|
+
throw new Error(`Payment currency ${newCurrencyId} not found`);
|
|
1216
|
+
}
|
|
1217
|
+
const newPaymentMethod = await PaymentMethod.findByPk(newPaymentCurrency.payment_method_id);
|
|
1218
|
+
if (!newPaymentMethod) {
|
|
1219
|
+
throw new Error(`Payment method for currency ${newCurrencyId} not found`);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// 4. Stripe payment method is not supported for migration
|
|
1223
|
+
if (newPaymentMethod.type === 'stripe') {
|
|
1224
|
+
logger.info('Skip billing migration for stripe payment method', {
|
|
1225
|
+
subscriptionId: subscription.id,
|
|
1226
|
+
});
|
|
1227
|
+
return { migrated: false, reason: 'stripe_payment_method' };
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
try {
|
|
1231
|
+
const customer = await Customer.findByPk(subscription.customer_id);
|
|
1232
|
+
if (!customer) {
|
|
1233
|
+
throw new Error(`Customer ${subscription.customer_id} not found`);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
const cancelOldInvoice = async (invoice: Invoice) => {
|
|
1237
|
+
try {
|
|
1238
|
+
if (invoice.payment_intent_id) {
|
|
1239
|
+
const paymentIntent = await PaymentIntent.findByPk(invoice.payment_intent_id);
|
|
1240
|
+
if (paymentIntent && paymentIntent.status !== 'canceled') {
|
|
1241
|
+
await paymentIntent.update({
|
|
1242
|
+
status: 'canceled',
|
|
1243
|
+
canceled_at: dayjs().unix(),
|
|
1244
|
+
cancellation_reason: 'void_invoice',
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
if (oldPaymentMethod.type === 'stripe' && invoice.metadata?.stripe_id) {
|
|
1250
|
+
const client = oldPaymentMethod.getStripeClient();
|
|
1251
|
+
await client.invoices.voidInvoice(invoice.metadata.stripe_id);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
await invoice.update({
|
|
1255
|
+
status: 'void',
|
|
1256
|
+
status_transitions: {
|
|
1257
|
+
...(invoice.status_transitions || {}),
|
|
1258
|
+
voided_at: dayjs().unix(),
|
|
1259
|
+
},
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
logger.info('Successfully voided old invoice for payment method change', {
|
|
1263
|
+
subscriptionId: subscription.id,
|
|
1264
|
+
oldInvoice: invoice.id,
|
|
1265
|
+
oldCurrency: oldCurrencyId,
|
|
1266
|
+
newCurrency: newCurrencyId,
|
|
1267
|
+
});
|
|
1268
|
+
} catch (error) {
|
|
1269
|
+
logger.error('Failed to void old invoice', {
|
|
1270
|
+
subscription: subscription.id,
|
|
1271
|
+
invoiceId: invoice.id,
|
|
1272
|
+
error: error.message,
|
|
1273
|
+
});
|
|
1274
|
+
throw error;
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1278
|
+
const createNewInvoice = async () => {
|
|
1279
|
+
const preInvoice = currentPeriodInvoice || voidedInvoice;
|
|
1280
|
+
if (!preInvoice) {
|
|
1281
|
+
throw new Error('No unpaid invoice found');
|
|
1282
|
+
}
|
|
1283
|
+
const metadata: Record<string, any> = {
|
|
1284
|
+
prev_invoice_id: preInvoice.id,
|
|
1285
|
+
};
|
|
1286
|
+
const amount = getSubscriptionCycleAmount(subscriptionItemsExpanded, newCurrencyId);
|
|
1287
|
+
|
|
1288
|
+
const { invoice } = await ensureInvoiceAndItems({
|
|
1289
|
+
customer,
|
|
1290
|
+
currency: newPaymentCurrency,
|
|
1291
|
+
subscription,
|
|
1292
|
+
trialing: subscription.status === 'trialing',
|
|
1293
|
+
metered: false,
|
|
1294
|
+
lineItems: subscriptionItemsExpanded,
|
|
1295
|
+
applyCredit: false,
|
|
1296
|
+
props: {
|
|
1297
|
+
status: 'open',
|
|
1298
|
+
total: amount.total,
|
|
1299
|
+
livemode: subscription.livemode,
|
|
1300
|
+
description: 'Subscription cycle',
|
|
1301
|
+
statement_descriptor: preInvoice.statement_descriptor,
|
|
1302
|
+
period_start: preInvoice.period_start,
|
|
1303
|
+
period_end: preInvoice.period_end,
|
|
1304
|
+
auto_advance: true,
|
|
1305
|
+
billing_reason: 'subscription_cycle',
|
|
1306
|
+
currency_id: newCurrencyId,
|
|
1307
|
+
default_payment_method_id: newPaymentMethod.id,
|
|
1308
|
+
custom_fields: preInvoice.custom_fields || [],
|
|
1309
|
+
footer: preInvoice.footer || '',
|
|
1310
|
+
payment_settings: subscription.payment_settings,
|
|
1311
|
+
metadata,
|
|
1312
|
+
} as Invoice,
|
|
1313
|
+
});
|
|
1314
|
+
return invoice;
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
// 5. Cancel old invoice, then create new invoice
|
|
1318
|
+
if (currentPeriodInvoice) {
|
|
1319
|
+
await cancelOldInvoice(currentPeriodInvoice);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
const invoice = await createNewInvoice();
|
|
1323
|
+
if (invoice) {
|
|
1324
|
+
await emitAsync('invoice.queued', invoice.id, { invoiceId: invoice.id, retryOnError: true }, { sync: false });
|
|
1325
|
+
logger.info('Successfully queued new invoice for payment method change', {
|
|
1326
|
+
subscriptionId: subscription.id,
|
|
1327
|
+
invoiceId: invoice.id,
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
logger.info('Successfully migrated invoice for payment method change', {
|
|
1332
|
+
subscriptionId: subscription.id,
|
|
1333
|
+
oldInvoiceId: currentPeriodInvoice?.id || voidedInvoice?.id,
|
|
1334
|
+
oldCurrency: oldCurrencyId,
|
|
1335
|
+
newCurrency: newCurrencyId,
|
|
1336
|
+
newInvoiceId: invoice.id,
|
|
1337
|
+
});
|
|
1338
|
+
return {
|
|
1339
|
+
migrated: true,
|
|
1340
|
+
oldInvoice: currentPeriodInvoice,
|
|
1341
|
+
newInvoice: invoice,
|
|
1342
|
+
};
|
|
1343
|
+
} catch (error) {
|
|
1344
|
+
logger.error('Failed to migrate invoice for payment method change', {
|
|
1345
|
+
subscriptionId: subscription.id,
|
|
1346
|
+
oldCurrencyId,
|
|
1347
|
+
newCurrencyId,
|
|
1348
|
+
error: error.message,
|
|
1349
|
+
});
|
|
1350
|
+
throw error;
|
|
1351
|
+
}
|
|
1352
|
+
};
|
package/api/src/libs/payment.ts
CHANGED
|
@@ -47,6 +47,98 @@ export interface SufficientForPaymentResult {
|
|
|
47
47
|
requestedAmount?: string;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
export async function checkTokenBalance(args: {
|
|
51
|
+
paymentMethod: PaymentMethod;
|
|
52
|
+
paymentCurrency: TPaymentCurrency;
|
|
53
|
+
userDid: string;
|
|
54
|
+
amount: string;
|
|
55
|
+
}): Promise<SufficientForPaymentResult> {
|
|
56
|
+
const { paymentMethod, paymentCurrency, userDid, amount } = args;
|
|
57
|
+
const tokenAddress = paymentCurrency.contract as string;
|
|
58
|
+
const totalAmount = new BN(amount);
|
|
59
|
+
|
|
60
|
+
if (paymentMethod.type === 'arcblock') {
|
|
61
|
+
// get user wallet did
|
|
62
|
+
const { user } = await blocklet.getUser(userDid, { enableConnectedAccount: true });
|
|
63
|
+
const delegator = getWalletDid(user);
|
|
64
|
+
if (!delegator) {
|
|
65
|
+
return {
|
|
66
|
+
sufficient: false,
|
|
67
|
+
reason: 'NO_DID_WALLET',
|
|
68
|
+
requestedAmount: totalAmount.toString(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const client = paymentMethod.getOcapClient();
|
|
73
|
+
|
|
74
|
+
// check token balance
|
|
75
|
+
const { tokens } = await client.getAccountTokens({ address: delegator, token: tokenAddress });
|
|
76
|
+
const [token] = tokens;
|
|
77
|
+
if (!token) {
|
|
78
|
+
return {
|
|
79
|
+
sufficient: false,
|
|
80
|
+
reason: 'NO_TOKEN',
|
|
81
|
+
requestedAmount: totalAmount.toString(),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (new BN(token.balance).lt(totalAmount)) {
|
|
86
|
+
return {
|
|
87
|
+
sufficient: false,
|
|
88
|
+
reason: 'NO_ENOUGH_TOKEN',
|
|
89
|
+
token,
|
|
90
|
+
requestedAmount: totalAmount.toString(),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
sufficient: true,
|
|
96
|
+
token,
|
|
97
|
+
requestedAmount: totalAmount.toString(),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
|
|
102
|
+
if (!paymentCurrency.contract) {
|
|
103
|
+
return {
|
|
104
|
+
sufficient: false,
|
|
105
|
+
reason: 'NO_TOKEN',
|
|
106
|
+
requestedAmount: totalAmount.toString(),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (isEthereumDid(userDid) === false) {
|
|
110
|
+
return {
|
|
111
|
+
sufficient: false,
|
|
112
|
+
reason: 'NO_TOKEN',
|
|
113
|
+
requestedAmount: totalAmount.toString(),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const provider = paymentMethod.getEvmClient();
|
|
118
|
+
const balance = await fetchErc20Balance(provider, paymentCurrency.contract, userDid);
|
|
119
|
+
|
|
120
|
+
if (new BN(balance).lt(totalAmount)) {
|
|
121
|
+
return {
|
|
122
|
+
sufficient: false,
|
|
123
|
+
reason: 'NO_ENOUGH_TOKEN',
|
|
124
|
+
token: { address: tokenAddress, balance },
|
|
125
|
+
requestedAmount: totalAmount.toString(),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
sufficient: true,
|
|
131
|
+
token: { address: tokenAddress, balance },
|
|
132
|
+
requestedAmount: totalAmount.toString(),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
sufficient: true,
|
|
138
|
+
requestedAmount: totalAmount.toString(),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
50
142
|
export async function isDelegationSufficientForPayment(args: {
|
|
51
143
|
paymentMethod: PaymentMethod;
|
|
52
144
|
paymentCurrency: TPaymentCurrency;
|
|
@@ -846,10 +846,8 @@ export const handlePayment = async (job: PaymentJob) => {
|
|
|
846
846
|
wallet,
|
|
847
847
|
delegator: result.delegator,
|
|
848
848
|
});
|
|
849
|
-
logger.info('PaymentIntent signed', { signed });
|
|
850
849
|
// @ts-ignore
|
|
851
850
|
const { buffer } = await client.encodeTransferV2Tx({ tx: signed });
|
|
852
|
-
logger.info('PaymentIntent buffer', { buffer, gas: getGasPayerExtra(buffer) });
|
|
853
851
|
const txHash = await client.sendTransferV2Tx(
|
|
854
852
|
// @ts-ignore
|
|
855
853
|
{ tx: signed, wallet, delegator: result.delegator },
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
shouldCancelSubscription,
|
|
27
27
|
slashOverdraftProtectionStake,
|
|
28
28
|
} from '../libs/subscription';
|
|
29
|
-
import { ensureInvoiceAndItems } from '../libs/invoice';
|
|
29
|
+
import { ensureInvoiceAndItems, migrateSubscriptionPaymentMethodInvoice } from '../libs/invoice';
|
|
30
30
|
import { PaymentCurrency, PaymentIntent, PaymentMethod, Refund, SetupIntent, UsageRecord } from '../store/models';
|
|
31
31
|
import { Customer } from '../store/models/customer';
|
|
32
32
|
import { Invoice } from '../store/models/invoice';
|
|
@@ -1280,5 +1280,28 @@ events.on('setup_intent.succeeded', async (setupIntent: SetupIntent) => {
|
|
|
1280
1280
|
logger.error('create return overdraft protection stake job failed', { error, subscription: subscription.id });
|
|
1281
1281
|
}
|
|
1282
1282
|
}
|
|
1283
|
+
|
|
1284
|
+
try {
|
|
1285
|
+
const migrationResult = await migrateSubscriptionPaymentMethodInvoice(
|
|
1286
|
+
subscription,
|
|
1287
|
+
setupIntent.metadata?.from_currency,
|
|
1288
|
+
setupIntent.metadata?.to_currency
|
|
1289
|
+
);
|
|
1290
|
+
if (migrationResult.migrated) {
|
|
1291
|
+
logger.info('Subscription payment method billing migration completed', {
|
|
1292
|
+
subscription: subscription.id,
|
|
1293
|
+
migrated: migrationResult.migrated,
|
|
1294
|
+
oldInvoice: migrationResult.oldInvoice?.id,
|
|
1295
|
+
newInvoice: migrationResult.newInvoice?.id,
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
} catch (error) {
|
|
1299
|
+
logger.error('Failed to migrate billing for payment method change', {
|
|
1300
|
+
subscription: subscription.id,
|
|
1301
|
+
fromCurrency: setupIntent.metadata?.from_currency,
|
|
1302
|
+
toCurrency: setupIntent.metadata?.to_currency,
|
|
1303
|
+
error,
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1283
1306
|
}
|
|
1284
1307
|
});
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
import { ensureStakeInvoice } from '../../libs/invoice';
|
|
14
14
|
import { EVM_CHAIN_TYPES } from '../../libs/constants';
|
|
15
15
|
import logger from '../../libs/logger';
|
|
16
|
+
import { getFastCheckoutAmount } from '../../libs/session';
|
|
17
|
+
import { isDelegationSufficientForPayment } from '../../libs/payment';
|
|
16
18
|
|
|
17
19
|
export default {
|
|
18
20
|
action: 'change-payment',
|
|
@@ -26,41 +28,53 @@ export default {
|
|
|
26
28
|
},
|
|
27
29
|
onConnect: async ({ userDid, userPk, extraParams }: CallbackArgs) => {
|
|
28
30
|
const { subscriptionId } = extraParams;
|
|
29
|
-
const { subscription, paymentMethod, paymentCurrency } = await ensureChangePaymentContext(subscriptionId);
|
|
31
|
+
const { subscription, paymentMethod, paymentCurrency, customer } = await ensureChangePaymentContext(subscriptionId);
|
|
30
32
|
|
|
31
33
|
const claimsList: any[] = [];
|
|
32
34
|
// @ts-ignore
|
|
33
35
|
const items = subscription!.items as TLineItemExpanded[];
|
|
34
36
|
const trialing = true;
|
|
35
37
|
const billingThreshold = Number(subscription.billing_thresholds?.amount_gte || 0);
|
|
38
|
+
const fastCheckoutAmount = getFastCheckoutAmount(items, 'subscription', paymentCurrency.id, false);
|
|
36
39
|
|
|
37
40
|
if (paymentMethod.type === 'arcblock') {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
nonce: `change-method-${subscription.id}`,
|
|
44
|
-
data: getTxMetadata({ subscriptionId: subscription.id }),
|
|
45
|
-
paymentCurrency,
|
|
46
|
-
paymentMethod,
|
|
47
|
-
trialing,
|
|
48
|
-
billingThreshold,
|
|
49
|
-
items,
|
|
50
|
-
}),
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
claimsList.push({
|
|
54
|
-
prepareTx: await getStakeTxClaim({
|
|
55
|
-
userDid,
|
|
56
|
-
userPk,
|
|
57
|
-
paymentCurrency,
|
|
58
|
-
paymentMethod,
|
|
59
|
-
items,
|
|
60
|
-
subscription,
|
|
61
|
-
}),
|
|
41
|
+
const delegation = await isDelegationSufficientForPayment({
|
|
42
|
+
paymentMethod,
|
|
43
|
+
paymentCurrency,
|
|
44
|
+
userDid: customer!.did,
|
|
45
|
+
amount: fastCheckoutAmount,
|
|
62
46
|
});
|
|
47
|
+
const needDelegation = delegation.sufficient === false;
|
|
48
|
+
const noStake = subscription.billing_thresholds?.no_stake;
|
|
49
|
+
if (needDelegation || noStake) {
|
|
50
|
+
claimsList.push({
|
|
51
|
+
signature: await getDelegationTxClaim({
|
|
52
|
+
mode: 'setup',
|
|
53
|
+
userDid,
|
|
54
|
+
userPk,
|
|
55
|
+
nonce: `change-method-${subscription.id}`,
|
|
56
|
+
data: getTxMetadata({ subscriptionId: subscription.id }),
|
|
57
|
+
paymentCurrency,
|
|
58
|
+
paymentMethod,
|
|
59
|
+
trialing,
|
|
60
|
+
billingThreshold,
|
|
61
|
+
items,
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
63
65
|
|
|
66
|
+
if (!noStake) {
|
|
67
|
+
claimsList.push({
|
|
68
|
+
prepareTx: await getStakeTxClaim({
|
|
69
|
+
userDid,
|
|
70
|
+
userPk,
|
|
71
|
+
paymentCurrency,
|
|
72
|
+
paymentMethod,
|
|
73
|
+
items,
|
|
74
|
+
subscription,
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
64
78
|
return claimsList;
|
|
65
79
|
}
|
|
66
80
|
|
|
@@ -95,6 +109,8 @@ export default {
|
|
|
95
109
|
const { setupIntent, subscription, paymentMethod, paymentCurrency, customer } =
|
|
96
110
|
await ensureChangePaymentContext(subscriptionId);
|
|
97
111
|
|
|
112
|
+
const noStake = subscription.billing_thresholds?.no_stake;
|
|
113
|
+
|
|
98
114
|
const result = request?.context?.store?.result || [];
|
|
99
115
|
result.push({
|
|
100
116
|
step,
|
|
@@ -106,7 +122,8 @@ export default {
|
|
|
106
122
|
|
|
107
123
|
// 判断是否为最后一步
|
|
108
124
|
const staking = result.find((x: any) => x.claim?.type === 'prepareTx' && x.claim?.meta?.purpose === 'staking');
|
|
109
|
-
const isFinalStep =
|
|
125
|
+
const isFinalStep =
|
|
126
|
+
(paymentMethod.type === 'arcblock' && (staking || noStake)) || paymentMethod.type !== 'arcblock';
|
|
110
127
|
|
|
111
128
|
if (!isFinalStep) {
|
|
112
129
|
await updateSession({
|
|
@@ -181,25 +198,28 @@ export default {
|
|
|
181
198
|
subscription?.id,
|
|
182
199
|
paymentCurrency.contract
|
|
183
200
|
);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
201
|
+
if (stakingAmount && stakingAmount !== '0') {
|
|
202
|
+
await ensureStakeInvoice(
|
|
203
|
+
{
|
|
204
|
+
total: stakingAmount,
|
|
205
|
+
description: 'Stake for subscription payment change',
|
|
206
|
+
currency_id: paymentCurrency.id,
|
|
207
|
+
metadata: {
|
|
208
|
+
payment_details: {
|
|
209
|
+
arcblock: {
|
|
210
|
+
tx_hash: paymentDetails?.staking?.tx_hash,
|
|
211
|
+
payer: paymentDetails?.payer,
|
|
212
|
+
address: paymentDetails?.staking?.address,
|
|
213
|
+
},
|
|
195
214
|
},
|
|
196
215
|
},
|
|
197
216
|
},
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
217
|
+
subscription!,
|
|
218
|
+
paymentMethod,
|
|
219
|
+
customer!
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
203
223
|
await afterTxExecution(paymentDetails);
|
|
204
224
|
return { hash: paymentDetails.tx_hash };
|
|
205
225
|
}
|
|
@@ -400,7 +400,7 @@ export async function ensureInvoiceForCheckout({
|
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
// invoice currency is aligned
|
|
403
|
-
if (invoice.currency_id === checkoutSession.currency_id) {
|
|
403
|
+
if (invoice.currency_id === checkoutSession.currency_id && invoice.status !== 'draft') {
|
|
404
404
|
await invoice.update({ status: 'open' });
|
|
405
405
|
logger.info(`Invoice status reset for checkout session ${checkoutSession.id}: ${existingInvoice}`);
|
|
406
406
|
|
|
@@ -512,7 +512,8 @@ export async function ensureInvoicesForSubscriptions({
|
|
|
512
512
|
checkoutSession,
|
|
513
513
|
customer,
|
|
514
514
|
subscriptions,
|
|
515
|
-
|
|
515
|
+
invoiceProps,
|
|
516
|
+
}: Omit<Args, 'subscription'> & { subscriptions: Subscription[]; invoiceProps?: Partial<TInvoice> }): Promise<{
|
|
516
517
|
invoices: Invoice[];
|
|
517
518
|
}> {
|
|
518
519
|
if (!subscriptions?.length) {
|
|
@@ -531,7 +532,8 @@ export async function ensureInvoicesForSubscriptions({
|
|
|
531
532
|
customer,
|
|
532
533
|
subscription,
|
|
533
534
|
subscriptions,
|
|
534
|
-
lineItems: subItems
|
|
535
|
+
lineItems: subItems,
|
|
536
|
+
props: invoiceProps,
|
|
535
537
|
});
|
|
536
538
|
return invoice;
|
|
537
539
|
})
|