@ic-pay/icpay-sdk 1.4.93 → 1.4.103
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/README.md +26 -20
- package/dist/base-builder-code.d.ts +6 -0
- package/dist/base-builder-code.d.ts.map +1 -0
- package/dist/base-builder-code.js +50 -0
- package/dist/base-builder-code.js.map +1 -0
- package/dist/http.d.ts +9 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +9 -0
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +215 -105
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +58 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +11 -8
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.IcpayWallet = exports.IcpayError = exports.Icpay = void 0;
|
|
18
|
+
const base_builder_code_1 = require("./base-builder-code");
|
|
18
19
|
const builders_1 = require("./x402/builders");
|
|
19
20
|
const errors_1 = require("./errors");
|
|
20
21
|
const events_1 = require("./events");
|
|
@@ -319,12 +320,15 @@ class Icpay {
|
|
|
319
320
|
async notifyPayment(params) {
|
|
320
321
|
this.emitMethodStart('notifyPayment', { paymentIntentId: params.paymentIntentId });
|
|
321
322
|
try {
|
|
322
|
-
const
|
|
323
|
+
const body = {
|
|
323
324
|
paymentIntentId: params.paymentIntentId,
|
|
324
325
|
canisterTxId: params.canisterTxId,
|
|
325
326
|
transactionId: params.transactionId,
|
|
326
327
|
orderId: params.orderId,
|
|
327
|
-
}
|
|
328
|
+
};
|
|
329
|
+
if (params.icpayPaymentLink && typeof params.icpayPaymentLink === 'object')
|
|
330
|
+
body.icpayPaymentLink = params.icpayPaymentLink;
|
|
331
|
+
const resp = await this.publicApiClient.post('/sdk/public/payments/notify', body);
|
|
328
332
|
this.emitMethodSuccess('notifyPayment', { status: resp?.status, paymentIntentId: resp?.paymentIntentId });
|
|
329
333
|
return resp;
|
|
330
334
|
}
|
|
@@ -765,6 +769,7 @@ class Icpay {
|
|
|
765
769
|
paymentIntentId: paymentIntentId,
|
|
766
770
|
maxAttempts: 120,
|
|
767
771
|
delayMs: 1000,
|
|
772
|
+
icpayPaymentLink: request?.metadata?.icpayPaymentLink,
|
|
768
773
|
});
|
|
769
774
|
// Derive status from API response
|
|
770
775
|
let statusString = 'pending';
|
|
@@ -1099,7 +1104,7 @@ class Icpay {
|
|
|
1099
1104
|
return out;
|
|
1100
1105
|
}
|
|
1101
1106
|
try {
|
|
1102
|
-
await this.performNotifyPaymentIntent({ paymentIntentId: params.paymentIntentId, transactionId: signature, maxAttempts: 1 });
|
|
1107
|
+
await this.performNotifyPaymentIntent({ paymentIntentId: params.paymentIntentId, transactionId: signature, maxAttempts: 1, icpayPaymentLink: params.metadata?.icpayPaymentLink });
|
|
1103
1108
|
}
|
|
1104
1109
|
catch { }
|
|
1105
1110
|
const finalQuick = await this.awaitIntentTerminal({
|
|
@@ -1164,6 +1169,13 @@ class Icpay {
|
|
|
1164
1169
|
}
|
|
1165
1170
|
}
|
|
1166
1171
|
catch { }
|
|
1172
|
+
// Resolve current chain id for Base Builder Code attribution (only EVM)
|
|
1173
|
+
let currentChainId = null;
|
|
1174
|
+
try {
|
|
1175
|
+
const currentHex = await eth.request({ method: 'eth_chainId' });
|
|
1176
|
+
currentChainId = parseInt(currentHex, 16);
|
|
1177
|
+
}
|
|
1178
|
+
catch { }
|
|
1167
1179
|
const tokenAddress = params.ledgerCanisterId || null;
|
|
1168
1180
|
const isNative = !tokenAddress || /^0x0{40}$/i.test(String(tokenAddress));
|
|
1169
1181
|
const amountHex = '0x' + params.amount.toString(16);
|
|
@@ -1242,8 +1254,9 @@ class Icpay {
|
|
|
1242
1254
|
throw new errors_1.IcpayError({ code: errors_1.ICPAY_ERROR_CODES.INVALID_CONFIG, message: 'Missing payNative selector from API; update API/chain metadata.' });
|
|
1243
1255
|
}
|
|
1244
1256
|
const data = extSel + idHex + toUint64(accountIdNum) + toUint256(externalCost) + toAddressPadded(recipient);
|
|
1245
|
-
(0,
|
|
1246
|
-
|
|
1257
|
+
const dataWithAttribution = (0, base_builder_code_1.appendBaseBuilderSuffixIfNeeded)(currentChainId, data);
|
|
1258
|
+
(0, utils_1.debugLog)(this.config.debug || false, 'evm native tx', { to: contractAddress, from: owner, dataLen: dataWithAttribution.length, value: amountHex, recipient });
|
|
1259
|
+
txHash = await eth.request({ method: 'eth_sendTransaction', params: [{ from: owner, to: contractAddress, data: dataWithAttribution, value: amountHex }] });
|
|
1247
1260
|
}
|
|
1248
1261
|
else {
|
|
1249
1262
|
// Ensure allowance(owner -> spender=contractAddress)
|
|
@@ -1257,8 +1270,9 @@ class Icpay {
|
|
|
1257
1270
|
(0, utils_1.debugLog)(this.config.debug || false, 'evm erc20 allowance result', { allowance: allowance.toString() });
|
|
1258
1271
|
if (allowance < params.amount) {
|
|
1259
1272
|
const approveData = approveSelector + toAddressPadded(contractAddress) + toUint256(params.amount);
|
|
1260
|
-
(0,
|
|
1261
|
-
|
|
1273
|
+
const approveDataWithAttribution = (0, base_builder_code_1.appendBaseBuilderSuffixIfNeeded)(currentChainId, approveData);
|
|
1274
|
+
(0, utils_1.debugLog)(this.config.debug || false, 'evm erc20 approve', { to: tokenAddress, from: owner, dataLen: approveDataWithAttribution.length, amount: params.amount.toString() });
|
|
1275
|
+
const approveTx = await eth.request({ method: 'eth_sendTransaction', params: [{ from: owner, to: String(tokenAddress), data: approveDataWithAttribution }] });
|
|
1262
1276
|
(0, utils_1.debugLog)(this.config.debug || false, 'evm erc20 approve sent', { tx: approveTx });
|
|
1263
1277
|
await waitForReceipt(approveTx, 90, 1000);
|
|
1264
1278
|
}
|
|
@@ -1271,8 +1285,9 @@ class Icpay {
|
|
|
1271
1285
|
}
|
|
1272
1286
|
const base = idHex + toUint64(accountIdNum) + toAddressPadded(String(tokenAddress)) + toUint256(params.amount) + toUint256(externalCost);
|
|
1273
1287
|
const data = extSel + base + toAddressPadded(recipient);
|
|
1274
|
-
(0,
|
|
1275
|
-
|
|
1288
|
+
const dataWithAttribution = (0, base_builder_code_1.appendBaseBuilderSuffixIfNeeded)(currentChainId, data);
|
|
1289
|
+
(0, utils_1.debugLog)(this.config.debug || false, 'evm erc20 pay', { to: contractAddress, from: owner, token: tokenAddress, dataLen: dataWithAttribution.length, recipient });
|
|
1290
|
+
txHash = await eth.request({ method: 'eth_sendTransaction', params: [{ from: owner, to: contractAddress, data: dataWithAttribution }] });
|
|
1276
1291
|
}
|
|
1277
1292
|
}
|
|
1278
1293
|
catch (e) {
|
|
@@ -1289,7 +1304,7 @@ class Icpay {
|
|
|
1289
1304
|
catch { }
|
|
1290
1305
|
// Inform API immediately with tx hash so it can start indexing
|
|
1291
1306
|
try {
|
|
1292
|
-
await this.performNotifyPaymentIntent({ paymentIntentId: params.paymentIntentId, transactionId: txHash, maxAttempts: 1 });
|
|
1307
|
+
await this.performNotifyPaymentIntent({ paymentIntentId: params.paymentIntentId, transactionId: txHash, maxAttempts: 1, icpayPaymentLink: params.metadata?.icpayPaymentLink });
|
|
1293
1308
|
}
|
|
1294
1309
|
catch { }
|
|
1295
1310
|
const finalResponse = await this.awaitIntentTerminal({
|
|
@@ -1425,6 +1440,40 @@ class Icpay {
|
|
|
1425
1440
|
}
|
|
1426
1441
|
return new Uint8Array(out);
|
|
1427
1442
|
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Resolve payment intent from config or request: use full intent if provided, else fetch by id via API.
|
|
1445
|
+
* Returns null when no pre-provided intent (caller should create via POST).
|
|
1446
|
+
*/
|
|
1447
|
+
async getOrResolvePaymentIntent(request) {
|
|
1448
|
+
const isFullIntent = (o) => o && typeof o === 'object' && o.id && typeof (o.amount === 'string' ? o.amount : String(o.amount ?? '')) === 'string' &&
|
|
1449
|
+
(o.chainType != null || o.chainId != null || (o.ledgerCanisterId != null && o.ledgerCanisterId !== ''));
|
|
1450
|
+
const fromRequest = request?.paymentIntent;
|
|
1451
|
+
const fromConfig = this.config?.paymentIntent;
|
|
1452
|
+
if (fromRequest && isFullIntent(fromRequest)) {
|
|
1453
|
+
(0, utils_1.debugLog)(this.config.debug || false, 'using payment intent from request');
|
|
1454
|
+
return { paymentIntent: fromRequest, onramp: null };
|
|
1455
|
+
}
|
|
1456
|
+
if (fromConfig && isFullIntent(fromConfig)) {
|
|
1457
|
+
(0, utils_1.debugLog)(this.config.debug || false, 'using payment intent from config');
|
|
1458
|
+
return { paymentIntent: fromConfig, onramp: null };
|
|
1459
|
+
}
|
|
1460
|
+
const intentId = (fromRequest && typeof fromRequest.id === 'string' && fromRequest.id) ||
|
|
1461
|
+
(fromConfig && typeof fromConfig.id === 'string' && fromConfig.id) ||
|
|
1462
|
+
(typeof this.config?.paymentIntentId === 'string' && this.config.paymentIntentId) || null;
|
|
1463
|
+
if (intentId && this.publicApiClient) {
|
|
1464
|
+
try {
|
|
1465
|
+
(0, utils_1.debugLog)(this.config.debug || false, 'fetching payment intent by id', { intentId });
|
|
1466
|
+
const resp = await this.publicApiClient.get(`/sdk/public/payments/intents/${encodeURIComponent(intentId)}`);
|
|
1467
|
+
if (resp?.paymentIntent) {
|
|
1468
|
+
return { paymentIntent: resp.paymentIntent, onramp: resp.onramp ?? null };
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
catch (e) {
|
|
1472
|
+
(0, utils_1.debugLog)(this.config.debug || false, 'fetch payment intent failed', e);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
return null;
|
|
1476
|
+
}
|
|
1428
1477
|
/**
|
|
1429
1478
|
* Create a payment to a specific canister/ledger (public method)
|
|
1430
1479
|
* This is now a real transaction
|
|
@@ -1437,17 +1486,18 @@ class Icpay {
|
|
|
1437
1486
|
let ledgerCanisterId = request.ledgerCanisterId;
|
|
1438
1487
|
const tokenShortcode = request?.tokenShortcode;
|
|
1439
1488
|
const isOnrampFlow = (request?.onrampPayment === true) || (this?.config?.onrampPayment === true);
|
|
1440
|
-
|
|
1489
|
+
const hasPaymentIntent = request?.paymentIntent != null || this.config?.paymentIntent != null || (typeof this.config?.paymentIntentId === 'string' && this.config.paymentIntentId);
|
|
1490
|
+
if (!ledgerCanisterId && !tokenShortcode && !request.symbol && !isOnrampFlow && !hasPaymentIntent) {
|
|
1441
1491
|
const err = new errors_1.IcpayError({
|
|
1442
1492
|
code: errors_1.ICPAY_ERROR_CODES.INVALID_CONFIG,
|
|
1443
|
-
message: 'Provide either tokenShortcode or ledgerCanisterId (symbol is deprecated).',
|
|
1493
|
+
message: 'Provide either tokenShortcode or ledgerCanisterId (symbol is deprecated), or pass paymentIntent.',
|
|
1444
1494
|
details: { request }
|
|
1445
1495
|
});
|
|
1446
1496
|
this.emitMethodError('createPayment', err);
|
|
1447
1497
|
throw err;
|
|
1448
1498
|
}
|
|
1449
1499
|
let memo = undefined;
|
|
1450
|
-
// 1)
|
|
1500
|
+
// 1) Resolve or create payment intent: use pre-provided (config/request) or fetch by id, else create via API
|
|
1451
1501
|
let paymentIntentId = null;
|
|
1452
1502
|
let paymentIntentCode = null;
|
|
1453
1503
|
let intentChainType;
|
|
@@ -1455,114 +1505,162 @@ class Icpay {
|
|
|
1455
1505
|
let accountCanisterId;
|
|
1456
1506
|
let resolvedAmountStr = typeof request.amount === 'string' ? request.amount : (request.amount != null ? String(request.amount) : undefined);
|
|
1457
1507
|
let intentResp;
|
|
1508
|
+
let expectedSenderPrincipal;
|
|
1458
1509
|
try {
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
this.
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
const evm = this.config?.evmProvider || globalThis?.ethereum;
|
|
1477
|
-
if (evm?.request) {
|
|
1478
|
-
try {
|
|
1479
|
-
const accounts = await evm.request({ method: 'eth_accounts' });
|
|
1480
|
-
if (Array.isArray(accounts) && accounts[0]) {
|
|
1481
|
-
const lowerAccounts = accounts.map((a) => String(a).toLowerCase());
|
|
1482
|
-
const providedRaw = request?.expectedSenderPrincipal;
|
|
1483
|
-
if (providedRaw) {
|
|
1484
|
-
const provided = String(providedRaw).toLowerCase();
|
|
1485
|
-
expectedSenderPrincipal = lowerAccounts.includes(provided) ? accounts[lowerAccounts.indexOf(provided)] : accounts[0];
|
|
1510
|
+
const meta = request?.metadata || {};
|
|
1511
|
+
const isAtxp = Boolean(meta?.icpay_atxp_request) && typeof (meta?.atxp_request_id) === 'string';
|
|
1512
|
+
const onramp = (request.onrampPayment === true || this.config.onrampPayment === true) && this.config.onrampDisabled !== true ? true : false;
|
|
1513
|
+
// If we have a pre-provided intent or an id to fetch, resolve first (skip create for non-ATXP/non-onramp create flows)
|
|
1514
|
+
if (!isAtxp && !onramp) {
|
|
1515
|
+
const resolved = await this.getOrResolvePaymentIntent(request);
|
|
1516
|
+
if (resolved) {
|
|
1517
|
+
let pi = resolved.paymentIntent;
|
|
1518
|
+
const intentHasToken = (pi?.ledgerId != null) || (pi?.ledgerCanisterId && String(pi.ledgerCanisterId).trim() !== '');
|
|
1519
|
+
if (!intentHasToken) {
|
|
1520
|
+
const tokenToSet = request?.tokenShortcode;
|
|
1521
|
+
if (tokenToSet && typeof tokenToSet === 'string' && this.publicApiClient) {
|
|
1522
|
+
try {
|
|
1523
|
+
await this.publicApiClient.patch(`/sdk/public/payments/intents/${encodeURIComponent(pi.id)}/set-token`, { tokenShortcode: tokenToSet.trim() });
|
|
1524
|
+
const refetched = await this.publicApiClient.get(`/sdk/public/payments/intents/${encodeURIComponent(pi.id)}`);
|
|
1525
|
+
if (refetched?.paymentIntent)
|
|
1526
|
+
pi = refetched.paymentIntent;
|
|
1486
1527
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1528
|
+
catch (e) {
|
|
1529
|
+
(0, utils_1.debugLog)(this.config.debug || false, 'set-token failed', e);
|
|
1530
|
+
throw new errors_1.IcpayError({
|
|
1531
|
+
code: errors_1.ICPAY_ERROR_CODES.API_ERROR,
|
|
1532
|
+
message: 'Payment intent requires a token to be selected. Setting token failed.',
|
|
1533
|
+
details: e,
|
|
1534
|
+
retryable: true,
|
|
1535
|
+
userAction: 'Ensure tokenShortcode is valid and try again',
|
|
1536
|
+
});
|
|
1489
1537
|
}
|
|
1490
1538
|
}
|
|
1539
|
+
else {
|
|
1540
|
+
throw new errors_1.IcpayError({
|
|
1541
|
+
code: errors_1.ICPAY_ERROR_CODES.INVALID_CONFIG,
|
|
1542
|
+
message: 'Payment intent requires a token to be selected before payment. Provide tokenShortcode in the request.',
|
|
1543
|
+
details: { paymentIntentId: pi?.id },
|
|
1544
|
+
retryable: false,
|
|
1545
|
+
userAction: 'Select a token (e.g. pass tokenShortcode) and try again',
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1491
1548
|
}
|
|
1492
|
-
|
|
1549
|
+
intentResp = { paymentIntent: pi, onramp: resolved.onramp };
|
|
1550
|
+
expectedSenderPrincipal = request.expectedSenderPrincipal ||
|
|
1551
|
+
this.connectedWallet?.owner || this.connectedWallet?.principal?.toString();
|
|
1493
1552
|
}
|
|
1494
1553
|
}
|
|
1495
|
-
if (!
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
widgetParams: request.widgetParams || undefined,
|
|
1541
|
-
recipientAddresses: request?.recipientAddresses || undefined,
|
|
1542
|
-
fiat_currency: request?.fiat_currency,
|
|
1554
|
+
if (!intentResp) {
|
|
1555
|
+
(0, utils_1.debugLog)(this.config.debug || false, 'creating payment intent via API');
|
|
1556
|
+
// Resolve expected sender principal:
|
|
1557
|
+
// Start with any value explicitly provided on the request or via connectedWallet.
|
|
1558
|
+
expectedSenderPrincipal =
|
|
1559
|
+
request.expectedSenderPrincipal ||
|
|
1560
|
+
this.connectedWallet?.owner ||
|
|
1561
|
+
this.connectedWallet?.principal?.toString();
|
|
1562
|
+
// If none yet and a Solana provider is present (e.g., Phantom), prefer its publicKey (base58).
|
|
1563
|
+
try {
|
|
1564
|
+
const solProv = this.config?.solanaProvider || globalThis?.solana;
|
|
1565
|
+
const solPk = solProv?.publicKey ? String(solProv.publicKey) : undefined;
|
|
1566
|
+
if (!expectedSenderPrincipal && solPk) {
|
|
1567
|
+
expectedSenderPrincipal = String(solPk);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
catch { }
|
|
1571
|
+
// Only if still missing, fall back to EVM accounts.
|
|
1572
|
+
if (!expectedSenderPrincipal) {
|
|
1573
|
+
const evm = this.config?.evmProvider || globalThis?.ethereum;
|
|
1574
|
+
if (evm?.request) {
|
|
1575
|
+
try {
|
|
1576
|
+
const accounts = await evm.request({ method: 'eth_accounts' });
|
|
1577
|
+
if (Array.isArray(accounts) && accounts[0]) {
|
|
1578
|
+
const lowerAccounts = accounts.map((a) => String(a).toLowerCase());
|
|
1579
|
+
const providedRaw = request?.expectedSenderPrincipal;
|
|
1580
|
+
if (providedRaw) {
|
|
1581
|
+
const provided = String(providedRaw).toLowerCase();
|
|
1582
|
+
expectedSenderPrincipal = lowerAccounts.includes(provided) ? accounts[lowerAccounts.indexOf(provided)] : accounts[0];
|
|
1583
|
+
}
|
|
1584
|
+
else {
|
|
1585
|
+
expectedSenderPrincipal = accounts[0];
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
catch { }
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
if (!expectedSenderPrincipal && !((request?.onrampPayment === true) || (this?.config?.onrampPayment === true))) {
|
|
1593
|
+
throw new errors_1.IcpayError({
|
|
1594
|
+
code: errors_1.ICPAY_ERROR_CODES.WALLET_NOT_CONNECTED,
|
|
1595
|
+
message: 'Wallet must be connected to create payment intent',
|
|
1596
|
+
details: { connectedWallet: this.connectedWallet },
|
|
1597
|
+
retryable: false,
|
|
1598
|
+
userAction: 'Connect your wallet first'
|
|
1543
1599
|
});
|
|
1544
1600
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1601
|
+
const onramp = (request.onrampPayment === true || this.config.onrampPayment === true) && this.config.onrampDisabled !== true ? true : false;
|
|
1602
|
+
const meta = request?.metadata || {};
|
|
1603
|
+
const isAtxp = Boolean(meta?.icpay_atxp_request) && typeof (meta?.atxp_request_id) === 'string';
|
|
1604
|
+
// Resolve recipientAddress only for non-onramp flows
|
|
1605
|
+
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
1606
|
+
const reqAny = request;
|
|
1607
|
+
const addrObj = (reqAny?.recipientAddresses) || {};
|
|
1608
|
+
const candidateEvm = addrObj.evm ? addrObj.evm : undefined;
|
|
1609
|
+
const candidateIC = addrObj.ic ? addrObj.ic : undefined;
|
|
1610
|
+
const candidateSol = addrObj.sol ? addrObj.sol : undefined;
|
|
1611
|
+
let recipientAddress = undefined;
|
|
1612
|
+
if (!onramp) {
|
|
1613
|
+
// Choose a default to persist on the intent; EVM will override to ZERO if non-hex when building tx
|
|
1614
|
+
recipientAddress = (reqAny?.recipientAddress) || candidateEvm || candidateIC || candidateSol || ZERO_ADDRESS;
|
|
1615
|
+
(0, utils_1.debugLog)(this.config.debug || false, 'recipientAddress resolved for intent', { recipientAddress });
|
|
1616
|
+
}
|
|
1617
|
+
if (isAtxp) {
|
|
1618
|
+
// Route ATXP intents to the ATXP endpoint so they link to the request
|
|
1619
|
+
const atxpRequestId = String(meta.atxp_request_id);
|
|
1620
|
+
const endpoint = `/sdk/public/atxp/requests/${encodeURIComponent(atxpRequestId)}/payment-intents`;
|
|
1621
|
+
intentResp = await this.publicApiClient.post(endpoint, {
|
|
1549
1622
|
tokenShortcode: tokenShortcode || undefined,
|
|
1550
|
-
// Legacy fields for backwards compatibility
|
|
1551
|
-
symbol: tokenShortcode ? undefined : request.symbol,
|
|
1552
|
-
ledgerCanisterId: tokenShortcode ? undefined : ledgerCanisterId,
|
|
1553
1623
|
description: request.description,
|
|
1554
|
-
expectedSenderPrincipal,
|
|
1555
|
-
metadata: normalizeSdkMetadata(request.metadata || {}),
|
|
1556
|
-
amountUsd: request.amountUsd,
|
|
1557
|
-
// With tokenShortcode, backend derives chain. Keep legacy chainId for old flows.
|
|
1558
|
-
chainId: tokenShortcode ? undefined : request.chainId,
|
|
1559
|
-
widgetParams: request.widgetParams || undefined,
|
|
1560
1624
|
recipientAddress,
|
|
1561
1625
|
recipientAddresses: request?.recipientAddresses || undefined,
|
|
1562
1626
|
externalCostAmount: request?.externalCostAmount ?? request?.metadata?.externalCostAmount ?? undefined,
|
|
1563
1627
|
fiat_currency: request?.fiat_currency,
|
|
1564
1628
|
});
|
|
1565
1629
|
}
|
|
1630
|
+
else {
|
|
1631
|
+
if (onramp) {
|
|
1632
|
+
// Route onramp flows to the dedicated onramp endpoint without requiring token/ledger
|
|
1633
|
+
intentResp = await this.publicApiClient.post('/sdk/public/onramp/intents', {
|
|
1634
|
+
usdAmount: request.amountUsd,
|
|
1635
|
+
description: request.description,
|
|
1636
|
+
metadata: normalizeSdkMetadata(request.metadata || {}),
|
|
1637
|
+
widgetParams: request.widgetParams || undefined,
|
|
1638
|
+
recipientAddresses: request?.recipientAddresses || undefined,
|
|
1639
|
+
fiat_currency: request?.fiat_currency,
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
else {
|
|
1643
|
+
intentResp = await this.publicApiClient.post('/sdk/public/payments/intents', {
|
|
1644
|
+
amount: (typeof request.amount === 'string' ? request.amount : (request.amount != null ? String(request.amount) : undefined)),
|
|
1645
|
+
// Prefer tokenShortcode if provided
|
|
1646
|
+
tokenShortcode: tokenShortcode || undefined,
|
|
1647
|
+
// Legacy fields for backwards compatibility
|
|
1648
|
+
symbol: tokenShortcode ? undefined : request.symbol,
|
|
1649
|
+
ledgerCanisterId: tokenShortcode ? undefined : ledgerCanisterId,
|
|
1650
|
+
description: request.description,
|
|
1651
|
+
expectedSenderPrincipal,
|
|
1652
|
+
metadata: normalizeSdkMetadata(request.metadata || {}),
|
|
1653
|
+
amountUsd: request.amountUsd,
|
|
1654
|
+
// With tokenShortcode, backend derives chain. Keep legacy chainId for old flows.
|
|
1655
|
+
chainId: tokenShortcode ? undefined : request.chainId,
|
|
1656
|
+
widgetParams: request.widgetParams || undefined,
|
|
1657
|
+
recipientAddress,
|
|
1658
|
+
recipientAddresses: request?.recipientAddresses || undefined,
|
|
1659
|
+
externalCostAmount: request?.externalCostAmount ?? request?.metadata?.externalCostAmount ?? undefined,
|
|
1660
|
+
fiat_currency: request?.fiat_currency,
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1566
1664
|
}
|
|
1567
1665
|
paymentIntentId = intentResp?.paymentIntent?.id || null;
|
|
1568
1666
|
paymentIntentCode = intentResp?.paymentIntent?.intentCode ?? null;
|
|
@@ -2251,6 +2349,12 @@ class Icpay {
|
|
|
2251
2349
|
// We forward both amountUsd and amount (if provided), and do not resolve canister here.
|
|
2252
2350
|
const ledgerCanisterId = request.ledgerCanisterId || '';
|
|
2253
2351
|
const tokenShortcode = request?.tokenShortcode;
|
|
2352
|
+
// Resolve existing payment intent id so API reuses it instead of creating a second intent (e.g. pay link page)
|
|
2353
|
+
const existingIntentId = (typeof request?.paymentIntentId === 'string' && request.paymentIntentId) ||
|
|
2354
|
+
(typeof request?.paymentIntent?.id === 'string' && request.paymentIntent.id) ||
|
|
2355
|
+
(typeof this.config?.paymentIntentId === 'string' && this.config.paymentIntentId) ||
|
|
2356
|
+
(typeof this.config?.paymentIntent?.id === 'string' && this.config.paymentIntent.id) ||
|
|
2357
|
+
null;
|
|
2254
2358
|
// Hit X402 endpoint
|
|
2255
2359
|
const body = {
|
|
2256
2360
|
amount: request.amount,
|
|
@@ -2266,6 +2370,9 @@ class Icpay {
|
|
|
2266
2370
|
recipientAddress: request?.recipientAddress || '0x0000000000000000000000000000000000000000',
|
|
2267
2371
|
fiat_currency: request?.fiat_currency ?? request?.fiatCurrency ?? this.config?.fiat_currency,
|
|
2268
2372
|
};
|
|
2373
|
+
if (existingIntentId) {
|
|
2374
|
+
body.paymentIntentId = existingIntentId;
|
|
2375
|
+
}
|
|
2269
2376
|
if (body.fiat_currency === undefined || body.fiat_currency === '') {
|
|
2270
2377
|
delete body.fiat_currency;
|
|
2271
2378
|
}
|
|
@@ -2589,7 +2696,7 @@ class Icpay {
|
|
|
2589
2696
|
catch { }
|
|
2590
2697
|
if (sig) {
|
|
2591
2698
|
try {
|
|
2592
|
-
await this.performNotifyPaymentIntent({ paymentIntentId, transactionId: sig, maxAttempts: 1 });
|
|
2699
|
+
await this.performNotifyPaymentIntent({ paymentIntentId, transactionId: sig, maxAttempts: 1, icpayPaymentLink: request.metadata?.icpayPaymentLink });
|
|
2593
2700
|
}
|
|
2594
2701
|
catch { }
|
|
2595
2702
|
}
|
|
@@ -3857,6 +3964,8 @@ class Icpay {
|
|
|
3857
3964
|
body.externalCostAmount = params.externalCostAmount;
|
|
3858
3965
|
if (typeof params.recipientPrincipal === 'string')
|
|
3859
3966
|
body.recipientPrincipal = params.recipientPrincipal;
|
|
3967
|
+
if (params.icpayPaymentLink && typeof params.icpayPaymentLink === 'object')
|
|
3968
|
+
body.icpayPaymentLink = params.icpayPaymentLink;
|
|
3860
3969
|
const resp = await notifyClient.post(notifyPath, body);
|
|
3861
3970
|
// If this is the last attempt, return whatever we got
|
|
3862
3971
|
if (attempt === maxAttempts) {
|
|
@@ -3908,6 +4017,7 @@ class Icpay {
|
|
|
3908
4017
|
accountCanisterId: params.accountCanisterId,
|
|
3909
4018
|
externalCostAmount: params.externalCostAmount,
|
|
3910
4019
|
recipientPrincipal: params.recipientPrincipal,
|
|
4020
|
+
icpayPaymentLink: params.metadata?.icpayPaymentLink,
|
|
3911
4021
|
});
|
|
3912
4022
|
const status = resp?.paymentIntent?.status || resp?.payment?.status || resp?.status || '';
|
|
3913
4023
|
const norm = typeof status === 'string' ? status.toLowerCase() : '';
|