@cimplify/sdk 0.104.0 → 0.104.1
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/dist/advanced.d.mts +3 -7
- package/dist/advanced.d.ts +3 -7
- package/dist/advanced.js +23 -23
- package/dist/advanced.mjs +4 -4
- package/dist/{client-BuLcOWLv.d.ts → catalogue-BlQ85Uma.d.mts} +415 -763
- package/dist/{client-D5AoobFc.d.mts → catalogue-C8TqOj9K.d.ts} +415 -763
- package/dist/{payment-g0HFkEDU.d.mts → checkout-BqK9XUz5.d.mts} +3824 -1446
- package/dist/{payment-g0HFkEDU.d.ts → checkout-BqK9XUz5.d.ts} +3824 -1446
- package/dist/{chunk-GOHSXBKW.mjs → chunk-33VCYWVL.mjs} +25 -1
- package/dist/{chunk-2WPVUIHX.mjs → chunk-6G3ANKQA.mjs} +1030 -360
- package/dist/{chunk-E4K2VQDD.js → chunk-BWEIMKLF.js} +541 -16
- package/dist/{chunk-IQFFSKE2.js → chunk-DQEXY542.js} +1345 -746
- package/dist/{chunk-CNN7Z2IP.js → chunk-EV2EVOYS.js} +27 -0
- package/dist/{chunk-YVAEY3VC.js → chunk-HYFA67A7.js} +1034 -364
- package/dist/{chunk-5RT4HYMP.mjs → chunk-L5SNEU2O.mjs} +524 -15
- package/dist/{chunk-4BKOKHE3.js → chunk-RBNBSQI7.mjs} +217 -19
- package/dist/{chunk-H3RATH4B.mjs → chunk-SFUJMS5E.js} +261 -2
- package/dist/{chunk-DYIAX3WI.mjs → chunk-U54CWUUT.mjs} +11 -2
- package/dist/{chunk-OKADBZAI.mjs → chunk-UFOWPN3D.mjs} +808 -574
- package/dist/{chunk-OCCNG6MO.mjs → chunk-V4457OKB.mjs} +1310 -722
- package/dist/{chunk-5BDYDZ4U.js → chunk-WXREIBFW.js} +892 -657
- package/dist/{chunk-7PDEX54K.js → chunk-XAUSDMMZ.js} +13 -3
- package/dist/{client-BVNWjk6W.d.ts → client-DhXeiUrf.d.ts} +2 -2
- package/dist/{client-fM716k7G.d.mts → client-mXiUrV4X.d.mts} +2 -2
- package/dist/{index-BtcoqrY1.d.ts → index-B0AdZvrM.d.mts} +9 -4
- package/dist/{index-D7Tf3v7c.d.mts → index-CK8Nk5uQ.d.ts} +9 -4
- package/dist/index-DPWI5XcU.d.ts +8 -0
- package/dist/index-DVfUh-ci.d.mts +8 -0
- package/dist/index.d.mts +31 -14
- package/dist/index.d.ts +31 -14
- package/dist/index.js +305 -133
- package/dist/index.mjs +6 -6
- package/dist/mock/cli.mjs +1442 -386
- package/dist/mock/library.js +1168 -352
- package/dist/mock/library.mjs +1168 -352
- package/dist/mock/msw.js +1168 -352
- package/dist/mock/msw.mjs +1168 -352
- package/dist/{price-BLIeAb8K.d.ts → price-6hQIIac9.d.ts} +1 -1
- package/dist/{price-Czq_cTC_.d.mts → price-BUDv1rI7.d.mts} +1 -1
- package/dist/react.d.mts +10 -5
- package/dist/react.d.ts +10 -5
- package/dist/react.js +227 -197
- package/dist/react.mjs +90 -60
- package/dist/{server-DKK_D-PP.d.ts → server-DJ0mSs9i.d.mts} +31 -57
- package/dist/{server-BkVhOIwn.d.mts → server-DSTX76Ki.d.ts} +31 -57
- package/dist/server.d.mts +3 -4
- package/dist/server.d.ts +3 -4
- package/dist/server.js +11 -11
- package/dist/server.mjs +5 -5
- package/dist/styles.css +2 -2
- package/dist/testing/msw.d.mts +2 -2
- package/dist/testing/msw.d.ts +2 -2
- package/dist/testing/msw.js +4 -4
- package/dist/testing/msw.mjs +3 -3
- package/dist/testing/suite.d.mts +5 -6
- package/dist/testing/suite.d.ts +5 -6
- package/dist/testing/suite.js +29 -28
- package/dist/testing/suite.mjs +10 -9
- package/dist/testing.d.mts +2177 -14
- package/dist/testing.d.ts +2177 -14
- package/dist/testing.js +167 -89
- package/dist/testing.mjs +24 -10
- package/dist/utils.d.mts +3 -4
- package/dist/utils.d.ts +3 -4
- package/dist/utils.js +35 -31
- package/dist/utils.mjs +2 -2
- package/package.json +9 -9
- package/registry/order-history.json +1 -1
- package/registry/order-summary.json +1 -1
- package/dist/link-D9iSVxt5.d.mts +0 -308
- package/dist/link-D9iSVxt5.d.ts +0 -308
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { CimplifyError, isSupportedCurrency,
|
|
1
|
+
import { CHECKOUT_STATUS, PAYMENT_METHOD, CHECKOUT_NEXT_ACTION, CHECKOUT_ORDER_RESULT_STATUS, ORDER_TYPE, AUTHORIZATION_TYPE } from './chunk-33VCYWVL.mjs';
|
|
2
|
+
import { paymentAuthorizationUrl, purchaseIntentMobileMoneyNetwork } from './chunk-U54CWUUT.mjs';
|
|
3
|
+
import { ErrorCode, PURCHASE_INTENT_RESOLVE_STATUS, CimplifyError, PAYMENT_OPTION_STATUS, isSupportedCurrency, PURCHASE_INTENT_CONFIRM_STATUS, PURCHASE_INTENT_ACTION_DECISION, PURCHASE_INTENT_RESOLUTION_KIND, PURCHASE_INTENT_ERROR_CODE, enrichError, LEGACY_CHECKOUT_PAYMENT_METHOD_ALIAS, PURCHASE_INTENT_PAYMENT_METHOD, PURCHASE_INTENT_SOURCE_KIND, LEGACY_CHECKOUT_PAYMENT_STATUS, getErrorHint, MOBILE_MONEY_TENDER_AUTHORITY_KIND } from './chunk-RBNBSQI7.mjs';
|
|
4
4
|
|
|
5
5
|
// src/types/result.ts
|
|
6
6
|
function ok(value) {
|
|
@@ -785,703 +785,796 @@ var CartOperations = class {
|
|
|
785
785
|
}
|
|
786
786
|
};
|
|
787
787
|
|
|
788
|
-
// src/
|
|
789
|
-
var
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
var
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
788
|
+
// src/payment-authorization/types.ts
|
|
789
|
+
var PAYMENT_AUTHORIZATION_OUTCOME_STATUS = {
|
|
790
|
+
SUBMITTED: "submitted",
|
|
791
|
+
CANCELLED: "cancelled",
|
|
792
|
+
FAILED: "failed"
|
|
793
|
+
};
|
|
794
|
+
var PAYMENT_AUTHORIZATION_PROVIDER = {
|
|
795
|
+
PAYSTACK: "paystack",
|
|
796
|
+
STRIPE: "stripe"
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
// src/payment-authorization/providers/paystack.ts
|
|
800
|
+
var PAYSTACK_POPUP_ERROR = {
|
|
801
|
+
PROVIDER_UNAVAILABLE: ErrorCode.PROVIDER_UNAVAILABLE,
|
|
802
|
+
SUBMISSION_UNKNOWN: "SUBMISSION_UNKNOWN",
|
|
803
|
+
CANCELLED: "CANCELLED",
|
|
804
|
+
PAYMENT_CANCELLED: ErrorCode.PAYMENT_CANCELLED,
|
|
805
|
+
PAYMENT_FAILED: ErrorCode.PAYMENT_FAILED,
|
|
806
|
+
POPUP_BLOCKED: ErrorCode.POPUP_BLOCKED
|
|
807
|
+
};
|
|
808
|
+
async function openPaystackPopup(options, signal) {
|
|
809
|
+
if (typeof window === "undefined") {
|
|
810
|
+
return { success: false, error: PAYSTACK_POPUP_ERROR.PROVIDER_UNAVAILABLE };
|
|
810
811
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
812
|
+
let PaystackPop;
|
|
813
|
+
try {
|
|
814
|
+
const imported = await import('@paystack/inline-js');
|
|
815
|
+
PaystackPop = imported.default;
|
|
816
|
+
} catch {
|
|
817
|
+
return { success: false, error: PAYSTACK_POPUP_ERROR.PROVIDER_UNAVAILABLE };
|
|
816
818
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
state,
|
|
830
|
-
zip_code: zipCode
|
|
819
|
+
return new Promise((resolve) => {
|
|
820
|
+
let settled = false;
|
|
821
|
+
let providerStarted = false;
|
|
822
|
+
const resolveOnce = (result) => {
|
|
823
|
+
if (settled) {
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
settled = true;
|
|
827
|
+
if (signal) {
|
|
828
|
+
signal.removeEventListener("abort", onAbort);
|
|
829
|
+
}
|
|
830
|
+
resolve(result);
|
|
831
831
|
};
|
|
832
|
-
}
|
|
833
|
-
const value = submission.value.trim();
|
|
834
|
-
if (!value) {
|
|
835
|
-
throw new Error("Authorization value is required.");
|
|
836
|
-
}
|
|
837
|
-
return { type: submission.type, value };
|
|
838
|
-
}
|
|
839
|
-
function formatMoney(value) {
|
|
840
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
841
|
-
return value.toFixed(2);
|
|
842
|
-
}
|
|
843
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
844
|
-
return value;
|
|
845
|
-
}
|
|
846
|
-
return "0.00";
|
|
847
|
-
}
|
|
848
|
-
function createAbortError() {
|
|
849
|
-
const error = new Error("CANCELLED");
|
|
850
|
-
error.name = "AbortError";
|
|
851
|
-
return error;
|
|
852
|
-
}
|
|
853
|
-
function isAbortError(error) {
|
|
854
|
-
if (error instanceof DOMException && error.name === "AbortError") {
|
|
855
|
-
return true;
|
|
856
|
-
}
|
|
857
|
-
return error instanceof Error && (error.name === "AbortError" || error.message === "CANCELLED");
|
|
858
|
-
}
|
|
859
|
-
function waitForAbortable(promise, signal) {
|
|
860
|
-
if (!signal) return promise;
|
|
861
|
-
if (signal.aborted) return Promise.reject(createAbortError());
|
|
862
|
-
return new Promise((resolve, reject) => {
|
|
863
832
|
const onAbort = () => {
|
|
864
|
-
|
|
865
|
-
|
|
833
|
+
resolveOnce({
|
|
834
|
+
success: false,
|
|
835
|
+
// Once Paystack owns the transaction, cancellation is ambiguous. The
|
|
836
|
+
// adapter converts this into submitted so Cimplify polling decides.
|
|
837
|
+
error: providerStarted ? PAYSTACK_POPUP_ERROR.SUBMISSION_UNKNOWN : PAYSTACK_POPUP_ERROR.CANCELLED
|
|
838
|
+
});
|
|
866
839
|
};
|
|
867
|
-
signal
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
signal.removeEventListener("abort", onAbort);
|
|
875
|
-
reject(error);
|
|
876
|
-
}
|
|
877
|
-
);
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
var CheckoutResolver = class {
|
|
881
|
-
constructor(options) {
|
|
882
|
-
this.client = options.client;
|
|
883
|
-
this.checkoutData = options.checkoutData;
|
|
884
|
-
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
885
|
-
this.maxPollAttempts = options.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS;
|
|
886
|
-
this.onStatusChange = options.onStatusChange;
|
|
887
|
-
this.onAuthorizationUiChange = options.onAuthorizationUiChange;
|
|
888
|
-
this.onAuthorizationRequired = options.onAuthorizationRequired;
|
|
889
|
-
this.signal = options.signal;
|
|
890
|
-
this.returnUrl = options.returnUrl;
|
|
891
|
-
this.enrollInLink = options.enrollInLink !== false;
|
|
892
|
-
this.businessId = options.businessId;
|
|
893
|
-
this.addressData = options.addressData;
|
|
894
|
-
this.paymentData = options.paymentData;
|
|
895
|
-
this.orderType = options.orderType;
|
|
896
|
-
this.cartTotal = options.cartTotal;
|
|
897
|
-
this.cartCurrency = options.cartCurrency;
|
|
898
|
-
this.allowPopups = options.allowPopups ?? typeof window !== "undefined";
|
|
899
|
-
}
|
|
900
|
-
async resolve(checkoutResult) {
|
|
840
|
+
if (signal?.aborted) {
|
|
841
|
+
resolveOnce({ success: false, error: PAYSTACK_POPUP_ERROR.CANCELLED });
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
if (signal) {
|
|
845
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
846
|
+
}
|
|
901
847
|
try {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
false
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
|
|
848
|
+
const popup = new PaystackPop();
|
|
849
|
+
popup.newTransaction({
|
|
850
|
+
key: options.key,
|
|
851
|
+
email: options.email,
|
|
852
|
+
amount: options.amount,
|
|
853
|
+
currency: options.currency,
|
|
854
|
+
reference: options.reference,
|
|
855
|
+
accessCode: options.accessCode,
|
|
856
|
+
onSuccess: (transaction) => {
|
|
857
|
+
resolveOnce({
|
|
858
|
+
success: true,
|
|
859
|
+
reference: transaction.reference ?? options.reference
|
|
860
|
+
});
|
|
861
|
+
},
|
|
862
|
+
onCancel: () => {
|
|
863
|
+
resolveOnce({ success: false, error: PAYSTACK_POPUP_ERROR.PAYMENT_CANCELLED });
|
|
864
|
+
},
|
|
865
|
+
onError: (error) => {
|
|
866
|
+
resolveOnce({
|
|
867
|
+
success: false,
|
|
868
|
+
error: error?.message || PAYSTACK_POPUP_ERROR.PAYMENT_FAILED
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
providerStarted = true;
|
|
873
|
+
} catch {
|
|
874
|
+
resolveOnce({ success: false, error: PAYSTACK_POPUP_ERROR.POPUP_BLOCKED });
|
|
928
875
|
}
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
var PaystackAuthorizationAdapter = class {
|
|
879
|
+
constructor(openPopup = openPaystackPopup) {
|
|
880
|
+
this.openPopup = openPopup;
|
|
881
|
+
this.provider = PAYMENT_AUTHORIZATION_PROVIDER.PAYSTACK;
|
|
929
882
|
}
|
|
930
|
-
|
|
931
|
-
|
|
883
|
+
async authorize(context) {
|
|
884
|
+
const result = await this.openPopup(
|
|
885
|
+
{
|
|
886
|
+
key: context.action.public_key,
|
|
887
|
+
email: context.email,
|
|
888
|
+
accessCode: context.action.client_secret,
|
|
889
|
+
reference: context.reference || context.action.client_secret,
|
|
890
|
+
currency: context.currency
|
|
891
|
+
},
|
|
892
|
+
context.signal
|
|
893
|
+
);
|
|
894
|
+
if (result.success) {
|
|
932
895
|
return {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
client_secret: checkoutResult.client_secret,
|
|
936
|
-
public_key: checkoutResult.public_key
|
|
896
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.SUBMITTED,
|
|
897
|
+
reference: result.reference || context.reference
|
|
937
898
|
};
|
|
938
899
|
}
|
|
939
|
-
if (
|
|
900
|
+
if (result.error === PAYSTACK_POPUP_ERROR.SUBMISSION_UNKNOWN) {
|
|
901
|
+
return {
|
|
902
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.SUBMITTED,
|
|
903
|
+
reference: context.reference
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
if (!result.error || result.error === PAYSTACK_POPUP_ERROR.CANCELLED || result.error === PAYSTACK_POPUP_ERROR.PAYMENT_CANCELLED) {
|
|
907
|
+
return { status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.CANCELLED };
|
|
908
|
+
}
|
|
909
|
+
if (result.error === PAYSTACK_POPUP_ERROR.POPUP_BLOCKED) {
|
|
940
910
|
return {
|
|
941
|
-
|
|
942
|
-
|
|
911
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.FAILED,
|
|
912
|
+
code: ErrorCode.POPUP_BLOCKED,
|
|
913
|
+
message: "Unable to open the Paystack payment popup. Please allow popups and try again.",
|
|
914
|
+
recoverable: true
|
|
943
915
|
};
|
|
944
916
|
}
|
|
945
|
-
if (
|
|
917
|
+
if (result.error === PAYSTACK_POPUP_ERROR.PROVIDER_UNAVAILABLE) {
|
|
946
918
|
return {
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
919
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.FAILED,
|
|
920
|
+
code: ErrorCode.PROVIDER_UNAVAILABLE,
|
|
921
|
+
message: "Paystack is unavailable in this environment.",
|
|
922
|
+
recoverable: false
|
|
950
923
|
};
|
|
951
924
|
}
|
|
952
|
-
return {
|
|
925
|
+
return {
|
|
926
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.FAILED,
|
|
927
|
+
code: ErrorCode.PAYMENT_FAILED,
|
|
928
|
+
message: result.error || "Paystack payment could not be submitted.",
|
|
929
|
+
recoverable: true
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
// src/payment-authorization/providers/stripe.ts
|
|
935
|
+
var STRIPE_REDIRECT_MODE = "if_required";
|
|
936
|
+
var STRIPE_PAYMENT_ELEMENT_TYPE = "payment";
|
|
937
|
+
async function loadStripeFromPeer(publicKey, options) {
|
|
938
|
+
try {
|
|
939
|
+
const imported = await import('@stripe/stripe-js');
|
|
940
|
+
return imported.loadStripe ? imported.loadStripe(publicKey, options) : null;
|
|
941
|
+
} catch {
|
|
942
|
+
return null;
|
|
953
943
|
}
|
|
954
|
-
|
|
955
|
-
|
|
944
|
+
}
|
|
945
|
+
function createStripeModal() {
|
|
946
|
+
if (typeof document === "undefined" || !document.body) return null;
|
|
947
|
+
const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
948
|
+
const root = document.createElement("div");
|
|
949
|
+
root.setAttribute("role", "dialog");
|
|
950
|
+
root.setAttribute("aria-modal", "true");
|
|
951
|
+
root.setAttribute("aria-label", "Secure card payment");
|
|
952
|
+
Object.assign(root.style, {
|
|
953
|
+
position: "fixed",
|
|
954
|
+
inset: "0",
|
|
955
|
+
zIndex: "2147483647",
|
|
956
|
+
display: "grid",
|
|
957
|
+
placeItems: "center",
|
|
958
|
+
padding: "16px",
|
|
959
|
+
background: "rgba(15, 23, 42, 0.58)"
|
|
960
|
+
});
|
|
961
|
+
const form = document.createElement("form");
|
|
962
|
+
Object.assign(form.style, {
|
|
963
|
+
width: "min(100%, 480px)",
|
|
964
|
+
maxHeight: "calc(100vh - 32px)",
|
|
965
|
+
overflow: "auto",
|
|
966
|
+
borderRadius: "12px",
|
|
967
|
+
padding: "24px",
|
|
968
|
+
background: "#fff",
|
|
969
|
+
boxShadow: "0 24px 80px rgba(15, 23, 42, 0.32)"
|
|
970
|
+
});
|
|
971
|
+
const title = document.createElement("h2");
|
|
972
|
+
title.textContent = "Complete card payment";
|
|
973
|
+
Object.assign(title.style, { margin: "0 0 18px", font: "600 18px/1.4 system-ui" });
|
|
974
|
+
const paymentMount = document.createElement("div");
|
|
975
|
+
paymentMount.setAttribute("data-cimplify-stripe-payment-element", "");
|
|
976
|
+
const error = document.createElement("p");
|
|
977
|
+
error.setAttribute("role", "alert");
|
|
978
|
+
Object.assign(error.style, {
|
|
979
|
+
minHeight: "20px",
|
|
980
|
+
margin: "14px 0 0",
|
|
981
|
+
color: "#b91c1c",
|
|
982
|
+
font: "14px/1.4 system-ui"
|
|
983
|
+
});
|
|
984
|
+
const actions = document.createElement("div");
|
|
985
|
+
Object.assign(actions.style, {
|
|
986
|
+
display: "flex",
|
|
987
|
+
justifyContent: "flex-end",
|
|
988
|
+
gap: "10px",
|
|
989
|
+
marginTop: "18px"
|
|
990
|
+
});
|
|
991
|
+
const cancelButton = document.createElement("button");
|
|
992
|
+
cancelButton.type = "button";
|
|
993
|
+
cancelButton.textContent = "Cancel";
|
|
994
|
+
Object.assign(cancelButton.style, {
|
|
995
|
+
border: "1px solid #cbd5e1",
|
|
996
|
+
borderRadius: "8px",
|
|
997
|
+
padding: "10px 16px",
|
|
998
|
+
background: "#fff",
|
|
999
|
+
color: "#0f172a",
|
|
1000
|
+
cursor: "pointer"
|
|
1001
|
+
});
|
|
1002
|
+
const submitButton = document.createElement("button");
|
|
1003
|
+
submitButton.type = "submit";
|
|
1004
|
+
submitButton.textContent = "Pay securely";
|
|
1005
|
+
Object.assign(submitButton.style, {
|
|
1006
|
+
border: "0",
|
|
1007
|
+
borderRadius: "8px",
|
|
1008
|
+
padding: "10px 16px",
|
|
1009
|
+
background: "#0f172a",
|
|
1010
|
+
color: "#fff",
|
|
1011
|
+
cursor: "pointer"
|
|
1012
|
+
});
|
|
1013
|
+
actions.append(cancelButton, submitButton);
|
|
1014
|
+
form.append(title, paymentMount, error, actions);
|
|
1015
|
+
root.append(form);
|
|
1016
|
+
document.body.append(root);
|
|
1017
|
+
cancelButton.focus();
|
|
1018
|
+
return { root, form, paymentMount, submitButton, cancelButton, error, previousFocus };
|
|
1019
|
+
}
|
|
1020
|
+
function stripeFailure(error) {
|
|
1021
|
+
const message = error?.message || "Stripe could not authorize this payment.";
|
|
1022
|
+
if (error?.code === "payment_intent_authentication_failure" || error?.type === "invalid_request_error") {
|
|
1023
|
+
return {
|
|
1024
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.FAILED,
|
|
1025
|
+
code: ErrorCode.AUTHORIZATION_FAILED,
|
|
1026
|
+
message,
|
|
1027
|
+
recoverable: true
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
return {
|
|
1031
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.FAILED,
|
|
1032
|
+
code: ErrorCode.PAYMENT_FAILED,
|
|
1033
|
+
message,
|
|
1034
|
+
recoverable: true
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
var StripeAuthorizationAdapter = class {
|
|
1038
|
+
constructor(loadStripe = loadStripeFromPeer) {
|
|
1039
|
+
this.loadStripe = loadStripe;
|
|
1040
|
+
this.provider = PAYMENT_AUTHORIZATION_PROVIDER.STRIPE;
|
|
1041
|
+
}
|
|
1042
|
+
async authorize(context) {
|
|
1043
|
+
if (typeof window === "undefined" || typeof document === "undefined" || context.signal?.aborted) {
|
|
1044
|
+
return context.signal?.aborted ? { status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.CANCELLED } : {
|
|
1045
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.FAILED,
|
|
1046
|
+
code: ErrorCode.PROVIDER_UNAVAILABLE,
|
|
1047
|
+
message: "Stripe Payment Element is unavailable in this environment.",
|
|
1048
|
+
recoverable: false
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
let stripe;
|
|
1052
|
+
try {
|
|
1053
|
+
stripe = await this.loadStripe(
|
|
1054
|
+
context.action.public_key,
|
|
1055
|
+
context.action.provider_account_id ? { stripeAccount: context.action.provider_account_id } : void 0
|
|
1056
|
+
);
|
|
1057
|
+
} catch (error) {
|
|
956
1058
|
return {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
false
|
|
962
|
-
)
|
|
1059
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.FAILED,
|
|
1060
|
+
code: ErrorCode.PROVIDER_UNAVAILABLE,
|
|
1061
|
+
message: error instanceof Error ? error.message : "Stripe.js could not be loaded.",
|
|
1062
|
+
recoverable: true
|
|
963
1063
|
};
|
|
964
1064
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
provider: action.provider
|
|
970
|
-
});
|
|
971
|
-
if (!this.allowPopups) {
|
|
1065
|
+
if (context.signal?.aborted) {
|
|
1066
|
+
return { status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.CANCELLED };
|
|
1067
|
+
}
|
|
1068
|
+
if (!stripe) {
|
|
972
1069
|
return {
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
false
|
|
978
|
-
)
|
|
1070
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.FAILED,
|
|
1071
|
+
code: ErrorCode.PROVIDER_UNAVAILABLE,
|
|
1072
|
+
message: "Stripe.js could not be loaded.",
|
|
1073
|
+
recoverable: false
|
|
979
1074
|
};
|
|
980
1075
|
}
|
|
981
|
-
const
|
|
982
|
-
if (!
|
|
1076
|
+
const modal = createStripeModal();
|
|
1077
|
+
if (!modal) {
|
|
983
1078
|
return {
|
|
984
|
-
|
|
985
|
-
|
|
1079
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.FAILED,
|
|
1080
|
+
code: ErrorCode.PROVIDER_UNAVAILABLE,
|
|
1081
|
+
message: "Stripe Payment Element could not be mounted.",
|
|
1082
|
+
recoverable: false
|
|
986
1083
|
};
|
|
987
1084
|
}
|
|
988
|
-
let
|
|
1085
|
+
let paymentElement;
|
|
1086
|
+
let elements;
|
|
989
1087
|
try {
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
signal: this.signal
|
|
997
|
-
});
|
|
998
|
-
} finally {
|
|
999
|
-
this.notifyAuthorizationUiChange(false);
|
|
1000
|
-
}
|
|
1001
|
-
if (outcome.status === "submitted") {
|
|
1002
|
-
return { ok: true, reference: outcome.reference };
|
|
1003
|
-
}
|
|
1004
|
-
if (outcome.status === "cancelled") {
|
|
1088
|
+
elements = stripe.elements({ clientSecret: context.action.client_secret });
|
|
1089
|
+
paymentElement = elements.create(STRIPE_PAYMENT_ELEMENT_TYPE);
|
|
1090
|
+
paymentElement.mount(modal.paymentMount);
|
|
1091
|
+
} catch {
|
|
1092
|
+
modal.root.remove();
|
|
1093
|
+
modal.previousFocus?.focus();
|
|
1005
1094
|
return {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
true
|
|
1011
|
-
)
|
|
1095
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.FAILED,
|
|
1096
|
+
code: ErrorCode.PROVIDER_UNAVAILABLE,
|
|
1097
|
+
message: "Stripe Payment Element could not be initialized.",
|
|
1098
|
+
recoverable: false
|
|
1012
1099
|
};
|
|
1013
1100
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1101
|
+
if (context.signal?.aborted) {
|
|
1102
|
+
if (paymentElement.destroy) paymentElement.destroy();
|
|
1103
|
+
else paymentElement.unmount?.();
|
|
1104
|
+
modal.root.remove();
|
|
1105
|
+
modal.previousFocus?.focus();
|
|
1106
|
+
return { status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.CANCELLED };
|
|
1107
|
+
}
|
|
1108
|
+
return new Promise((resolve) => {
|
|
1109
|
+
let settled = false;
|
|
1110
|
+
let submitting = false;
|
|
1111
|
+
let confirmationStarted = false;
|
|
1112
|
+
const cleanup = () => {
|
|
1113
|
+
context.signal?.removeEventListener("abort", onAbort);
|
|
1114
|
+
modal.form.removeEventListener("submit", onSubmit);
|
|
1115
|
+
modal.cancelButton.removeEventListener("click", onCancel);
|
|
1116
|
+
if (paymentElement.destroy) paymentElement.destroy();
|
|
1117
|
+
else paymentElement.unmount?.();
|
|
1118
|
+
modal.root.remove();
|
|
1119
|
+
modal.previousFocus?.focus();
|
|
1120
|
+
};
|
|
1121
|
+
const settle = (outcome) => {
|
|
1122
|
+
if (settled) return;
|
|
1123
|
+
settled = true;
|
|
1124
|
+
cleanup();
|
|
1125
|
+
resolve(outcome);
|
|
1126
|
+
};
|
|
1127
|
+
const onAbort = () => {
|
|
1128
|
+
if (confirmationStarted) {
|
|
1129
|
+
settle({
|
|
1130
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.SUBMITTED,
|
|
1131
|
+
reference: context.reference
|
|
1132
|
+
});
|
|
1133
|
+
return;
|
|
1042
1134
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
return this.fail(
|
|
1049
|
-
"INVALID_REDIRECT_AUTHORIZATION",
|
|
1050
|
-
"The payment provider returned an invalid authorization URL.",
|
|
1051
|
-
false
|
|
1052
|
-
);
|
|
1053
|
-
}
|
|
1054
|
-
if (typeof window !== "undefined" && this.returnUrl) {
|
|
1055
|
-
window.location.assign(authorizationUrl);
|
|
1056
|
-
return this.fail(
|
|
1057
|
-
"REDIRECT_REQUIRED",
|
|
1058
|
-
"Redirecting to complete payment authorization.",
|
|
1059
|
-
true
|
|
1060
|
-
);
|
|
1061
|
-
}
|
|
1062
|
-
if (typeof window !== "undefined") {
|
|
1063
|
-
const popup = window.open(
|
|
1064
|
-
authorizationUrl,
|
|
1065
|
-
"cimplify-auth",
|
|
1066
|
-
"width=520,height=760,noopener,noreferrer"
|
|
1067
|
-
);
|
|
1068
|
-
if (!popup) {
|
|
1069
|
-
return this.fail(
|
|
1070
|
-
"POPUP_BLOCKED",
|
|
1071
|
-
"Authorization popup was blocked. Please allow popups and retry.",
|
|
1072
|
-
true
|
|
1073
|
-
);
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
break actionLoop;
|
|
1135
|
+
settle({ status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.CANCELLED });
|
|
1136
|
+
};
|
|
1137
|
+
const onCancel = () => {
|
|
1138
|
+
if (!confirmationStarted) {
|
|
1139
|
+
settle({ status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.CANCELLED });
|
|
1077
1140
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
return
|
|
1141
|
+
};
|
|
1142
|
+
const onSubmit = async (event) => {
|
|
1143
|
+
event.preventDefault();
|
|
1144
|
+
if (submitting || settled) return;
|
|
1145
|
+
submitting = true;
|
|
1146
|
+
modal.submitButton.disabled = true;
|
|
1147
|
+
modal.error.textContent = "";
|
|
1148
|
+
try {
|
|
1149
|
+
const validation = elements.submit ? await elements.submit() : {};
|
|
1150
|
+
if (validation.error) {
|
|
1151
|
+
modal.error.textContent = validation.error.message || "Check your payment details.";
|
|
1152
|
+
submitting = false;
|
|
1153
|
+
modal.submitButton.disabled = false;
|
|
1154
|
+
return;
|
|
1092
1155
|
}
|
|
1093
|
-
if (
|
|
1094
|
-
|
|
1156
|
+
if (settled || context.signal?.aborted) {
|
|
1157
|
+
settle({ status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.CANCELLED });
|
|
1158
|
+
return;
|
|
1095
1159
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1160
|
+
confirmationStarted = true;
|
|
1161
|
+
modal.cancelButton.disabled = true;
|
|
1162
|
+
const confirmation = await stripe.confirmPayment({
|
|
1163
|
+
elements,
|
|
1164
|
+
confirmParams: { return_url: context.returnUrl || window.location.href },
|
|
1165
|
+
redirect: STRIPE_REDIRECT_MODE
|
|
1166
|
+
});
|
|
1167
|
+
if (settled) return;
|
|
1168
|
+
if (confirmation.error) {
|
|
1169
|
+
settle(stripeFailure(confirmation.error));
|
|
1170
|
+
return;
|
|
1100
1171
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1172
|
+
settle({
|
|
1173
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.SUBMITTED,
|
|
1174
|
+
reference: confirmation.paymentIntent?.id || context.reference
|
|
1175
|
+
});
|
|
1176
|
+
} catch (error) {
|
|
1177
|
+
if (confirmationStarted) {
|
|
1178
|
+
settle({
|
|
1179
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.SUBMITTED,
|
|
1180
|
+
reference: context.reference
|
|
1181
|
+
});
|
|
1182
|
+
return;
|
|
1107
1183
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
break actionLoop;
|
|
1113
|
-
default:
|
|
1114
|
-
return this.fail(
|
|
1115
|
-
"UNSUPPORTED_NEXT_ACTION",
|
|
1116
|
-
"The payment provider requested an unsupported authorization action.",
|
|
1117
|
-
false
|
|
1184
|
+
settle(
|
|
1185
|
+
stripeFailure({
|
|
1186
|
+
message: error instanceof Error ? error.message : "Stripe confirmation failed."
|
|
1187
|
+
})
|
|
1118
1188
|
);
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
modal.form.addEventListener("submit", onSubmit);
|
|
1192
|
+
modal.cancelButton.addEventListener("click", onCancel);
|
|
1193
|
+
context.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1194
|
+
paymentElement.on?.("escape", onCancel);
|
|
1195
|
+
paymentElement.on?.("loaderror", (event) => {
|
|
1196
|
+
if (confirmationStarted) return;
|
|
1197
|
+
settle({
|
|
1198
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.FAILED,
|
|
1199
|
+
code: ErrorCode.PROVIDER_UNAVAILABLE,
|
|
1200
|
+
message: event?.error?.message || "Stripe Payment Element failed to load.",
|
|
1201
|
+
recoverable: true
|
|
1202
|
+
});
|
|
1203
|
+
});
|
|
1126
1204
|
});
|
|
1127
1205
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
// src/payment-authorization/registry.ts
|
|
1209
|
+
function normalizeProvider(provider) {
|
|
1210
|
+
return typeof provider === "string" ? provider.trim().toLowerCase() : "";
|
|
1211
|
+
}
|
|
1212
|
+
var PaymentAuthorizationAdapterRegistry = class {
|
|
1213
|
+
constructor(adapters = []) {
|
|
1214
|
+
this.adapters = /* @__PURE__ */ new Map();
|
|
1215
|
+
for (const adapter of adapters) {
|
|
1216
|
+
this.register(adapter);
|
|
1135
1217
|
}
|
|
1136
|
-
resolutionState.authorizationSteps += 1;
|
|
1137
|
-
return void 0;
|
|
1138
1218
|
}
|
|
1139
|
-
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
while (resolutionState.pollAttempts < this.maxPollAttempts) {
|
|
1144
|
-
this.ensureNotAborted();
|
|
1145
|
-
const attempt = resolutionState.pollAttempts;
|
|
1146
|
-
resolutionState.pollAttempts += 1;
|
|
1147
|
-
this.emit("polling", {
|
|
1148
|
-
poll_attempt: attempt,
|
|
1149
|
-
max_poll_attempts: this.maxPollAttempts,
|
|
1150
|
-
order_id: input.orderId,
|
|
1151
|
-
order_number: latestCheckoutResult.order_number
|
|
1152
|
-
});
|
|
1153
|
-
const statusResult = await this.client.checkout.pollPaymentStatus(input.orderId);
|
|
1154
|
-
if (!statusResult.ok) {
|
|
1155
|
-
resolutionState.consecutivePollErrors += 1;
|
|
1156
|
-
if (resolutionState.consecutivePollErrors >= MAX_CONSECUTIVE_NETWORK_ERRORS) {
|
|
1157
|
-
return this.fail(
|
|
1158
|
-
"NETWORK_ERROR",
|
|
1159
|
-
"Unable to confirm payment due to repeated network errors.",
|
|
1160
|
-
true
|
|
1161
|
-
);
|
|
1162
|
-
}
|
|
1163
|
-
await this.wait(this.pollIntervalMs);
|
|
1164
|
-
continue;
|
|
1165
|
-
}
|
|
1166
|
-
resolutionState.consecutivePollErrors = 0;
|
|
1167
|
-
if (statusResult.value.reference) {
|
|
1168
|
-
paymentReference = statusResult.value.reference;
|
|
1169
|
-
}
|
|
1170
|
-
const normalized = normalizeStatusResponse(statusResult.value);
|
|
1171
|
-
if (normalized.paid || isPaymentStatusSuccess(normalized.status)) {
|
|
1172
|
-
return this.finalizeSuccess({
|
|
1173
|
-
...latestCheckoutResult,
|
|
1174
|
-
payment_status: normalized.status,
|
|
1175
|
-
payment_reference: paymentReference || latestCheckoutResult.payment_reference
|
|
1176
|
-
});
|
|
1177
|
-
}
|
|
1178
|
-
if (isPaymentStatusFailure(normalized.status)) {
|
|
1179
|
-
return this.fail(
|
|
1180
|
-
normalized.status ? normalized.status.toUpperCase() : "PAYMENT_FAILED",
|
|
1181
|
-
normalized.message || "Payment failed during confirmation.",
|
|
1182
|
-
false
|
|
1183
|
-
);
|
|
1184
|
-
}
|
|
1185
|
-
if (isPaymentStatusRequiresAction(normalized.status)) {
|
|
1186
|
-
const authorizationType = latestCheckoutResult.authorization_type;
|
|
1187
|
-
const paymentId = latestCheckoutResult.payment_id;
|
|
1188
|
-
if (!authorizationType || !normalizeAuthorizationType(authorizationType) || !paymentId) {
|
|
1189
|
-
return this.fail(
|
|
1190
|
-
"INVALID_AUTHORIZATION_RESPONSE",
|
|
1191
|
-
"The payment provider returned an incomplete authorization response.",
|
|
1192
|
-
false
|
|
1193
|
-
);
|
|
1194
|
-
}
|
|
1195
|
-
return this.resolveNextAction(
|
|
1196
|
-
{
|
|
1197
|
-
...latestCheckoutResult,
|
|
1198
|
-
payment_status: normalized.status,
|
|
1199
|
-
payment_reference: paymentReference,
|
|
1200
|
-
display_text: normalized.message || latestCheckoutResult.display_text,
|
|
1201
|
-
next_action: {
|
|
1202
|
-
type: NEXT_ACTION_AUTHORIZATION,
|
|
1203
|
-
authorization_type: authorizationType,
|
|
1204
|
-
display_text: normalized.message || latestCheckoutResult.display_text
|
|
1205
|
-
}
|
|
1206
|
-
},
|
|
1207
|
-
resolutionState
|
|
1208
|
-
);
|
|
1209
|
-
}
|
|
1210
|
-
await this.wait(this.pollIntervalMs);
|
|
1219
|
+
register(adapter) {
|
|
1220
|
+
const provider = normalizeProvider(adapter.provider);
|
|
1221
|
+
if (!provider) {
|
|
1222
|
+
throw new TypeError("Payment authorization adapter provider cannot be empty.");
|
|
1211
1223
|
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
"Payment confirmation timed out. Please retry checkout.",
|
|
1215
|
-
true
|
|
1216
|
-
);
|
|
1224
|
+
this.adapters.set(provider, adapter);
|
|
1225
|
+
return this;
|
|
1217
1226
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
const paymentId = input.paymentId;
|
|
1221
|
-
if (!authorizationType || !paymentId) {
|
|
1222
|
-
return {
|
|
1223
|
-
ok: false,
|
|
1224
|
-
result: this.fail(
|
|
1225
|
-
"INVALID_AUTHORIZATION_RESPONSE",
|
|
1226
|
-
"The payment provider returned an incomplete authorization response.",
|
|
1227
|
-
false
|
|
1228
|
-
)
|
|
1229
|
-
};
|
|
1230
|
-
}
|
|
1231
|
-
this.emit("awaiting_authorization", {
|
|
1232
|
-
authorization_type: authorizationType,
|
|
1233
|
-
display_text: input.displayText,
|
|
1234
|
-
provider: input.provider,
|
|
1235
|
-
order_id: input.orderId,
|
|
1236
|
-
order_number: input.orderNumber
|
|
1237
|
-
});
|
|
1238
|
-
let authorizationRequest;
|
|
1239
|
-
const submit = (authorization) => {
|
|
1240
|
-
this.ensureNotAborted();
|
|
1241
|
-
let normalizedAuthorization;
|
|
1242
|
-
try {
|
|
1243
|
-
normalizedAuthorization = normalizeAuthorizationSubmission(
|
|
1244
|
-
authorizationType,
|
|
1245
|
-
authorization
|
|
1246
|
-
);
|
|
1247
|
-
} catch (error) {
|
|
1248
|
-
return Promise.reject(error);
|
|
1249
|
-
}
|
|
1250
|
-
if (!authorizationRequest) {
|
|
1251
|
-
const request = this.client.checkout.submitAuthorization({
|
|
1252
|
-
payment_id: paymentId,
|
|
1253
|
-
authorization: normalizedAuthorization
|
|
1254
|
-
}).then((submitResult) => {
|
|
1255
|
-
if (!submitResult.ok) {
|
|
1256
|
-
throw new Error(submitResult.error.message || "Authorization failed.");
|
|
1257
|
-
}
|
|
1258
|
-
return submitResult.value;
|
|
1259
|
-
});
|
|
1260
|
-
authorizationRequest = request;
|
|
1261
|
-
void request.catch(() => {
|
|
1262
|
-
if (authorizationRequest === request) {
|
|
1263
|
-
authorizationRequest = void 0;
|
|
1264
|
-
}
|
|
1265
|
-
});
|
|
1266
|
-
}
|
|
1267
|
-
return authorizationRequest.then(() => void 0);
|
|
1268
|
-
};
|
|
1269
|
-
try {
|
|
1270
|
-
if (this.onAuthorizationRequired) {
|
|
1271
|
-
await waitForAbortable(
|
|
1272
|
-
Promise.resolve(this.onAuthorizationRequired(authorizationType, submit)),
|
|
1273
|
-
this.signal
|
|
1274
|
-
);
|
|
1275
|
-
} else {
|
|
1276
|
-
return {
|
|
1277
|
-
ok: false,
|
|
1278
|
-
result: this.fail(
|
|
1279
|
-
"AUTHORIZATION_REQUIRED",
|
|
1280
|
-
"Authorization callback is required in headless checkout mode.",
|
|
1281
|
-
false
|
|
1282
|
-
)
|
|
1283
|
-
};
|
|
1284
|
-
}
|
|
1285
|
-
if (!authorizationRequest) {
|
|
1286
|
-
return {
|
|
1287
|
-
ok: false,
|
|
1288
|
-
result: this.fail(
|
|
1289
|
-
"AUTHORIZATION_NOT_SUBMITTED",
|
|
1290
|
-
"The authorization callback returned without submitting a value.",
|
|
1291
|
-
true
|
|
1292
|
-
)
|
|
1293
|
-
};
|
|
1294
|
-
}
|
|
1295
|
-
return {
|
|
1296
|
-
ok: true,
|
|
1297
|
-
value: await waitForAbortable(authorizationRequest, this.signal)
|
|
1298
|
-
};
|
|
1299
|
-
} catch (error) {
|
|
1300
|
-
if (isAbortError(error)) {
|
|
1301
|
-
return {
|
|
1302
|
-
ok: false,
|
|
1303
|
-
result: this.fail("CANCELLED", "Checkout was cancelled.", true)
|
|
1304
|
-
};
|
|
1305
|
-
}
|
|
1306
|
-
const message = error instanceof Error ? error.message : "Payment authorization failed.";
|
|
1307
|
-
return {
|
|
1308
|
-
ok: false,
|
|
1309
|
-
result: this.fail("AUTHORIZATION_FAILED", message, true)
|
|
1310
|
-
};
|
|
1311
|
-
}
|
|
1227
|
+
unregister(provider) {
|
|
1228
|
+
return this.adapters.delete(normalizeProvider(provider));
|
|
1312
1229
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
if (!currency) {
|
|
1316
|
-
return this.fail("INVALID_CURRENCY", "Payment currency is missing or unsupported.", false);
|
|
1317
|
-
}
|
|
1318
|
-
this.emit("finalizing", {
|
|
1319
|
-
order_id: checkoutResult.order_id,
|
|
1320
|
-
order_number: checkoutResult.order_number
|
|
1321
|
-
});
|
|
1322
|
-
const enrolledInLink = await this.maybeEnrollInLink(checkoutResult.order_id);
|
|
1323
|
-
this.emit("success", {
|
|
1324
|
-
order_id: checkoutResult.order_id,
|
|
1325
|
-
order_number: checkoutResult.order_number
|
|
1326
|
-
});
|
|
1327
|
-
return {
|
|
1328
|
-
success: true,
|
|
1329
|
-
order: {
|
|
1330
|
-
id: checkoutResult.order_id,
|
|
1331
|
-
order_number: checkoutResult.order_number || checkoutResult.order_id,
|
|
1332
|
-
status: checkoutResult.payment_status || "paid",
|
|
1333
|
-
total: this.getOrderTotal(checkoutResult),
|
|
1334
|
-
currency
|
|
1335
|
-
},
|
|
1336
|
-
enrolled_in_link: enrolledInLink
|
|
1337
|
-
};
|
|
1230
|
+
get(provider) {
|
|
1231
|
+
return this.adapters.get(normalizeProvider(provider));
|
|
1338
1232
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
return false;
|
|
1342
|
-
}
|
|
1343
|
-
let businessId = this.businessId;
|
|
1344
|
-
if (!businessId) {
|
|
1345
|
-
const businessResult = await this.client.business.getInfo();
|
|
1346
|
-
if (!businessResult.ok || !businessResult.value?.id) {
|
|
1347
|
-
return false;
|
|
1348
|
-
}
|
|
1349
|
-
businessId = businessResult.value.id;
|
|
1350
|
-
}
|
|
1351
|
-
const address = this.getEnrollmentAddress();
|
|
1352
|
-
const mobileMoney = this.getEnrollmentMobileMoney();
|
|
1353
|
-
try {
|
|
1354
|
-
const enrollment = await this.client.link.enrollAndLinkOrder({
|
|
1355
|
-
order_id: orderId,
|
|
1356
|
-
business_id: businessId,
|
|
1357
|
-
order_type: this.orderType || this.checkoutData.order_type,
|
|
1358
|
-
address,
|
|
1359
|
-
mobile_money: mobileMoney
|
|
1360
|
-
});
|
|
1361
|
-
return enrollment.ok && enrollment.value.success;
|
|
1362
|
-
} catch {
|
|
1363
|
-
return false;
|
|
1364
|
-
}
|
|
1233
|
+
supports(provider) {
|
|
1234
|
+
return this.adapters.has(normalizeProvider(provider));
|
|
1365
1235
|
}
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
if (!source?.street_address) {
|
|
1369
|
-
return void 0;
|
|
1370
|
-
}
|
|
1371
|
-
return {
|
|
1372
|
-
label: "Default",
|
|
1373
|
-
street_address: source.street_address,
|
|
1374
|
-
apartment: source.apartment || void 0,
|
|
1375
|
-
city: source.city || "",
|
|
1376
|
-
region: source.region || "",
|
|
1377
|
-
postal_code: source.postal_code || void 0,
|
|
1378
|
-
country: source.country || void 0,
|
|
1379
|
-
delivery_instructions: source.delivery_instructions || void 0,
|
|
1380
|
-
phone_for_delivery: source.phone_for_delivery || void 0,
|
|
1381
|
-
latitude: source.latitude,
|
|
1382
|
-
longitude: source.longitude
|
|
1383
|
-
};
|
|
1236
|
+
providers() {
|
|
1237
|
+
return [...this.adapters.keys()];
|
|
1384
1238
|
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1239
|
+
async authorize(action, options) {
|
|
1240
|
+
const provider = normalizeProvider(action.provider);
|
|
1241
|
+
if (!provider) {
|
|
1387
1242
|
return {
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1243
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.FAILED,
|
|
1244
|
+
code: ErrorCode.PROVIDER_UNAVAILABLE,
|
|
1245
|
+
message: "Card authorization response did not identify a payment provider.",
|
|
1246
|
+
recoverable: false
|
|
1391
1247
|
};
|
|
1392
1248
|
}
|
|
1393
|
-
|
|
1249
|
+
const adapter = this.adapters.get(provider);
|
|
1250
|
+
if (!adapter) {
|
|
1394
1251
|
return {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1252
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.FAILED,
|
|
1253
|
+
code: ErrorCode.PROVIDER_UNAVAILABLE,
|
|
1254
|
+
message: `Card authorization provider '${provider}' is not supported by this SDK client.`,
|
|
1255
|
+
recoverable: false
|
|
1398
1256
|
};
|
|
1399
1257
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
return
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1258
|
+
try {
|
|
1259
|
+
const normalizedAction = action.provider === provider ? action : { ...action, provider };
|
|
1260
|
+
return await adapter.authorize({ ...options, action: normalizedAction });
|
|
1261
|
+
} catch (error) {
|
|
1262
|
+
return {
|
|
1263
|
+
status: PAYMENT_AUTHORIZATION_OUTCOME_STATUS.FAILED,
|
|
1264
|
+
code: ErrorCode.PAYMENT_FAILED,
|
|
1265
|
+
message: error instanceof Error ? error.message : `Card authorization provider '${provider}' failed unexpectedly.`,
|
|
1266
|
+
recoverable: true
|
|
1267
|
+
};
|
|
1408
1268
|
}
|
|
1409
|
-
return "0.00";
|
|
1410
1269
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1270
|
+
};
|
|
1271
|
+
function createDefaultPaymentAuthorizationAdapterRegistry() {
|
|
1272
|
+
return new PaymentAuthorizationAdapterRegistry([
|
|
1273
|
+
new PaystackAuthorizationAdapter(),
|
|
1274
|
+
new StripeAuthorizationAdapter()
|
|
1275
|
+
]);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// src/utils/purchase-intent-confirmation.ts
|
|
1279
|
+
var EXACT_ZERO_MONEY = /^[+-]?0+(?:\.0+)?(?:[eE][+-]?\d+)?$/;
|
|
1280
|
+
function isExactlyZeroMoney(value) {
|
|
1281
|
+
if (typeof value === "number") return Object.is(value, 0) || Object.is(value, -0);
|
|
1282
|
+
return EXACT_ZERO_MONEY.test(value.trim());
|
|
1283
|
+
}
|
|
1284
|
+
function selectPurchaseIntentPaymentOption(terms, selection) {
|
|
1285
|
+
return terms.payment_options.filter((option) => option.status === PAYMENT_OPTION_STATUS.ELIGIBLE).sort((left, right) => left.rank - right.rank).find(
|
|
1286
|
+
(option) => option.method_kind === selection.method_kind && (selection.payment_option_id === void 0 || option.id === selection.payment_option_id)
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
function createPurchaseIntentConfirmation(terms, paymentOption, idempotencyKey) {
|
|
1290
|
+
const normalizedKey = idempotencyKey.trim();
|
|
1291
|
+
if (!normalizedKey) throw new TypeError("idempotencyKey must not be empty");
|
|
1292
|
+
const requiresOption = purchaseIntentRequiresPaymentOption(terms);
|
|
1293
|
+
if (!paymentOption && requiresOption) {
|
|
1294
|
+
throw new TypeError("Nonzero rail terms require an eligible payment option");
|
|
1295
|
+
}
|
|
1296
|
+
if (paymentOption && !requiresOption) {
|
|
1297
|
+
throw new TypeError("Zero-rail terms must not name a payment option");
|
|
1298
|
+
}
|
|
1299
|
+
const authoritativeOption = paymentOption ? terms.payment_options.find(
|
|
1300
|
+
(candidate) => candidate.id === paymentOption.id && candidate.status === PAYMENT_OPTION_STATUS.ELIGIBLE
|
|
1301
|
+
) : void 0;
|
|
1302
|
+
if (paymentOption && !authoritativeOption) {
|
|
1303
|
+
throw new TypeError("Payment option is not eligible for these checkout terms");
|
|
1422
1304
|
}
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1305
|
+
return {
|
|
1306
|
+
accepted_intent_revision: terms.intent_revision,
|
|
1307
|
+
accepted_terms_version: terms.terms_version,
|
|
1308
|
+
accepted_due_now: terms.money.due_now,
|
|
1309
|
+
// Rail fees and routes may differ by option. Consent is to the exact
|
|
1310
|
+
// server-issued buyer debit, never an ambiguous principal/fee aggregate.
|
|
1311
|
+
accepted_customer_debit_amount: authoritativeOption?.customer_debit_amount ?? terms.money.rail_amount,
|
|
1312
|
+
currency: terms.money.currency,
|
|
1313
|
+
payment_option_id: authoritativeOption?.id ?? null,
|
|
1314
|
+
idempotency_key: normalizedKey
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
function purchaseIntentRequiresPaymentOption(terms) {
|
|
1318
|
+
return terms.payment_options.length > 0 || !isExactlyZeroMoney(terms.money.rail_amount);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// src/utils/purchase-intent-adapter.ts
|
|
1322
|
+
function tenderPreference(form) {
|
|
1323
|
+
if (form.payment_method === PURCHASE_INTENT_PAYMENT_METHOD.MOBILE_MONEY) {
|
|
1324
|
+
if (form.link_payment_method_id) {
|
|
1325
|
+
return {
|
|
1326
|
+
method_kind: PURCHASE_INTENT_PAYMENT_METHOD.MOBILE_MONEY,
|
|
1327
|
+
authority: {
|
|
1328
|
+
authority_kind: MOBILE_MONEY_TENDER_AUTHORITY_KIND.SAVED_REFERENCE,
|
|
1329
|
+
stored_instrument_id: form.link_payment_method_id
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1426
1332
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1333
|
+
const details = form.mobile_money_details;
|
|
1334
|
+
if (!details?.phone_number || !details.provider) {
|
|
1335
|
+
throw new CimplifyError(
|
|
1336
|
+
ErrorCode.INVALID_PAYMENT_METHOD,
|
|
1337
|
+
"Mobile money requires a phone number and network.",
|
|
1338
|
+
false
|
|
1339
|
+
);
|
|
1430
1340
|
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1341
|
+
const network = purchaseIntentMobileMoneyNetwork(details.provider);
|
|
1342
|
+
if (!network) {
|
|
1343
|
+
throw new CimplifyError(
|
|
1344
|
+
ErrorCode.INVALID_PAYMENT_METHOD,
|
|
1345
|
+
"That mobile money network is not available for PurchaseIntent checkout.",
|
|
1346
|
+
false
|
|
1347
|
+
);
|
|
1436
1348
|
}
|
|
1437
|
-
}
|
|
1438
|
-
fail(code, message, recoverable) {
|
|
1439
|
-
this.emit("failed", {
|
|
1440
|
-
display_text: message
|
|
1441
|
-
});
|
|
1442
1349
|
return {
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1350
|
+
method_kind: PURCHASE_INTENT_PAYMENT_METHOD.MOBILE_MONEY,
|
|
1351
|
+
authority: {
|
|
1352
|
+
authority_kind: MOBILE_MONEY_TENDER_AUTHORITY_KIND.TRANSIENT,
|
|
1353
|
+
phone_number: details.phone_number,
|
|
1354
|
+
network
|
|
1448
1355
|
}
|
|
1449
1356
|
};
|
|
1450
1357
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1358
|
+
if (form.payment_method === LEGACY_CHECKOUT_PAYMENT_METHOD_ALIAS.CASH || form.payment_method === PURCHASE_INTENT_PAYMENT_METHOD.OFFLINE) {
|
|
1359
|
+
throw new CimplifyError(
|
|
1360
|
+
ErrorCode.INVALID_PAYMENT_METHOD,
|
|
1361
|
+
"Offline tender requires an authenticated staff checkout.",
|
|
1362
|
+
false
|
|
1363
|
+
);
|
|
1454
1364
|
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1365
|
+
if (form.payment_method === PURCHASE_INTENT_PAYMENT_METHOD.CARD) {
|
|
1366
|
+
return {
|
|
1367
|
+
method_kind: PURCHASE_INTENT_PAYMENT_METHOD.CARD,
|
|
1368
|
+
stored_instrument_id: form.link_payment_method_id
|
|
1369
|
+
};
|
|
1458
1370
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
throw createAbortError();
|
|
1462
|
-
}
|
|
1371
|
+
if (form.payment_method === LEGACY_CHECKOUT_PAYMENT_METHOD_ALIAS.STORE_CREDIT) {
|
|
1372
|
+
return void 0;
|
|
1463
1373
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1374
|
+
throw new CimplifyError(
|
|
1375
|
+
ErrorCode.INVALID_PAYMENT_METHOD,
|
|
1376
|
+
"Checkout payment method is not supported.",
|
|
1377
|
+
false
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
function checkoutFormToIntentRequest(form, clientIntentKey) {
|
|
1381
|
+
return {
|
|
1382
|
+
client_intent_key: clientIntentKey,
|
|
1383
|
+
source: { kind: PURCHASE_INTENT_SOURCE_KIND.CART, cart_id: form.cart_id },
|
|
1384
|
+
checkout: {
|
|
1385
|
+
customer: form.customer,
|
|
1386
|
+
order_type: form.order_type,
|
|
1387
|
+
address_info: form.address_info,
|
|
1388
|
+
location_id: form.location_id,
|
|
1389
|
+
delivery_rate_id: form.delivery_rate_id,
|
|
1390
|
+
special_instructions: form.special_instructions,
|
|
1391
|
+
link_address_id: form.link_address_id,
|
|
1392
|
+
metadata: form.metadata,
|
|
1393
|
+
pay_currency: form.pay_currency,
|
|
1394
|
+
fx_quote_id: form.fx_quote_id,
|
|
1395
|
+
use_store_credit: form.use_store_credit || form.payment_method === LEGACY_CHECKOUT_PAYMENT_METHOD_ALIAS.STORE_CREDIT,
|
|
1396
|
+
voucher_code: form.voucher_code,
|
|
1397
|
+
pay_deposit: form.pay_deposit,
|
|
1398
|
+
tender_preference: tenderPreference(form)
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
function selectCheckoutPaymentOption(terms, form) {
|
|
1403
|
+
const preference = tenderPreference(form);
|
|
1404
|
+
if (!preference) return void 0;
|
|
1405
|
+
return terms.payment_options.filter((option) => option.status === PAYMENT_OPTION_STATUS.ELIGIBLE).sort((left, right) => left.rank - right.rank).find((option) => option.method_kind === preference.method_kind);
|
|
1406
|
+
}
|
|
1407
|
+
function checkoutFormToConfirmation(_form, terms, paymentOption, idempotencyKey) {
|
|
1408
|
+
return createPurchaseIntentConfirmation(terms, paymentOption, idempotencyKey);
|
|
1409
|
+
}
|
|
1410
|
+
function committedBase(response) {
|
|
1411
|
+
return {
|
|
1412
|
+
intent_id: response.intent_id,
|
|
1413
|
+
attempt_id: response.attempt_id,
|
|
1414
|
+
order_id: response.order_id,
|
|
1415
|
+
order_number: response.order_number || response.order_id,
|
|
1416
|
+
payment_id: response.payment_id
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
function confirmationToCheckoutResult(response) {
|
|
1420
|
+
switch (response.status) {
|
|
1421
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.ORDER_CREATED:
|
|
1422
|
+
return ok({
|
|
1423
|
+
...committedBase(response),
|
|
1424
|
+
payment_status: LEGACY_CHECKOUT_PAYMENT_STATUS.SUCCEEDED,
|
|
1425
|
+
requires_authorization: false,
|
|
1426
|
+
next_action: { type: CHECKOUT_NEXT_ACTION.NONE }
|
|
1427
|
+
});
|
|
1428
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.COLLECTION_PROCESSING:
|
|
1429
|
+
return ok({
|
|
1430
|
+
...committedBase(response),
|
|
1431
|
+
payment_status: LEGACY_CHECKOUT_PAYMENT_STATUS.PROCESSING,
|
|
1432
|
+
requires_authorization: false,
|
|
1433
|
+
next_action: { type: CHECKOUT_NEXT_ACTION.POLL }
|
|
1434
|
+
});
|
|
1435
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.PAYMENT_OUTCOME_UNKNOWN:
|
|
1436
|
+
return ok({
|
|
1437
|
+
...committedBase(response),
|
|
1438
|
+
payment_status: LEGACY_CHECKOUT_PAYMENT_STATUS.UNKNOWN,
|
|
1439
|
+
requires_authorization: false,
|
|
1440
|
+
next_action: { type: CHECKOUT_NEXT_ACTION.POLL }
|
|
1441
|
+
});
|
|
1442
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.PAYMENT_FAILED:
|
|
1443
|
+
return err(
|
|
1444
|
+
new CimplifyError(
|
|
1445
|
+
PURCHASE_INTENT_ERROR_CODE.PAYMENT_FAILED,
|
|
1446
|
+
"Payment failed. The order remains recorded, but collection did not succeed.",
|
|
1447
|
+
true
|
|
1448
|
+
)
|
|
1449
|
+
);
|
|
1450
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.REDIRECT_REQUIRED:
|
|
1451
|
+
return ok({
|
|
1452
|
+
...committedBase(response),
|
|
1453
|
+
payment_status: LEGACY_CHECKOUT_PAYMENT_STATUS.REQUIRES_ACTION,
|
|
1454
|
+
requires_authorization: false,
|
|
1455
|
+
authorization_url: response.authorization_url,
|
|
1456
|
+
next_action: {
|
|
1457
|
+
type: CHECKOUT_NEXT_ACTION.REDIRECT,
|
|
1458
|
+
authorization_url: response.authorization_url
|
|
1470
1459
|
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1460
|
+
});
|
|
1461
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.CARD_POPUP_REQUIRED:
|
|
1462
|
+
return ok({
|
|
1463
|
+
...committedBase(response),
|
|
1464
|
+
payment_status: LEGACY_CHECKOUT_PAYMENT_STATUS.REQUIRES_ACTION,
|
|
1465
|
+
requires_authorization: false,
|
|
1466
|
+
provider: response.provider,
|
|
1467
|
+
client_secret: response.client_secret,
|
|
1468
|
+
public_key: response.public_key,
|
|
1469
|
+
next_action: {
|
|
1470
|
+
type: CHECKOUT_NEXT_ACTION.CARD_POPUP,
|
|
1471
|
+
provider: response.provider,
|
|
1472
|
+
client_secret: response.client_secret,
|
|
1473
|
+
public_key: response.public_key,
|
|
1474
|
+
provider_account_id: response.provider_account_id ?? void 0
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1477
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.AUTHORIZATION_REQUIRED:
|
|
1478
|
+
return ok({
|
|
1479
|
+
...committedBase(response),
|
|
1480
|
+
payment_status: LEGACY_CHECKOUT_PAYMENT_STATUS.REQUIRES_ACTION,
|
|
1481
|
+
requires_authorization: true,
|
|
1482
|
+
authorization_type: response.authorization_type,
|
|
1483
|
+
display_text: response.display_text,
|
|
1484
|
+
next_action: {
|
|
1485
|
+
type: CHECKOUT_NEXT_ACTION.AUTHORIZATION,
|
|
1486
|
+
authorization_type: response.authorization_type,
|
|
1487
|
+
display_text: response.display_text
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1490
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.TERMS_CHANGED:
|
|
1491
|
+
return err(
|
|
1492
|
+
new CimplifyError(
|
|
1493
|
+
PURCHASE_INTENT_ERROR_CODE.TERMS_CHANGED,
|
|
1494
|
+
"Checkout terms changed. Review the replacement terms before confirming again.",
|
|
1495
|
+
true
|
|
1496
|
+
)
|
|
1497
|
+
);
|
|
1498
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.SELECTION_UNAVAILABLE:
|
|
1499
|
+
return err(
|
|
1500
|
+
new CimplifyError(
|
|
1501
|
+
PURCHASE_INTENT_ERROR_CODE.SELECTION_UNAVAILABLE,
|
|
1502
|
+
"One or more checkout selections are no longer available.",
|
|
1503
|
+
true
|
|
1504
|
+
)
|
|
1505
|
+
);
|
|
1506
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.OFFER_ENDED:
|
|
1507
|
+
return err(
|
|
1508
|
+
new CimplifyError(
|
|
1509
|
+
PURCHASE_INTENT_ERROR_CODE.OFFER_ENDED,
|
|
1510
|
+
"This offer has ended.",
|
|
1511
|
+
false
|
|
1512
|
+
)
|
|
1513
|
+
);
|
|
1514
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.SOURCE_UNAVAILABLE:
|
|
1515
|
+
return err(
|
|
1516
|
+
new CimplifyError(
|
|
1517
|
+
PURCHASE_INTENT_ERROR_CODE.SOURCE_UNAVAILABLE,
|
|
1518
|
+
"This purchase source is unavailable.",
|
|
1519
|
+
false
|
|
1520
|
+
)
|
|
1521
|
+
);
|
|
1481
1522
|
}
|
|
1482
|
-
}
|
|
1523
|
+
}
|
|
1483
1524
|
|
|
1484
1525
|
// src/checkout.ts
|
|
1526
|
+
function checkoutActionAuthority(response) {
|
|
1527
|
+
if (response.status !== PURCHASE_INTENT_CONFIRM_STATUS.CARD_POPUP_REQUIRED && response.status !== PURCHASE_INTENT_CONFIRM_STATUS.AUTHORIZATION_REQUIRED) {
|
|
1528
|
+
return null;
|
|
1529
|
+
}
|
|
1530
|
+
const paymentId = response.payment_id?.trim();
|
|
1531
|
+
return paymentId ? {
|
|
1532
|
+
intentId: response.intent_id,
|
|
1533
|
+
attemptId: response.attempt_id,
|
|
1534
|
+
paymentId
|
|
1535
|
+
} : null;
|
|
1536
|
+
}
|
|
1537
|
+
function isAmbiguousCheckoutError(error) {
|
|
1538
|
+
return error.code === ErrorCode.NETWORK_ERROR || error.code === ErrorCode.TIMEOUT;
|
|
1539
|
+
}
|
|
1540
|
+
function authorizationKind(value) {
|
|
1541
|
+
if (value && typeof value === "object" && AUTHORIZATION_TYPE.ADDRESS in value) {
|
|
1542
|
+
return AUTHORIZATION_TYPE.ADDRESS;
|
|
1543
|
+
}
|
|
1544
|
+
if (value === AUTHORIZATION_TYPE.OTP || value === AUTHORIZATION_TYPE.PIN || value === AUTHORIZATION_TYPE.PHONE || value === AUTHORIZATION_TYPE.BIRTHDAY) {
|
|
1545
|
+
return value;
|
|
1546
|
+
}
|
|
1547
|
+
return void 0;
|
|
1548
|
+
}
|
|
1549
|
+
function normalizeAuthorizationSubmission(expected, submission) {
|
|
1550
|
+
if (submission.type !== expected) throw new Error(`Expected ${expected} authorization details.`);
|
|
1551
|
+
if (submission.type === AUTHORIZATION_TYPE.ADDRESS) {
|
|
1552
|
+
const address = submission.address.trim();
|
|
1553
|
+
const city = submission.city.trim();
|
|
1554
|
+
const state = submission.state.trim();
|
|
1555
|
+
const zipCode = submission.zip_code.trim();
|
|
1556
|
+
if (!address || !city || !state || !zipCode) {
|
|
1557
|
+
throw new Error("Complete all authorization address fields.");
|
|
1558
|
+
}
|
|
1559
|
+
return { type: AUTHORIZATION_TYPE.ADDRESS, address, city, state, zip_code: zipCode };
|
|
1560
|
+
}
|
|
1561
|
+
const value = submission.value.trim();
|
|
1562
|
+
if (!value) throw new Error("Authorization value is required.");
|
|
1563
|
+
return { type: submission.type, value };
|
|
1564
|
+
}
|
|
1565
|
+
var DECIMAL_WIRE_VALUE = /^([+-]?)(\d+)(?:\.(\d+))?(?:[eE][+-]?\d+)?$/;
|
|
1566
|
+
function decimalSign(value) {
|
|
1567
|
+
if (typeof value === "number") {
|
|
1568
|
+
if (!Number.isFinite(value)) return null;
|
|
1569
|
+
if (Object.is(value, 0) || Object.is(value, -0)) return 0;
|
|
1570
|
+
return value > 0 ? 1 : -1;
|
|
1571
|
+
}
|
|
1572
|
+
const match = DECIMAL_WIRE_VALUE.exec(value.trim());
|
|
1573
|
+
if (!match) return null;
|
|
1574
|
+
const nonzero = [...match[2], ...match[3] ?? ""].some((digit) => digit !== "0");
|
|
1575
|
+
if (!nonzero) return 0;
|
|
1576
|
+
return match[1] === "-" ? -1 : 1;
|
|
1577
|
+
}
|
|
1485
1578
|
function toTerminalFailure(code, message, recoverable) {
|
|
1486
1579
|
return {
|
|
1487
1580
|
success: false,
|
|
@@ -1497,7 +1590,7 @@ function isValidCustomerName(name) {
|
|
|
1497
1590
|
return trimmed.length >= 2 && /\p{L}/u.test(trimmed);
|
|
1498
1591
|
}
|
|
1499
1592
|
function isOfflinePaymentMethod(method) {
|
|
1500
|
-
return method ===
|
|
1593
|
+
return method === LEGACY_CHECKOUT_PAYMENT_METHOD_ALIAS.CASH || method === PURCHASE_INTENT_PAYMENT_METHOD.OFFLINE;
|
|
1501
1594
|
}
|
|
1502
1595
|
function notifyCheckoutStatus(observer, status, context = {}) {
|
|
1503
1596
|
try {
|
|
@@ -1505,7 +1598,35 @@ function notifyCheckoutStatus(observer, status, context = {}) {
|
|
|
1505
1598
|
} catch {
|
|
1506
1599
|
}
|
|
1507
1600
|
}
|
|
1601
|
+
function notifyAuthorizationUi(observer, open) {
|
|
1602
|
+
try {
|
|
1603
|
+
observer?.(open);
|
|
1604
|
+
} catch {
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1508
1607
|
var MAX_STOREFRONT_COLLECTION_OPTIONS = 32;
|
|
1608
|
+
var CHECKOUT_INTENT_SCOPE_PREFIX = "checkout:";
|
|
1609
|
+
var CHECKOUT_CONFIRMATION_SCOPE_PREFIX = "checkout-confirm:";
|
|
1610
|
+
var CHECKOUT_INTENT_RECOVERY_FINGERPRINT = "cart-intent-v1";
|
|
1611
|
+
var ABORT_ERROR_NAME = "AbortError";
|
|
1612
|
+
var AUTHORIZATION_POPUP_NAME = "cimplify-auth";
|
|
1613
|
+
var AUTHORIZATION_POPUP_FEATURES = "width=520,height=760,noopener,noreferrer";
|
|
1614
|
+
var ORDER_TOKEN_QUERY_PARAMETER = "token";
|
|
1615
|
+
var CHECKOUT_API_ROUTE = {
|
|
1616
|
+
COLLECTION_OPTIONS: "/api/v1/payments/collection-options",
|
|
1617
|
+
paymentStatus: (orderId) => `/api/v1/orders/${orderId}/payment-status`,
|
|
1618
|
+
updateCustomer: (orderId) => `/api/v1/orders/${orderId}/customer`,
|
|
1619
|
+
verifyPayment: (orderId) => `/api/v1/orders/${orderId}/verify-payment`
|
|
1620
|
+
};
|
|
1621
|
+
var CHECKOUT_STATUS_BY_PURCHASE_INTENT_RESOLVE_STATUS = {
|
|
1622
|
+
[PURCHASE_INTENT_RESOLVE_STATUS.CONFIRMING]: CHECKOUT_STATUS.PROCESSING,
|
|
1623
|
+
[PURCHASE_INTENT_RESOLVE_STATUS.POLLING]: CHECKOUT_STATUS.POLLING,
|
|
1624
|
+
[PURCHASE_INTENT_RESOLVE_STATUS.AWAITING_ACTION]: CHECKOUT_STATUS.AWAITING_AUTHORIZATION,
|
|
1625
|
+
[PURCHASE_INTENT_RESOLVE_STATUS.REVIEWING_TERMS]: CHECKOUT_STATUS.REVIEWING_TERMS,
|
|
1626
|
+
[PURCHASE_INTENT_RESOLVE_STATUS.COMPLETED]: CHECKOUT_STATUS.FINALIZING,
|
|
1627
|
+
[PURCHASE_INTENT_RESOLVE_STATUS.FAILED]: CHECKOUT_STATUS.FAILED,
|
|
1628
|
+
[PURCHASE_INTENT_RESOLVE_STATUS.OUTCOME_UNKNOWN]: CHECKOUT_STATUS.RECOVERING
|
|
1629
|
+
};
|
|
1509
1630
|
function normalizeCollectionOptions(value, expectedCurrency) {
|
|
1510
1631
|
if (!value || typeof value !== "object") return null;
|
|
1511
1632
|
const response = value;
|
|
@@ -1528,7 +1649,8 @@ function normalizeCollectionOptions(value, expectedCurrency) {
|
|
|
1528
1649
|
if (option.method !== PAYMENT_METHOD.MOBILE_MONEY || typeof option.network !== "string" || !option.network.trim()) {
|
|
1529
1650
|
continue;
|
|
1530
1651
|
}
|
|
1531
|
-
const network = option.network
|
|
1652
|
+
const network = purchaseIntentMobileMoneyNetwork(option.network);
|
|
1653
|
+
if (!network) continue;
|
|
1532
1654
|
const key = `${PAYMENT_METHOD.MOBILE_MONEY}:${network}`;
|
|
1533
1655
|
if (seen.has(key)) continue;
|
|
1534
1656
|
seen.add(key);
|
|
@@ -1539,18 +1661,22 @@ function normalizeCollectionOptions(value, expectedCurrency) {
|
|
|
1539
1661
|
var CheckoutService = class {
|
|
1540
1662
|
constructor(client) {
|
|
1541
1663
|
this.client = client;
|
|
1664
|
+
/** Raw checkout material is component-lifetime only, never browser storage. */
|
|
1665
|
+
this.intentCommands = /* @__PURE__ */ new Map();
|
|
1666
|
+
/** Opaque owner/attempt authority for deprecated action submission. */
|
|
1667
|
+
this.actionAuthorities = /* @__PURE__ */ new Map();
|
|
1542
1668
|
}
|
|
1543
1669
|
validateCheckoutData(data) {
|
|
1544
1670
|
if (isOfflinePaymentMethod(data.payment_method)) {
|
|
1545
1671
|
return new CimplifyError(
|
|
1546
|
-
|
|
1672
|
+
ErrorCode.INVALID_PAYMENT_METHOD,
|
|
1547
1673
|
"Offline payment isn't available for online checkout. Please pay by card or mobile money.",
|
|
1548
1674
|
false
|
|
1549
1675
|
);
|
|
1550
1676
|
}
|
|
1551
1677
|
if (!isValidCustomerName(data.customer?.name)) {
|
|
1552
1678
|
return new CimplifyError(
|
|
1553
|
-
|
|
1679
|
+
ErrorCode.INVALID_CUSTOMER_NAME,
|
|
1554
1680
|
"Enter your name, not a phone number.",
|
|
1555
1681
|
false
|
|
1556
1682
|
);
|
|
@@ -1559,7 +1685,7 @@ var CheckoutService = class {
|
|
|
1559
1685
|
}
|
|
1560
1686
|
orderTokenParam(orderId) {
|
|
1561
1687
|
const token = this.client.getOrderToken(orderId);
|
|
1562
|
-
return token ?
|
|
1688
|
+
return token ? `?${ORDER_TOKEN_QUERY_PARAMETER}=${encodeURIComponent(token)}` : "";
|
|
1563
1689
|
}
|
|
1564
1690
|
/** Available storefront collection methods for one presentment currency. */
|
|
1565
1691
|
async collectionOptions(currency) {
|
|
@@ -1575,7 +1701,7 @@ var CheckoutService = class {
|
|
|
1575
1701
|
}
|
|
1576
1702
|
const response = await safe(
|
|
1577
1703
|
this.client.get(
|
|
1578
|
-
|
|
1704
|
+
`${CHECKOUT_API_ROUTE.COLLECTION_OPTIONS}?currency=${encodeURIComponent(normalized)}`
|
|
1579
1705
|
)
|
|
1580
1706
|
);
|
|
1581
1707
|
if (!response.ok) return response;
|
|
@@ -1595,52 +1721,181 @@ var CheckoutService = class {
|
|
|
1595
1721
|
authorizeCardPopup(action, options) {
|
|
1596
1722
|
return this.client.authorizationAdapters.authorize(action, options);
|
|
1597
1723
|
}
|
|
1598
|
-
async
|
|
1724
|
+
async startIntentCheckout(data, options) {
|
|
1599
1725
|
const invalid = this.validateCheckoutData(data);
|
|
1600
|
-
if (invalid)
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
const
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
};
|
|
1615
|
-
const result = await safe(this.client.post("/api/v1/checkout", checkoutData));
|
|
1616
|
-
if (result.ok) {
|
|
1726
|
+
if (invalid) return err(invalid);
|
|
1727
|
+
const intentScope = `${CHECKOUT_INTENT_SCOPE_PREFIX}${data.cart_id}`;
|
|
1728
|
+
const inMemoryCommand = JSON.stringify(data);
|
|
1729
|
+
const previousCommand = this.intentCommands.get(intentScope);
|
|
1730
|
+
if (previousCommand !== void 0 && previousCommand !== inMemoryCommand) {
|
|
1731
|
+
clearIntentIdempotencyKey(intentScope);
|
|
1732
|
+
}
|
|
1733
|
+
this.intentCommands.set(intentScope, inMemoryCommand);
|
|
1734
|
+
const clientIntentKey = data.idempotency_key || intentIdempotencyKey(intentScope, CHECKOUT_INTENT_RECOVERY_FINGERPRINT);
|
|
1735
|
+
const checkoutData = { ...data, idempotency_key: clientIntentKey };
|
|
1736
|
+
let mintRequest;
|
|
1737
|
+
try {
|
|
1738
|
+
mintRequest = checkoutFormToIntentRequest(checkoutData, clientIntentKey);
|
|
1739
|
+
} catch (error) {
|
|
1617
1740
|
clearIntentIdempotencyKey(intentScope);
|
|
1618
|
-
|
|
1619
|
-
|
|
1741
|
+
this.intentCommands.delete(intentScope);
|
|
1742
|
+
return err(
|
|
1743
|
+
error instanceof CimplifyError ? error : new CimplifyError(
|
|
1744
|
+
ErrorCode.CHECKOUT_VALIDATION_FAILED,
|
|
1745
|
+
"Checkout details are invalid.",
|
|
1746
|
+
false
|
|
1747
|
+
)
|
|
1748
|
+
);
|
|
1749
|
+
}
|
|
1750
|
+
const minted = await this.client.purchaseIntents.mint(mintRequest, options);
|
|
1751
|
+
if (!minted.ok) {
|
|
1752
|
+
if (!isAmbiguousCheckoutError(minted.error)) {
|
|
1753
|
+
clearIntentIdempotencyKey(intentScope);
|
|
1754
|
+
this.intentCommands.delete(intentScope);
|
|
1620
1755
|
}
|
|
1621
|
-
|
|
1756
|
+
return minted;
|
|
1757
|
+
}
|
|
1758
|
+
const terms = minted.value.current_terms;
|
|
1759
|
+
if (!terms) {
|
|
1622
1760
|
clearIntentIdempotencyKey(intentScope);
|
|
1761
|
+
this.intentCommands.delete(intentScope);
|
|
1762
|
+
return err(
|
|
1763
|
+
new CimplifyError(
|
|
1764
|
+
ErrorCode.CHECKOUT_TERMS_UNAVAILABLE,
|
|
1765
|
+
"Checkout terms are not available for this purchase intent.",
|
|
1766
|
+
true
|
|
1767
|
+
)
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1770
|
+
let paymentOption;
|
|
1771
|
+
try {
|
|
1772
|
+
paymentOption = selectCheckoutPaymentOption(terms, checkoutData);
|
|
1773
|
+
} catch (error) {
|
|
1774
|
+
return err(
|
|
1775
|
+
error instanceof CimplifyError ? error : new CimplifyError(
|
|
1776
|
+
ErrorCode.INVALID_PAYMENT_METHOD,
|
|
1777
|
+
"Payment details are invalid.",
|
|
1778
|
+
false
|
|
1779
|
+
)
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
if (!paymentOption && purchaseIntentRequiresPaymentOption(terms)) {
|
|
1783
|
+
return err(
|
|
1784
|
+
new CimplifyError(
|
|
1785
|
+
ErrorCode.PAYMENT_CAPABILITY_MISMATCH,
|
|
1786
|
+
"The selected payment method is not eligible for these checkout terms.",
|
|
1787
|
+
true
|
|
1788
|
+
)
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
const confirmationScope = `${CHECKOUT_CONFIRMATION_SCOPE_PREFIX}${minted.value.id}`;
|
|
1792
|
+
const confirmationFingerprint = JSON.stringify([
|
|
1793
|
+
terms.intent_revision,
|
|
1794
|
+
terms.terms_version,
|
|
1795
|
+
terms.terms_digest,
|
|
1796
|
+
paymentOption?.id ?? null,
|
|
1797
|
+
terms.money.due_now,
|
|
1798
|
+
paymentOption?.customer_debit_amount ?? terms.money.rail_amount,
|
|
1799
|
+
terms.money.currency
|
|
1800
|
+
]);
|
|
1801
|
+
const confirmationKey = intentIdempotencyKey(
|
|
1802
|
+
confirmationScope,
|
|
1803
|
+
confirmationFingerprint
|
|
1804
|
+
);
|
|
1805
|
+
const confirmation = checkoutFormToConfirmation(
|
|
1806
|
+
checkoutData,
|
|
1807
|
+
terms,
|
|
1808
|
+
paymentOption,
|
|
1809
|
+
confirmationKey
|
|
1810
|
+
);
|
|
1811
|
+
const confirmed = await this.client.purchaseIntents.confirm(
|
|
1812
|
+
minted.value.id,
|
|
1813
|
+
confirmation,
|
|
1814
|
+
options
|
|
1815
|
+
);
|
|
1816
|
+
if (!confirmed.ok) {
|
|
1817
|
+
if (!isAmbiguousCheckoutError(confirmed.error)) {
|
|
1818
|
+
clearIntentIdempotencyKey(confirmationScope);
|
|
1819
|
+
}
|
|
1820
|
+
return confirmed;
|
|
1821
|
+
}
|
|
1822
|
+
return ok({
|
|
1823
|
+
intentId: minted.value.id,
|
|
1824
|
+
intentScope,
|
|
1825
|
+
confirmationScope,
|
|
1826
|
+
confirmation,
|
|
1827
|
+
initialResponse: confirmed.value,
|
|
1828
|
+
terms
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
/** @deprecated Use `client.purchaseIntents.mint()` then `confirm()`. */
|
|
1832
|
+
async process(data, options) {
|
|
1833
|
+
const started = await this.startIntentCheckout(data, options);
|
|
1834
|
+
if (!started.ok) return started;
|
|
1835
|
+
this.reconcileActionAuthority(started.value.initialResponse);
|
|
1836
|
+
const result = confirmationToCheckoutResult(started.value.initialResponse);
|
|
1837
|
+
if (started.value.initialResponse.status === PURCHASE_INTENT_CONFIRM_STATUS.ORDER_CREATED || started.value.initialResponse.status === PURCHASE_INTENT_CONFIRM_STATUS.PAYMENT_FAILED) {
|
|
1838
|
+
clearIntentIdempotencyKey(started.value.intentScope);
|
|
1839
|
+
clearIntentIdempotencyKey(started.value.confirmationScope);
|
|
1840
|
+
this.intentCommands.delete(started.value.intentScope);
|
|
1623
1841
|
}
|
|
1624
1842
|
return result;
|
|
1625
1843
|
}
|
|
1626
1844
|
async initializePayment(_orderId, _method) {
|
|
1627
1845
|
return err(
|
|
1628
1846
|
new CimplifyError(
|
|
1629
|
-
|
|
1847
|
+
ErrorCode.NOT_IMPLEMENTED,
|
|
1630
1848
|
"initializePayment is not available in REST mode.",
|
|
1631
1849
|
false
|
|
1632
1850
|
)
|
|
1633
1851
|
);
|
|
1634
1852
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1853
|
+
/**
|
|
1854
|
+
* @deprecated Use `client.purchaseIntents.submitAction()`. This adapter works
|
|
1855
|
+
* only while this CheckoutService instance retains the opaque authority
|
|
1856
|
+
* returned by its own `process()` call; it never falls back to a payment-id
|
|
1857
|
+
* mutation.
|
|
1858
|
+
*/
|
|
1859
|
+
async submitAuthorization(input, options) {
|
|
1860
|
+
const paymentId = input.payment_id.trim();
|
|
1861
|
+
const authority = this.actionAuthorities.get(paymentId);
|
|
1862
|
+
if (!authority) {
|
|
1863
|
+
return err(
|
|
1864
|
+
new CimplifyError(
|
|
1865
|
+
ErrorCode.CHECKOUT_AUTHORITY_REQUIRED,
|
|
1866
|
+
"The purchase-intent action authority is unavailable. Restart checkout before submitting authorization.",
|
|
1867
|
+
false
|
|
1868
|
+
)
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
const submitted = await this.client.purchaseIntents.submitAction(
|
|
1872
|
+
authority.intentId,
|
|
1873
|
+
authority.attemptId,
|
|
1874
|
+
{ authorization: input.authorization },
|
|
1875
|
+
{
|
|
1876
|
+
signal: options?.signal,
|
|
1877
|
+
timeoutMs: options?.timeoutMs
|
|
1878
|
+
}
|
|
1879
|
+
);
|
|
1880
|
+
if (!submitted.ok) return submitted;
|
|
1881
|
+
this.reconcileActionAuthority(submitted.value, paymentId);
|
|
1882
|
+
return confirmationToCheckoutResult(submitted.value);
|
|
1883
|
+
}
|
|
1884
|
+
reconcileActionAuthority(response, previousPaymentId) {
|
|
1885
|
+
if (previousPaymentId) this.actionAuthorities.delete(previousPaymentId);
|
|
1886
|
+
if ("payment_id" in response && typeof response.payment_id === "string" && response.payment_id.trim()) {
|
|
1887
|
+
this.actionAuthorities.delete(response.payment_id.trim());
|
|
1888
|
+
}
|
|
1889
|
+
const authority = checkoutActionAuthority(response);
|
|
1890
|
+
if (authority) this.actionAuthorities.set(authority.paymentId, authority);
|
|
1637
1891
|
}
|
|
1638
|
-
async pollPaymentStatus(orderId) {
|
|
1892
|
+
async pollPaymentStatus(orderId, options) {
|
|
1639
1893
|
const encodedId = encodeURIComponent(orderId);
|
|
1640
1894
|
const tokenParam = this.orderTokenParam(orderId);
|
|
1641
1895
|
return safe(
|
|
1642
1896
|
this.client.get(
|
|
1643
|
-
|
|
1897
|
+
`${CHECKOUT_API_ROUTE.paymentStatus(encodedId)}${tokenParam}`,
|
|
1898
|
+
options
|
|
1644
1899
|
)
|
|
1645
1900
|
);
|
|
1646
1901
|
}
|
|
@@ -1648,20 +1903,58 @@ var CheckoutService = class {
|
|
|
1648
1903
|
const encodedId = encodeURIComponent(orderId);
|
|
1649
1904
|
const tokenParam = this.orderTokenParam(orderId);
|
|
1650
1905
|
return safe(
|
|
1651
|
-
this.client.post(
|
|
1906
|
+
this.client.post(`${CHECKOUT_API_ROUTE.updateCustomer(encodedId)}${tokenParam}`, customer)
|
|
1652
1907
|
);
|
|
1653
1908
|
}
|
|
1654
1909
|
async verifyPayment(orderId) {
|
|
1655
1910
|
const encodedId = encodeURIComponent(orderId);
|
|
1656
1911
|
const tokenParam = this.orderTokenParam(orderId);
|
|
1657
|
-
return safe(
|
|
1912
|
+
return safe(
|
|
1913
|
+
this.client.post(`${CHECKOUT_API_ROUTE.verifyPayment(encodedId)}${tokenParam}`)
|
|
1914
|
+
);
|
|
1658
1915
|
}
|
|
1916
|
+
async maybeEnrollResolvedOrder(orderId, data) {
|
|
1917
|
+
if (data.enroll_in_link === false) return false;
|
|
1918
|
+
const business = await safe(this.client.resolveBusinessId());
|
|
1919
|
+
if (!business.ok) return false;
|
|
1920
|
+
const address = data.address_info.street_address ? {
|
|
1921
|
+
label: "Default",
|
|
1922
|
+
street_address: data.address_info.street_address,
|
|
1923
|
+
apartment: data.address_info.apartment,
|
|
1924
|
+
city: data.address_info.city ?? "",
|
|
1925
|
+
region: data.address_info.region ?? "",
|
|
1926
|
+
postal_code: data.address_info.postal_code,
|
|
1927
|
+
country: data.address_info.country,
|
|
1928
|
+
delivery_instructions: data.address_info.delivery_instructions,
|
|
1929
|
+
phone_for_delivery: data.address_info.phone_for_delivery,
|
|
1930
|
+
latitude: data.address_info.latitude,
|
|
1931
|
+
longitude: data.address_info.longitude
|
|
1932
|
+
} : void 0;
|
|
1933
|
+
const mobileMoney = data.payment_method === PAYMENT_METHOD.MOBILE_MONEY && data.mobile_money_details ? {
|
|
1934
|
+
phone_number: data.mobile_money_details.phone_number,
|
|
1935
|
+
provider: data.mobile_money_details.provider,
|
|
1936
|
+
label: "Mobile Money"
|
|
1937
|
+
} : void 0;
|
|
1938
|
+
try {
|
|
1939
|
+
const enrollment = await this.client.link.enrollAndLinkOrder({
|
|
1940
|
+
order_id: orderId,
|
|
1941
|
+
business_id: business.value,
|
|
1942
|
+
order_type: data.order_type,
|
|
1943
|
+
address,
|
|
1944
|
+
mobile_money: mobileMoney
|
|
1945
|
+
});
|
|
1946
|
+
return enrollment.ok && enrollment.value.success;
|
|
1947
|
+
} catch {
|
|
1948
|
+
return false;
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
/** @deprecated Use `client.purchaseIntents.resolve()`. */
|
|
1659
1952
|
async processAndResolve(data) {
|
|
1660
|
-
notifyCheckoutStatus(data.on_status_change,
|
|
1953
|
+
notifyCheckoutStatus(data.on_status_change, CHECKOUT_STATUS.PREPARING);
|
|
1661
1954
|
if (!data.cart_id) {
|
|
1662
1955
|
return ok(
|
|
1663
1956
|
toTerminalFailure(
|
|
1664
|
-
|
|
1957
|
+
ErrorCode.INVALID_CART,
|
|
1665
1958
|
"A valid cart is required before checkout can start.",
|
|
1666
1959
|
false
|
|
1667
1960
|
)
|
|
@@ -1669,13 +1962,19 @@ var CheckoutService = class {
|
|
|
1669
1962
|
}
|
|
1670
1963
|
const cartResult = await this.client.cart.get();
|
|
1671
1964
|
if (!cartResult.ok || !cartResult.value?.id) {
|
|
1672
|
-
return ok(
|
|
1965
|
+
return ok(
|
|
1966
|
+
toTerminalFailure(
|
|
1967
|
+
ErrorCode.INVALID_CART,
|
|
1968
|
+
"Unable to load cart for checkout.",
|
|
1969
|
+
false
|
|
1970
|
+
)
|
|
1971
|
+
);
|
|
1673
1972
|
}
|
|
1674
1973
|
const cart = cartResult.value;
|
|
1675
1974
|
if (cart.id !== data.cart_id || cart.items.length === 0) {
|
|
1676
1975
|
return ok(
|
|
1677
1976
|
toTerminalFailure(
|
|
1678
|
-
|
|
1977
|
+
ErrorCode.INVALID_CART,
|
|
1679
1978
|
"Cart is empty or no longer valid. Please refresh and try again.",
|
|
1680
1979
|
false
|
|
1681
1980
|
)
|
|
@@ -1705,7 +2004,7 @@ var CheckoutService = class {
|
|
|
1705
2004
|
if (!isSupportedCurrency(rawBaseCurrency)) {
|
|
1706
2005
|
return ok(
|
|
1707
2006
|
toTerminalFailure(
|
|
1708
|
-
|
|
2007
|
+
ErrorCode.UNSUPPORTED_CURRENCY,
|
|
1709
2008
|
"Cart currency is missing or unsupported. Refresh the cart before checkout.",
|
|
1710
2009
|
false
|
|
1711
2010
|
)
|
|
@@ -1716,15 +2015,24 @@ var CheckoutService = class {
|
|
|
1716
2015
|
if (payCurrency && !isSupportedCurrency(payCurrency)) {
|
|
1717
2016
|
return ok(
|
|
1718
2017
|
toTerminalFailure(
|
|
1719
|
-
|
|
2018
|
+
ErrorCode.UNSUPPORTED_CURRENCY,
|
|
1720
2019
|
"Payment currency is unsupported for checkout.",
|
|
1721
2020
|
false
|
|
1722
2021
|
)
|
|
1723
2022
|
);
|
|
1724
2023
|
}
|
|
1725
2024
|
const payCurrencyCode = payCurrency && isSupportedCurrency(payCurrency) ? payCurrency : void 0;
|
|
1726
|
-
const
|
|
1727
|
-
if (
|
|
2025
|
+
const cartTotalSign = decimalSign(cart.pricing.total_price);
|
|
2026
|
+
if (cartTotalSign === null || cartTotalSign < 0) {
|
|
2027
|
+
return ok(
|
|
2028
|
+
toTerminalFailure(
|
|
2029
|
+
ErrorCode.CHECKOUT_VALIDATION_FAILED,
|
|
2030
|
+
"Cart total is not a valid non-negative decimal amount.",
|
|
2031
|
+
false
|
|
2032
|
+
)
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
if (payCurrencyCode && payCurrencyCode !== baseCurrency && !checkoutData.fx_quote_id && cartTotalSign > 0) {
|
|
1728
2036
|
const fxQuoteResult = await this.client.fx.lockQuote({
|
|
1729
2037
|
from: baseCurrency,
|
|
1730
2038
|
to: payCurrencyCode,
|
|
@@ -1733,7 +2041,7 @@ var CheckoutService = class {
|
|
|
1733
2041
|
if (!fxQuoteResult.ok) {
|
|
1734
2042
|
return ok(
|
|
1735
2043
|
toTerminalFailure(
|
|
1736
|
-
|
|
2044
|
+
ErrorCode.FX_QUOTE_FAILED,
|
|
1737
2045
|
fxQuoteResult.error.message || "Unable to lock FX quote for checkout.",
|
|
1738
2046
|
true
|
|
1739
2047
|
)
|
|
@@ -1742,35 +2050,266 @@ var CheckoutService = class {
|
|
|
1742
2050
|
checkoutData.pay_currency = payCurrencyCode;
|
|
1743
2051
|
checkoutData.fx_quote_id = fxQuoteResult.value.id;
|
|
1744
2052
|
}
|
|
1745
|
-
notifyCheckoutStatus(data.on_status_change,
|
|
1746
|
-
const
|
|
1747
|
-
if (!
|
|
2053
|
+
notifyCheckoutStatus(data.on_status_change, CHECKOUT_STATUS.PROCESSING);
|
|
2054
|
+
const started = await this.startIntentCheckout(checkoutData, { signal: data.signal });
|
|
2055
|
+
if (!started.ok) {
|
|
1748
2056
|
return ok(
|
|
1749
2057
|
toTerminalFailure(
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
2058
|
+
started.error.code || ErrorCode.CHECKOUT_FAILED,
|
|
2059
|
+
started.error.message || "Unable to process checkout.",
|
|
2060
|
+
started.error.retryable ?? true
|
|
2061
|
+
)
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
let actionFailure;
|
|
2065
|
+
try {
|
|
2066
|
+
const resolved = await this.client.purchaseIntents.resolve(started.value.intentId, {
|
|
2067
|
+
confirmation: started.value.confirmation,
|
|
2068
|
+
initialResponse: started.value.initialResponse,
|
|
2069
|
+
pollIntervalMs: data.poll_interval_ms,
|
|
2070
|
+
maxPollAttempts: data.max_poll_attempts,
|
|
2071
|
+
signal: data.signal,
|
|
2072
|
+
onStatusChange: (status, response) => {
|
|
2073
|
+
const context = {};
|
|
2074
|
+
if (response && "order_id" in response) context.order_id = response.order_id;
|
|
2075
|
+
if (response?.status === PURCHASE_INTENT_CONFIRM_STATUS.TERMS_CHANGED) {
|
|
2076
|
+
context.replacement_terms = response.replacement;
|
|
2077
|
+
context.terms_deltas = response.deltas;
|
|
2078
|
+
}
|
|
2079
|
+
if (response?.status === PURCHASE_INTENT_CONFIRM_STATUS.SELECTION_UNAVAILABLE) {
|
|
2080
|
+
context.unavailable_lines = response.lines;
|
|
2081
|
+
}
|
|
2082
|
+
const checkoutStatus = CHECKOUT_STATUS_BY_PURCHASE_INTENT_RESOLVE_STATUS[status];
|
|
2083
|
+
notifyCheckoutStatus(data.on_status_change, checkoutStatus, context);
|
|
2084
|
+
},
|
|
2085
|
+
onActionRequired: async (action) => {
|
|
2086
|
+
switch (action.status) {
|
|
2087
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.CARD_POPUP_REQUIRED: {
|
|
2088
|
+
const allowPopups = data.allow_popups ?? typeof window !== "undefined";
|
|
2089
|
+
if (!allowPopups) {
|
|
2090
|
+
actionFailure = {
|
|
2091
|
+
code: ErrorCode.PROVIDER_UNAVAILABLE,
|
|
2092
|
+
message: "Card authorization UI is unavailable in this environment.",
|
|
2093
|
+
recoverable: false
|
|
2094
|
+
};
|
|
2095
|
+
return PURCHASE_INTENT_ACTION_DECISION.STOP;
|
|
2096
|
+
}
|
|
2097
|
+
let outcome;
|
|
2098
|
+
try {
|
|
2099
|
+
notifyAuthorizationUi(data.on_authorization_ui_change, true);
|
|
2100
|
+
outcome = await this.authorizeCardPopup(
|
|
2101
|
+
{
|
|
2102
|
+
type: CHECKOUT_NEXT_ACTION.CARD_POPUP,
|
|
2103
|
+
provider: action.provider,
|
|
2104
|
+
client_secret: action.client_secret,
|
|
2105
|
+
public_key: action.public_key,
|
|
2106
|
+
provider_account_id: action.provider_account_id ?? void 0
|
|
2107
|
+
},
|
|
2108
|
+
{
|
|
2109
|
+
email: checkoutData.customer.email,
|
|
2110
|
+
currency: started.value.terms.money.currency,
|
|
2111
|
+
returnUrl: data.return_url,
|
|
2112
|
+
signal: data.signal
|
|
2113
|
+
}
|
|
2114
|
+
);
|
|
2115
|
+
} finally {
|
|
2116
|
+
notifyAuthorizationUi(data.on_authorization_ui_change, false);
|
|
2117
|
+
}
|
|
2118
|
+
if (outcome.status === PAYMENT_AUTHORIZATION_OUTCOME_STATUS.SUBMITTED) {
|
|
2119
|
+
return PURCHASE_INTENT_ACTION_DECISION.CONTINUE;
|
|
2120
|
+
}
|
|
2121
|
+
actionFailure = outcome.status === PAYMENT_AUTHORIZATION_OUTCOME_STATUS.CANCELLED ? {
|
|
2122
|
+
code: ErrorCode.PAYMENT_CANCELLED,
|
|
2123
|
+
message: "Card payment was cancelled before submission.",
|
|
2124
|
+
recoverable: true
|
|
2125
|
+
} : {
|
|
2126
|
+
code: outcome.code,
|
|
2127
|
+
message: outcome.message,
|
|
2128
|
+
recoverable: outcome.recoverable
|
|
2129
|
+
};
|
|
2130
|
+
return PURCHASE_INTENT_ACTION_DECISION.STOP;
|
|
2131
|
+
}
|
|
2132
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.REDIRECT_REQUIRED: {
|
|
2133
|
+
const authorizationUrl = paymentAuthorizationUrl(action.authorization_url);
|
|
2134
|
+
if (!authorizationUrl) {
|
|
2135
|
+
actionFailure = {
|
|
2136
|
+
code: ErrorCode.AUTHORIZATION_FAILED,
|
|
2137
|
+
message: "The provider returned an invalid authorization URL.",
|
|
2138
|
+
recoverable: false
|
|
2139
|
+
};
|
|
2140
|
+
return PURCHASE_INTENT_ACTION_DECISION.STOP;
|
|
2141
|
+
}
|
|
2142
|
+
if (typeof window !== "undefined") {
|
|
2143
|
+
if (data.allow_popups === false) {
|
|
2144
|
+
window.location.assign(authorizationUrl);
|
|
2145
|
+
} else {
|
|
2146
|
+
const popup = window.open(
|
|
2147
|
+
authorizationUrl,
|
|
2148
|
+
AUTHORIZATION_POPUP_NAME,
|
|
2149
|
+
AUTHORIZATION_POPUP_FEATURES
|
|
2150
|
+
);
|
|
2151
|
+
if (!popup) {
|
|
2152
|
+
actionFailure = {
|
|
2153
|
+
code: ErrorCode.POPUP_BLOCKED,
|
|
2154
|
+
message: "Authorization popup was blocked.",
|
|
2155
|
+
recoverable: true
|
|
2156
|
+
};
|
|
2157
|
+
return PURCHASE_INTENT_ACTION_DECISION.STOP;
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
actionFailure = {
|
|
2162
|
+
code: ErrorCode.REDIRECT_REQUIRED,
|
|
2163
|
+
message: "Complete authorization at the payment provider, then resume checkout.",
|
|
2164
|
+
recoverable: true
|
|
2165
|
+
};
|
|
2166
|
+
return PURCHASE_INTENT_ACTION_DECISION.STOP;
|
|
2167
|
+
}
|
|
2168
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.AUTHORIZATION_REQUIRED: {
|
|
2169
|
+
const expected = authorizationKind(action.authorization_type);
|
|
2170
|
+
if (!expected || !data.on_authorization_required) {
|
|
2171
|
+
actionFailure = {
|
|
2172
|
+
code: ErrorCode.AUTHORIZATION_REQUIRED,
|
|
2173
|
+
message: "Additional payment authorization is required.",
|
|
2174
|
+
recoverable: true
|
|
2175
|
+
};
|
|
2176
|
+
return PURCHASE_INTENT_ACTION_DECISION.STOP;
|
|
2177
|
+
}
|
|
2178
|
+
let submission;
|
|
2179
|
+
await data.on_authorization_required(expected, async (candidate) => {
|
|
2180
|
+
if (submission) throw new Error("Authorization was already submitted.");
|
|
2181
|
+
submission = normalizeAuthorizationSubmission(expected, candidate);
|
|
2182
|
+
});
|
|
2183
|
+
if (!submission) {
|
|
2184
|
+
actionFailure = {
|
|
2185
|
+
code: ErrorCode.AUTHORIZATION_REQUIRED,
|
|
2186
|
+
message: "Payment authorization was not submitted.",
|
|
2187
|
+
recoverable: true
|
|
2188
|
+
};
|
|
2189
|
+
return PURCHASE_INTENT_ACTION_DECISION.STOP;
|
|
2190
|
+
}
|
|
2191
|
+
return {
|
|
2192
|
+
kind: PURCHASE_INTENT_ACTION_DECISION.SUBMIT,
|
|
2193
|
+
request: { authorization: submission }
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
});
|
|
2199
|
+
if (!resolved.ok) {
|
|
2200
|
+
return ok(
|
|
2201
|
+
toTerminalFailure(
|
|
2202
|
+
resolved.error.code || ErrorCode.CHECKOUT_FAILED,
|
|
2203
|
+
resolved.error.message || "Unable to resolve checkout.",
|
|
2204
|
+
resolved.error.retryable ?? true
|
|
2205
|
+
)
|
|
2206
|
+
);
|
|
2207
|
+
}
|
|
2208
|
+
switch (resolved.value.kind) {
|
|
2209
|
+
case PURCHASE_INTENT_RESOLUTION_KIND.COMPLETED: {
|
|
2210
|
+
clearIntentIdempotencyKey(started.value.intentScope);
|
|
2211
|
+
clearIntentIdempotencyKey(started.value.confirmationScope);
|
|
2212
|
+
const response = resolved.value.response;
|
|
2213
|
+
notifyCheckoutStatus(data.on_status_change, CHECKOUT_STATUS.FINALIZING, {
|
|
2214
|
+
order_id: response.order_id
|
|
2215
|
+
});
|
|
2216
|
+
const enrolledInLink = await this.maybeEnrollResolvedOrder(response.order_id, data);
|
|
2217
|
+
notifyCheckoutStatus(data.on_status_change, CHECKOUT_STATUS.SUCCESS, {
|
|
2218
|
+
order_id: response.order_id
|
|
2219
|
+
});
|
|
2220
|
+
return ok({
|
|
2221
|
+
success: true,
|
|
2222
|
+
order: {
|
|
2223
|
+
id: response.order_id,
|
|
2224
|
+
order_number: response.order_number ?? response.order_id,
|
|
2225
|
+
status: CHECKOUT_ORDER_RESULT_STATUS.CONFIRMED,
|
|
2226
|
+
total: String(cart.pricing.total_price),
|
|
2227
|
+
currency: started.value.terms.money.currency
|
|
2228
|
+
},
|
|
2229
|
+
enrolled_in_link: enrolledInLink
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
case PURCHASE_INTENT_RESOLUTION_KIND.FAILED:
|
|
2233
|
+
clearIntentIdempotencyKey(started.value.confirmationScope);
|
|
2234
|
+
return ok(
|
|
2235
|
+
toTerminalFailure(
|
|
2236
|
+
PURCHASE_INTENT_ERROR_CODE.PAYMENT_FAILED,
|
|
2237
|
+
"Payment failed. Choose another method or retry collection.",
|
|
2238
|
+
true
|
|
2239
|
+
)
|
|
2240
|
+
);
|
|
2241
|
+
case PURCHASE_INTENT_RESOLUTION_KIND.OUTCOME_UNKNOWN:
|
|
2242
|
+
return ok(
|
|
2243
|
+
toTerminalFailure(
|
|
2244
|
+
ErrorCode.PAYMENT_OUTCOME_UNKNOWN,
|
|
2245
|
+
"Payment outcome is still unknown. Resume this intent before retrying.",
|
|
2246
|
+
true
|
|
2247
|
+
)
|
|
2248
|
+
);
|
|
2249
|
+
case PURCHASE_INTENT_RESOLUTION_KIND.TIMED_OUT:
|
|
2250
|
+
return ok(
|
|
2251
|
+
toTerminalFailure(
|
|
2252
|
+
ErrorCode.TIMEOUT,
|
|
2253
|
+
"Payment is still processing. Resume this intent to continue polling.",
|
|
2254
|
+
true
|
|
2255
|
+
)
|
|
2256
|
+
);
|
|
2257
|
+
case PURCHASE_INTENT_RESOLUTION_KIND.ACTION_REQUIRED:
|
|
2258
|
+
return ok(
|
|
2259
|
+
toTerminalFailure(
|
|
2260
|
+
actionFailure?.code ?? ErrorCode.AUTHORIZATION_REQUIRED,
|
|
2261
|
+
actionFailure?.message ?? "Checkout requires additional authorization.",
|
|
2262
|
+
actionFailure?.recoverable ?? true
|
|
2263
|
+
)
|
|
2264
|
+
);
|
|
2265
|
+
case PURCHASE_INTENT_RESOLUTION_KIND.REVIEW_REQUIRED: {
|
|
2266
|
+
const response = resolved.value.response;
|
|
2267
|
+
switch (response.status) {
|
|
2268
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.TERMS_CHANGED:
|
|
2269
|
+
return ok(
|
|
2270
|
+
toTerminalFailure(
|
|
2271
|
+
PURCHASE_INTENT_ERROR_CODE.TERMS_CHANGED,
|
|
2272
|
+
"Checkout terms changed and require explicit review.",
|
|
2273
|
+
true
|
|
2274
|
+
)
|
|
2275
|
+
);
|
|
2276
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.SELECTION_UNAVAILABLE:
|
|
2277
|
+
return ok(
|
|
2278
|
+
toTerminalFailure(
|
|
2279
|
+
PURCHASE_INTENT_ERROR_CODE.SELECTION_UNAVAILABLE,
|
|
2280
|
+
"One or more checkout selections are unavailable.",
|
|
2281
|
+
true
|
|
2282
|
+
)
|
|
2283
|
+
);
|
|
2284
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.OFFER_ENDED:
|
|
2285
|
+
return ok(
|
|
2286
|
+
toTerminalFailure(
|
|
2287
|
+
PURCHASE_INTENT_ERROR_CODE.OFFER_ENDED,
|
|
2288
|
+
"This offer has ended.",
|
|
2289
|
+
false
|
|
2290
|
+
)
|
|
2291
|
+
);
|
|
2292
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.SOURCE_UNAVAILABLE:
|
|
2293
|
+
return ok(
|
|
2294
|
+
toTerminalFailure(
|
|
2295
|
+
PURCHASE_INTENT_ERROR_CODE.SOURCE_UNAVAILABLE,
|
|
2296
|
+
"This purchase source is unavailable.",
|
|
2297
|
+
false
|
|
2298
|
+
)
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
} catch (error) {
|
|
2304
|
+
const cancelled = data.signal?.aborted || error instanceof Error && error.name === ABORT_ERROR_NAME;
|
|
2305
|
+
return ok(
|
|
2306
|
+
toTerminalFailure(
|
|
2307
|
+
cancelled ? ErrorCode.CANCELLED : ErrorCode.AUTHORIZATION_FAILED,
|
|
2308
|
+
cancelled ? "Checkout was cancelled." : error instanceof Error ? error.message : "Checkout authorization failed.",
|
|
2309
|
+
true
|
|
1753
2310
|
)
|
|
1754
2311
|
);
|
|
1755
2312
|
}
|
|
1756
|
-
const resolver = new CheckoutResolver({
|
|
1757
|
-
client: this.client,
|
|
1758
|
-
checkoutData,
|
|
1759
|
-
pollIntervalMs: data.poll_interval_ms,
|
|
1760
|
-
maxPollAttempts: data.max_poll_attempts,
|
|
1761
|
-
onStatusChange: data.on_status_change,
|
|
1762
|
-
onAuthorizationUiChange: data.on_authorization_ui_change,
|
|
1763
|
-
onAuthorizationRequired: data.on_authorization_required,
|
|
1764
|
-
signal: data.signal,
|
|
1765
|
-
returnUrl: data.return_url,
|
|
1766
|
-
enrollInLink: data.enroll_in_link,
|
|
1767
|
-
cartTotal: cart.pricing.total_price,
|
|
1768
|
-
cartCurrency: checkoutData.pay_currency || baseCurrency,
|
|
1769
|
-
orderType: checkoutData.order_type,
|
|
1770
|
-
allowPopups: data.allow_popups
|
|
1771
|
-
});
|
|
1772
|
-
const resolved = await resolver.resolve(processResult.value);
|
|
1773
|
-
return ok(resolved);
|
|
1774
2313
|
}
|
|
1775
2314
|
};
|
|
1776
2315
|
|
|
@@ -2405,6 +2944,8 @@ var MESSAGE_TYPES = {
|
|
|
2405
2944
|
CONTACT_PROVIDED: "contact_provided",
|
|
2406
2945
|
CHECKOUT_STATUS: "checkout_status",
|
|
2407
2946
|
CHECKOUT_COMPLETE: "checkout_complete",
|
|
2947
|
+
TERMS_CHANGED: "terms_changed",
|
|
2948
|
+
SELECTION_UNAVAILABLE: "selection_unavailable",
|
|
2408
2949
|
ORDER_TYPE_CHANGED: "order_type_changed",
|
|
2409
2950
|
REQUEST_SUBMIT: "request_submit"
|
|
2410
2951
|
};
|
|
@@ -2825,6 +3366,8 @@ var KNOWN_IFRAME_TYPES = /* @__PURE__ */ new Set([
|
|
|
2825
3366
|
MESSAGE_TYPES.LOGOUT_COMPLETE,
|
|
2826
3367
|
MESSAGE_TYPES.CHECKOUT_STATUS,
|
|
2827
3368
|
MESSAGE_TYPES.CHECKOUT_COMPLETE,
|
|
3369
|
+
MESSAGE_TYPES.TERMS_CHANGED,
|
|
3370
|
+
MESSAGE_TYPES.SELECTION_UNAVAILABLE,
|
|
2828
3371
|
MESSAGE_TYPES.ORDER_TYPE_CHANGED,
|
|
2829
3372
|
MESSAGE_TYPES.REQUEST_SUBMIT
|
|
2830
3373
|
]);
|
|
@@ -2874,6 +3417,13 @@ function parseIframeMessage(data) {
|
|
|
2874
3417
|
if (typeof record.status !== "string" || !record.context || typeof record.context !== "object")
|
|
2875
3418
|
return null;
|
|
2876
3419
|
break;
|
|
3420
|
+
case MESSAGE_TYPES.TERMS_CHANGED:
|
|
3421
|
+
if (typeof record.intent_id !== "string" || !record.replacement || typeof record.replacement !== "object" || !Array.isArray(record.deltas))
|
|
3422
|
+
return null;
|
|
3423
|
+
break;
|
|
3424
|
+
case MESSAGE_TYPES.SELECTION_UNAVAILABLE:
|
|
3425
|
+
if (typeof record.intent_id !== "string" || !Array.isArray(record.lines)) return null;
|
|
3426
|
+
break;
|
|
2877
3427
|
case MESSAGE_TYPES.READY:
|
|
2878
3428
|
case MESSAGE_TYPES.HEIGHT_CHANGE:
|
|
2879
3429
|
if (typeof record.height !== "number") return null;
|
|
@@ -2946,7 +3496,10 @@ var CimplifyElements = class {
|
|
|
2946
3496
|
async submitCheckout(data) {
|
|
2947
3497
|
const result = await this.processCheckout({
|
|
2948
3498
|
cart_id: data.cart_id,
|
|
2949
|
-
|
|
3499
|
+
intent_id: data.intent_id,
|
|
3500
|
+
hosted_session_id: data.hosted_session_id,
|
|
3501
|
+
initial_terms: data.initial_terms,
|
|
3502
|
+
order_type: data.order_type ?? ORDER_TYPE.DELIVERY,
|
|
2950
3503
|
location_id: data.location_id,
|
|
2951
3504
|
notes: data.notes,
|
|
2952
3505
|
scheduled_time: data.scheduled_time,
|
|
@@ -2958,7 +3511,7 @@ var CimplifyElements = class {
|
|
|
2958
3511
|
success: true,
|
|
2959
3512
|
order: {
|
|
2960
3513
|
id: result.order?.id || "",
|
|
2961
|
-
status: result.order?.status ||
|
|
3514
|
+
status: result.order?.status || CHECKOUT_ORDER_RESULT_STATUS.UNKNOWN,
|
|
2962
3515
|
total: result.order?.total || ""
|
|
2963
3516
|
}
|
|
2964
3517
|
};
|
|
@@ -2966,7 +3519,7 @@ var CimplifyElements = class {
|
|
|
2966
3519
|
return {
|
|
2967
3520
|
success: false,
|
|
2968
3521
|
error: {
|
|
2969
|
-
code: result.error?.code ||
|
|
3522
|
+
code: result.error?.code || ErrorCode.CHECKOUT_FAILED,
|
|
2970
3523
|
message: result.error?.message || "Checkout failed"
|
|
2971
3524
|
}
|
|
2972
3525
|
};
|
|
@@ -2976,12 +3529,18 @@ var CimplifyElements = class {
|
|
|
2976
3529
|
const task = (async () => {
|
|
2977
3530
|
this.debug("[cimplify:checkout] processCheckout called", {
|
|
2978
3531
|
cart_id: options.cart_id,
|
|
3532
|
+
intent_id: options.intent_id,
|
|
3533
|
+
hosted_session_id: options.hosted_session_id,
|
|
2979
3534
|
order_type: options.order_type,
|
|
2980
3535
|
location_id: options.location_id
|
|
2981
3536
|
});
|
|
2982
3537
|
if (this.checkoutInProgress) {
|
|
2983
3538
|
this.debug("[cimplify:checkout] BLOCKED: already processing");
|
|
2984
|
-
return toCheckoutError(
|
|
3539
|
+
return toCheckoutError(
|
|
3540
|
+
ErrorCode.ALREADY_PROCESSING,
|
|
3541
|
+
"Checkout is already in progress.",
|
|
3542
|
+
false
|
|
3543
|
+
);
|
|
2985
3544
|
}
|
|
2986
3545
|
this.checkoutInProgress = true;
|
|
2987
3546
|
if (this.authEstablishmentInProgress) {
|
|
@@ -2989,18 +3548,18 @@ var CimplifyElements = class {
|
|
|
2989
3548
|
if (this.authEstablishmentInProgress) {
|
|
2990
3549
|
this.checkoutInProgress = false;
|
|
2991
3550
|
return toCheckoutError(
|
|
2992
|
-
|
|
3551
|
+
ErrorCode.AUTH_INCOMPLETE,
|
|
2993
3552
|
"Authentication must finish establishing the checkout session.",
|
|
2994
3553
|
true
|
|
2995
3554
|
);
|
|
2996
3555
|
}
|
|
2997
3556
|
}
|
|
2998
|
-
if (!options.cart_id) {
|
|
2999
|
-
this.debug("[cimplify:checkout] BLOCKED: no cart_id");
|
|
3557
|
+
if (!options.cart_id && !options.intent_id) {
|
|
3558
|
+
this.debug("[cimplify:checkout] BLOCKED: no cart_id or intent_id");
|
|
3000
3559
|
this.checkoutInProgress = false;
|
|
3001
3560
|
return toCheckoutError(
|
|
3002
|
-
|
|
3003
|
-
"A
|
|
3561
|
+
ErrorCode.CHECKOUT_AUTHORITY_REQUIRED,
|
|
3562
|
+
"A cart or existing purchase intent is required before checkout can start.",
|
|
3004
3563
|
false
|
|
3005
3564
|
);
|
|
3006
3565
|
}
|
|
@@ -3008,7 +3567,7 @@ var CimplifyElements = class {
|
|
|
3008
3567
|
this.debug("[cimplify:checkout] BLOCKED: no order_type");
|
|
3009
3568
|
this.checkoutInProgress = false;
|
|
3010
3569
|
return toCheckoutError(
|
|
3011
|
-
|
|
3570
|
+
ErrorCode.ORDER_TYPE_REQUIRED,
|
|
3012
3571
|
"Order type is required before checkout can start.",
|
|
3013
3572
|
false
|
|
3014
3573
|
);
|
|
@@ -3018,7 +3577,7 @@ var CimplifyElements = class {
|
|
|
3018
3577
|
this.debug("[cimplify:checkout] BLOCKED: no checkout element");
|
|
3019
3578
|
this.checkoutInProgress = false;
|
|
3020
3579
|
return toCheckoutError(
|
|
3021
|
-
|
|
3580
|
+
ErrorCode.NO_PAYMENT_ELEMENT,
|
|
3022
3581
|
"Checkout element must be mounted before checkout.",
|
|
3023
3582
|
false
|
|
3024
3583
|
);
|
|
@@ -3027,7 +3586,7 @@ var CimplifyElements = class {
|
|
|
3027
3586
|
this.debug("[cimplify:checkout] BLOCKED: checkout element not mounted");
|
|
3028
3587
|
this.checkoutInProgress = false;
|
|
3029
3588
|
return toCheckoutError(
|
|
3030
|
-
|
|
3589
|
+
ErrorCode.PAYMENT_NOT_MOUNTED,
|
|
3031
3590
|
"Checkout element must be mounted before checkout.",
|
|
3032
3591
|
false
|
|
3033
3592
|
);
|
|
@@ -3039,7 +3598,7 @@ var CimplifyElements = class {
|
|
|
3039
3598
|
this.debug("[cimplify:checkout] BLOCKED: auth incomplete");
|
|
3040
3599
|
this.checkoutInProgress = false;
|
|
3041
3600
|
return toCheckoutError(
|
|
3042
|
-
|
|
3601
|
+
ErrorCode.AUTH_INCOMPLETE,
|
|
3043
3602
|
"Authentication must complete before checkout can start.",
|
|
3044
3603
|
true
|
|
3045
3604
|
);
|
|
@@ -3059,7 +3618,12 @@ var CimplifyElements = class {
|
|
|
3059
3618
|
});
|
|
3060
3619
|
const processMessage = {
|
|
3061
3620
|
type: MESSAGE_TYPES.PROCESS_CHECKOUT,
|
|
3062
|
-
|
|
3621
|
+
// Existing intent authority wins if a compatibility caller supplied
|
|
3622
|
+
// both values; the iframe must never mint a replacement.
|
|
3623
|
+
cart_id: options.intent_id ? void 0 : options.cart_id,
|
|
3624
|
+
intent_id: options.intent_id,
|
|
3625
|
+
hosted_session_id: options.hosted_session_id,
|
|
3626
|
+
initial_terms: options.intent_id ? options.initial_terms : void 0,
|
|
3063
3627
|
order_type: options.order_type,
|
|
3064
3628
|
location_id: options.location_id,
|
|
3065
3629
|
notes: options.notes,
|
|
@@ -3098,7 +3662,7 @@ var CimplifyElements = class {
|
|
|
3098
3662
|
const timeout = setTimeout(() => {
|
|
3099
3663
|
settle(
|
|
3100
3664
|
toCheckoutError(
|
|
3101
|
-
|
|
3665
|
+
ErrorCode.TIMEOUT,
|
|
3102
3666
|
"Checkout timed out before receiving a terminal response.",
|
|
3103
3667
|
true
|
|
3104
3668
|
)
|
|
@@ -3119,6 +3683,21 @@ var CimplifyElements = class {
|
|
|
3119
3683
|
options.on_status_change?.(message.status, message.context);
|
|
3120
3684
|
return;
|
|
3121
3685
|
}
|
|
3686
|
+
if (message.type === MESSAGE_TYPES.TERMS_CHANGED) {
|
|
3687
|
+
options.on_status_change?.(CHECKOUT_STATUS.REVIEWING_TERMS, {
|
|
3688
|
+
intent_id: message.intent_id,
|
|
3689
|
+
replacement_terms: message.replacement,
|
|
3690
|
+
terms_deltas: message.deltas
|
|
3691
|
+
});
|
|
3692
|
+
return;
|
|
3693
|
+
}
|
|
3694
|
+
if (message.type === MESSAGE_TYPES.SELECTION_UNAVAILABLE) {
|
|
3695
|
+
options.on_status_change?.(CHECKOUT_STATUS.REVIEWING_TERMS, {
|
|
3696
|
+
intent_id: message.intent_id,
|
|
3697
|
+
unavailable_lines: message.lines
|
|
3698
|
+
});
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3122
3701
|
if (message.type === MESSAGE_TYPES.CHECKOUT_COMPLETE) {
|
|
3123
3702
|
this.debug("[cimplify:checkout] received checkout_complete", {
|
|
3124
3703
|
success: message.success,
|
|
@@ -3134,7 +3713,11 @@ var CimplifyElements = class {
|
|
|
3134
3713
|
if (message.type === MESSAGE_TYPES.LOGOUT_COMPLETE) {
|
|
3135
3714
|
checkoutElement.sendMessage({ type: MESSAGE_TYPES.ABORT_CHECKOUT });
|
|
3136
3715
|
settle(
|
|
3137
|
-
toCheckoutError(
|
|
3716
|
+
toCheckoutError(
|
|
3717
|
+
ErrorCode.AUTH_LOST,
|
|
3718
|
+
"Authentication was cleared during checkout.",
|
|
3719
|
+
true
|
|
3720
|
+
)
|
|
3138
3721
|
);
|
|
3139
3722
|
}
|
|
3140
3723
|
};
|
|
@@ -3143,11 +3726,13 @@ var CimplifyElements = class {
|
|
|
3143
3726
|
}
|
|
3144
3727
|
abortFn = () => {
|
|
3145
3728
|
checkoutElement.sendMessage({ type: MESSAGE_TYPES.ABORT_CHECKOUT });
|
|
3146
|
-
settle(toCheckoutError(
|
|
3729
|
+
settle(toCheckoutError(ErrorCode.CANCELLED, "Checkout was cancelled.", true));
|
|
3147
3730
|
};
|
|
3148
3731
|
this.activeCheckoutAbort = abortFn;
|
|
3149
3732
|
this.debug("[cimplify:checkout] sending process_checkout to checkout iframe", {
|
|
3150
3733
|
cart_id: processMessage.cart_id,
|
|
3734
|
+
intent_id: processMessage.intent_id,
|
|
3735
|
+
hosted_session_id: processMessage.hosted_session_id,
|
|
3151
3736
|
order_type: processMessage.order_type,
|
|
3152
3737
|
hasCustomer: !!processMessage.customer,
|
|
3153
3738
|
hasAddress: !!processMessage.address,
|
|
@@ -3380,7 +3965,7 @@ var CimplifyElements = class {
|
|
|
3380
3965
|
const redirectUri = auth.redirectUri ?? readPublicEnv("NEXT_PUBLIC_CIMPLIFY_REDIRECT_URI");
|
|
3381
3966
|
if (!clientId || !redirectUri) {
|
|
3382
3967
|
sourceElement._emitError({
|
|
3383
|
-
code:
|
|
3968
|
+
code: ErrorCode.AUTH_CONFIG_REQUIRED,
|
|
3384
3969
|
message: "Cimplify OAuth clientId and redirectUri are required."
|
|
3385
3970
|
});
|
|
3386
3971
|
return;
|
|
@@ -3404,7 +3989,7 @@ var CimplifyElements = class {
|
|
|
3404
3989
|
this.authInProgress = false;
|
|
3405
3990
|
if (!accessToken) {
|
|
3406
3991
|
sourceElement._emitError({
|
|
3407
|
-
code:
|
|
3992
|
+
code: ErrorCode.AUTH_CALLBACK_MISSING_TOKEN,
|
|
3408
3993
|
message: "Sign-in callback did not return an access token."
|
|
3409
3994
|
});
|
|
3410
3995
|
return;
|
|
@@ -3431,7 +4016,7 @@ var CimplifyElements = class {
|
|
|
3431
4016
|
if (this.authGeneration !== generation) return;
|
|
3432
4017
|
this.authInProgress = false;
|
|
3433
4018
|
sourceElement._emitError({
|
|
3434
|
-
code:
|
|
4019
|
+
code: ErrorCode.AUTH_START_FAILED,
|
|
3435
4020
|
message: error instanceof Error ? error.message : "Unable to start sign-in."
|
|
3436
4021
|
});
|
|
3437
4022
|
});
|
|
@@ -3484,7 +4069,7 @@ var CimplifyElements = class {
|
|
|
3484
4069
|
if (!this.customerData) {
|
|
3485
4070
|
this.customerData = { name: "", email: null, phone: null };
|
|
3486
4071
|
}
|
|
3487
|
-
if (message.contactType ===
|
|
4072
|
+
if (message.contactType === ELEMENTS_AUTH_CONTACT_TYPES.EMAIL) {
|
|
3488
4073
|
this.customerData.email = message.contact;
|
|
3489
4074
|
} else {
|
|
3490
4075
|
this.customerData.phone = message.contact;
|
|
@@ -3508,7 +4093,7 @@ var CimplifyElements = class {
|
|
|
3508
4093
|
this.client.setAccessToken(null);
|
|
3509
4094
|
}
|
|
3510
4095
|
this.broadcastClearedSession();
|
|
3511
|
-
if (message.contactType ===
|
|
4096
|
+
if (message.contactType === ELEMENTS_AUTH_CONTACT_TYPES.EMAIL) {
|
|
3512
4097
|
this.customerData.email = message.contact;
|
|
3513
4098
|
} else {
|
|
3514
4099
|
this.customerData.phone = message.contact;
|
|
@@ -3544,7 +4129,7 @@ var CimplifyElements = class {
|
|
|
3544
4129
|
if (!this.customerData) {
|
|
3545
4130
|
this.customerData = { name: "", email: null, phone: null };
|
|
3546
4131
|
}
|
|
3547
|
-
if (contactType ===
|
|
4132
|
+
if (contactType === ELEMENTS_AUTH_CONTACT_TYPES.EMAIL) {
|
|
3548
4133
|
this.customerData.email = contact;
|
|
3549
4134
|
} else {
|
|
3550
4135
|
this.customerData.phone = contact;
|
|
@@ -3707,7 +4292,7 @@ var CimplifyElement = class {
|
|
|
3707
4292
|
if (!resolvedBusinessId || !this.container || !this.mounted) {
|
|
3708
4293
|
console.error("Unable to mount element without a resolved business ID");
|
|
3709
4294
|
this.emit(EVENT_TYPES.ERROR, {
|
|
3710
|
-
code:
|
|
4295
|
+
code: ErrorCode.BUSINESS_ID_REQUIRED,
|
|
3711
4296
|
message: "Unable to initialize checkout without a business ID."
|
|
3712
4297
|
});
|
|
3713
4298
|
return;
|
|
@@ -3762,7 +4347,10 @@ var CimplifyElement = class {
|
|
|
3762
4347
|
orderTypes: this.options.orderTypes,
|
|
3763
4348
|
defaultOrderType: this.options.defaultOrderType,
|
|
3764
4349
|
renderSubmitButton: true,
|
|
3765
|
-
submitLabel: this.options.submitLabel
|
|
4350
|
+
submitLabel: this.options.submitLabel,
|
|
4351
|
+
intentId: this.options.intentId,
|
|
4352
|
+
hostedSessionId: this.options.hostedSessionId,
|
|
4353
|
+
initialTerms: this.options.initialTerms
|
|
3766
4354
|
});
|
|
3767
4355
|
const token = this.parent.getAccessToken();
|
|
3768
4356
|
const sessionToken = this.parent.getSessionToken();
|
|
@@ -3820,4 +4408,4 @@ function createElements(client, businessId, options) {
|
|
|
3820
4408
|
return new CimplifyElements(client, businessId, options);
|
|
3821
4409
|
}
|
|
3822
4410
|
|
|
3823
|
-
export { AuthService, BusinessService, CartOperations, CatalogueQueries, CheckoutService, CimplifyElement, CimplifyElements, DEFAULT_ISSUER, ELEMENTS_AUTH_CONTACT_TYPES, ELEMENTS_AUTH_MODES, ELEMENTS_AUTH_PROMPTS, ELEMENT_TYPES, EVENT_TYPES, FxService, InventoryService, LinkService, LiteService, MESSAGE_TYPES, OrderQueries, PKCE_COOKIE, PKCE_COOKIE_PATH, SchedulingService, combine, combineObject, createElements, decodePkceState, err, flatMap, fromPromise, generateIdempotencyKey, generatePkce, generateState, getAuthorizationEndpoint, getOrElse, isErr, isOk, mapError, mapResult, ok, safe, startSignIn, toNullable, tryCatch, unwrap };
|
|
4411
|
+
export { AuthService, BusinessService, CartOperations, CatalogueQueries, CheckoutService, CimplifyElement, CimplifyElements, DEFAULT_ISSUER, ELEMENTS_AUTH_CONTACT_TYPES, ELEMENTS_AUTH_MODES, ELEMENTS_AUTH_PROMPTS, ELEMENT_TYPES, EVENT_TYPES, FxService, InventoryService, LinkService, LiteService, MESSAGE_TYPES, OrderQueries, PAYMENT_AUTHORIZATION_OUTCOME_STATUS, PAYMENT_AUTHORIZATION_PROVIDER, PKCE_COOKIE, PKCE_COOKIE_PATH, PaymentAuthorizationAdapterRegistry, PaystackAuthorizationAdapter, SchedulingService, StripeAuthorizationAdapter, clearIntentIdempotencyKey, combine, combineObject, createDefaultPaymentAuthorizationAdapterRegistry, createElements, createPurchaseIntentConfirmation, decodePkceState, err, flatMap, fromPromise, generateIdempotencyKey, generatePkce, generateState, getAuthorizationEndpoint, getOrElse, intentIdempotencyKey, isErr, isOk, mapError, mapResult, ok, purchaseIntentRequiresPaymentOption, safe, selectPurchaseIntentPaymentOption, startSignIn, toNullable, tryCatch, unwrap };
|