@tagadapay/headless-sdk 1.20.0 → 1.21.0
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/CHANGELOG.md +56 -0
- package/README.md +2 -0
- package/dist/core/helpers/cryptoOnchain.d.ts +83 -0
- package/dist/core/helpers/cryptoOnchain.d.ts.map +1 -0
- package/dist/core/helpers/cryptoOnchain.js +247 -0
- package/dist/core/helpers/cryptoOnchain.js.map +1 -0
- package/dist/core/helpers/oceanpayment.d.ts +33 -0
- package/dist/core/helpers/oceanpayment.d.ts.map +1 -0
- package/dist/core/helpers/oceanpayment.js +111 -0
- package/dist/core/helpers/oceanpayment.js.map +1 -0
- package/dist/core/helpers/paymentsApi.d.ts +139 -0
- package/dist/core/helpers/paymentsApi.d.ts.map +1 -0
- package/dist/core/helpers/paymentsApi.js +287 -0
- package/dist/core/helpers/paymentsApi.js.map +1 -0
- package/dist/core/helpers/paymentsApiAnswer.d.ts +22 -0
- package/dist/core/helpers/paymentsApiAnswer.d.ts.map +1 -0
- package/dist/core/helpers/paymentsApiAnswer.js +51 -0
- package/dist/core/helpers/paymentsApiAnswer.js.map +1 -0
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/http.js +3 -0
- package/dist/core/http.js.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/modules/checkout.d.ts.map +1 -1
- package/dist/core/modules/checkout.js +1 -0
- package/dist/core/modules/checkout.js.map +1 -1
- package/dist/core/modules/payment.d.ts +74 -1
- package/dist/core/modules/payment.d.ts.map +1 -1
- package/dist/core/modules/payment.js +331 -8
- package/dist/core/modules/payment.js.map +1 -1
- package/dist/core/modules/portal/types.d.ts +2 -0
- package/dist/core/modules/portal/types.d.ts.map +1 -1
- package/dist/core/types.d.ts +164 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/pixels/pixelMapping.d.ts.map +1 -1
- package/dist/pixels/pixelMapping.js +9 -1
- package/dist/pixels/pixelMapping.js.map +1 -1
- package/dist/react/hooks/useCryptoOnchainPayment.d.ts +40 -0
- package/dist/react/hooks/useCryptoOnchainPayment.d.ts.map +1 -0
- package/dist/react/hooks/useCryptoOnchainPayment.js +77 -0
- package/dist/react/hooks/useCryptoOnchainPayment.js.map +1 -0
- package/dist/react/hooks/usePayment.d.ts.map +1 -1
- package/dist/react/hooks/usePayment.js +3 -0
- package/dist/react/hooks/usePayment.js.map +1 -1
- package/dist/react/index.d.ts +4 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -0
- package/dist/react/index.js.map +1 -1
- package/dist/tagada-headless-commerce.min.js +1 -1
- package/dist/tagada-headless-react.min.js +2 -2
- package/dist/tagada-headless-react.min.js.map +4 -4
- package/dist/tagada-headless.js +685 -16
- package/dist/tagada-headless.min.js +2 -2
- package/dist/tagada-headless.min.js.map +4 -4
- package/dist/tagada-pixels.min.js +2 -2
- package/dist/tagada-pixels.min.js.map +3 -3
- package/package.json +2 -2
package/dist/tagada-headless.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @tagadapay/headless-sdk v1.
|
|
2
|
+
* @tagadapay/headless-sdk v1.21.0
|
|
3
3
|
* Headless checkout, payments, offers, and CRM
|
|
4
4
|
* @license MIT
|
|
5
5
|
*/
|
|
@@ -44,8 +44,12 @@ var TagadaHeadlessBundle = (() => {
|
|
|
44
44
|
var core_exports = {};
|
|
45
45
|
__export(core_exports, {
|
|
46
46
|
AVAILABLE_LANGUAGES: () => AVAILABLE_LANGUAGES,
|
|
47
|
+
CRYPTO_ONCHAIN_FINAL_PHASES: () => CRYPTO_ONCHAIN_FINAL_PHASES,
|
|
48
|
+
CRYPTO_ONCHAIN_PAYMENT_METHOD: () => CRYPTO_ONCHAIN_PAYMENT_METHOD,
|
|
49
|
+
CRYPTO_ONCHAIN_STATUS_POLL_MS: () => CRYPTO_ONCHAIN_STATUS_POLL_MS,
|
|
47
50
|
CatalogModule: () => CatalogModule,
|
|
48
51
|
CheckoutModule: () => CheckoutModule,
|
|
52
|
+
CryptoOnchainCheckout: () => CryptoOnchainCheckout,
|
|
49
53
|
CurrencyUtils: () => CurrencyUtils,
|
|
50
54
|
CustomerModule: () => CustomerModule,
|
|
51
55
|
CustomerPortalModule: () => CustomerPortalModule,
|
|
@@ -67,14 +71,20 @@ var TagadaHeadlessBundle = (() => {
|
|
|
67
71
|
TagadaValidationError: () => TagadaValidationError,
|
|
68
72
|
TrackModule: () => TrackModule,
|
|
69
73
|
createHeadlessClient: () => createHeadlessClient,
|
|
74
|
+
cryptoOnchainPhase: () => cryptoOnchainPhase,
|
|
75
|
+
cryptoOnchainSecondsLeft: () => cryptoOnchainSecondsLeft,
|
|
76
|
+
defaultCryptoOnchainSelection: () => defaultCryptoOnchainSelection,
|
|
70
77
|
ensureGeoDataLoaded: () => ensureGeoDataLoaded,
|
|
78
|
+
formatCryptoOnchainCountdown: () => formatCryptoOnchainCountdown,
|
|
71
79
|
formatMoney: () => formatMoney,
|
|
72
80
|
getCountries: () => getCountries,
|
|
73
81
|
getStatesForCountry: () => getStatesForCountry,
|
|
82
|
+
isCryptoOnchainMethod: () => isCryptoOnchainMethod,
|
|
74
83
|
isLanguageLoaded: () => isLanguageLoaded,
|
|
75
84
|
isQuestionActive: () => isQuestionActive,
|
|
76
85
|
loadGoogleMapsScript: () => loadGoogleMapsScript,
|
|
77
86
|
parseTokensFromUrl: () => parseTokensFromUrl,
|
|
87
|
+
selectCryptoOnchainNetwork: () => selectCryptoOnchainNetwork,
|
|
78
88
|
toIntakeAnswers: () => toIntakeAnswers
|
|
79
89
|
});
|
|
80
90
|
|
|
@@ -130,6 +140,10 @@ var TagadaHeadlessBundle = (() => {
|
|
|
130
140
|
}
|
|
131
141
|
};
|
|
132
142
|
|
|
143
|
+
// src/core/version.ts
|
|
144
|
+
var SDK_NAME = "headless";
|
|
145
|
+
var SDK_VERSION = "1.21.0";
|
|
146
|
+
|
|
133
147
|
// src/core/http.ts
|
|
134
148
|
var BASE_URLS = {
|
|
135
149
|
production: "https://api.tagada.io",
|
|
@@ -163,6 +177,8 @@ var TagadaHeadlessBundle = (() => {
|
|
|
163
177
|
}
|
|
164
178
|
const reqHeaders = {
|
|
165
179
|
"Content-Type": "application/json",
|
|
180
|
+
// Which SDK and version sent the call: the Payments API readiness check counts them per store.
|
|
181
|
+
"X-Tagada-SDK": `headless-sdk/${SDK_VERSION}`,
|
|
166
182
|
...headers
|
|
167
183
|
};
|
|
168
184
|
const authToken = token ?? this.apiKey ?? ((_a = this.getToken) == null ? void 0 : _a.call(this));
|
|
@@ -708,7 +724,8 @@ var TagadaHeadlessBundle = (() => {
|
|
|
708
724
|
shippingAddress: this.normalizeAddress(cs.shippingAddress),
|
|
709
725
|
billingAddress: this.normalizeAddress(cs.billingAddress),
|
|
710
726
|
selectedShippingRateId: cs.shippingRateId ?? null,
|
|
711
|
-
metadata: cs.metadata
|
|
727
|
+
metadata: cs.metadata,
|
|
728
|
+
...typeof cs.draft === "boolean" ? { draft: cs.draft } : {}
|
|
712
729
|
};
|
|
713
730
|
}
|
|
714
731
|
/**
|
|
@@ -787,12 +804,8 @@ var TagadaHeadlessBundle = (() => {
|
|
|
787
804
|
return Boolean(((_a = address.line1) == null ? void 0 : _a.trim()) && ((_b = address.country) == null ? void 0 : _b.trim()));
|
|
788
805
|
}
|
|
789
806
|
|
|
790
|
-
// src/core/version.ts
|
|
791
|
-
var SDK_NAME = "headless";
|
|
792
|
-
var SDK_VERSION = "1.20.0";
|
|
793
|
-
|
|
794
807
|
// src/core/modules/track.ts
|
|
795
|
-
var CDN_URL = false ? "" : "https://cdn.jsdelivr.net/npm/@tagadapay/headless-sdk@1.
|
|
808
|
+
var CDN_URL = false ? "" : "https://cdn.jsdelivr.net/npm/@tagadapay/headless-sdk@1.21.0/dist/tagada-pixels.min.js";
|
|
796
809
|
function loadScript(src, isReady) {
|
|
797
810
|
if (isReady())
|
|
798
811
|
return Promise.resolve();
|
|
@@ -1188,7 +1201,369 @@ var TagadaHeadlessBundle = (() => {
|
|
|
1188
1201
|
}
|
|
1189
1202
|
};
|
|
1190
1203
|
|
|
1204
|
+
// src/core/helpers/cryptoOnchain.ts
|
|
1205
|
+
var CRYPTO_ONCHAIN_PAYMENT_METHOD = "crypto_onchain";
|
|
1206
|
+
var CRYPTO_ONCHAIN_STATUS_POLL_MS = 5e3;
|
|
1207
|
+
var CRYPTO_ONCHAIN_FINAL_PHASES = ["paid", "underpaid", "failed", "expired"];
|
|
1208
|
+
function isCryptoOnchainMethod(method) {
|
|
1209
|
+
if (!method)
|
|
1210
|
+
return false;
|
|
1211
|
+
return method.trim().toLowerCase().replace(/-/g, "_").split(":")[0] === CRYPTO_ONCHAIN_PAYMENT_METHOD;
|
|
1212
|
+
}
|
|
1213
|
+
function defaultCryptoOnchainSelection(networks) {
|
|
1214
|
+
const usable = networks.filter((network2) => network2.assets.length > 0);
|
|
1215
|
+
const network = usable.find((candidate) => !candidate.testnet) ?? usable[0];
|
|
1216
|
+
if (!network)
|
|
1217
|
+
return null;
|
|
1218
|
+
const asset = network.assets.find((candidate) => candidate.stable) ?? network.assets[0];
|
|
1219
|
+
return { chainId: network.chainId, asset: asset.symbol };
|
|
1220
|
+
}
|
|
1221
|
+
function selectCryptoOnchainNetwork(networks, chainId, currentAsset) {
|
|
1222
|
+
var _a, _b;
|
|
1223
|
+
const assets = ((_a = networks.find((network) => network.chainId === chainId)) == null ? void 0 : _a.assets) ?? [];
|
|
1224
|
+
const keep = assets.find((asset) => asset.symbol === currentAsset);
|
|
1225
|
+
const fallback = assets.find((asset) => asset.stable) ?? assets[0];
|
|
1226
|
+
return { chainId, asset: ((_b = keep ?? fallback) == null ? void 0 : _b.symbol) ?? currentAsset };
|
|
1227
|
+
}
|
|
1228
|
+
function cryptoOnchainPhase(status) {
|
|
1229
|
+
if (!status)
|
|
1230
|
+
return "waiting";
|
|
1231
|
+
if (status.paid)
|
|
1232
|
+
return "paid";
|
|
1233
|
+
switch (status.status) {
|
|
1234
|
+
case "detected":
|
|
1235
|
+
case "overpaid":
|
|
1236
|
+
return "confirming";
|
|
1237
|
+
case "underpaid":
|
|
1238
|
+
return "underpaid";
|
|
1239
|
+
case "failed":
|
|
1240
|
+
return "failed";
|
|
1241
|
+
case "expired":
|
|
1242
|
+
return "expired";
|
|
1243
|
+
default:
|
|
1244
|
+
return status.expired ? "expired" : "waiting";
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
function cryptoOnchainSecondsLeft(expiresAt, nowMs) {
|
|
1248
|
+
if (!expiresAt)
|
|
1249
|
+
return 0;
|
|
1250
|
+
const end = Date.parse(expiresAt);
|
|
1251
|
+
if (Number.isNaN(end))
|
|
1252
|
+
return 0;
|
|
1253
|
+
return Math.max(0, Math.floor((end - nowMs) / 1e3));
|
|
1254
|
+
}
|
|
1255
|
+
function formatCryptoOnchainCountdown(seconds) {
|
|
1256
|
+
const s = Math.max(0, Math.floor(seconds));
|
|
1257
|
+
const h = Math.floor(s / 3600);
|
|
1258
|
+
const m = Math.floor(s % 3600 / 60);
|
|
1259
|
+
const sec = String(s % 60).padStart(2, "0");
|
|
1260
|
+
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${sec}` : `${m}:${sec}`;
|
|
1261
|
+
}
|
|
1262
|
+
var UNAVAILABLE = "Crypto payments are not available right now. Choose another payment method.";
|
|
1263
|
+
var CryptoOnchainCheckout = class {
|
|
1264
|
+
constructor(api, options) {
|
|
1265
|
+
this.api = api;
|
|
1266
|
+
this.options = options;
|
|
1267
|
+
this.state = {
|
|
1268
|
+
options: "idle",
|
|
1269
|
+
networks: [],
|
|
1270
|
+
selection: null,
|
|
1271
|
+
payment: null,
|
|
1272
|
+
paymentStatus: null,
|
|
1273
|
+
isCreating: false,
|
|
1274
|
+
error: null
|
|
1275
|
+
};
|
|
1276
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
1277
|
+
this.pollTimer = null;
|
|
1278
|
+
this.stopped = false;
|
|
1279
|
+
this.paidNotified = false;
|
|
1280
|
+
this.subscribe = this.subscribe.bind(this);
|
|
1281
|
+
this.getState = this.getState.bind(this);
|
|
1282
|
+
}
|
|
1283
|
+
getState() {
|
|
1284
|
+
return this.state;
|
|
1285
|
+
}
|
|
1286
|
+
/** Listens to every state change; returns the unsubscribe function. */
|
|
1287
|
+
subscribe(listener) {
|
|
1288
|
+
this.listeners.add(listener);
|
|
1289
|
+
return () => {
|
|
1290
|
+
this.listeners.delete(listener);
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
/** What to show: the polled phase, or `expired` once an unpaid quote ran out (renew with `pay()`). */
|
|
1294
|
+
getPhase(nowMs = Date.now()) {
|
|
1295
|
+
const { payment, paymentStatus } = this.state;
|
|
1296
|
+
const phase = cryptoOnchainPhase(paymentStatus ?? (payment ? { status: payment.status, paid: false, expired: false } : null));
|
|
1297
|
+
const quoteOver = payment !== null && phase === "waiting" && cryptoOnchainSecondsLeft(payment.expiresAt, nowMs) === 0;
|
|
1298
|
+
return quoteOver ? "expired" : phase;
|
|
1299
|
+
}
|
|
1300
|
+
/** Whole seconds left on the current quote. */
|
|
1301
|
+
getSecondsLeft(nowMs = Date.now()) {
|
|
1302
|
+
var _a;
|
|
1303
|
+
return cryptoOnchainSecondsLeft(((_a = this.state.payment) == null ? void 0 : _a.expiresAt) ?? null, nowMs);
|
|
1304
|
+
}
|
|
1305
|
+
/** Loads the networks and coins and picks the default network and coin. */
|
|
1306
|
+
async loadOptions() {
|
|
1307
|
+
this.setState({ options: "loading", error: null });
|
|
1308
|
+
try {
|
|
1309
|
+
const result = await this.api.getCryptoOnchainOptions(this.options.checkoutSessionId);
|
|
1310
|
+
if (!result.ok) {
|
|
1311
|
+
this.setState({ options: "unavailable", networks: [], selection: null, error: result.message || UNAVAILABLE });
|
|
1312
|
+
return this.state;
|
|
1313
|
+
}
|
|
1314
|
+
const selection = this.state.selection ?? defaultCryptoOnchainSelection(result.networks);
|
|
1315
|
+
this.setState(
|
|
1316
|
+
selection ? { options: "ready", networks: result.networks, selection } : { options: "unavailable", networks: result.networks, selection: null, error: UNAVAILABLE }
|
|
1317
|
+
);
|
|
1318
|
+
} catch (error) {
|
|
1319
|
+
this.setState({ options: "unavailable", error: messageOf(error) });
|
|
1320
|
+
}
|
|
1321
|
+
return this.state;
|
|
1322
|
+
}
|
|
1323
|
+
/** Changes the pick. A new network keeps the coin when it offers it, else takes its default coin. */
|
|
1324
|
+
select(next) {
|
|
1325
|
+
const current = this.state.selection;
|
|
1326
|
+
if (next.chainId !== void 0 && next.chainId !== (current == null ? void 0 : current.chainId)) {
|
|
1327
|
+
this.setState({ selection: selectCryptoOnchainNetwork(this.state.networks, next.chainId, next.asset ?? (current == null ? void 0 : current.asset) ?? "") });
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
if (next.asset !== void 0 && current)
|
|
1331
|
+
this.setState({ selection: { ...current, asset: next.asset } });
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Creates the payment for the pick (or `selection`), or re-quotes it: another coin, or a quote
|
|
1335
|
+
* that expired. Save the customer and address on the session first. Starts polling.
|
|
1336
|
+
*/
|
|
1337
|
+
async pay(selection) {
|
|
1338
|
+
const target = selection ?? this.state.selection;
|
|
1339
|
+
if (!target)
|
|
1340
|
+
return { ok: false, code: "ASSET_NOT_AVAILABLE", message: "Choose a network and a coin" };
|
|
1341
|
+
if (this.state.isCreating)
|
|
1342
|
+
return { ok: false, code: "CHECKOUT_FAILED", message: "A payment is already being created" };
|
|
1343
|
+
this.setState({ isCreating: true, error: null, selection: target });
|
|
1344
|
+
try {
|
|
1345
|
+
const result = await this.api.createCryptoOnchainPayment({
|
|
1346
|
+
checkoutSessionId: this.options.checkoutSessionId,
|
|
1347
|
+
chainId: target.chainId,
|
|
1348
|
+
asset: target.asset
|
|
1349
|
+
});
|
|
1350
|
+
if (!result.ok) {
|
|
1351
|
+
this.setState({ isCreating: false, error: result.message });
|
|
1352
|
+
return result;
|
|
1353
|
+
}
|
|
1354
|
+
this.stopped = false;
|
|
1355
|
+
this.setState({ isCreating: false, payment: result.payment, paymentStatus: null });
|
|
1356
|
+
this.schedulePoll();
|
|
1357
|
+
return result;
|
|
1358
|
+
} catch (error) {
|
|
1359
|
+
const message = messageOf(error);
|
|
1360
|
+
this.setState({ isCreating: false, error: message });
|
|
1361
|
+
return { ok: false, code: "CHECKOUT_FAILED", message };
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
/** Polls the status once now (the timer keeps polling until the payment is final). */
|
|
1365
|
+
async refreshStatus() {
|
|
1366
|
+
var _a, _b, _c;
|
|
1367
|
+
const payment = this.state.payment;
|
|
1368
|
+
if (!payment)
|
|
1369
|
+
return null;
|
|
1370
|
+
try {
|
|
1371
|
+
const result = await this.api.getCryptoOnchainStatus({
|
|
1372
|
+
checkoutSessionId: this.options.checkoutSessionId,
|
|
1373
|
+
cryptoOnchainPaymentId: payment.cryptoOnchainPaymentId
|
|
1374
|
+
});
|
|
1375
|
+
if (!result.ok || ((_a = this.state.payment) == null ? void 0 : _a.cryptoOnchainPaymentId) !== payment.cryptoOnchainPaymentId)
|
|
1376
|
+
return null;
|
|
1377
|
+
const status = {
|
|
1378
|
+
status: result.status,
|
|
1379
|
+
paymentId: result.paymentId,
|
|
1380
|
+
orderId: result.orderId,
|
|
1381
|
+
confirmations: result.confirmations,
|
|
1382
|
+
requiredConfirmations: result.requiredConfirmations,
|
|
1383
|
+
txHash: result.txHash,
|
|
1384
|
+
explorerTxUrl: result.explorerTxUrl,
|
|
1385
|
+
paid: result.paid,
|
|
1386
|
+
expired: result.expired,
|
|
1387
|
+
redirectUrl: result.redirectUrl
|
|
1388
|
+
};
|
|
1389
|
+
this.setState({ paymentStatus: status });
|
|
1390
|
+
if (status.paid && !this.paidNotified) {
|
|
1391
|
+
this.paidNotified = true;
|
|
1392
|
+
(_c = (_b = this.options).onPaid) == null ? void 0 : _c.call(_b, status);
|
|
1393
|
+
}
|
|
1394
|
+
return status;
|
|
1395
|
+
} catch {
|
|
1396
|
+
return null;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
/** Polls again after `stop()`, while a payment is open (the customer came back to crypto). */
|
|
1400
|
+
resume() {
|
|
1401
|
+
if (!this.state.payment || CRYPTO_ONCHAIN_FINAL_PHASES.includes(this.getPhase()))
|
|
1402
|
+
return;
|
|
1403
|
+
this.stopped = false;
|
|
1404
|
+
this.schedulePoll();
|
|
1405
|
+
}
|
|
1406
|
+
/** Stops polling (unmount, or the customer chose another payment method). */
|
|
1407
|
+
stop() {
|
|
1408
|
+
this.stopped = true;
|
|
1409
|
+
if (this.pollTimer)
|
|
1410
|
+
clearTimeout(this.pollTimer);
|
|
1411
|
+
this.pollTimer = null;
|
|
1412
|
+
}
|
|
1413
|
+
schedulePoll() {
|
|
1414
|
+
if (this.pollTimer)
|
|
1415
|
+
clearTimeout(this.pollTimer);
|
|
1416
|
+
this.pollTimer = setTimeout(async () => {
|
|
1417
|
+
this.pollTimer = null;
|
|
1418
|
+
await this.refreshStatus();
|
|
1419
|
+
if (!this.stopped && !CRYPTO_ONCHAIN_FINAL_PHASES.includes(this.getPhase()))
|
|
1420
|
+
this.schedulePoll();
|
|
1421
|
+
}, this.options.pollIntervalMs ?? CRYPTO_ONCHAIN_STATUS_POLL_MS);
|
|
1422
|
+
}
|
|
1423
|
+
setState(patch) {
|
|
1424
|
+
var _a, _b;
|
|
1425
|
+
this.state = { ...this.state, ...patch };
|
|
1426
|
+
(_b = (_a = this.options).onChange) == null ? void 0 : _b.call(_a, this.state);
|
|
1427
|
+
this.listeners.forEach((listener) => listener(this.state));
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
function messageOf(error) {
|
|
1431
|
+
return error instanceof Error && error.message ? error.message : UNAVAILABLE;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
// src/core/helpers/paymentsApiAnswer.ts
|
|
1435
|
+
function readStartedPayment(answer) {
|
|
1436
|
+
if (!answer || typeof answer !== "object" || Array.isArray(answer))
|
|
1437
|
+
return null;
|
|
1438
|
+
const { payment, order, paymentId, clientSecret, amount, currency, checkoutSessionId } = answer;
|
|
1439
|
+
if (payment !== void 0 || order !== void 0)
|
|
1440
|
+
return null;
|
|
1441
|
+
if (typeof paymentId !== "string" || !paymentId || typeof clientSecret !== "string" || !clientSecret)
|
|
1442
|
+
return null;
|
|
1443
|
+
return {
|
|
1444
|
+
paymentId,
|
|
1445
|
+
clientSecret,
|
|
1446
|
+
...typeof amount === "number" ? { amount } : {},
|
|
1447
|
+
...typeof currency === "string" ? { currency } : {},
|
|
1448
|
+
...typeof checkoutSessionId === "string" ? { checkoutSessionId } : {}
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
var PENDING_KEY = "tgd_payments_api_return";
|
|
1452
|
+
var PENDING_TTL_MS = 60 * 6e4;
|
|
1453
|
+
function takeParkedPaymentsApiReturn(paymentId) {
|
|
1454
|
+
try {
|
|
1455
|
+
const raw = sessionStorage.getItem(PENDING_KEY);
|
|
1456
|
+
if (!raw)
|
|
1457
|
+
return null;
|
|
1458
|
+
const parked = JSON.parse(raw);
|
|
1459
|
+
if (parked.paymentId !== paymentId)
|
|
1460
|
+
return null;
|
|
1461
|
+
sessionStorage.removeItem(PENDING_KEY);
|
|
1462
|
+
if (!parked.checkoutSessionId || !parked.at || Date.now() - parked.at > PENDING_TTL_MS)
|
|
1463
|
+
return null;
|
|
1464
|
+
return { checkoutSessionId: parked.checkoutSessionId };
|
|
1465
|
+
} catch {
|
|
1466
|
+
return null;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
// src/core/helpers/oceanpayment.ts
|
|
1471
|
+
var OCEAN_SDK_URL = "https://secure.oceanpayment.com/pages/js/oceanpayment.js";
|
|
1472
|
+
var OCEAN_CONTAINER_ID = "oceanpayment-element";
|
|
1473
|
+
var OCEAN_CALLBACK_TIMEOUT_MS = 12e4;
|
|
1474
|
+
var OCEAN_LANGUAGE_OVERRIDES = { fi: "en" };
|
|
1475
|
+
function oceanLanguage(locale) {
|
|
1476
|
+
var _a;
|
|
1477
|
+
const primary = (_a = locale == null ? void 0 : locale.split(/[-_]/)[0]) == null ? void 0 : _a.toLowerCase();
|
|
1478
|
+
if (!primary)
|
|
1479
|
+
return "en";
|
|
1480
|
+
return OCEAN_LANGUAGE_OVERRIDES[primary] ?? primary;
|
|
1481
|
+
}
|
|
1482
|
+
var OCEAN_SDK_LOAD_TIMEOUT_MS = 3e4;
|
|
1483
|
+
var oceanSdkLoading = null;
|
|
1484
|
+
function loadOceanSdk() {
|
|
1485
|
+
const w = window;
|
|
1486
|
+
if (w.Oceanpayment)
|
|
1487
|
+
return Promise.resolve(w.Oceanpayment);
|
|
1488
|
+
if (oceanSdkLoading)
|
|
1489
|
+
return oceanSdkLoading;
|
|
1490
|
+
oceanSdkLoading = new Promise((resolve, reject) => {
|
|
1491
|
+
const script2 = document.createElement("script");
|
|
1492
|
+
const fail = (message) => {
|
|
1493
|
+
clearTimeout(timer);
|
|
1494
|
+
script2.remove();
|
|
1495
|
+
oceanSdkLoading = null;
|
|
1496
|
+
reject(new Error(message));
|
|
1497
|
+
};
|
|
1498
|
+
const timer = setTimeout(() => fail("The Ocean Payment card form took too long to load"), OCEAN_SDK_LOAD_TIMEOUT_MS);
|
|
1499
|
+
script2.addEventListener("load", () => {
|
|
1500
|
+
if (!w.Oceanpayment)
|
|
1501
|
+
return fail("Ocean Payment script loaded without window.Oceanpayment");
|
|
1502
|
+
clearTimeout(timer);
|
|
1503
|
+
resolve(w.Oceanpayment);
|
|
1504
|
+
});
|
|
1505
|
+
script2.addEventListener("error", () => fail("Failed to load the Ocean Payment card form"));
|
|
1506
|
+
script2.src = OCEAN_SDK_URL;
|
|
1507
|
+
document.head.appendChild(script2);
|
|
1508
|
+
});
|
|
1509
|
+
return oceanSdkLoading;
|
|
1510
|
+
}
|
|
1511
|
+
var OCEAN_PENDING_KEY = "tgd:headless:oceanpayment:pending";
|
|
1512
|
+
var OCEAN_PENDING_TTL_MS = 30 * 60 * 1e3;
|
|
1513
|
+
function parkOceanPayment(paymentId) {
|
|
1514
|
+
try {
|
|
1515
|
+
sessionStorage.setItem(OCEAN_PENDING_KEY, JSON.stringify({ paymentId, at: Date.now() }));
|
|
1516
|
+
} catch {
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
function takeParkedOceanPayment() {
|
|
1520
|
+
try {
|
|
1521
|
+
const raw = sessionStorage.getItem(OCEAN_PENDING_KEY);
|
|
1522
|
+
if (!raw)
|
|
1523
|
+
return void 0;
|
|
1524
|
+
sessionStorage.removeItem(OCEAN_PENDING_KEY);
|
|
1525
|
+
const { paymentId, at } = JSON.parse(raw);
|
|
1526
|
+
if (!paymentId || !at || Date.now() - at > OCEAN_PENDING_TTL_MS)
|
|
1527
|
+
return void 0;
|
|
1528
|
+
return paymentId;
|
|
1529
|
+
} catch {
|
|
1530
|
+
return void 0;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
function restoreOceanPaymentReturn() {
|
|
1534
|
+
if (typeof window === "undefined")
|
|
1535
|
+
return;
|
|
1536
|
+
const paymentId = takeParkedOceanPayment();
|
|
1537
|
+
if (!paymentId)
|
|
1538
|
+
return;
|
|
1539
|
+
try {
|
|
1540
|
+
const url2 = new URL(window.location.href);
|
|
1541
|
+
if (url2.searchParams.get("paymentId"))
|
|
1542
|
+
return;
|
|
1543
|
+
url2.searchParams.set("paymentId", paymentId);
|
|
1544
|
+
url2.searchParams.set("paymentAction", "requireAction");
|
|
1545
|
+
url2.searchParams.set("paymentActionStatus", "completed");
|
|
1546
|
+
window.history.replaceState(window.history.state, "", url2.toString());
|
|
1547
|
+
} catch {
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1191
1551
|
// src/core/modules/payment.ts
|
|
1552
|
+
function loadPaymentsApiFlow() {
|
|
1553
|
+
if (true) {
|
|
1554
|
+
return Promise.reject(
|
|
1555
|
+
new Error(
|
|
1556
|
+
"This store takes payments through the Payments API, which the CDN build does not support yet: install @tagadapay/headless-sdk and @tagadapay/core-js 3.7.0 or later from npm."
|
|
1557
|
+
)
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1560
|
+
return import("../helpers/paymentsApi.js");
|
|
1561
|
+
}
|
|
1562
|
+
var FAILED_PAYMENT_STATUSES = /* @__PURE__ */ new Set(["failed", "declined", "rejected", "error"]);
|
|
1563
|
+
var oceanSlot = null;
|
|
1564
|
+
var oceanSlotTaken = () => !!oceanSlot && (!oceanSlot.attached || oceanSlot.element.isConnected);
|
|
1565
|
+
var OCEAN_TIMED_OUT = Symbol("ocean-timed-out");
|
|
1566
|
+
var OCEAN_UNMOUNTED = Symbol("ocean-unmounted");
|
|
1192
1567
|
var EXPRESS_METHOD_TYPES = /* @__PURE__ */ new Set(["apple_pay", "google_pay", "paypal", "klarna", "klarna_express", "link"]);
|
|
1193
1568
|
function paymentMethodListEntryToConfig(entry) {
|
|
1194
1569
|
var _a;
|
|
@@ -1247,6 +1622,11 @@ var TagadaHeadlessBundle = (() => {
|
|
|
1247
1622
|
* without a second network round-trip inside Apple's authorization window.
|
|
1248
1623
|
*/
|
|
1249
1624
|
this.lastPaymentSetup = null;
|
|
1625
|
+
/**
|
|
1626
|
+
* Payments API (V3) store: what the pay call knew about each payment it started (the session it
|
|
1627
|
+
* pays, the shopper's method), so `finalize()` can confirm it from the bare pay answer.
|
|
1628
|
+
*/
|
|
1629
|
+
this.paymentsApiStarts = /* @__PURE__ */ new Map();
|
|
1250
1630
|
}
|
|
1251
1631
|
// ---------------------------------------------------------------------------
|
|
1252
1632
|
// Discovery: what payment methods are available?
|
|
@@ -1457,7 +1837,7 @@ var TagadaHeadlessBundle = (() => {
|
|
|
1457
1837
|
*/
|
|
1458
1838
|
async pay(opts) {
|
|
1459
1839
|
if (opts.tagadaToken && !opts.paymentInstrumentId) {
|
|
1460
|
-
return this.http.post("/api/v1/payment/pay-with-token", {
|
|
1840
|
+
return this.rememberPaymentsApiStart(await this.http.post("/api/v1/payment/pay-with-token", {
|
|
1461
1841
|
tagadaToken: opts.tagadaToken,
|
|
1462
1842
|
checkoutSessionId: opts.checkoutSessionId,
|
|
1463
1843
|
returnUrl: opts.returnUrl,
|
|
@@ -1466,9 +1846,9 @@ var TagadaHeadlessBundle = (() => {
|
|
|
1466
1846
|
paymentMethod: opts.paymentMethod,
|
|
1467
1847
|
isExpress: opts.isExpress,
|
|
1468
1848
|
metadata: opts.metadata
|
|
1469
|
-
});
|
|
1849
|
+
}), opts);
|
|
1470
1850
|
}
|
|
1471
|
-
return this.http.post("/api/public/v1/checkout/pay-v2", {
|
|
1851
|
+
return this.rememberPaymentsApiStart(await this.http.post("/api/public/v1/checkout/pay-v2", {
|
|
1472
1852
|
checkoutSessionId: opts.checkoutSessionId,
|
|
1473
1853
|
paymentInstrumentId: opts.paymentInstrumentId,
|
|
1474
1854
|
storeId: this.storeId,
|
|
@@ -1479,14 +1859,14 @@ var TagadaHeadlessBundle = (() => {
|
|
|
1479
1859
|
isExpress: opts.isExpress,
|
|
1480
1860
|
metadata: opts.metadata,
|
|
1481
1861
|
returnUrl: opts.returnUrl
|
|
1482
|
-
});
|
|
1862
|
+
}), opts);
|
|
1483
1863
|
}
|
|
1484
1864
|
/**
|
|
1485
1865
|
* Low-level pay: process payment with an existing instrument or for APM/express flows
|
|
1486
1866
|
* where no instrument is needed (redirect-based methods).
|
|
1487
1867
|
*/
|
|
1488
1868
|
async payDirect(opts) {
|
|
1489
|
-
return this.http.post("/api/public/v1/checkout/pay-v2", {
|
|
1869
|
+
return this.rememberPaymentsApiStart(await this.http.post("/api/public/v1/checkout/pay-v2", {
|
|
1490
1870
|
checkoutSessionId: opts.checkoutSessionId,
|
|
1491
1871
|
paymentInstrumentId: opts.paymentInstrumentId ?? "",
|
|
1492
1872
|
storeId: this.storeId,
|
|
@@ -1496,7 +1876,7 @@ var TagadaHeadlessBundle = (() => {
|
|
|
1496
1876
|
isExpress: opts.isExpress,
|
|
1497
1877
|
threedsSessionId: opts.threedsSessionId,
|
|
1498
1878
|
returnUrl: opts.returnUrl
|
|
1499
|
-
});
|
|
1879
|
+
}), opts);
|
|
1500
1880
|
}
|
|
1501
1881
|
/**
|
|
1502
1882
|
* Persist a wallet contact onto the session before the charge.
|
|
@@ -1701,6 +2081,228 @@ var TagadaHeadlessBundle = (() => {
|
|
|
1701
2081
|
});
|
|
1702
2082
|
}
|
|
1703
2083
|
// ---------------------------------------------------------------------------
|
|
2084
|
+
// On-chain crypto (`crypto_onchain`)
|
|
2085
|
+
// ---------------------------------------------------------------------------
|
|
2086
|
+
/**
|
|
2087
|
+
* Networks and coins the store accepts for on-chain crypto on this checkout session. On-chain
|
|
2088
|
+
* crypto is paid from your page (address, exact amount, QR) and never through `pay()` /
|
|
2089
|
+
* `processApm()`: pay-v2 refuses it with `422 CRYPTO_ONCHAIN_INLINE_ONLY`.
|
|
2090
|
+
*/
|
|
2091
|
+
async getCryptoOnchainOptions(checkoutSessionId) {
|
|
2092
|
+
return this.http.get(
|
|
2093
|
+
`/api/v1/checkout-sessions/${encodeURIComponent(checkoutSessionId)}/crypto-onchain/options`
|
|
2094
|
+
);
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* Create the order's on-chain payment for a network and a coin, or re-quote it (another coin,
|
|
2098
|
+
* an expired quote): the deposit address, exact amount and wallet URI for the QR code. Save the
|
|
2099
|
+
* customer and address on the session first.
|
|
2100
|
+
*/
|
|
2101
|
+
async createCryptoOnchainPayment(opts) {
|
|
2102
|
+
return this.http.post(
|
|
2103
|
+
`/api/v1/checkout-sessions/${encodeURIComponent(opts.checkoutSessionId)}/crypto-onchain/session`,
|
|
2104
|
+
{ chainId: opts.chainId, asset: opts.asset }
|
|
2105
|
+
);
|
|
2106
|
+
}
|
|
2107
|
+
/** The payment's state; poll it every few seconds until `paid` (or underpaid / expired / failed). */
|
|
2108
|
+
async getCryptoOnchainStatus(opts) {
|
|
2109
|
+
return this.http.get(
|
|
2110
|
+
`/api/v1/checkout-sessions/${encodeURIComponent(opts.checkoutSessionId)}/crypto-onchain/status`,
|
|
2111
|
+
{ params: { cryptoOnchainPaymentId: opts.cryptoOnchainPaymentId } }
|
|
2112
|
+
);
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* The whole inline crypto payment of a checkout session, the way the hosted checkout runs it.
|
|
2116
|
+
*
|
|
2117
|
+
* @example
|
|
2118
|
+
* ```ts
|
|
2119
|
+
* const crypto = tagada.payment.createCryptoOnchainCheckout({
|
|
2120
|
+
* checkoutSessionId: session.id,
|
|
2121
|
+
* onPaid: ({ orderId }) => showThankYou(orderId),
|
|
2122
|
+
* });
|
|
2123
|
+
* crypto.subscribe(render); // networks, selection, payment (address, amount, paymentUri), status
|
|
2124
|
+
* await crypto.loadOptions();
|
|
2125
|
+
* payButton.onclick = () => crypto.pay(); // after saving the customer and address
|
|
2126
|
+
* ```
|
|
2127
|
+
*/
|
|
2128
|
+
createCryptoOnchainCheckout(opts) {
|
|
2129
|
+
return new CryptoOnchainCheckout(this, opts);
|
|
2130
|
+
}
|
|
2131
|
+
// ---------------------------------------------------------------------------
|
|
2132
|
+
// Ocean Payment
|
|
2133
|
+
// ---------------------------------------------------------------------------
|
|
2134
|
+
/** Whether the store's Ocean Payment integration renders its inline card form or redirects. */
|
|
2135
|
+
async getOceanPaymentConfig(checkoutSessionId) {
|
|
2136
|
+
const data = await this.http.post(
|
|
2137
|
+
`/api/v1/checkout-sessions/${checkoutSessionId}/oceanpayment-embedded-config`,
|
|
2138
|
+
{}
|
|
2139
|
+
);
|
|
2140
|
+
return { embedded: !!(data == null ? void 0 : data.embedded), sandboxed: !!(data == null ? void 0 : data.sandboxed) };
|
|
2141
|
+
}
|
|
2142
|
+
/**
|
|
2143
|
+
* Set up Ocean Payment for a checkout session, the way the hosted checkout
|
|
2144
|
+
* does: Ocean's card fields render inside `container` when the integration
|
|
2145
|
+
* has Ocean's public key (`embedded`), otherwise `pay()` sends the shopper
|
|
2146
|
+
* to Ocean's page and back to `returnUrl` (`redirect`).
|
|
2147
|
+
*
|
|
2148
|
+
* @example
|
|
2149
|
+
* ```ts
|
|
2150
|
+
* const ocean = await tagada.payment.mountOceanPayment({
|
|
2151
|
+
* checkoutSessionId: session.id,
|
|
2152
|
+
* container: '#ocean-card',
|
|
2153
|
+
* });
|
|
2154
|
+
* payButton.onclick = async () => {
|
|
2155
|
+
* const result = await ocean.pay();
|
|
2156
|
+
* if (result.status === 'requires_redirect') window.location.href = result.redirectUrl;
|
|
2157
|
+
* else if (result.status === 'succeeded') showThankYou(result.order);
|
|
2158
|
+
* else if (result.status === 'failed') showError(result.error);
|
|
2159
|
+
* };
|
|
2160
|
+
* ```
|
|
2161
|
+
*/
|
|
2162
|
+
async mountOceanPayment(opts) {
|
|
2163
|
+
if (typeof window === "undefined")
|
|
2164
|
+
throw new Error("Ocean Payment needs a browser");
|
|
2165
|
+
const { checkoutSessionId, signal } = opts;
|
|
2166
|
+
const aborted = () => Object.assign(new Error("Ocean Payment mount aborted"), { name: "AbortError" });
|
|
2167
|
+
if (signal == null ? void 0 : signal.aborted)
|
|
2168
|
+
throw aborted();
|
|
2169
|
+
const host = typeof opts.container === "string" ? document.querySelector(opts.container) : opts.container;
|
|
2170
|
+
if (!host && opts.mode !== "redirect") {
|
|
2171
|
+
throw new Error(`Ocean Payment container not found: ${String(opts.container)}`);
|
|
2172
|
+
}
|
|
2173
|
+
if (oceanSlotTaken() || document.getElementById(OCEAN_CONTAINER_ID)) {
|
|
2174
|
+
throw new Error("An Ocean Payment card form is already mounted on this page");
|
|
2175
|
+
}
|
|
2176
|
+
const w = window;
|
|
2177
|
+
const element = document.createElement("div");
|
|
2178
|
+
element.id = OCEAN_CONTAINER_ID;
|
|
2179
|
+
const slot = { element, attached: false };
|
|
2180
|
+
oceanSlot = slot;
|
|
2181
|
+
const releaseSlot = () => {
|
|
2182
|
+
if (oceanSlot === slot)
|
|
2183
|
+
oceanSlot = null;
|
|
2184
|
+
};
|
|
2185
|
+
let mounted = true;
|
|
2186
|
+
let pendingCallback = null;
|
|
2187
|
+
const callback = (data) => {
|
|
2188
|
+
const resolve = pendingCallback;
|
|
2189
|
+
pendingCallback = null;
|
|
2190
|
+
resolve == null ? void 0 : resolve(data);
|
|
2191
|
+
};
|
|
2192
|
+
const unmount = () => {
|
|
2193
|
+
if (!mounted)
|
|
2194
|
+
return;
|
|
2195
|
+
mounted = false;
|
|
2196
|
+
if (w.oceanpaymentCallBack === callback)
|
|
2197
|
+
delete w.oceanpaymentCallBack;
|
|
2198
|
+
element.remove();
|
|
2199
|
+
releaseSlot();
|
|
2200
|
+
callback(OCEAN_UNMOUNTED);
|
|
2201
|
+
};
|
|
2202
|
+
signal == null ? void 0 : signal.addEventListener("abort", unmount, { once: true });
|
|
2203
|
+
let config;
|
|
2204
|
+
let ocean;
|
|
2205
|
+
try {
|
|
2206
|
+
config = opts.mode === "redirect" ? { embedded: false, sandboxed: false } : await this.getOceanPaymentConfig(checkoutSessionId);
|
|
2207
|
+
if (signal == null ? void 0 : signal.aborted)
|
|
2208
|
+
throw aborted();
|
|
2209
|
+
if (config.embedded) {
|
|
2210
|
+
ocean = await loadOceanSdk();
|
|
2211
|
+
if (signal == null ? void 0 : signal.aborted)
|
|
2212
|
+
throw aborted();
|
|
2213
|
+
}
|
|
2214
|
+
} catch (error) {
|
|
2215
|
+
unmount();
|
|
2216
|
+
throw error;
|
|
2217
|
+
}
|
|
2218
|
+
let submitting = false;
|
|
2219
|
+
if (!ocean) {
|
|
2220
|
+
releaseSlot();
|
|
2221
|
+
return {
|
|
2222
|
+
mode: "redirect",
|
|
2223
|
+
pay: async () => {
|
|
2224
|
+
if (!mounted)
|
|
2225
|
+
return { status: "failed", error: "The Ocean Payment form was removed" };
|
|
2226
|
+
if (submitting)
|
|
2227
|
+
return { status: "failed", error: "Payment already in progress" };
|
|
2228
|
+
submitting = true;
|
|
2229
|
+
try {
|
|
2230
|
+
const data = await this.http.post(
|
|
2231
|
+
`/api/v1/checkout-sessions/${checkoutSessionId}/oceanpayment-link`,
|
|
2232
|
+
{ returnUrl: opts.returnUrl ?? window.location.href }
|
|
2233
|
+
);
|
|
2234
|
+
if (!(data == null ? void 0 : data.url) || !data.paymentId) {
|
|
2235
|
+
return { status: "failed", error: "Ocean Payment is not available for this store" };
|
|
2236
|
+
}
|
|
2237
|
+
return { status: "requires_redirect", redirectUrl: data.url, method: "GET", paymentId: data.paymentId };
|
|
2238
|
+
} catch (error) {
|
|
2239
|
+
return { status: "failed", error: error instanceof Error ? error.message : String(error) };
|
|
2240
|
+
} finally {
|
|
2241
|
+
submitting = false;
|
|
2242
|
+
}
|
|
2243
|
+
},
|
|
2244
|
+
unmount
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
const sdk = ocean;
|
|
2248
|
+
host.appendChild(element);
|
|
2249
|
+
slot.attached = true;
|
|
2250
|
+
w.oceanpaymentCallBack = callback;
|
|
2251
|
+
const backUrl = window.location.href;
|
|
2252
|
+
sdk.init(config.sandboxed, "", oceanLanguage(opts.locale), { showCardName: false });
|
|
2253
|
+
const pay = async () => {
|
|
2254
|
+
if (!mounted)
|
|
2255
|
+
return { status: "failed", error: "The Ocean Payment card form was removed" };
|
|
2256
|
+
if (submitting)
|
|
2257
|
+
return { status: "failed", error: "Payment already in progress" };
|
|
2258
|
+
submitting = true;
|
|
2259
|
+
try {
|
|
2260
|
+
const created = await this.http.post(
|
|
2261
|
+
`/api/v1/checkout-sessions/${checkoutSessionId}/oceanpayment-embedded-payment`,
|
|
2262
|
+
{ backUrl }
|
|
2263
|
+
);
|
|
2264
|
+
if (!(created == null ? void 0 : created.paymentId) || !created.params) {
|
|
2265
|
+
return { status: "failed", error: "Failed to start the payment" };
|
|
2266
|
+
}
|
|
2267
|
+
const paymentId = created.paymentId;
|
|
2268
|
+
const answer = await new Promise((resolve) => {
|
|
2269
|
+
const timer = setTimeout(() => {
|
|
2270
|
+
pendingCallback = null;
|
|
2271
|
+
resolve(OCEAN_TIMED_OUT);
|
|
2272
|
+
}, OCEAN_CALLBACK_TIMEOUT_MS);
|
|
2273
|
+
pendingCallback = (data) => {
|
|
2274
|
+
clearTimeout(timer);
|
|
2275
|
+
resolve(data);
|
|
2276
|
+
};
|
|
2277
|
+
sdk.checkout(created.params);
|
|
2278
|
+
});
|
|
2279
|
+
if (answer === OCEAN_TIMED_OUT || answer === OCEAN_UNMOUNTED) {
|
|
2280
|
+
return this.pollUntilDone(paymentId);
|
|
2281
|
+
}
|
|
2282
|
+
if (answer && typeof answer === "object" && "msg" in answer) {
|
|
2283
|
+
return { status: "failed", error: String(answer.msg || "Please check your card details.") };
|
|
2284
|
+
}
|
|
2285
|
+
const result = await this.http.post(
|
|
2286
|
+
`/api/v1/checkout-sessions/${checkoutSessionId}/oceanpayment-embedded-result`,
|
|
2287
|
+
{ paymentId, response: String(answer) }
|
|
2288
|
+
);
|
|
2289
|
+
if ((result == null ? void 0 : result.status) === "requires_action" && result.payUrl) {
|
|
2290
|
+
parkOceanPayment(paymentId);
|
|
2291
|
+
return { status: "requires_redirect", redirectUrl: result.payUrl, method: "GET", paymentId };
|
|
2292
|
+
}
|
|
2293
|
+
if ((result == null ? void 0 : result.status) === "succeeded" || (result == null ? void 0 : result.status) === "pending") {
|
|
2294
|
+
return this.pollUntilDone(paymentId);
|
|
2295
|
+
}
|
|
2296
|
+
return { status: "failed", error: (result == null ? void 0 : result.message) || "Payment declined" };
|
|
2297
|
+
} catch (error) {
|
|
2298
|
+
return { status: "failed", error: error instanceof Error ? error.message : String(error) };
|
|
2299
|
+
} finally {
|
|
2300
|
+
submitting = false;
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
return { mode: "embedded", pay, unmount };
|
|
2304
|
+
}
|
|
2305
|
+
// ---------------------------------------------------------------------------
|
|
1704
2306
|
// High-level orchestrator — the ONE function beginners should call
|
|
1705
2307
|
// ---------------------------------------------------------------------------
|
|
1706
2308
|
/**
|
|
@@ -1757,6 +2359,11 @@ var TagadaHeadlessBundle = (() => {
|
|
|
1757
2359
|
var _a, _b, _c;
|
|
1758
2360
|
const payment = input.payment;
|
|
1759
2361
|
const returnUrl = (opts == null ? void 0 : opts.returnUrl) ?? (typeof window !== "undefined" ? window.location.href : void 0);
|
|
2362
|
+
if (!payment) {
|
|
2363
|
+
const started = readStartedPayment(input);
|
|
2364
|
+
if (started)
|
|
2365
|
+
return this.finalizeOnPaymentsApi(started, returnUrl);
|
|
2366
|
+
}
|
|
1760
2367
|
if (payment.status === "succeeded") {
|
|
1761
2368
|
this.emitPaid((_a = input.order) == null ? void 0 : _a.id, payment);
|
|
1762
2369
|
return { status: "succeeded", payment, order: input.order };
|
|
@@ -1777,6 +2384,11 @@ var TagadaHeadlessBundle = (() => {
|
|
|
1777
2384
|
* You can also call it manually in non-React apps.
|
|
1778
2385
|
*/
|
|
1779
2386
|
async resumeAfterRedirect(paymentId) {
|
|
2387
|
+
const parked = takeParkedPaymentsApiReturn(paymentId);
|
|
2388
|
+
if (parked) {
|
|
2389
|
+
const url2 = typeof window !== "undefined" ? window.location.href : void 0;
|
|
2390
|
+
return this.onPaymentsApi((flow, host) => flow.resumeOnPaymentsApi(host, paymentId, parked.checkoutSessionId, url2));
|
|
2391
|
+
}
|
|
1780
2392
|
return this.pollUntilDone(paymentId);
|
|
1781
2393
|
}
|
|
1782
2394
|
/**
|
|
@@ -1812,6 +2424,8 @@ var TagadaHeadlessBundle = (() => {
|
|
|
1812
2424
|
async maybeResumeFromUrl(opts) {
|
|
1813
2425
|
if (typeof window === "undefined" && !(opts == null ? void 0 : opts.url))
|
|
1814
2426
|
return null;
|
|
2427
|
+
if (!(opts == null ? void 0 : opts.url))
|
|
2428
|
+
restoreOceanPaymentReturn();
|
|
1815
2429
|
const href = (opts == null ? void 0 : opts.url) ?? window.location.href;
|
|
1816
2430
|
let url2;
|
|
1817
2431
|
try {
|
|
@@ -1867,7 +2481,7 @@ var TagadaHeadlessBundle = (() => {
|
|
|
1867
2481
|
const start = Date.now();
|
|
1868
2482
|
while (Date.now() - start < timeout) {
|
|
1869
2483
|
const status = await this.getPaymentStatus(paymentId);
|
|
1870
|
-
if (status.status === "succeeded" ||
|
|
2484
|
+
if (status.status === "succeeded" || FAILED_PAYMENT_STATUSES.has(status.status)) {
|
|
1871
2485
|
return status;
|
|
1872
2486
|
}
|
|
1873
2487
|
await new Promise((r) => setTimeout(r, interval));
|
|
@@ -2050,7 +2664,7 @@ var TagadaHeadlessBundle = (() => {
|
|
|
2050
2664
|
}
|
|
2051
2665
|
return { status: "succeeded", payment: status, order: status.order };
|
|
2052
2666
|
}
|
|
2053
|
-
if (
|
|
2667
|
+
if (FAILED_PAYMENT_STATUSES.has(status.status)) {
|
|
2054
2668
|
return { status: "failed", error: status.status, payment: status };
|
|
2055
2669
|
}
|
|
2056
2670
|
if (status.requireAction && status.requireAction !== "none" && status.requireActionData && !status.requireActionData.processed) {
|
|
@@ -2064,6 +2678,48 @@ var TagadaHeadlessBundle = (() => {
|
|
|
2064
2678
|
return { status: "pending", paymentId };
|
|
2065
2679
|
}
|
|
2066
2680
|
// ---------------------------------------------------------------------------
|
|
2681
|
+
// Internal: Payments API (V3) stores
|
|
2682
|
+
// ---------------------------------------------------------------------------
|
|
2683
|
+
/** Keeps what a V3 pay answer does not say (its session, the method); returns the answer as is. */
|
|
2684
|
+
rememberPaymentsApiStart(answer, opts) {
|
|
2685
|
+
const started = answer && !answer.payment ? readStartedPayment(answer) : null;
|
|
2686
|
+
if (started) {
|
|
2687
|
+
this.paymentsApiStarts.set(started.paymentId, {
|
|
2688
|
+
checkoutSessionId: started.checkoutSessionId ?? opts.checkoutSessionId,
|
|
2689
|
+
...opts.paymentMethod ? { paymentMethod: opts.paymentMethod } : {}
|
|
2690
|
+
});
|
|
2691
|
+
}
|
|
2692
|
+
return answer;
|
|
2693
|
+
}
|
|
2694
|
+
/** Confirms a started V3 payment with payments-js and runs it to the V2-shaped result. */
|
|
2695
|
+
finalizeOnPaymentsApi(started, returnUrl) {
|
|
2696
|
+
const known = this.paymentsApiStarts.get(started.paymentId);
|
|
2697
|
+
this.paymentsApiStarts.delete(started.paymentId);
|
|
2698
|
+
return this.onPaymentsApi(
|
|
2699
|
+
(flow, host) => flow.finalizeOnPaymentsApi(host, started, {
|
|
2700
|
+
checkoutSessionId: known == null ? void 0 : known.checkoutSessionId,
|
|
2701
|
+
paymentMethod: known == null ? void 0 : known.paymentMethod,
|
|
2702
|
+
returnUrl
|
|
2703
|
+
})
|
|
2704
|
+
);
|
|
2705
|
+
}
|
|
2706
|
+
/** Runs `run` with the lazily imported V3 flow; a flow that cannot load is a failed payment. */
|
|
2707
|
+
async onPaymentsApi(run) {
|
|
2708
|
+
let flow;
|
|
2709
|
+
try {
|
|
2710
|
+
flow = await loadPaymentsApiFlow();
|
|
2711
|
+
} catch (error) {
|
|
2712
|
+
return { status: "failed", error: error instanceof Error ? error.message : String(error) };
|
|
2713
|
+
}
|
|
2714
|
+
return run(flow, {
|
|
2715
|
+
http: this.http,
|
|
2716
|
+
storeId: this.storeId,
|
|
2717
|
+
checkout: this.checkout,
|
|
2718
|
+
stepConfig: this.readInjectedStepConfigRaw(),
|
|
2719
|
+
onPaid: (orderId, payment) => this.emitPaid(orderId, payment)
|
|
2720
|
+
});
|
|
2721
|
+
}
|
|
2722
|
+
// ---------------------------------------------------------------------------
|
|
2067
2723
|
// Internal helpers
|
|
2068
2724
|
// ---------------------------------------------------------------------------
|
|
2069
2725
|
/**
|
|
@@ -2083,6 +2739,19 @@ var TagadaHeadlessBundle = (() => {
|
|
|
2083
2739
|
return null;
|
|
2084
2740
|
}
|
|
2085
2741
|
}
|
|
2742
|
+
/** The whole injected step config (Tagada-hosted pages), or null. */
|
|
2743
|
+
readInjectedStepConfigRaw() {
|
|
2744
|
+
if (typeof window === "undefined")
|
|
2745
|
+
return null;
|
|
2746
|
+
const raw = window.__TGD_STEP_CONFIG__;
|
|
2747
|
+
if (!raw)
|
|
2748
|
+
return null;
|
|
2749
|
+
try {
|
|
2750
|
+
return typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
2751
|
+
} catch {
|
|
2752
|
+
return null;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2086
2755
|
/** Tracking hook. Never throws, never awaits: a pixel must not be able to break a payment. */
|
|
2087
2756
|
emitPaid(orderId, payment) {
|
|
2088
2757
|
var _a;
|
|
@@ -2115,7 +2784,7 @@ var TagadaHeadlessBundle = (() => {
|
|
|
2115
2784
|
|
|
2116
2785
|
// src/core/modules/lazyCommerce.ts
|
|
2117
2786
|
var script = typeof document === "undefined" ? null : document.currentScript;
|
|
2118
|
-
var url = (script == null ? void 0 : script.src) ? new URL("tagada-headless-commerce.min.js", script.src).href : "https://cdn.jsdelivr.net/npm/@tagadapay/headless-sdk@1.
|
|
2787
|
+
var url = (script == null ? void 0 : script.src) ? new URL("tagada-headless-commerce.min.js", script.src).href : "https://cdn.jsdelivr.net/npm/@tagadapay/headless-sdk@1.21.0/dist/tagada-headless-commerce.min.js";
|
|
2119
2788
|
var loading;
|
|
2120
2789
|
function loadCommerce() {
|
|
2121
2790
|
if (window.TagadaHeadlessCommerce)
|