@cimplify/sdk 0.6.1 → 0.6.3
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/{ads-DRfobDQ9.d.mts → ads-Cz14AMnc.d.mts} +119 -3
- package/dist/{ads-DRfobDQ9.d.ts → ads-Cz14AMnc.d.ts} +119 -3
- package/dist/index.d.mts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +1908 -903
- package/dist/index.mjs +1906 -904
- package/dist/react.d.mts +30 -3
- package/dist/react.d.ts +30 -3
- package/dist/react.js +215 -1
- package/dist/react.mjs +217 -4
- package/package.json +5 -1
package/dist/index.mjs
CHANGED
|
@@ -149,6 +149,63 @@ async function safe(promise) {
|
|
|
149
149
|
return err(toCimplifyError(error));
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
+
function isRecord(value) {
|
|
153
|
+
return typeof value === "object" && value !== null;
|
|
154
|
+
}
|
|
155
|
+
function readFinalPrice(value) {
|
|
156
|
+
if (!isRecord(value)) return void 0;
|
|
157
|
+
const finalPrice = value.final_price;
|
|
158
|
+
if (typeof finalPrice === "string" || typeof finalPrice === "number") {
|
|
159
|
+
return finalPrice;
|
|
160
|
+
}
|
|
161
|
+
return void 0;
|
|
162
|
+
}
|
|
163
|
+
function normalizeCatalogueProductPayload(product) {
|
|
164
|
+
const normalized = { ...product };
|
|
165
|
+
const defaultPrice = normalized["default_price"];
|
|
166
|
+
if (defaultPrice === void 0 || defaultPrice === null || defaultPrice === "") {
|
|
167
|
+
const derivedDefaultPrice = readFinalPrice(normalized["default_price_info"]) || readFinalPrice(normalized["price_info"]) || (typeof normalized["final_price"] === "string" || typeof normalized["final_price"] === "number" ? normalized["final_price"] : void 0);
|
|
168
|
+
normalized["default_price"] = derivedDefaultPrice ?? "0";
|
|
169
|
+
}
|
|
170
|
+
const variants = normalized["variants"];
|
|
171
|
+
if (Array.isArray(variants)) {
|
|
172
|
+
normalized["variants"] = variants.map((variant) => {
|
|
173
|
+
if (!isRecord(variant)) return variant;
|
|
174
|
+
const normalizedVariant = { ...variant };
|
|
175
|
+
const variantAdjustment = normalizedVariant["price_adjustment"];
|
|
176
|
+
if (variantAdjustment === void 0 || variantAdjustment === null || variantAdjustment === "") {
|
|
177
|
+
normalizedVariant["price_adjustment"] = readFinalPrice(normalizedVariant["price_info"]) ?? "0";
|
|
178
|
+
}
|
|
179
|
+
return normalizedVariant;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const addOns = normalized["add_ons"];
|
|
183
|
+
if (Array.isArray(addOns)) {
|
|
184
|
+
normalized["add_ons"] = addOns.map((addOn) => {
|
|
185
|
+
if (!isRecord(addOn)) return addOn;
|
|
186
|
+
const normalizedAddOn = { ...addOn };
|
|
187
|
+
const options = normalizedAddOn["options"];
|
|
188
|
+
if (!Array.isArray(options)) return normalizedAddOn;
|
|
189
|
+
normalizedAddOn["options"] = options.map((option) => {
|
|
190
|
+
if (!isRecord(option)) return option;
|
|
191
|
+
const normalizedOption = { ...option };
|
|
192
|
+
const optionPrice = normalizedOption["default_price"];
|
|
193
|
+
if (optionPrice === void 0 || optionPrice === null || optionPrice === "") {
|
|
194
|
+
normalizedOption["default_price"] = readFinalPrice(normalizedOption["default_price_info"]) || readFinalPrice(normalizedOption["price_info"]) || "0";
|
|
195
|
+
}
|
|
196
|
+
return normalizedOption;
|
|
197
|
+
});
|
|
198
|
+
return normalizedAddOn;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return normalized;
|
|
202
|
+
}
|
|
203
|
+
function findProductBySlug(products, slug) {
|
|
204
|
+
return products.find((product) => {
|
|
205
|
+
const value = product["slug"];
|
|
206
|
+
return typeof value === "string" && value === slug;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
152
209
|
var CatalogueQueries = class {
|
|
153
210
|
constructor(client) {
|
|
154
211
|
this.client = client;
|
|
@@ -186,20 +243,34 @@ var CatalogueQueries = class {
|
|
|
186
243
|
if (options?.offset) {
|
|
187
244
|
query2 += `#offset(${options.offset})`;
|
|
188
245
|
}
|
|
189
|
-
|
|
246
|
+
const result = await safe(this.client.query(query2));
|
|
247
|
+
if (!result.ok) return result;
|
|
248
|
+
return ok(result.value.map((product) => normalizeCatalogueProductPayload(product)));
|
|
190
249
|
}
|
|
191
250
|
async getProduct(id) {
|
|
192
|
-
|
|
251
|
+
const result = await safe(this.client.query(`products.${id}`));
|
|
252
|
+
if (!result.ok) return result;
|
|
253
|
+
return ok(normalizeCatalogueProductPayload(result.value));
|
|
193
254
|
}
|
|
194
255
|
async getProductBySlug(slug) {
|
|
195
|
-
const
|
|
256
|
+
const filteredResult = await safe(
|
|
196
257
|
this.client.query(`products[?(@.slug=='${slug}')]`)
|
|
197
258
|
);
|
|
198
|
-
if (!
|
|
199
|
-
|
|
259
|
+
if (!filteredResult.ok) return filteredResult;
|
|
260
|
+
const exactMatch = findProductBySlug(filteredResult.value, slug);
|
|
261
|
+
if (exactMatch) {
|
|
262
|
+
return ok(normalizeCatalogueProductPayload(exactMatch));
|
|
263
|
+
}
|
|
264
|
+
if (filteredResult.value.length === 1) {
|
|
265
|
+
return ok(normalizeCatalogueProductPayload(filteredResult.value[0]));
|
|
266
|
+
}
|
|
267
|
+
const unfilteredResult = await safe(this.client.query("products"));
|
|
268
|
+
if (!unfilteredResult.ok) return unfilteredResult;
|
|
269
|
+
const fallbackMatch = findProductBySlug(unfilteredResult.value, slug);
|
|
270
|
+
if (!fallbackMatch) {
|
|
200
271
|
return err(new CimplifyError("NOT_FOUND", `Product not found: ${slug}`, false));
|
|
201
272
|
}
|
|
202
|
-
return ok(
|
|
273
|
+
return ok(normalizeCatalogueProductPayload(fallbackMatch));
|
|
203
274
|
}
|
|
204
275
|
async getVariants(productId) {
|
|
205
276
|
return safe(this.client.query(`products.${productId}.variants`));
|
|
@@ -591,535 +662,1592 @@ var ORDER_MUTATION = {
|
|
|
591
662
|
var DEFAULT_CURRENCY = "GHS";
|
|
592
663
|
var DEFAULT_COUNTRY = "GHA";
|
|
593
664
|
|
|
594
|
-
// src/
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
665
|
+
// src/utils/price.ts
|
|
666
|
+
var CURRENCY_SYMBOLS = {
|
|
667
|
+
// Major world currencies
|
|
668
|
+
USD: "$",
|
|
669
|
+
EUR: "\u20AC",
|
|
670
|
+
GBP: "\xA3",
|
|
671
|
+
JPY: "\xA5",
|
|
672
|
+
CNY: "\xA5",
|
|
673
|
+
CHF: "CHF",
|
|
674
|
+
CAD: "C$",
|
|
675
|
+
AUD: "A$",
|
|
676
|
+
NZD: "NZ$",
|
|
677
|
+
HKD: "HK$",
|
|
678
|
+
SGD: "S$",
|
|
679
|
+
INR: "\u20B9",
|
|
680
|
+
BRL: "R$",
|
|
681
|
+
MXN: "MX$",
|
|
682
|
+
KRW: "\u20A9",
|
|
683
|
+
RUB: "\u20BD",
|
|
684
|
+
TRY: "\u20BA",
|
|
685
|
+
THB: "\u0E3F",
|
|
686
|
+
PLN: "z\u0142",
|
|
687
|
+
SEK: "kr",
|
|
688
|
+
NOK: "kr",
|
|
689
|
+
DKK: "kr",
|
|
690
|
+
CZK: "K\u010D",
|
|
691
|
+
HUF: "Ft",
|
|
692
|
+
ILS: "\u20AA",
|
|
693
|
+
AED: "\u062F.\u0625",
|
|
694
|
+
SAR: "\uFDFC",
|
|
695
|
+
MYR: "RM",
|
|
696
|
+
PHP: "\u20B1",
|
|
697
|
+
IDR: "Rp",
|
|
698
|
+
VND: "\u20AB",
|
|
699
|
+
TWD: "NT$",
|
|
700
|
+
// African currencies
|
|
701
|
+
GHS: "GH\u20B5",
|
|
702
|
+
NGN: "\u20A6",
|
|
703
|
+
KES: "KSh",
|
|
704
|
+
ZAR: "R",
|
|
705
|
+
XOF: "CFA",
|
|
706
|
+
XAF: "FCFA",
|
|
707
|
+
EGP: "E\xA3",
|
|
708
|
+
MAD: "MAD",
|
|
709
|
+
TZS: "TSh",
|
|
710
|
+
UGX: "USh",
|
|
711
|
+
RWF: "FRw",
|
|
712
|
+
ETB: "Br",
|
|
713
|
+
ZMW: "ZK",
|
|
714
|
+
BWP: "P",
|
|
715
|
+
MUR: "\u20A8",
|
|
716
|
+
SCR: "\u20A8",
|
|
717
|
+
NAD: "N$",
|
|
718
|
+
SZL: "E",
|
|
719
|
+
LSL: "L",
|
|
720
|
+
MWK: "MK",
|
|
721
|
+
AOA: "Kz",
|
|
722
|
+
CDF: "FC",
|
|
723
|
+
GMD: "D",
|
|
724
|
+
GNF: "FG",
|
|
725
|
+
LRD: "L$",
|
|
726
|
+
SLL: "Le",
|
|
727
|
+
MZN: "MT",
|
|
728
|
+
SDG: "SDG",
|
|
729
|
+
SSP: "SSP",
|
|
730
|
+
SOS: "Sh.So.",
|
|
731
|
+
DJF: "Fdj",
|
|
732
|
+
ERN: "Nfk",
|
|
733
|
+
CVE: "$",
|
|
734
|
+
STN: "Db",
|
|
735
|
+
KMF: "CF",
|
|
736
|
+
BIF: "FBu"
|
|
737
|
+
};
|
|
738
|
+
function getCurrencySymbol(currencyCode) {
|
|
739
|
+
return CURRENCY_SYMBOLS[currencyCode.toUpperCase()] || currencyCode;
|
|
740
|
+
}
|
|
741
|
+
function formatNumberCompact(value, decimals = 1) {
|
|
742
|
+
const absValue = Math.abs(value);
|
|
743
|
+
const sign = value < 0 ? "-" : "";
|
|
744
|
+
if (absValue >= 1e9) {
|
|
745
|
+
return `${sign}${(absValue / 1e9).toFixed(decimals)}B`;
|
|
599
746
|
}
|
|
600
|
-
|
|
747
|
+
if (absValue >= 1e6) {
|
|
748
|
+
return `${sign}${(absValue / 1e6).toFixed(decimals)}M`;
|
|
749
|
+
}
|
|
750
|
+
if (absValue >= 1e3) {
|
|
751
|
+
return `${sign}${(absValue / 1e3).toFixed(decimals)}K`;
|
|
752
|
+
}
|
|
753
|
+
return `${sign}${absValue.toFixed(decimals)}`;
|
|
601
754
|
}
|
|
602
|
-
|
|
755
|
+
function formatPrice(amount, currency = "GHS", locale = "en-US") {
|
|
756
|
+
const numAmount = typeof amount === "string" ? parseFloat(amount) : amount;
|
|
757
|
+
if (isNaN(numAmount)) {
|
|
758
|
+
return `${getCurrencySymbol(currency)}0.00`;
|
|
759
|
+
}
|
|
603
760
|
try {
|
|
604
|
-
return
|
|
605
|
-
|
|
606
|
-
|
|
761
|
+
return new Intl.NumberFormat(locale, {
|
|
762
|
+
style: "currency",
|
|
763
|
+
currency: currency.toUpperCase(),
|
|
764
|
+
minimumFractionDigits: 2,
|
|
765
|
+
maximumFractionDigits: 2
|
|
766
|
+
}).format(numAmount);
|
|
767
|
+
} catch {
|
|
768
|
+
return `${getCurrencySymbol(currency)}${numAmount.toFixed(2)}`;
|
|
607
769
|
}
|
|
608
770
|
}
|
|
609
|
-
function
|
|
610
|
-
|
|
611
|
-
|
|
771
|
+
function formatPriceAdjustment(amount, currency = "GHS", locale = "en-US") {
|
|
772
|
+
const formatted = formatPrice(Math.abs(amount), currency, locale);
|
|
773
|
+
if (amount > 0) {
|
|
774
|
+
return `+${formatted}`;
|
|
775
|
+
} else if (amount < 0) {
|
|
776
|
+
return `-${formatted}`;
|
|
612
777
|
}
|
|
613
|
-
|
|
614
|
-
const random = Math.random().toString(36).substring(2, 15);
|
|
615
|
-
return `idem_${timestamp}_${random}`;
|
|
778
|
+
return formatted;
|
|
616
779
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
async process(data) {
|
|
622
|
-
const checkoutData = {
|
|
623
|
-
...data,
|
|
624
|
-
idempotency_key: data.idempotency_key || generateIdempotencyKey()
|
|
625
|
-
};
|
|
626
|
-
return safe3(
|
|
627
|
-
this.client.call(CHECKOUT_MUTATION.PROCESS, {
|
|
628
|
-
checkout_data: checkoutData
|
|
629
|
-
})
|
|
630
|
-
);
|
|
780
|
+
function formatPriceCompact(amount, currency = "GHS", decimals = 1) {
|
|
781
|
+
const numAmount = typeof amount === "string" ? parseFloat(amount) : amount;
|
|
782
|
+
if (isNaN(numAmount)) {
|
|
783
|
+
return `${getCurrencySymbol(currency)}0`;
|
|
631
784
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
order_id: orderId,
|
|
636
|
-
payment_method: method
|
|
637
|
-
})
|
|
638
|
-
);
|
|
785
|
+
const symbol = getCurrencySymbol(currency);
|
|
786
|
+
if (Math.abs(numAmount) < 1e3) {
|
|
787
|
+
return `${symbol}${numAmount.toFixed(2)}`;
|
|
639
788
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
789
|
+
return `${symbol}${formatNumberCompact(numAmount, decimals)}`;
|
|
790
|
+
}
|
|
791
|
+
function formatMoney(amount, currency = "GHS") {
|
|
792
|
+
const symbol = getCurrencySymbol(currency);
|
|
793
|
+
const numAmount = typeof amount === "string" ? parseFloat(amount) : amount;
|
|
794
|
+
if (isNaN(numAmount)) {
|
|
795
|
+
return `${symbol}0.00`;
|
|
644
796
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
797
|
+
return `${symbol}${numAmount.toFixed(2)}`;
|
|
798
|
+
}
|
|
799
|
+
function parsePrice(value) {
|
|
800
|
+
if (value === void 0 || value === null) {
|
|
801
|
+
return 0;
|
|
649
802
|
}
|
|
650
|
-
|
|
651
|
-
return
|
|
652
|
-
this.client.call(ORDER_MUTATION.UPDATE_CUSTOMER, {
|
|
653
|
-
order_id: orderId,
|
|
654
|
-
...customer
|
|
655
|
-
})
|
|
656
|
-
);
|
|
803
|
+
if (typeof value === "number") {
|
|
804
|
+
return isNaN(value) ? 0 : value;
|
|
657
805
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
806
|
+
const cleaned = value.replace(/[^\d.-]/g, "");
|
|
807
|
+
const parsed = parseFloat(cleaned);
|
|
808
|
+
return isNaN(parsed) ? 0 : parsed;
|
|
809
|
+
}
|
|
810
|
+
function getDisplayPrice(product) {
|
|
811
|
+
if (product.price_info) {
|
|
812
|
+
return parsePrice(product.price_info.final_price);
|
|
664
813
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
// src/orders.ts
|
|
668
|
-
function toCimplifyError4(error) {
|
|
669
|
-
if (error instanceof CimplifyError) return error;
|
|
670
|
-
if (error instanceof Error) {
|
|
671
|
-
return new CimplifyError("UNKNOWN_ERROR", error.message, false);
|
|
814
|
+
if (product.final_price !== void 0 && product.final_price !== null) {
|
|
815
|
+
return parsePrice(product.final_price);
|
|
672
816
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
async function safe4(promise) {
|
|
676
|
-
try {
|
|
677
|
-
return ok(await promise);
|
|
678
|
-
} catch (error) {
|
|
679
|
-
return err(toCimplifyError4(error));
|
|
817
|
+
if (product.default_price !== void 0 && product.default_price !== null) {
|
|
818
|
+
return parsePrice(product.default_price);
|
|
680
819
|
}
|
|
820
|
+
return 0;
|
|
681
821
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
822
|
+
function getBasePrice(product) {
|
|
823
|
+
if (product.price_info) {
|
|
824
|
+
return parsePrice(product.price_info.base_price);
|
|
685
825
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
if (options?.status) {
|
|
689
|
-
query2 += `[?(@.status=='${options.status}')]`;
|
|
690
|
-
}
|
|
691
|
-
query2 += "#sort(created_at,desc)";
|
|
692
|
-
if (options?.limit) {
|
|
693
|
-
query2 += `#limit(${options.limit})`;
|
|
694
|
-
}
|
|
695
|
-
if (options?.offset) {
|
|
696
|
-
query2 += `#offset(${options.offset})`;
|
|
697
|
-
}
|
|
698
|
-
return safe4(this.client.query(query2));
|
|
826
|
+
if (product.base_price !== void 0 && product.base_price !== null) {
|
|
827
|
+
return parsePrice(product.base_price);
|
|
699
828
|
}
|
|
700
|
-
|
|
701
|
-
return
|
|
829
|
+
if (product.default_price !== void 0 && product.default_price !== null) {
|
|
830
|
+
return parsePrice(product.default_price);
|
|
702
831
|
}
|
|
703
|
-
|
|
704
|
-
return safe4(this.client.query(`orders#sort(created_at,desc)#limit(${limit})`));
|
|
705
|
-
}
|
|
706
|
-
async getByStatus(status) {
|
|
707
|
-
return safe4(this.client.query(`orders[?(@.status=='${status}')]`));
|
|
708
|
-
}
|
|
709
|
-
async cancel(orderId, reason) {
|
|
710
|
-
return safe4(
|
|
711
|
-
this.client.call("order.cancelOrder", {
|
|
712
|
-
order_id: orderId,
|
|
713
|
-
reason
|
|
714
|
-
})
|
|
715
|
-
);
|
|
716
|
-
}
|
|
717
|
-
};
|
|
718
|
-
|
|
719
|
-
// src/link.ts
|
|
720
|
-
function toCimplifyError5(error) {
|
|
721
|
-
if (error instanceof CimplifyError) return error;
|
|
722
|
-
if (error instanceof Error) {
|
|
723
|
-
return new CimplifyError("UNKNOWN_ERROR", error.message, false);
|
|
724
|
-
}
|
|
725
|
-
return new CimplifyError("UNKNOWN_ERROR", String(error), false);
|
|
832
|
+
return 0;
|
|
726
833
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
return err(toCimplifyError5(error));
|
|
732
|
-
}
|
|
834
|
+
function isOnSale(product) {
|
|
835
|
+
const basePrice = getBasePrice(product);
|
|
836
|
+
const finalPrice = getDisplayPrice(product);
|
|
837
|
+
return basePrice > finalPrice && basePrice > 0;
|
|
733
838
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
return safe5(this.client.linkPost("/v1/link/auth/request-otp", input));
|
|
839
|
+
function getDiscountPercentage(product) {
|
|
840
|
+
const basePrice = getBasePrice(product);
|
|
841
|
+
const finalPrice = getDisplayPrice(product);
|
|
842
|
+
if (basePrice > finalPrice && basePrice > 0) {
|
|
843
|
+
return Math.round((basePrice - finalPrice) / basePrice * 100);
|
|
740
844
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
return result;
|
|
845
|
+
return 0;
|
|
846
|
+
}
|
|
847
|
+
function getMarkupPercentage(product) {
|
|
848
|
+
const basePrice = getBasePrice(product);
|
|
849
|
+
const finalPrice = getDisplayPrice(product);
|
|
850
|
+
if (finalPrice > basePrice && basePrice > 0) {
|
|
851
|
+
return Math.round((finalPrice - basePrice) / basePrice * 100);
|
|
749
852
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
return result;
|
|
853
|
+
return 0;
|
|
854
|
+
}
|
|
855
|
+
function getProductCurrency(product) {
|
|
856
|
+
if (product.price_info?.currency) {
|
|
857
|
+
return product.price_info.currency;
|
|
756
858
|
}
|
|
757
|
-
|
|
758
|
-
return
|
|
759
|
-
this.client.call(LINK_MUTATION.CHECK_STATUS, {
|
|
760
|
-
contact
|
|
761
|
-
})
|
|
762
|
-
);
|
|
859
|
+
if (product.currency) {
|
|
860
|
+
return product.currency;
|
|
763
861
|
}
|
|
764
|
-
|
|
765
|
-
|
|
862
|
+
return "GHS";
|
|
863
|
+
}
|
|
864
|
+
function formatProductPrice(product, locale = "en-US") {
|
|
865
|
+
const price = getDisplayPrice(product);
|
|
866
|
+
const currency = getProductCurrency(product);
|
|
867
|
+
return formatPrice(price, currency, locale);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/utils/payment.ts
|
|
871
|
+
function categorizePaymentError(error, errorCode) {
|
|
872
|
+
let message = "An unexpected error occurred during payment processing. Please try again or contact support.";
|
|
873
|
+
let recoverable = true;
|
|
874
|
+
let code = errorCode || "PAYMENT_ERROR";
|
|
875
|
+
const technical = error.stack;
|
|
876
|
+
const errorMessage = error.message?.toLowerCase() || "";
|
|
877
|
+
if (errorCode === "INSUFFICIENT_FUNDS" || errorMessage.includes("insufficient") || errorMessage.includes("funds")) {
|
|
878
|
+
code = "INSUFFICIENT_FUNDS";
|
|
879
|
+
message = "Your payment method has insufficient funds. Please try another payment method.";
|
|
880
|
+
} else if (errorCode === "CARD_DECLINED" || errorMessage.includes("declined")) {
|
|
881
|
+
code = "CARD_DECLINED";
|
|
882
|
+
message = "Your card was declined. Please try another card or payment method.";
|
|
883
|
+
} else if (errorMessage.includes("cancelled") || errorMessage.includes("canceled") || errorCode === "PAYMENT_CANCELLED") {
|
|
884
|
+
code = "PAYMENT_CANCELLED";
|
|
885
|
+
message = "Payment was cancelled. You can try again when ready.";
|
|
886
|
+
} else if (errorMessage.includes("network") || errorMessage.includes("connection") || errorCode === "NETWORK_ERROR") {
|
|
887
|
+
code = "NETWORK_ERROR";
|
|
888
|
+
message = "Network connection issue. Please check your internet connection and try again.";
|
|
889
|
+
} else if (errorMessage.includes("timeout") || errorCode === "TIMEOUT") {
|
|
890
|
+
code = "TIMEOUT";
|
|
891
|
+
message = "Payment processing timed out. Please try again.";
|
|
892
|
+
} else if (errorCode === "PAYMENT_ACTION_NOT_COMPLETED") {
|
|
893
|
+
code = "PAYMENT_ACTION_NOT_COMPLETED";
|
|
894
|
+
message = "Payment action was not completed. Please try again.";
|
|
895
|
+
} else if (errorCode === "AUTHORIZATION_FAILED") {
|
|
896
|
+
code = "AUTHORIZATION_FAILED";
|
|
897
|
+
message = "Authorization failed. Please check your code and try again.";
|
|
898
|
+
} else if (errorCode === "INVALID_OTP") {
|
|
899
|
+
code = "INVALID_OTP";
|
|
900
|
+
message = "Invalid verification code. Please check and try again.";
|
|
766
901
|
}
|
|
767
|
-
|
|
768
|
-
|
|
902
|
+
return { code, message, recoverable, technical };
|
|
903
|
+
}
|
|
904
|
+
function isQuickPaymentResponse(response) {
|
|
905
|
+
return response !== null && typeof response === "object" && "payment" in response && typeof response.payment === "object";
|
|
906
|
+
}
|
|
907
|
+
function isWebPaymentResponse(response) {
|
|
908
|
+
return response !== null && typeof response === "object" && "transaction" in response && typeof response.transaction === "object";
|
|
909
|
+
}
|
|
910
|
+
function normalizePaymentResponse(response) {
|
|
911
|
+
if (!response) {
|
|
912
|
+
return {
|
|
913
|
+
method: "unknown",
|
|
914
|
+
provider: "unknown",
|
|
915
|
+
requires_action: false,
|
|
916
|
+
metadata: {}
|
|
917
|
+
};
|
|
769
918
|
}
|
|
770
|
-
|
|
771
|
-
return
|
|
919
|
+
if (isQuickPaymentResponse(response)) {
|
|
920
|
+
return {
|
|
921
|
+
method: response.payment.type?.toLowerCase() || "unknown",
|
|
922
|
+
provider: response.payment.provider?.toLowerCase() || "unknown",
|
|
923
|
+
requires_action: !!response.payment.redirect_url || !!response.payment.access_code,
|
|
924
|
+
public_key: response.payment.public_key,
|
|
925
|
+
client_secret: response.payment.access_code,
|
|
926
|
+
access_code: response.payment.access_code,
|
|
927
|
+
redirect_url: response.payment.redirect_url,
|
|
928
|
+
transaction_id: response.payment.reference,
|
|
929
|
+
order_id: response.order_id,
|
|
930
|
+
reference: response.payment.reference,
|
|
931
|
+
instructions: response.payment.instructions,
|
|
932
|
+
metadata: response.payment.metadata
|
|
933
|
+
};
|
|
772
934
|
}
|
|
773
|
-
|
|
774
|
-
|
|
935
|
+
if (isWebPaymentResponse(response)) {
|
|
936
|
+
const authType = response.authorization_type?.toLowerCase();
|
|
937
|
+
const validAuthTypes = ["otp", "pin", "phone", "birthday", "address"];
|
|
938
|
+
const safeAuthType = authType && validAuthTypes.includes(authType) ? authType : void 0;
|
|
939
|
+
return {
|
|
940
|
+
provider: response.transaction.provider_type?.toLowerCase() || "unknown",
|
|
941
|
+
requires_action: response.requires_action || false,
|
|
942
|
+
public_key: response.public_key,
|
|
943
|
+
client_secret: response.client_secret,
|
|
944
|
+
redirect_url: response.authorization_url,
|
|
945
|
+
transaction_id: response.transaction.id,
|
|
946
|
+
order_id: response.transaction.order_id,
|
|
947
|
+
reference: response.transaction.provider_reference,
|
|
948
|
+
metadata: response.transaction.metadata,
|
|
949
|
+
method: response.transaction.payment_method?.toLowerCase() || "unknown",
|
|
950
|
+
instructions: response.display_text,
|
|
951
|
+
display_text: response.display_text,
|
|
952
|
+
requires_authorization: response.requires_authorization,
|
|
953
|
+
authorization_type: safeAuthType,
|
|
954
|
+
provider_payment_id: response.provider_payment_id || response.transaction.provider_reference
|
|
955
|
+
};
|
|
775
956
|
}
|
|
776
|
-
|
|
777
|
-
|
|
957
|
+
return {
|
|
958
|
+
method: "unknown",
|
|
959
|
+
provider: "unknown",
|
|
960
|
+
requires_action: false,
|
|
961
|
+
metadata: {}
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
var PAYMENT_SUCCESS_STATUSES = /* @__PURE__ */ new Set([
|
|
965
|
+
"success",
|
|
966
|
+
"succeeded",
|
|
967
|
+
"paid",
|
|
968
|
+
"captured",
|
|
969
|
+
"completed",
|
|
970
|
+
"authorized"
|
|
971
|
+
]);
|
|
972
|
+
var PAYMENT_FAILURE_STATUSES = /* @__PURE__ */ new Set([
|
|
973
|
+
"failed",
|
|
974
|
+
"declined",
|
|
975
|
+
"cancelled",
|
|
976
|
+
"voided",
|
|
977
|
+
"error"
|
|
978
|
+
]);
|
|
979
|
+
var PAYMENT_REQUIRES_ACTION_STATUSES = /* @__PURE__ */ new Set([
|
|
980
|
+
"requires_action",
|
|
981
|
+
"requires_payment_method",
|
|
982
|
+
"requires_capture"
|
|
983
|
+
]);
|
|
984
|
+
var PAYMENT_STATUS_ALIAS_MAP = {
|
|
985
|
+
ok: "success",
|
|
986
|
+
done: "success",
|
|
987
|
+
paid: "paid",
|
|
988
|
+
paid_in_full: "paid",
|
|
989
|
+
paid_successfully: "paid",
|
|
990
|
+
succeeded: "success",
|
|
991
|
+
captured: "captured",
|
|
992
|
+
completed: "completed",
|
|
993
|
+
pending_confirmation: "pending_confirmation",
|
|
994
|
+
requires_authorization: "requires_action",
|
|
995
|
+
requires_action: "requires_action",
|
|
996
|
+
requires_payment_method: "requires_payment_method",
|
|
997
|
+
requires_capture: "requires_capture",
|
|
998
|
+
partially_paid: "partially_paid",
|
|
999
|
+
partially_refunded: "partially_refunded",
|
|
1000
|
+
card_declined: "declined",
|
|
1001
|
+
canceled: "cancelled",
|
|
1002
|
+
authorized: "authorized",
|
|
1003
|
+
cancelled: "cancelled",
|
|
1004
|
+
unresolved: "pending"
|
|
1005
|
+
};
|
|
1006
|
+
var KNOWN_PAYMENT_STATUSES = /* @__PURE__ */ new Set([
|
|
1007
|
+
"pending",
|
|
1008
|
+
"processing",
|
|
1009
|
+
"created",
|
|
1010
|
+
"pending_confirmation",
|
|
1011
|
+
"success",
|
|
1012
|
+
"succeeded",
|
|
1013
|
+
"failed",
|
|
1014
|
+
"declined",
|
|
1015
|
+
"authorized",
|
|
1016
|
+
"refunded",
|
|
1017
|
+
"partially_refunded",
|
|
1018
|
+
"partially_paid",
|
|
1019
|
+
"paid",
|
|
1020
|
+
"unpaid",
|
|
1021
|
+
"requires_action",
|
|
1022
|
+
"requires_payment_method",
|
|
1023
|
+
"requires_capture",
|
|
1024
|
+
"captured",
|
|
1025
|
+
"cancelled",
|
|
1026
|
+
"completed",
|
|
1027
|
+
"voided",
|
|
1028
|
+
"error",
|
|
1029
|
+
"unknown"
|
|
1030
|
+
]);
|
|
1031
|
+
function normalizeStatusToken(status) {
|
|
1032
|
+
return status?.trim().toLowerCase().replace(/[\s-]+/g, "_") ?? "";
|
|
1033
|
+
}
|
|
1034
|
+
function normalizePaymentStatusValue(status) {
|
|
1035
|
+
const normalized = normalizeStatusToken(status);
|
|
1036
|
+
if (Object.prototype.hasOwnProperty.call(PAYMENT_STATUS_ALIAS_MAP, normalized)) {
|
|
1037
|
+
return PAYMENT_STATUS_ALIAS_MAP[normalized];
|
|
1038
|
+
}
|
|
1039
|
+
return KNOWN_PAYMENT_STATUSES.has(normalized) ? normalized : "unknown";
|
|
1040
|
+
}
|
|
1041
|
+
function isPaymentStatusSuccess(status) {
|
|
1042
|
+
const normalizedStatus = normalizePaymentStatusValue(status);
|
|
1043
|
+
return PAYMENT_SUCCESS_STATUSES.has(normalizedStatus);
|
|
1044
|
+
}
|
|
1045
|
+
function isPaymentStatusFailure(status) {
|
|
1046
|
+
const normalizedStatus = normalizePaymentStatusValue(status);
|
|
1047
|
+
return PAYMENT_FAILURE_STATUSES.has(normalizedStatus);
|
|
1048
|
+
}
|
|
1049
|
+
function isPaymentStatusRequiresAction(status) {
|
|
1050
|
+
const normalizedStatus = normalizePaymentStatusValue(status);
|
|
1051
|
+
return PAYMENT_REQUIRES_ACTION_STATUSES.has(normalizedStatus);
|
|
1052
|
+
}
|
|
1053
|
+
function normalizeStatusResponse(response) {
|
|
1054
|
+
if (!response || typeof response !== "object") {
|
|
1055
|
+
return {
|
|
1056
|
+
status: "pending",
|
|
1057
|
+
paid: false,
|
|
1058
|
+
message: "No status available"
|
|
1059
|
+
};
|
|
778
1060
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
1061
|
+
const res = response;
|
|
1062
|
+
const normalizedStatus = normalizePaymentStatusValue(res.status ?? void 0);
|
|
1063
|
+
const paidValue = res.paid === true;
|
|
1064
|
+
const derivedPaid = paidValue || [
|
|
1065
|
+
"success",
|
|
1066
|
+
"succeeded",
|
|
1067
|
+
"paid",
|
|
1068
|
+
"captured",
|
|
1069
|
+
"authorized",
|
|
1070
|
+
"completed"
|
|
1071
|
+
].includes(normalizedStatus);
|
|
1072
|
+
return {
|
|
1073
|
+
status: normalizedStatus,
|
|
1074
|
+
paid: derivedPaid,
|
|
1075
|
+
amount: res.amount,
|
|
1076
|
+
currency: res.currency,
|
|
1077
|
+
reference: res.reference,
|
|
1078
|
+
message: res.message || ""
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
var MOBILE_MONEY_PROVIDERS = {
|
|
1082
|
+
mtn: { name: "MTN Mobile Money", prefix: ["024", "054", "055", "059"] },
|
|
1083
|
+
vodafone: { name: "Vodafone Cash", prefix: ["020", "050"] },
|
|
1084
|
+
airtel: { name: "AirtelTigo Money", prefix: ["027", "057", "026", "056"] }
|
|
1085
|
+
};
|
|
1086
|
+
function detectMobileMoneyProvider(phoneNumber) {
|
|
1087
|
+
const cleaned = phoneNumber.replace(/\D/g, "");
|
|
1088
|
+
const prefix = cleaned.slice(-9, -6);
|
|
1089
|
+
for (const [provider, info] of Object.entries(MOBILE_MONEY_PROVIDERS)) {
|
|
1090
|
+
if (info.prefix.some((p) => prefix.startsWith(p.slice(1)))) {
|
|
1091
|
+
return provider;
|
|
1092
|
+
}
|
|
783
1093
|
}
|
|
784
|
-
|
|
785
|
-
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// src/utils/paystack.ts
|
|
1098
|
+
async function openPaystackPopup(options, signal) {
|
|
1099
|
+
if (typeof window === "undefined") {
|
|
1100
|
+
return { success: false, error: "PAYSTACK_UNAVAILABLE" };
|
|
786
1101
|
}
|
|
787
|
-
|
|
788
|
-
|
|
1102
|
+
let PaystackPop;
|
|
1103
|
+
try {
|
|
1104
|
+
const imported = await import('@paystack/inline-js');
|
|
1105
|
+
PaystackPop = imported.default;
|
|
1106
|
+
} catch {
|
|
1107
|
+
return { success: false, error: "PAYSTACK_UNAVAILABLE" };
|
|
789
1108
|
}
|
|
790
|
-
|
|
791
|
-
|
|
1109
|
+
return new Promise((resolve) => {
|
|
1110
|
+
let settled = false;
|
|
1111
|
+
const resolveOnce = (result) => {
|
|
1112
|
+
if (settled) {
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
settled = true;
|
|
1116
|
+
if (signal) {
|
|
1117
|
+
signal.removeEventListener("abort", onAbort);
|
|
1118
|
+
}
|
|
1119
|
+
resolve(result);
|
|
1120
|
+
};
|
|
1121
|
+
const onAbort = () => {
|
|
1122
|
+
resolveOnce({ success: false, error: "CANCELLED" });
|
|
1123
|
+
};
|
|
1124
|
+
if (signal?.aborted) {
|
|
1125
|
+
resolveOnce({ success: false, error: "CANCELLED" });
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
if (signal) {
|
|
1129
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1130
|
+
}
|
|
1131
|
+
try {
|
|
1132
|
+
const popup = new PaystackPop();
|
|
1133
|
+
popup.newTransaction({
|
|
1134
|
+
key: options.key,
|
|
1135
|
+
email: options.email,
|
|
1136
|
+
amount: options.amount,
|
|
1137
|
+
currency: options.currency,
|
|
1138
|
+
reference: options.reference,
|
|
1139
|
+
accessCode: options.accessCode,
|
|
1140
|
+
onSuccess: (transaction) => {
|
|
1141
|
+
resolveOnce({
|
|
1142
|
+
success: true,
|
|
1143
|
+
reference: transaction.reference ?? options.reference
|
|
1144
|
+
});
|
|
1145
|
+
},
|
|
1146
|
+
onCancel: () => {
|
|
1147
|
+
resolveOnce({ success: false, error: "PAYMENT_CANCELLED" });
|
|
1148
|
+
},
|
|
1149
|
+
onError: (error) => {
|
|
1150
|
+
resolveOnce({
|
|
1151
|
+
success: false,
|
|
1152
|
+
error: error?.message || "PAYMENT_FAILED"
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
});
|
|
1156
|
+
} catch {
|
|
1157
|
+
resolveOnce({ success: false, error: "POPUP_BLOCKED" });
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// src/utils/checkout-resolver.ts
|
|
1163
|
+
var DEFAULT_POLL_INTERVAL_MS = 3e3;
|
|
1164
|
+
var DEFAULT_MAX_POLL_ATTEMPTS = 60;
|
|
1165
|
+
var MAX_CONSECUTIVE_NETWORK_ERRORS = 5;
|
|
1166
|
+
function normalizeAuthorizationType(value) {
|
|
1167
|
+
return value === "otp" || value === "pin" ? value : void 0;
|
|
1168
|
+
}
|
|
1169
|
+
function formatMoney2(value) {
|
|
1170
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1171
|
+
return value.toFixed(2);
|
|
792
1172
|
}
|
|
793
|
-
|
|
794
|
-
return
|
|
1173
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
1174
|
+
return value;
|
|
795
1175
|
}
|
|
796
|
-
|
|
797
|
-
|
|
1176
|
+
return "0.00";
|
|
1177
|
+
}
|
|
1178
|
+
function createAbortError() {
|
|
1179
|
+
const error = new Error("CANCELLED");
|
|
1180
|
+
error.name = "AbortError";
|
|
1181
|
+
return error;
|
|
1182
|
+
}
|
|
1183
|
+
function isAbortError(error) {
|
|
1184
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
1185
|
+
return true;
|
|
798
1186
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1187
|
+
return error instanceof Error && (error.name === "AbortError" || error.message === "CANCELLED");
|
|
1188
|
+
}
|
|
1189
|
+
var CheckoutResolver = class {
|
|
1190
|
+
constructor(options) {
|
|
1191
|
+
this.client = options.client;
|
|
1192
|
+
this.checkoutData = options.checkoutData;
|
|
1193
|
+
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
1194
|
+
this.maxPollAttempts = options.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS;
|
|
1195
|
+
this.onStatusChange = options.onStatusChange;
|
|
1196
|
+
this.onAuthorizationRequired = options.onAuthorizationRequired;
|
|
1197
|
+
this.signal = options.signal;
|
|
1198
|
+
this.returnUrl = options.returnUrl;
|
|
1199
|
+
this.enrollInLink = options.enrollInLink !== false;
|
|
1200
|
+
this.businessId = options.businessId;
|
|
1201
|
+
this.addressData = options.addressData;
|
|
1202
|
+
this.paymentData = options.paymentData;
|
|
1203
|
+
this.orderType = options.orderType;
|
|
1204
|
+
this.cartTotal = options.cartTotal;
|
|
1205
|
+
this.cartCurrency = options.cartCurrency;
|
|
1206
|
+
this.allowCardPopup = options.allowCardPopup ?? false;
|
|
1207
|
+
}
|
|
1208
|
+
async resolve(checkoutResult) {
|
|
1209
|
+
try {
|
|
1210
|
+
this.ensureNotAborted();
|
|
1211
|
+
if (!checkoutResult.order_id) {
|
|
1212
|
+
return this.fail("CHECKOUT_FAILED", "Checkout did not return an order ID.", false);
|
|
1213
|
+
}
|
|
1214
|
+
let latestCheckoutResult = checkoutResult;
|
|
1215
|
+
let authorizationType = normalizeAuthorizationType(checkoutResult.authorization_type);
|
|
1216
|
+
let paymentReference = checkoutResult.payment_reference;
|
|
1217
|
+
if (this.isSuccessfulStatus(checkoutResult.payment_status)) {
|
|
1218
|
+
return this.finalizeSuccess(checkoutResult);
|
|
1219
|
+
}
|
|
1220
|
+
if (checkoutResult.client_secret && checkoutResult.public_key) {
|
|
1221
|
+
if (!this.allowCardPopup) {
|
|
1222
|
+
return this.fail(
|
|
1223
|
+
"PAYSTACK_HANDOFF_REQUIRED",
|
|
1224
|
+
"Card authorization requires Elements. Use elements.processCheckout() for card flows.",
|
|
1225
|
+
true
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
this.emit("awaiting_authorization", {
|
|
1229
|
+
authorization_type: authorizationType,
|
|
1230
|
+
display_text: "Complete payment in the card authorization popup.",
|
|
1231
|
+
order_id: checkoutResult.order_id,
|
|
1232
|
+
order_number: checkoutResult.order_number,
|
|
1233
|
+
provider: checkoutResult.provider ?? "paystack"
|
|
1234
|
+
});
|
|
1235
|
+
const popupResult = await openPaystackPopup(
|
|
1236
|
+
{
|
|
1237
|
+
key: checkoutResult.public_key,
|
|
1238
|
+
email: this.checkoutData.customer.email || "customer@cimplify.io",
|
|
1239
|
+
accessCode: checkoutResult.client_secret,
|
|
1240
|
+
reference: paymentReference || checkoutResult.client_secret,
|
|
1241
|
+
currency: this.getOrderCurrency(checkoutResult)
|
|
1242
|
+
},
|
|
1243
|
+
this.signal
|
|
1244
|
+
);
|
|
1245
|
+
if (!popupResult.success) {
|
|
1246
|
+
if (popupResult.error === "POPUP_BLOCKED" || popupResult.error === "PAYSTACK_UNAVAILABLE") {
|
|
1247
|
+
return this.fail(
|
|
1248
|
+
"POPUP_BLOCKED",
|
|
1249
|
+
"Unable to open card payment popup. Please allow popups and try again.",
|
|
1250
|
+
true
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
return this.fail(
|
|
1254
|
+
popupResult.error || "PAYMENT_CANCELLED",
|
|
1255
|
+
"Card payment was cancelled before completion.",
|
|
1256
|
+
true
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if (checkoutResult.authorization_url) {
|
|
1261
|
+
if (typeof window !== "undefined" && this.returnUrl) {
|
|
1262
|
+
window.location.assign(checkoutResult.authorization_url);
|
|
1263
|
+
return this.fail(
|
|
1264
|
+
"REDIRECT_REQUIRED",
|
|
1265
|
+
"Redirecting to complete payment authorization.",
|
|
1266
|
+
true
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
if (typeof window !== "undefined") {
|
|
1270
|
+
const popup = window.open(
|
|
1271
|
+
checkoutResult.authorization_url,
|
|
1272
|
+
"cimplify-auth",
|
|
1273
|
+
"width=520,height=760,noopener,noreferrer"
|
|
1274
|
+
);
|
|
1275
|
+
if (!popup) {
|
|
1276
|
+
return this.fail(
|
|
1277
|
+
"POPUP_BLOCKED",
|
|
1278
|
+
"Authorization popup was blocked. Please allow popups and retry.",
|
|
1279
|
+
true
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (checkoutResult.requires_authorization || isPaymentStatusRequiresAction(checkoutResult.payment_status)) {
|
|
1285
|
+
const authorization = await this.handleAuthorization({
|
|
1286
|
+
authorizationType,
|
|
1287
|
+
paymentReference,
|
|
1288
|
+
displayText: checkoutResult.display_text,
|
|
1289
|
+
provider: checkoutResult.provider
|
|
1290
|
+
});
|
|
1291
|
+
if (!authorization.ok) {
|
|
1292
|
+
return authorization.result;
|
|
1293
|
+
}
|
|
1294
|
+
if (authorization.value) {
|
|
1295
|
+
latestCheckoutResult = authorization.value;
|
|
1296
|
+
paymentReference = authorization.value.payment_reference || paymentReference;
|
|
1297
|
+
authorizationType = normalizeAuthorizationType(authorization.value.authorization_type) || authorizationType;
|
|
1298
|
+
if (this.isSuccessfulStatus(authorization.value.payment_status)) {
|
|
1299
|
+
return this.finalizeSuccess(authorization.value);
|
|
1300
|
+
}
|
|
1301
|
+
if (this.isFailureStatus(authorization.value.payment_status)) {
|
|
1302
|
+
return this.fail(
|
|
1303
|
+
"AUTHORIZATION_FAILED",
|
|
1304
|
+
authorization.value.display_text || "Payment authorization failed.",
|
|
1305
|
+
false
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
return this.pollUntilTerminal({
|
|
1311
|
+
orderId: checkoutResult.order_id,
|
|
1312
|
+
latestCheckoutResult,
|
|
1313
|
+
paymentReference,
|
|
1314
|
+
authorizationType
|
|
1315
|
+
});
|
|
1316
|
+
} catch (error) {
|
|
1317
|
+
if (isAbortError(error)) {
|
|
1318
|
+
return this.fail("CANCELLED", "Checkout was cancelled.", true);
|
|
1319
|
+
}
|
|
1320
|
+
const message = error instanceof Error ? error.message : "Checkout failed unexpectedly.";
|
|
1321
|
+
return this.fail("CHECKOUT_FAILED", message, true);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
async pollUntilTerminal(input) {
|
|
1325
|
+
let consecutiveErrors = 0;
|
|
1326
|
+
let latestCheckoutResult = input.latestCheckoutResult;
|
|
1327
|
+
let paymentReference = input.paymentReference;
|
|
1328
|
+
let authorizationType = input.authorizationType;
|
|
1329
|
+
for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) {
|
|
1330
|
+
this.ensureNotAborted();
|
|
1331
|
+
this.emit("polling", {
|
|
1332
|
+
poll_attempt: attempt,
|
|
1333
|
+
max_poll_attempts: this.maxPollAttempts,
|
|
1334
|
+
order_id: input.orderId,
|
|
1335
|
+
order_number: latestCheckoutResult.order_number
|
|
1336
|
+
});
|
|
1337
|
+
const statusResult = await this.client.checkout.pollPaymentStatus(input.orderId);
|
|
1338
|
+
if (!statusResult.ok) {
|
|
1339
|
+
consecutiveErrors += 1;
|
|
1340
|
+
if (consecutiveErrors >= MAX_CONSECUTIVE_NETWORK_ERRORS) {
|
|
1341
|
+
return this.fail(
|
|
1342
|
+
"NETWORK_ERROR",
|
|
1343
|
+
"Unable to confirm payment due to repeated network errors.",
|
|
1344
|
+
true
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
await this.wait(this.pollIntervalMs);
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
consecutiveErrors = 0;
|
|
1351
|
+
if (statusResult.value.reference) {
|
|
1352
|
+
paymentReference = statusResult.value.reference;
|
|
1353
|
+
}
|
|
1354
|
+
const normalized = normalizeStatusResponse(statusResult.value);
|
|
1355
|
+
if (normalized.paid || isPaymentStatusSuccess(normalized.status)) {
|
|
1356
|
+
return this.finalizeSuccess(latestCheckoutResult);
|
|
1357
|
+
}
|
|
1358
|
+
if (isPaymentStatusFailure(normalized.status)) {
|
|
1359
|
+
return this.fail(
|
|
1360
|
+
normalized.status ? normalized.status.toUpperCase() : "PAYMENT_FAILED",
|
|
1361
|
+
normalized.message || "Payment failed during confirmation.",
|
|
1362
|
+
false
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
if (isPaymentStatusRequiresAction(normalized.status)) {
|
|
1366
|
+
const authorization = await this.handleAuthorization({
|
|
1367
|
+
authorizationType,
|
|
1368
|
+
paymentReference,
|
|
1369
|
+
displayText: normalized.message || latestCheckoutResult.display_text,
|
|
1370
|
+
provider: latestCheckoutResult.provider
|
|
1371
|
+
});
|
|
1372
|
+
if (!authorization.ok) {
|
|
1373
|
+
return authorization.result;
|
|
1374
|
+
}
|
|
1375
|
+
if (authorization.value) {
|
|
1376
|
+
latestCheckoutResult = authorization.value;
|
|
1377
|
+
paymentReference = authorization.value.payment_reference || paymentReference;
|
|
1378
|
+
authorizationType = normalizeAuthorizationType(authorization.value.authorization_type) || authorizationType;
|
|
1379
|
+
if (this.isSuccessfulStatus(authorization.value.payment_status)) {
|
|
1380
|
+
return this.finalizeSuccess(authorization.value);
|
|
1381
|
+
}
|
|
1382
|
+
if (this.isFailureStatus(authorization.value.payment_status)) {
|
|
1383
|
+
return this.fail(
|
|
1384
|
+
"AUTHORIZATION_FAILED",
|
|
1385
|
+
authorization.value.display_text || "Payment authorization failed.",
|
|
1386
|
+
false
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
await this.wait(this.pollIntervalMs);
|
|
1392
|
+
}
|
|
1393
|
+
return this.fail(
|
|
1394
|
+
"PAYMENT_TIMEOUT",
|
|
1395
|
+
"Payment confirmation timed out. Please retry checkout.",
|
|
1396
|
+
true
|
|
815
1397
|
);
|
|
816
1398
|
}
|
|
817
|
-
async
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
}
|
|
822
|
-
|
|
1399
|
+
async handleAuthorization(input) {
|
|
1400
|
+
const authorizationType = input.authorizationType;
|
|
1401
|
+
const paymentReference = input.paymentReference;
|
|
1402
|
+
if (!authorizationType || !paymentReference) {
|
|
1403
|
+
return { ok: true };
|
|
1404
|
+
}
|
|
1405
|
+
this.emit("awaiting_authorization", {
|
|
1406
|
+
authorization_type: authorizationType,
|
|
1407
|
+
display_text: input.displayText,
|
|
1408
|
+
provider: input.provider
|
|
1409
|
+
});
|
|
1410
|
+
let authorizationResult;
|
|
1411
|
+
const submit = async (code) => {
|
|
1412
|
+
this.ensureNotAborted();
|
|
1413
|
+
const trimmedCode = code.trim();
|
|
1414
|
+
if (!trimmedCode) {
|
|
1415
|
+
throw new Error("Authorization code is required.");
|
|
1416
|
+
}
|
|
1417
|
+
const submitResult = await this.client.checkout.submitAuthorization({
|
|
1418
|
+
reference: paymentReference,
|
|
1419
|
+
auth_type: authorizationType,
|
|
1420
|
+
value: trimmedCode
|
|
1421
|
+
});
|
|
1422
|
+
if (!submitResult.ok) {
|
|
1423
|
+
throw new Error(submitResult.error.message || "Authorization failed.");
|
|
1424
|
+
}
|
|
1425
|
+
authorizationResult = submitResult.value;
|
|
1426
|
+
};
|
|
1427
|
+
try {
|
|
1428
|
+
if (this.onAuthorizationRequired) {
|
|
1429
|
+
await this.onAuthorizationRequired(authorizationType, submit);
|
|
1430
|
+
} else {
|
|
1431
|
+
return {
|
|
1432
|
+
ok: false,
|
|
1433
|
+
result: this.fail(
|
|
1434
|
+
"AUTHORIZATION_REQUIRED",
|
|
1435
|
+
"Authorization callback is required in headless checkout mode.",
|
|
1436
|
+
false
|
|
1437
|
+
)
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
} catch (error) {
|
|
1441
|
+
if (isAbortError(error)) {
|
|
1442
|
+
return {
|
|
1443
|
+
ok: false,
|
|
1444
|
+
result: this.fail("CANCELLED", "Checkout was cancelled.", true)
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
const message = error instanceof Error ? error.message : "Payment authorization failed.";
|
|
1448
|
+
return {
|
|
1449
|
+
ok: false,
|
|
1450
|
+
result: this.fail("AUTHORIZATION_FAILED", message, true)
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
return {
|
|
1454
|
+
ok: true,
|
|
1455
|
+
value: authorizationResult
|
|
1456
|
+
};
|
|
823
1457
|
}
|
|
824
|
-
async
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1458
|
+
async finalizeSuccess(checkoutResult) {
|
|
1459
|
+
this.emit("finalizing", {
|
|
1460
|
+
order_id: checkoutResult.order_id,
|
|
1461
|
+
order_number: checkoutResult.order_number
|
|
1462
|
+
});
|
|
1463
|
+
const enrolledInLink = await this.maybeEnrollInLink(checkoutResult.order_id);
|
|
1464
|
+
this.emit("success", {
|
|
1465
|
+
order_id: checkoutResult.order_id,
|
|
1466
|
+
order_number: checkoutResult.order_number
|
|
1467
|
+
});
|
|
1468
|
+
return {
|
|
1469
|
+
success: true,
|
|
1470
|
+
order: {
|
|
1471
|
+
id: checkoutResult.order_id,
|
|
1472
|
+
order_number: checkoutResult.order_number || checkoutResult.order_id,
|
|
1473
|
+
status: checkoutResult.payment_status || "paid",
|
|
1474
|
+
total: this.getOrderTotal(checkoutResult),
|
|
1475
|
+
currency: this.getOrderCurrency(checkoutResult)
|
|
1476
|
+
},
|
|
1477
|
+
enrolled_in_link: enrolledInLink
|
|
1478
|
+
};
|
|
828
1479
|
}
|
|
829
|
-
async
|
|
830
|
-
|
|
1480
|
+
async maybeEnrollInLink(orderId) {
|
|
1481
|
+
if (!this.enrollInLink || !orderId) {
|
|
1482
|
+
return false;
|
|
1483
|
+
}
|
|
1484
|
+
let businessId = this.businessId;
|
|
1485
|
+
if (!businessId) {
|
|
1486
|
+
const businessResult = await this.client.business.getInfo();
|
|
1487
|
+
if (!businessResult.ok || !businessResult.value?.id) {
|
|
1488
|
+
return false;
|
|
1489
|
+
}
|
|
1490
|
+
businessId = businessResult.value.id;
|
|
1491
|
+
}
|
|
1492
|
+
const address = this.getEnrollmentAddress();
|
|
1493
|
+
const mobileMoney = this.getEnrollmentMobileMoney();
|
|
1494
|
+
try {
|
|
1495
|
+
const enrollment = await this.client.link.enrollAndLinkOrder({
|
|
1496
|
+
order_id: orderId,
|
|
1497
|
+
business_id: businessId,
|
|
1498
|
+
order_type: this.orderType || this.checkoutData.order_type,
|
|
1499
|
+
address,
|
|
1500
|
+
mobile_money: mobileMoney
|
|
1501
|
+
});
|
|
1502
|
+
return enrollment.ok && enrollment.value.success;
|
|
1503
|
+
} catch {
|
|
1504
|
+
return false;
|
|
1505
|
+
}
|
|
831
1506
|
}
|
|
832
|
-
|
|
833
|
-
|
|
1507
|
+
getEnrollmentAddress() {
|
|
1508
|
+
const source = this.addressData ?? this.checkoutData.address_info;
|
|
1509
|
+
if (!source?.street_address) {
|
|
1510
|
+
return void 0;
|
|
1511
|
+
}
|
|
1512
|
+
return {
|
|
1513
|
+
label: "Default",
|
|
1514
|
+
street_address: source.street_address,
|
|
1515
|
+
apartment: source.apartment || void 0,
|
|
1516
|
+
city: source.city || "",
|
|
1517
|
+
region: source.region || "",
|
|
1518
|
+
delivery_instructions: source.delivery_instructions || void 0,
|
|
1519
|
+
phone_for_delivery: source.phone_for_delivery || void 0
|
|
1520
|
+
};
|
|
834
1521
|
}
|
|
835
|
-
|
|
836
|
-
|
|
1522
|
+
getEnrollmentMobileMoney() {
|
|
1523
|
+
if (this.paymentData?.type === "mobile_money" && this.paymentData.phone_number && this.paymentData.provider) {
|
|
1524
|
+
return {
|
|
1525
|
+
phone_number: this.paymentData.phone_number,
|
|
1526
|
+
provider: this.paymentData.provider,
|
|
1527
|
+
label: this.paymentData.label || "Mobile Money"
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
if (this.checkoutData.mobile_money_details?.phone_number) {
|
|
1531
|
+
return {
|
|
1532
|
+
phone_number: this.checkoutData.mobile_money_details.phone_number,
|
|
1533
|
+
provider: this.checkoutData.mobile_money_details.provider,
|
|
1534
|
+
label: "Mobile Money"
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
return void 0;
|
|
837
1538
|
}
|
|
838
|
-
|
|
839
|
-
|
|
1539
|
+
getOrderTotal(checkoutResult) {
|
|
1540
|
+
if (checkoutResult.fx?.pay_amount !== void 0) {
|
|
1541
|
+
return formatMoney2(checkoutResult.fx.pay_amount);
|
|
1542
|
+
}
|
|
1543
|
+
if (this.cartTotal !== void 0) {
|
|
1544
|
+
return formatMoney2(this.cartTotal);
|
|
1545
|
+
}
|
|
1546
|
+
return "0.00";
|
|
840
1547
|
}
|
|
841
|
-
|
|
842
|
-
|
|
1548
|
+
getOrderCurrency(checkoutResult) {
|
|
1549
|
+
if (checkoutResult.fx?.pay_currency) {
|
|
1550
|
+
return checkoutResult.fx.pay_currency;
|
|
1551
|
+
}
|
|
1552
|
+
if (this.cartCurrency) {
|
|
1553
|
+
return this.cartCurrency;
|
|
1554
|
+
}
|
|
1555
|
+
if (this.checkoutData.pay_currency) {
|
|
1556
|
+
return this.checkoutData.pay_currency;
|
|
1557
|
+
}
|
|
1558
|
+
return "GHS";
|
|
843
1559
|
}
|
|
844
|
-
|
|
845
|
-
|
|
1560
|
+
emit(status, context = {}) {
|
|
1561
|
+
if (!this.onStatusChange) {
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
try {
|
|
1565
|
+
this.onStatusChange(status, context);
|
|
1566
|
+
} catch {
|
|
1567
|
+
}
|
|
846
1568
|
}
|
|
847
|
-
|
|
848
|
-
|
|
1569
|
+
fail(code, message, recoverable) {
|
|
1570
|
+
this.emit("failed", {
|
|
1571
|
+
display_text: message
|
|
1572
|
+
});
|
|
1573
|
+
return {
|
|
1574
|
+
success: false,
|
|
1575
|
+
error: {
|
|
1576
|
+
code,
|
|
1577
|
+
message,
|
|
1578
|
+
recoverable
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
849
1581
|
}
|
|
850
|
-
|
|
851
|
-
|
|
1582
|
+
isSuccessfulStatus(status) {
|
|
1583
|
+
const normalized = normalizeStatusResponse({ status, paid: false });
|
|
1584
|
+
return normalized.paid || isPaymentStatusSuccess(normalized.status);
|
|
852
1585
|
}
|
|
853
|
-
|
|
854
|
-
|
|
1586
|
+
isFailureStatus(status) {
|
|
1587
|
+
const normalized = normalizeStatusResponse({ status, paid: false });
|
|
1588
|
+
return isPaymentStatusFailure(normalized.status);
|
|
855
1589
|
}
|
|
856
|
-
|
|
857
|
-
|
|
1590
|
+
ensureNotAborted() {
|
|
1591
|
+
if (this.signal?.aborted) {
|
|
1592
|
+
throw createAbortError();
|
|
1593
|
+
}
|
|
858
1594
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1595
|
+
wait(ms) {
|
|
1596
|
+
this.ensureNotAborted();
|
|
1597
|
+
return new Promise((resolve, reject) => {
|
|
1598
|
+
const timer = setTimeout(() => {
|
|
1599
|
+
if (this.signal) {
|
|
1600
|
+
this.signal.removeEventListener("abort", onAbort);
|
|
1601
|
+
}
|
|
1602
|
+
resolve();
|
|
1603
|
+
}, ms);
|
|
1604
|
+
const onAbort = () => {
|
|
1605
|
+
clearTimeout(timer);
|
|
1606
|
+
reject(createAbortError());
|
|
1607
|
+
};
|
|
1608
|
+
if (this.signal) {
|
|
1609
|
+
this.signal.addEventListener("abort", onAbort, { once: true });
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
863
1612
|
}
|
|
864
1613
|
};
|
|
865
1614
|
|
|
866
|
-
// src/
|
|
867
|
-
function
|
|
1615
|
+
// src/checkout.ts
|
|
1616
|
+
function toCimplifyError3(error) {
|
|
868
1617
|
if (error instanceof CimplifyError) return error;
|
|
869
1618
|
if (error instanceof Error) {
|
|
870
1619
|
return new CimplifyError("UNKNOWN_ERROR", error.message, false);
|
|
871
1620
|
}
|
|
872
1621
|
return new CimplifyError("UNKNOWN_ERROR", String(error), false);
|
|
873
1622
|
}
|
|
874
|
-
async function
|
|
1623
|
+
async function safe3(promise) {
|
|
875
1624
|
try {
|
|
876
1625
|
return ok(await promise);
|
|
877
1626
|
} catch (error) {
|
|
878
|
-
return err(
|
|
1627
|
+
return err(toCimplifyError3(error));
|
|
879
1628
|
}
|
|
880
1629
|
}
|
|
881
|
-
|
|
1630
|
+
function toTerminalFailure(code, message, recoverable) {
|
|
1631
|
+
return {
|
|
1632
|
+
success: false,
|
|
1633
|
+
error: {
|
|
1634
|
+
code,
|
|
1635
|
+
message,
|
|
1636
|
+
recoverable
|
|
1637
|
+
}
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
function generateIdempotencyKey() {
|
|
1641
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
1642
|
+
return `idem_${crypto.randomUUID()}`;
|
|
1643
|
+
}
|
|
1644
|
+
const timestamp = Date.now().toString(36);
|
|
1645
|
+
const random = Math.random().toString(36).substring(2, 15);
|
|
1646
|
+
return `idem_${timestamp}_${random}`;
|
|
1647
|
+
}
|
|
1648
|
+
var CheckoutService = class {
|
|
882
1649
|
constructor(client) {
|
|
883
1650
|
this.client = client;
|
|
884
1651
|
}
|
|
885
|
-
async
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
async isAuthenticated() {
|
|
894
|
-
const result = await this.getStatus();
|
|
895
|
-
if (!result.ok) return result;
|
|
896
|
-
return ok(result.value.is_authenticated);
|
|
897
|
-
}
|
|
898
|
-
async requestOtp(contact, contactType) {
|
|
899
|
-
return safe6(
|
|
900
|
-
this.client.call(AUTH_MUTATION.REQUEST_OTP, {
|
|
901
|
-
contact,
|
|
902
|
-
contact_type: contactType
|
|
1652
|
+
async process(data) {
|
|
1653
|
+
const checkoutData = {
|
|
1654
|
+
...data,
|
|
1655
|
+
idempotency_key: data.idempotency_key || generateIdempotencyKey()
|
|
1656
|
+
};
|
|
1657
|
+
return safe3(
|
|
1658
|
+
this.client.call(CHECKOUT_MUTATION.PROCESS, {
|
|
1659
|
+
checkout_data: checkoutData
|
|
903
1660
|
})
|
|
904
1661
|
);
|
|
905
1662
|
}
|
|
906
|
-
async
|
|
907
|
-
return
|
|
908
|
-
this.client.call(
|
|
909
|
-
|
|
910
|
-
|
|
1663
|
+
async initializePayment(orderId, method) {
|
|
1664
|
+
return safe3(
|
|
1665
|
+
this.client.call("order.initializePayment", {
|
|
1666
|
+
order_id: orderId,
|
|
1667
|
+
payment_method: method
|
|
911
1668
|
})
|
|
912
1669
|
);
|
|
913
1670
|
}
|
|
914
|
-
async
|
|
915
|
-
return
|
|
1671
|
+
async submitAuthorization(input) {
|
|
1672
|
+
return safe3(
|
|
1673
|
+
this.client.call(PAYMENT_MUTATION.SUBMIT_AUTHORIZATION, input)
|
|
1674
|
+
);
|
|
916
1675
|
}
|
|
917
|
-
async
|
|
918
|
-
return
|
|
1676
|
+
async pollPaymentStatus(orderId) {
|
|
1677
|
+
return safe3(
|
|
1678
|
+
this.client.call(PAYMENT_MUTATION.CHECK_STATUS, orderId)
|
|
1679
|
+
);
|
|
919
1680
|
}
|
|
920
|
-
async
|
|
921
|
-
return
|
|
1681
|
+
async updateOrderCustomer(orderId, customer) {
|
|
1682
|
+
return safe3(
|
|
1683
|
+
this.client.call(ORDER_MUTATION.UPDATE_CUSTOMER, {
|
|
1684
|
+
order_id: orderId,
|
|
1685
|
+
...customer
|
|
1686
|
+
})
|
|
1687
|
+
);
|
|
922
1688
|
}
|
|
923
|
-
async
|
|
924
|
-
return
|
|
1689
|
+
async verifyPayment(orderId) {
|
|
1690
|
+
return safe3(
|
|
1691
|
+
this.client.call("order.verifyPayment", {
|
|
1692
|
+
order_id: orderId
|
|
1693
|
+
})
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1696
|
+
async processAndResolve(data) {
|
|
1697
|
+
data.on_status_change?.("preparing", {});
|
|
1698
|
+
if (!data.cart_id) {
|
|
1699
|
+
return ok(
|
|
1700
|
+
toTerminalFailure(
|
|
1701
|
+
"INVALID_CART",
|
|
1702
|
+
"A valid cart is required before checkout can start.",
|
|
1703
|
+
false
|
|
1704
|
+
)
|
|
1705
|
+
);
|
|
1706
|
+
}
|
|
1707
|
+
const cartResult = await this.client.cart.get();
|
|
1708
|
+
if (!cartResult.ok || !cartResult.value?.id) {
|
|
1709
|
+
return ok(
|
|
1710
|
+
toTerminalFailure(
|
|
1711
|
+
"INVALID_CART",
|
|
1712
|
+
"Unable to load cart for checkout.",
|
|
1713
|
+
false
|
|
1714
|
+
)
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
const cart = cartResult.value;
|
|
1718
|
+
if (cart.id !== data.cart_id || cart.items.length === 0) {
|
|
1719
|
+
return ok(
|
|
1720
|
+
toTerminalFailure(
|
|
1721
|
+
"INVALID_CART",
|
|
1722
|
+
"Cart is empty or no longer valid. Please refresh and try again.",
|
|
1723
|
+
false
|
|
1724
|
+
)
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
const checkoutData = {
|
|
1728
|
+
cart_id: data.cart_id,
|
|
1729
|
+
location_id: data.location_id,
|
|
1730
|
+
customer: data.customer,
|
|
1731
|
+
order_type: data.order_type,
|
|
1732
|
+
address_info: data.address_info,
|
|
1733
|
+
payment_method: data.payment_method,
|
|
1734
|
+
mobile_money_details: data.mobile_money_details,
|
|
1735
|
+
special_instructions: data.special_instructions,
|
|
1736
|
+
link_address_id: data.link_address_id,
|
|
1737
|
+
link_payment_method_id: data.link_payment_method_id,
|
|
1738
|
+
idempotency_key: data.idempotency_key || generateIdempotencyKey(),
|
|
1739
|
+
metadata: data.metadata,
|
|
1740
|
+
pay_currency: data.pay_currency,
|
|
1741
|
+
fx_quote_id: data.fx_quote_id
|
|
1742
|
+
};
|
|
1743
|
+
const baseCurrency = (cart.pricing.currency || checkoutData.pay_currency || "GHS").toUpperCase();
|
|
1744
|
+
const payCurrency = data.pay_currency?.trim().toUpperCase();
|
|
1745
|
+
const cartTotalAmount = Number.parseFloat(cart.pricing.total_price || "0");
|
|
1746
|
+
if (payCurrency && payCurrency !== baseCurrency && !checkoutData.fx_quote_id && Number.isFinite(cartTotalAmount) && cartTotalAmount > 0) {
|
|
1747
|
+
const fxQuoteResult = await this.client.fx.lockQuote({
|
|
1748
|
+
from: baseCurrency,
|
|
1749
|
+
to: payCurrency,
|
|
1750
|
+
amount: cartTotalAmount
|
|
1751
|
+
});
|
|
1752
|
+
if (!fxQuoteResult.ok) {
|
|
1753
|
+
return ok(
|
|
1754
|
+
toTerminalFailure(
|
|
1755
|
+
"FX_QUOTE_FAILED",
|
|
1756
|
+
fxQuoteResult.error.message || "Unable to lock FX quote for checkout.",
|
|
1757
|
+
true
|
|
1758
|
+
)
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
checkoutData.pay_currency = payCurrency;
|
|
1762
|
+
checkoutData.fx_quote_id = fxQuoteResult.value.id;
|
|
1763
|
+
}
|
|
1764
|
+
data.on_status_change?.("processing", {});
|
|
1765
|
+
const processResult = await this.process(checkoutData);
|
|
1766
|
+
if (!processResult.ok) {
|
|
1767
|
+
return ok(
|
|
1768
|
+
toTerminalFailure(
|
|
1769
|
+
processResult.error.code || "CHECKOUT_FAILED",
|
|
1770
|
+
processResult.error.message || "Unable to process checkout.",
|
|
1771
|
+
processResult.error.retryable ?? true
|
|
1772
|
+
)
|
|
1773
|
+
);
|
|
1774
|
+
}
|
|
1775
|
+
const resolver = new CheckoutResolver({
|
|
1776
|
+
client: this.client,
|
|
1777
|
+
checkoutData,
|
|
1778
|
+
pollIntervalMs: data.poll_interval_ms,
|
|
1779
|
+
maxPollAttempts: data.max_poll_attempts,
|
|
1780
|
+
onStatusChange: data.on_status_change,
|
|
1781
|
+
onAuthorizationRequired: data.on_authorization_required,
|
|
1782
|
+
signal: data.signal,
|
|
1783
|
+
returnUrl: data.return_url,
|
|
1784
|
+
enrollInLink: data.enroll_in_link,
|
|
1785
|
+
cartTotal: cart.pricing.total_price,
|
|
1786
|
+
cartCurrency: checkoutData.pay_currency || cart.pricing.currency || "GHS",
|
|
1787
|
+
orderType: checkoutData.order_type,
|
|
1788
|
+
allowCardPopup: false
|
|
1789
|
+
});
|
|
1790
|
+
const resolved = await resolver.resolve(processResult.value);
|
|
1791
|
+
return ok(resolved);
|
|
925
1792
|
}
|
|
926
1793
|
};
|
|
927
1794
|
|
|
928
|
-
// src/
|
|
929
|
-
function
|
|
1795
|
+
// src/orders.ts
|
|
1796
|
+
function toCimplifyError4(error) {
|
|
930
1797
|
if (error instanceof CimplifyError) return error;
|
|
931
1798
|
if (error instanceof Error) {
|
|
932
1799
|
return new CimplifyError("UNKNOWN_ERROR", error.message, false);
|
|
933
1800
|
}
|
|
934
1801
|
return new CimplifyError("UNKNOWN_ERROR", String(error), false);
|
|
935
1802
|
}
|
|
936
|
-
async function
|
|
1803
|
+
async function safe4(promise) {
|
|
937
1804
|
try {
|
|
938
1805
|
return ok(await promise);
|
|
939
1806
|
} catch (error) {
|
|
940
|
-
return err(
|
|
1807
|
+
return err(toCimplifyError4(error));
|
|
941
1808
|
}
|
|
942
1809
|
}
|
|
943
|
-
var
|
|
1810
|
+
var OrderQueries = class {
|
|
944
1811
|
constructor(client) {
|
|
945
1812
|
this.client = client;
|
|
946
1813
|
}
|
|
947
|
-
async
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
return safe7(this.client.query("business.theme"));
|
|
961
|
-
}
|
|
962
|
-
async getLocations() {
|
|
963
|
-
return safe7(this.client.query("business.locations"));
|
|
1814
|
+
async list(options) {
|
|
1815
|
+
let query2 = "orders";
|
|
1816
|
+
if (options?.status) {
|
|
1817
|
+
query2 += `[?(@.status=='${options.status}')]`;
|
|
1818
|
+
}
|
|
1819
|
+
query2 += "#sort(created_at,desc)";
|
|
1820
|
+
if (options?.limit) {
|
|
1821
|
+
query2 += `#limit(${options.limit})`;
|
|
1822
|
+
}
|
|
1823
|
+
if (options?.offset) {
|
|
1824
|
+
query2 += `#offset(${options.offset})`;
|
|
1825
|
+
}
|
|
1826
|
+
return safe4(this.client.query(query2));
|
|
964
1827
|
}
|
|
965
|
-
async
|
|
966
|
-
return
|
|
1828
|
+
async get(orderId) {
|
|
1829
|
+
return safe4(this.client.query(`orders.${orderId}`));
|
|
967
1830
|
}
|
|
968
|
-
async
|
|
969
|
-
return
|
|
1831
|
+
async getRecent(limit = 5) {
|
|
1832
|
+
return safe4(this.client.query(`orders#sort(created_at,desc)#limit(${limit})`));
|
|
970
1833
|
}
|
|
971
|
-
async
|
|
972
|
-
return
|
|
1834
|
+
async getByStatus(status) {
|
|
1835
|
+
return safe4(this.client.query(`orders[?(@.status=='${status}')]`));
|
|
973
1836
|
}
|
|
974
|
-
async
|
|
975
|
-
|
|
976
|
-
this.
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
if (!locationsResult.ok) return locationsResult;
|
|
982
|
-
if (!categoriesResult.ok) return categoriesResult;
|
|
983
|
-
const business = businessResult.value;
|
|
984
|
-
const locations = locationsResult.value;
|
|
985
|
-
const categories = categoriesResult.value;
|
|
986
|
-
const defaultLocation = locations[0];
|
|
987
|
-
return ok({
|
|
988
|
-
business,
|
|
989
|
-
location: defaultLocation,
|
|
990
|
-
locations,
|
|
991
|
-
categories,
|
|
992
|
-
currency: business.default_currency,
|
|
993
|
-
is_open: defaultLocation?.accepts_online_orders ?? false,
|
|
994
|
-
accepts_orders: defaultLocation?.accepts_online_orders ?? false
|
|
995
|
-
});
|
|
1837
|
+
async cancel(orderId, reason) {
|
|
1838
|
+
return safe4(
|
|
1839
|
+
this.client.call("order.cancelOrder", {
|
|
1840
|
+
order_id: orderId,
|
|
1841
|
+
reason
|
|
1842
|
+
})
|
|
1843
|
+
);
|
|
996
1844
|
}
|
|
997
1845
|
};
|
|
998
1846
|
|
|
999
|
-
// src/
|
|
1000
|
-
function
|
|
1847
|
+
// src/link.ts
|
|
1848
|
+
function toCimplifyError5(error) {
|
|
1001
1849
|
if (error instanceof CimplifyError) return error;
|
|
1002
1850
|
if (error instanceof Error) {
|
|
1003
1851
|
return new CimplifyError("UNKNOWN_ERROR", error.message, false);
|
|
1004
1852
|
}
|
|
1005
1853
|
return new CimplifyError("UNKNOWN_ERROR", String(error), false);
|
|
1006
1854
|
}
|
|
1007
|
-
async function
|
|
1855
|
+
async function safe5(promise) {
|
|
1008
1856
|
try {
|
|
1009
1857
|
return ok(await promise);
|
|
1010
1858
|
} catch (error) {
|
|
1011
|
-
return err(
|
|
1859
|
+
return err(toCimplifyError5(error));
|
|
1012
1860
|
}
|
|
1013
1861
|
}
|
|
1014
|
-
var
|
|
1862
|
+
var LinkService = class {
|
|
1015
1863
|
constructor(client) {
|
|
1016
1864
|
this.client = client;
|
|
1017
1865
|
}
|
|
1018
|
-
async
|
|
1019
|
-
return
|
|
1866
|
+
async requestOtp(input) {
|
|
1867
|
+
return safe5(this.client.linkPost("/v1/link/auth/request-otp", input));
|
|
1020
1868
|
}
|
|
1021
|
-
async
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
})
|
|
1028
|
-
);
|
|
1869
|
+
async verifyOtp(input) {
|
|
1870
|
+
const result = await safe5(
|
|
1871
|
+
this.client.linkPost("/v1/link/auth/verify-otp", input)
|
|
1872
|
+
);
|
|
1873
|
+
if (result.ok && result.value.session_token) {
|
|
1874
|
+
this.client.setSessionToken(result.value.session_token);
|
|
1029
1875
|
}
|
|
1030
|
-
return
|
|
1031
|
-
|
|
1032
|
-
|
|
1876
|
+
return result;
|
|
1877
|
+
}
|
|
1878
|
+
async logout() {
|
|
1879
|
+
const result = await safe5(this.client.linkPost("/v1/link/auth/logout"));
|
|
1880
|
+
if (result.ok) {
|
|
1881
|
+
this.client.clearSession();
|
|
1882
|
+
}
|
|
1883
|
+
return result;
|
|
1884
|
+
}
|
|
1885
|
+
async checkStatus(contact) {
|
|
1886
|
+
return safe5(
|
|
1887
|
+
this.client.call(LINK_MUTATION.CHECK_STATUS, {
|
|
1888
|
+
contact
|
|
1033
1889
|
})
|
|
1034
1890
|
);
|
|
1035
1891
|
}
|
|
1036
|
-
async
|
|
1037
|
-
return
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1892
|
+
async getLinkData() {
|
|
1893
|
+
return safe5(this.client.query(LINK_QUERY.DATA));
|
|
1894
|
+
}
|
|
1895
|
+
async getAddresses() {
|
|
1896
|
+
return safe5(this.client.query(LINK_QUERY.ADDRESSES));
|
|
1897
|
+
}
|
|
1898
|
+
async getMobileMoney() {
|
|
1899
|
+
return safe5(this.client.query(LINK_QUERY.MOBILE_MONEY));
|
|
1900
|
+
}
|
|
1901
|
+
async getPreferences() {
|
|
1902
|
+
return safe5(this.client.query(LINK_QUERY.PREFERENCES));
|
|
1903
|
+
}
|
|
1904
|
+
async enroll(data) {
|
|
1905
|
+
return safe5(this.client.call(LINK_MUTATION.ENROLL, data));
|
|
1906
|
+
}
|
|
1907
|
+
async enrollAndLinkOrder(data) {
|
|
1908
|
+
return safe5(
|
|
1909
|
+
this.client.call(LINK_MUTATION.ENROLL_AND_LINK_ORDER, data)
|
|
1042
1910
|
);
|
|
1043
1911
|
}
|
|
1044
|
-
async
|
|
1045
|
-
return
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1912
|
+
async updatePreferences(preferences) {
|
|
1913
|
+
return safe5(this.client.call(LINK_MUTATION.UPDATE_PREFERENCES, preferences));
|
|
1914
|
+
}
|
|
1915
|
+
async createAddress(input) {
|
|
1916
|
+
return safe5(this.client.call(LINK_MUTATION.CREATE_ADDRESS, input));
|
|
1917
|
+
}
|
|
1918
|
+
async updateAddress(input) {
|
|
1919
|
+
return safe5(this.client.call(LINK_MUTATION.UPDATE_ADDRESS, input));
|
|
1920
|
+
}
|
|
1921
|
+
async deleteAddress(addressId) {
|
|
1922
|
+
return safe5(this.client.call(LINK_MUTATION.DELETE_ADDRESS, addressId));
|
|
1923
|
+
}
|
|
1924
|
+
async setDefaultAddress(addressId) {
|
|
1925
|
+
return safe5(this.client.call(LINK_MUTATION.SET_DEFAULT_ADDRESS, addressId));
|
|
1926
|
+
}
|
|
1927
|
+
async trackAddressUsage(addressId) {
|
|
1928
|
+
return safe5(
|
|
1929
|
+
this.client.call(LINK_MUTATION.TRACK_ADDRESS_USAGE, {
|
|
1930
|
+
address_id: addressId
|
|
1050
1931
|
})
|
|
1051
1932
|
);
|
|
1052
1933
|
}
|
|
1053
|
-
async
|
|
1054
|
-
return
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1934
|
+
async createMobileMoney(input) {
|
|
1935
|
+
return safe5(this.client.call(LINK_MUTATION.CREATE_MOBILE_MONEY, input));
|
|
1936
|
+
}
|
|
1937
|
+
async deleteMobileMoney(mobileMoneyId) {
|
|
1938
|
+
return safe5(this.client.call(LINK_MUTATION.DELETE_MOBILE_MONEY, mobileMoneyId));
|
|
1939
|
+
}
|
|
1940
|
+
async setDefaultMobileMoney(mobileMoneyId) {
|
|
1941
|
+
return safe5(
|
|
1942
|
+
this.client.call(LINK_MUTATION.SET_DEFAULT_MOBILE_MONEY, mobileMoneyId)
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
async trackMobileMoneyUsage(mobileMoneyId) {
|
|
1946
|
+
return safe5(
|
|
1947
|
+
this.client.call(LINK_MUTATION.TRACK_MOBILE_MONEY_USAGE, {
|
|
1948
|
+
mobile_money_id: mobileMoneyId
|
|
1059
1949
|
})
|
|
1060
1950
|
);
|
|
1061
1951
|
}
|
|
1062
|
-
async
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
(item) => item.variant_id ? this.checkVariantAvailability(item.variant_id, item.quantity, locationId) : this.checkProductAvailability(item.product_id, item.quantity, locationId)
|
|
1066
|
-
)
|
|
1952
|
+
async verifyMobileMoney(mobileMoneyId) {
|
|
1953
|
+
return safe5(
|
|
1954
|
+
this.client.call(LINK_MUTATION.VERIFY_MOBILE_MONEY, mobileMoneyId)
|
|
1067
1955
|
);
|
|
1068
|
-
for (const result of results) {
|
|
1069
|
-
if (!result.ok) return result;
|
|
1070
|
-
}
|
|
1071
|
-
return ok(results.map((r) => r.value));
|
|
1072
1956
|
}
|
|
1073
|
-
async
|
|
1074
|
-
return
|
|
1957
|
+
async getSessions() {
|
|
1958
|
+
return safe5(this.client.linkGet("/v1/link/sessions"));
|
|
1075
1959
|
}
|
|
1076
|
-
async
|
|
1077
|
-
|
|
1078
|
-
if (!result.ok) return result;
|
|
1079
|
-
return ok(result.value.is_available);
|
|
1960
|
+
async revokeSession(sessionId) {
|
|
1961
|
+
return safe5(this.client.linkDelete(`/v1/link/sessions/${sessionId}`));
|
|
1080
1962
|
}
|
|
1081
|
-
async
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1963
|
+
async revokeAllSessions() {
|
|
1964
|
+
return safe5(this.client.linkDelete("/v1/link/sessions"));
|
|
1965
|
+
}
|
|
1966
|
+
async getAddressesRest() {
|
|
1967
|
+
return safe5(this.client.linkGet("/v1/link/addresses"));
|
|
1968
|
+
}
|
|
1969
|
+
async createAddressRest(input) {
|
|
1970
|
+
return safe5(this.client.linkPost("/v1/link/addresses", input));
|
|
1971
|
+
}
|
|
1972
|
+
async deleteAddressRest(addressId) {
|
|
1973
|
+
return safe5(this.client.linkDelete(`/v1/link/addresses/${addressId}`));
|
|
1974
|
+
}
|
|
1975
|
+
async setDefaultAddressRest(addressId) {
|
|
1976
|
+
return safe5(this.client.linkPost(`/v1/link/addresses/${addressId}/default`));
|
|
1977
|
+
}
|
|
1978
|
+
async getMobileMoneyRest() {
|
|
1979
|
+
return safe5(this.client.linkGet("/v1/link/mobile-money"));
|
|
1980
|
+
}
|
|
1981
|
+
async createMobileMoneyRest(input) {
|
|
1982
|
+
return safe5(this.client.linkPost("/v1/link/mobile-money", input));
|
|
1983
|
+
}
|
|
1984
|
+
async deleteMobileMoneyRest(mobileMoneyId) {
|
|
1985
|
+
return safe5(this.client.linkDelete(`/v1/link/mobile-money/${mobileMoneyId}`));
|
|
1986
|
+
}
|
|
1987
|
+
async setDefaultMobileMoneyRest(mobileMoneyId) {
|
|
1988
|
+
return safe5(
|
|
1989
|
+
this.client.linkPost(`/v1/link/mobile-money/${mobileMoneyId}/default`)
|
|
1990
|
+
);
|
|
1085
1991
|
}
|
|
1086
1992
|
};
|
|
1087
1993
|
|
|
1088
|
-
// src/
|
|
1089
|
-
function
|
|
1090
|
-
return Object.fromEntries(Object.entries(input));
|
|
1091
|
-
}
|
|
1092
|
-
function toCimplifyError9(error) {
|
|
1994
|
+
// src/auth.ts
|
|
1995
|
+
function toCimplifyError6(error) {
|
|
1093
1996
|
if (error instanceof CimplifyError) return error;
|
|
1094
1997
|
if (error instanceof Error) {
|
|
1095
1998
|
return new CimplifyError("UNKNOWN_ERROR", error.message, false);
|
|
1096
1999
|
}
|
|
1097
2000
|
return new CimplifyError("UNKNOWN_ERROR", String(error), false);
|
|
1098
2001
|
}
|
|
1099
|
-
async function
|
|
2002
|
+
async function safe6(promise) {
|
|
1100
2003
|
try {
|
|
1101
2004
|
return ok(await promise);
|
|
1102
2005
|
} catch (error) {
|
|
1103
|
-
return err(
|
|
2006
|
+
return err(toCimplifyError6(error));
|
|
1104
2007
|
}
|
|
1105
2008
|
}
|
|
1106
|
-
var
|
|
2009
|
+
var AuthService = class {
|
|
1107
2010
|
constructor(client) {
|
|
1108
2011
|
this.client = client;
|
|
1109
2012
|
}
|
|
1110
|
-
async
|
|
1111
|
-
return
|
|
2013
|
+
async getStatus() {
|
|
2014
|
+
return safe6(this.client.query("auth"));
|
|
1112
2015
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
* Note: Filters from all services client-side (no single-service endpoint)
|
|
1116
|
-
*/
|
|
1117
|
-
async getService(serviceId) {
|
|
1118
|
-
const result = await this.getServices();
|
|
2016
|
+
async getCurrentUser() {
|
|
2017
|
+
const result = await this.getStatus();
|
|
1119
2018
|
if (!result.ok) return result;
|
|
1120
|
-
return ok(result.value.
|
|
2019
|
+
return ok(result.value.customer || null);
|
|
1121
2020
|
}
|
|
1122
|
-
async
|
|
2021
|
+
async isAuthenticated() {
|
|
2022
|
+
const result = await this.getStatus();
|
|
2023
|
+
if (!result.ok) return result;
|
|
2024
|
+
return ok(result.value.is_authenticated);
|
|
2025
|
+
}
|
|
2026
|
+
async requestOtp(contact, contactType) {
|
|
2027
|
+
return safe6(
|
|
2028
|
+
this.client.call(AUTH_MUTATION.REQUEST_OTP, {
|
|
2029
|
+
contact,
|
|
2030
|
+
contact_type: contactType
|
|
2031
|
+
})
|
|
2032
|
+
);
|
|
2033
|
+
}
|
|
2034
|
+
async verifyOtp(code, contact) {
|
|
2035
|
+
return safe6(
|
|
2036
|
+
this.client.call(AUTH_MUTATION.VERIFY_OTP, {
|
|
2037
|
+
otp_code: code,
|
|
2038
|
+
contact
|
|
2039
|
+
})
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
async logout() {
|
|
2043
|
+
return safe6(this.client.call("auth.logout"));
|
|
2044
|
+
}
|
|
2045
|
+
async updateProfile(input) {
|
|
2046
|
+
return safe6(this.client.call("auth.update_profile", input));
|
|
2047
|
+
}
|
|
2048
|
+
async changePassword(input) {
|
|
2049
|
+
return safe6(this.client.call("auth.change_password", input));
|
|
2050
|
+
}
|
|
2051
|
+
async resetPassword(email) {
|
|
2052
|
+
return safe6(this.client.call("auth.reset_password", { email }));
|
|
2053
|
+
}
|
|
2054
|
+
};
|
|
2055
|
+
|
|
2056
|
+
// src/business.ts
|
|
2057
|
+
function toCimplifyError7(error) {
|
|
2058
|
+
if (error instanceof CimplifyError) return error;
|
|
2059
|
+
if (error instanceof Error) {
|
|
2060
|
+
return new CimplifyError("UNKNOWN_ERROR", error.message, false);
|
|
2061
|
+
}
|
|
2062
|
+
return new CimplifyError("UNKNOWN_ERROR", String(error), false);
|
|
2063
|
+
}
|
|
2064
|
+
async function safe7(promise) {
|
|
2065
|
+
try {
|
|
2066
|
+
return ok(await promise);
|
|
2067
|
+
} catch (error) {
|
|
2068
|
+
return err(toCimplifyError7(error));
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
var BusinessService = class {
|
|
2072
|
+
constructor(client) {
|
|
2073
|
+
this.client = client;
|
|
2074
|
+
}
|
|
2075
|
+
async getInfo() {
|
|
2076
|
+
return safe7(this.client.query("business.info"));
|
|
2077
|
+
}
|
|
2078
|
+
async getByHandle(handle) {
|
|
2079
|
+
return safe7(this.client.query(`business.handle.${handle}`));
|
|
2080
|
+
}
|
|
2081
|
+
async getByDomain(domain) {
|
|
2082
|
+
return safe7(this.client.query("business.domain", { domain }));
|
|
2083
|
+
}
|
|
2084
|
+
async getSettings() {
|
|
2085
|
+
return safe7(this.client.query("business.settings"));
|
|
2086
|
+
}
|
|
2087
|
+
async getTheme() {
|
|
2088
|
+
return safe7(this.client.query("business.theme"));
|
|
2089
|
+
}
|
|
2090
|
+
async getLocations() {
|
|
2091
|
+
return safe7(this.client.query("business.locations"));
|
|
2092
|
+
}
|
|
2093
|
+
async getLocation(locationId) {
|
|
2094
|
+
return safe7(this.client.query(`business.locations.${locationId}`));
|
|
2095
|
+
}
|
|
2096
|
+
async getHours() {
|
|
2097
|
+
return safe7(this.client.query("business.hours"));
|
|
2098
|
+
}
|
|
2099
|
+
async getLocationHours(locationId) {
|
|
2100
|
+
return safe7(this.client.query(`business.locations.${locationId}.hours`));
|
|
2101
|
+
}
|
|
2102
|
+
async getBootstrap() {
|
|
2103
|
+
const [businessResult, locationsResult, categoriesResult] = await Promise.all([
|
|
2104
|
+
this.getInfo(),
|
|
2105
|
+
this.getLocations(),
|
|
2106
|
+
safe7(this.client.query("categories#select(id,name,slug)"))
|
|
2107
|
+
]);
|
|
2108
|
+
if (!businessResult.ok) return businessResult;
|
|
2109
|
+
if (!locationsResult.ok) return locationsResult;
|
|
2110
|
+
if (!categoriesResult.ok) return categoriesResult;
|
|
2111
|
+
const business = businessResult.value;
|
|
2112
|
+
const locations = locationsResult.value;
|
|
2113
|
+
const categories = categoriesResult.value;
|
|
2114
|
+
const defaultLocation = locations[0];
|
|
2115
|
+
return ok({
|
|
2116
|
+
business,
|
|
2117
|
+
location: defaultLocation,
|
|
2118
|
+
locations,
|
|
2119
|
+
categories,
|
|
2120
|
+
currency: business.default_currency,
|
|
2121
|
+
is_open: defaultLocation?.accepts_online_orders ?? false,
|
|
2122
|
+
accepts_orders: defaultLocation?.accepts_online_orders ?? false
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
};
|
|
2126
|
+
|
|
2127
|
+
// src/inventory.ts
|
|
2128
|
+
function toCimplifyError8(error) {
|
|
2129
|
+
if (error instanceof CimplifyError) return error;
|
|
2130
|
+
if (error instanceof Error) {
|
|
2131
|
+
return new CimplifyError("UNKNOWN_ERROR", error.message, false);
|
|
2132
|
+
}
|
|
2133
|
+
return new CimplifyError("UNKNOWN_ERROR", String(error), false);
|
|
2134
|
+
}
|
|
2135
|
+
async function safe8(promise) {
|
|
2136
|
+
try {
|
|
2137
|
+
return ok(await promise);
|
|
2138
|
+
} catch (error) {
|
|
2139
|
+
return err(toCimplifyError8(error));
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
var InventoryService = class {
|
|
2143
|
+
constructor(client) {
|
|
2144
|
+
this.client = client;
|
|
2145
|
+
}
|
|
2146
|
+
async getStockLevels() {
|
|
2147
|
+
return safe8(this.client.query("inventory.stock_levels"));
|
|
2148
|
+
}
|
|
2149
|
+
async getProductStock(productId, locationId) {
|
|
2150
|
+
if (locationId) {
|
|
2151
|
+
return safe8(
|
|
2152
|
+
this.client.query("inventory.product", {
|
|
2153
|
+
product_id: productId,
|
|
2154
|
+
location_id: locationId
|
|
2155
|
+
})
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
2158
|
+
return safe8(
|
|
2159
|
+
this.client.query("inventory.product", {
|
|
2160
|
+
product_id: productId
|
|
2161
|
+
})
|
|
2162
|
+
);
|
|
2163
|
+
}
|
|
2164
|
+
async getVariantStock(variantId, locationId) {
|
|
2165
|
+
return safe8(
|
|
2166
|
+
this.client.query("inventory.variant", {
|
|
2167
|
+
variant_id: variantId,
|
|
2168
|
+
location_id: locationId
|
|
2169
|
+
})
|
|
2170
|
+
);
|
|
2171
|
+
}
|
|
2172
|
+
async checkProductAvailability(productId, quantity, locationId) {
|
|
2173
|
+
return safe8(
|
|
2174
|
+
this.client.query("inventory.check_availability", {
|
|
2175
|
+
product_id: productId,
|
|
2176
|
+
quantity,
|
|
2177
|
+
location_id: locationId
|
|
2178
|
+
})
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
async checkVariantAvailability(variantId, quantity, locationId) {
|
|
2182
|
+
return safe8(
|
|
2183
|
+
this.client.query("inventory.check_availability", {
|
|
2184
|
+
variant_id: variantId,
|
|
2185
|
+
quantity,
|
|
2186
|
+
location_id: locationId
|
|
2187
|
+
})
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
async checkMultipleAvailability(items, locationId) {
|
|
2191
|
+
const results = await Promise.all(
|
|
2192
|
+
items.map(
|
|
2193
|
+
(item) => item.variant_id ? this.checkVariantAvailability(item.variant_id, item.quantity, locationId) : this.checkProductAvailability(item.product_id, item.quantity, locationId)
|
|
2194
|
+
)
|
|
2195
|
+
);
|
|
2196
|
+
for (const result of results) {
|
|
2197
|
+
if (!result.ok) return result;
|
|
2198
|
+
}
|
|
2199
|
+
return ok(results.map((r) => r.value));
|
|
2200
|
+
}
|
|
2201
|
+
async getSummary() {
|
|
2202
|
+
return safe8(this.client.query("inventory.summary"));
|
|
2203
|
+
}
|
|
2204
|
+
async isInStock(productId, locationId) {
|
|
2205
|
+
const result = await this.checkProductAvailability(productId, 1, locationId);
|
|
2206
|
+
if (!result.ok) return result;
|
|
2207
|
+
return ok(result.value.is_available);
|
|
2208
|
+
}
|
|
2209
|
+
async getAvailableQuantity(productId, locationId) {
|
|
2210
|
+
const result = await this.getProductStock(productId, locationId);
|
|
2211
|
+
if (!result.ok) return result;
|
|
2212
|
+
return ok(result.value.available_quantity);
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
|
|
2216
|
+
// src/scheduling.ts
|
|
2217
|
+
function toVariables(input) {
|
|
2218
|
+
return Object.fromEntries(Object.entries(input));
|
|
2219
|
+
}
|
|
2220
|
+
function toCimplifyError9(error) {
|
|
2221
|
+
if (error instanceof CimplifyError) return error;
|
|
2222
|
+
if (error instanceof Error) {
|
|
2223
|
+
return new CimplifyError("UNKNOWN_ERROR", error.message, false);
|
|
2224
|
+
}
|
|
2225
|
+
return new CimplifyError("UNKNOWN_ERROR", String(error), false);
|
|
2226
|
+
}
|
|
2227
|
+
async function safe9(promise) {
|
|
2228
|
+
try {
|
|
2229
|
+
return ok(await promise);
|
|
2230
|
+
} catch (error) {
|
|
2231
|
+
return err(toCimplifyError9(error));
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
var SchedulingService = class {
|
|
2235
|
+
constructor(client) {
|
|
2236
|
+
this.client = client;
|
|
2237
|
+
}
|
|
2238
|
+
async getServices() {
|
|
2239
|
+
return safe9(this.client.query("scheduling.services"));
|
|
2240
|
+
}
|
|
2241
|
+
/**
|
|
2242
|
+
* Get a specific service by ID
|
|
2243
|
+
* Note: Filters from all services client-side (no single-service endpoint)
|
|
2244
|
+
*/
|
|
2245
|
+
async getService(serviceId) {
|
|
2246
|
+
const result = await this.getServices();
|
|
2247
|
+
if (!result.ok) return result;
|
|
2248
|
+
return ok(result.value.find((s) => s.id === serviceId) || null);
|
|
2249
|
+
}
|
|
2250
|
+
async getAvailableSlots(input) {
|
|
1123
2251
|
return safe9(
|
|
1124
2252
|
this.client.query("scheduling.slots", toVariables(input))
|
|
1125
2253
|
);
|
|
@@ -1289,6 +2417,8 @@ var MESSAGE_TYPES = {
|
|
|
1289
2417
|
GET_DATA: "get_data",
|
|
1290
2418
|
REFRESH_TOKEN: "refresh_token",
|
|
1291
2419
|
LOGOUT: "logout",
|
|
2420
|
+
PROCESS_CHECKOUT: "process_checkout",
|
|
2421
|
+
ABORT_CHECKOUT: "abort_checkout",
|
|
1292
2422
|
// Iframe → Parent
|
|
1293
2423
|
READY: "ready",
|
|
1294
2424
|
HEIGHT_CHANGE: "height_change",
|
|
@@ -1299,7 +2429,9 @@ var MESSAGE_TYPES = {
|
|
|
1299
2429
|
ADDRESS_SELECTED: "address_selected",
|
|
1300
2430
|
PAYMENT_METHOD_SELECTED: "payment_method_selected",
|
|
1301
2431
|
TOKEN_REFRESHED: "token_refreshed",
|
|
1302
|
-
LOGOUT_COMPLETE: "logout_complete"
|
|
2432
|
+
LOGOUT_COMPLETE: "logout_complete",
|
|
2433
|
+
CHECKOUT_STATUS: "checkout_status",
|
|
2434
|
+
CHECKOUT_COMPLETE: "checkout_complete"
|
|
1303
2435
|
};
|
|
1304
2436
|
var EVENT_TYPES = {
|
|
1305
2437
|
READY: "ready",
|
|
@@ -1312,38 +2444,14 @@ var EVENT_TYPES = {
|
|
|
1312
2444
|
};
|
|
1313
2445
|
|
|
1314
2446
|
// src/elements.ts
|
|
1315
|
-
function
|
|
1316
|
-
if (orderType === "dine_in") return "dine-in";
|
|
1317
|
-
return orderType ?? "delivery";
|
|
1318
|
-
}
|
|
1319
|
-
function toCheckoutFormData(data) {
|
|
2447
|
+
function toCheckoutError(code, message, recoverable) {
|
|
1320
2448
|
return {
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
},
|
|
1328
|
-
order_type: mapOrderType(data.order_type),
|
|
1329
|
-
address_info: data.address ? {
|
|
1330
|
-
street_address: data.address.street_address,
|
|
1331
|
-
apartment: data.address.apartment,
|
|
1332
|
-
city: data.address.city,
|
|
1333
|
-
region: data.address.region,
|
|
1334
|
-
postal_code: data.address.postal_code,
|
|
1335
|
-
country: data.address.country,
|
|
1336
|
-
delivery_instructions: data.address.delivery_instructions,
|
|
1337
|
-
phone_for_delivery: data.address.phone_for_delivery
|
|
1338
|
-
} : {},
|
|
1339
|
-
payment_method: data.payment_method?.type ?? "mobile_money",
|
|
1340
|
-
mobile_money_details: data.payment_method?.type === "mobile_money" && data.payment_method.phone_number ? {
|
|
1341
|
-
phone_number: data.payment_method.phone_number,
|
|
1342
|
-
provider: data.payment_method.provider ?? "mtn"
|
|
1343
|
-
} : void 0,
|
|
1344
|
-
special_instructions: data.notes,
|
|
1345
|
-
link_address_id: data.address?.id,
|
|
1346
|
-
link_payment_method_id: data.payment_method?.id
|
|
2449
|
+
success: false,
|
|
2450
|
+
error: {
|
|
2451
|
+
code,
|
|
2452
|
+
message,
|
|
2453
|
+
recoverable
|
|
2454
|
+
}
|
|
1347
2455
|
};
|
|
1348
2456
|
}
|
|
1349
2457
|
var DEFAULT_LINK_URL = "https://link.cimplify.io";
|
|
@@ -1362,14 +2470,27 @@ function isAllowedOrigin(origin) {
|
|
|
1362
2470
|
return false;
|
|
1363
2471
|
}
|
|
1364
2472
|
}
|
|
2473
|
+
function parseIframeMessage(data) {
|
|
2474
|
+
if (!data || typeof data !== "object") {
|
|
2475
|
+
return null;
|
|
2476
|
+
}
|
|
2477
|
+
const maybeType = data.type;
|
|
2478
|
+
if (typeof maybeType !== "string") {
|
|
2479
|
+
return null;
|
|
2480
|
+
}
|
|
2481
|
+
return data;
|
|
2482
|
+
}
|
|
1365
2483
|
var CimplifyElements = class {
|
|
1366
2484
|
constructor(client, businessId, options = {}) {
|
|
1367
2485
|
this.elements = /* @__PURE__ */ new Map();
|
|
1368
2486
|
this.accessToken = null;
|
|
1369
2487
|
this.accountId = null;
|
|
1370
2488
|
this.customerId = null;
|
|
2489
|
+
this.customerData = null;
|
|
1371
2490
|
this.addressData = null;
|
|
1372
2491
|
this.paymentData = null;
|
|
2492
|
+
this.checkoutInProgress = false;
|
|
2493
|
+
this.activeCheckoutAbort = null;
|
|
1373
2494
|
this.client = client;
|
|
1374
2495
|
this.businessId = businessId;
|
|
1375
2496
|
this.linkUrl = options.linkUrl || DEFAULT_LINK_URL;
|
|
@@ -1397,54 +2518,242 @@ var CimplifyElements = class {
|
|
|
1397
2518
|
}
|
|
1398
2519
|
}
|
|
1399
2520
|
async submitCheckout(data) {
|
|
1400
|
-
const
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
const checkoutFormData = toCheckoutFormData(internalData);
|
|
1411
|
-
const result = await this.client.checkout.process(checkoutFormData);
|
|
1412
|
-
if (result.ok) {
|
|
2521
|
+
const result = await this.processCheckout({
|
|
2522
|
+
cart_id: data.cart_id,
|
|
2523
|
+
order_type: data.order_type ?? "delivery",
|
|
2524
|
+
location_id: data.location_id,
|
|
2525
|
+
notes: data.notes,
|
|
2526
|
+
scheduled_time: data.scheduled_time,
|
|
2527
|
+
tip_amount: data.tip_amount,
|
|
2528
|
+
enroll_in_link: true
|
|
2529
|
+
});
|
|
2530
|
+
if (result.success) {
|
|
1413
2531
|
return {
|
|
1414
2532
|
success: true,
|
|
1415
2533
|
order: {
|
|
1416
|
-
id: result.
|
|
1417
|
-
status: result.
|
|
1418
|
-
total: ""
|
|
1419
|
-
// Total is not returned by checkout result
|
|
2534
|
+
id: result.order?.id || "",
|
|
2535
|
+
status: result.order?.status || "unknown",
|
|
2536
|
+
total: result.order?.total || ""
|
|
1420
2537
|
}
|
|
1421
2538
|
};
|
|
1422
2539
|
}
|
|
1423
2540
|
return {
|
|
1424
2541
|
success: false,
|
|
1425
2542
|
error: {
|
|
1426
|
-
code: result.error
|
|
1427
|
-
message: result.error
|
|
2543
|
+
code: result.error?.code || "CHECKOUT_FAILED",
|
|
2544
|
+
message: result.error?.message || "Checkout failed"
|
|
1428
2545
|
}
|
|
1429
2546
|
};
|
|
1430
2547
|
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
2548
|
+
processCheckout(options) {
|
|
2549
|
+
let abortFn = null;
|
|
2550
|
+
const task = (async () => {
|
|
2551
|
+
if (this.checkoutInProgress) {
|
|
2552
|
+
return toCheckoutError(
|
|
2553
|
+
"ALREADY_PROCESSING",
|
|
2554
|
+
"Checkout is already in progress.",
|
|
2555
|
+
false
|
|
2556
|
+
);
|
|
2557
|
+
}
|
|
2558
|
+
if (!options.cart_id) {
|
|
2559
|
+
return toCheckoutError(
|
|
2560
|
+
"INVALID_CART",
|
|
2561
|
+
"A valid cart is required before checkout can start.",
|
|
2562
|
+
false
|
|
2563
|
+
);
|
|
2564
|
+
}
|
|
2565
|
+
if (!options.order_type) {
|
|
2566
|
+
return toCheckoutError(
|
|
2567
|
+
"ORDER_TYPE_REQUIRED",
|
|
2568
|
+
"Order type is required before checkout can start.",
|
|
2569
|
+
false
|
|
2570
|
+
);
|
|
2571
|
+
}
|
|
2572
|
+
const paymentElement = this.elements.get(ELEMENT_TYPES.PAYMENT);
|
|
2573
|
+
if (!paymentElement) {
|
|
2574
|
+
return toCheckoutError(
|
|
2575
|
+
"NO_PAYMENT_ELEMENT",
|
|
2576
|
+
"Payment element must be mounted before checkout.",
|
|
2577
|
+
false
|
|
2578
|
+
);
|
|
2579
|
+
}
|
|
2580
|
+
if (!paymentElement.isMounted()) {
|
|
2581
|
+
return toCheckoutError(
|
|
2582
|
+
"PAYMENT_NOT_MOUNTED",
|
|
2583
|
+
"Payment element must be mounted before checkout.",
|
|
2584
|
+
false
|
|
2585
|
+
);
|
|
2586
|
+
}
|
|
2587
|
+
const authElement = this.elements.get(ELEMENT_TYPES.AUTH);
|
|
2588
|
+
if (authElement && !this.accessToken) {
|
|
2589
|
+
return toCheckoutError(
|
|
2590
|
+
"AUTH_INCOMPLETE",
|
|
2591
|
+
"Authentication must complete before checkout can start.",
|
|
2592
|
+
true
|
|
2593
|
+
);
|
|
2594
|
+
}
|
|
2595
|
+
const addressElement = this.elements.get(ELEMENT_TYPES.ADDRESS);
|
|
2596
|
+
if (addressElement) {
|
|
2597
|
+
await addressElement.getData();
|
|
2598
|
+
}
|
|
2599
|
+
await this.hydrateCustomerData();
|
|
2600
|
+
const processMessage = {
|
|
2601
|
+
type: MESSAGE_TYPES.PROCESS_CHECKOUT,
|
|
2602
|
+
cart_id: options.cart_id,
|
|
2603
|
+
order_type: options.order_type,
|
|
2604
|
+
location_id: options.location_id,
|
|
2605
|
+
notes: options.notes,
|
|
2606
|
+
scheduled_time: options.scheduled_time,
|
|
2607
|
+
tip_amount: options.tip_amount,
|
|
2608
|
+
pay_currency: options.pay_currency,
|
|
2609
|
+
enroll_in_link: options.enroll_in_link ?? true,
|
|
2610
|
+
address: this.addressData ?? void 0,
|
|
2611
|
+
customer: this.customerData ? {
|
|
2612
|
+
name: this.customerData.name,
|
|
2613
|
+
email: this.customerData.email,
|
|
2614
|
+
phone: this.customerData.phone
|
|
2615
|
+
} : null,
|
|
2616
|
+
account_id: this.accountId ?? void 0,
|
|
2617
|
+
customer_id: this.customerId ?? void 0
|
|
2618
|
+
};
|
|
2619
|
+
const timeoutMs = options.timeout_ms ?? 18e4;
|
|
2620
|
+
const paymentWindow = paymentElement.getContentWindow();
|
|
2621
|
+
this.checkoutInProgress = true;
|
|
2622
|
+
return new Promise((resolve) => {
|
|
2623
|
+
let settled = false;
|
|
2624
|
+
const cleanup = () => {
|
|
2625
|
+
if (typeof window !== "undefined") {
|
|
2626
|
+
window.removeEventListener("message", handleCheckoutMessage);
|
|
2627
|
+
}
|
|
2628
|
+
clearTimeout(timeout);
|
|
2629
|
+
this.checkoutInProgress = false;
|
|
2630
|
+
this.activeCheckoutAbort = null;
|
|
2631
|
+
};
|
|
2632
|
+
const settle = (result) => {
|
|
2633
|
+
if (settled) {
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
settled = true;
|
|
2637
|
+
cleanup();
|
|
2638
|
+
resolve(result);
|
|
2639
|
+
};
|
|
2640
|
+
const timeout = setTimeout(() => {
|
|
2641
|
+
settle(
|
|
2642
|
+
toCheckoutError(
|
|
2643
|
+
"TIMEOUT",
|
|
2644
|
+
"Checkout timed out before receiving a terminal response.",
|
|
2645
|
+
true
|
|
2646
|
+
)
|
|
2647
|
+
);
|
|
2648
|
+
}, timeoutMs);
|
|
2649
|
+
const handleCheckoutMessage = (event) => {
|
|
2650
|
+
if (!isAllowedOrigin(event.origin)) {
|
|
2651
|
+
return;
|
|
2652
|
+
}
|
|
2653
|
+
const message = parseIframeMessage(event.data);
|
|
2654
|
+
if (!message) {
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
if (message.type === MESSAGE_TYPES.LOGOUT_COMPLETE) {
|
|
2658
|
+
paymentElement.sendMessage({ type: MESSAGE_TYPES.ABORT_CHECKOUT });
|
|
2659
|
+
settle(
|
|
2660
|
+
toCheckoutError(
|
|
2661
|
+
"AUTH_LOST",
|
|
2662
|
+
"Authentication was cleared during checkout.",
|
|
2663
|
+
true
|
|
2664
|
+
)
|
|
2665
|
+
);
|
|
2666
|
+
return;
|
|
2667
|
+
}
|
|
2668
|
+
if (paymentWindow && event.source && event.source !== paymentWindow) {
|
|
2669
|
+
return;
|
|
2670
|
+
}
|
|
2671
|
+
if (message.type === MESSAGE_TYPES.CHECKOUT_STATUS) {
|
|
2672
|
+
options.on_status_change?.(message.status, message.context);
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
if (message.type === MESSAGE_TYPES.CHECKOUT_COMPLETE) {
|
|
2676
|
+
settle({
|
|
2677
|
+
success: message.success,
|
|
2678
|
+
order: message.order,
|
|
2679
|
+
error: message.error,
|
|
2680
|
+
enrolled_in_link: message.enrolled_in_link
|
|
2681
|
+
});
|
|
2682
|
+
}
|
|
2683
|
+
};
|
|
2684
|
+
if (typeof window !== "undefined") {
|
|
2685
|
+
window.addEventListener("message", handleCheckoutMessage);
|
|
2686
|
+
}
|
|
2687
|
+
abortFn = () => {
|
|
2688
|
+
paymentElement.sendMessage({ type: MESSAGE_TYPES.ABORT_CHECKOUT });
|
|
2689
|
+
settle(
|
|
2690
|
+
toCheckoutError(
|
|
2691
|
+
"CANCELLED",
|
|
2692
|
+
"Checkout was cancelled.",
|
|
2693
|
+
true
|
|
2694
|
+
)
|
|
2695
|
+
);
|
|
2696
|
+
};
|
|
2697
|
+
this.activeCheckoutAbort = abortFn;
|
|
2698
|
+
paymentElement.sendMessage(processMessage);
|
|
2699
|
+
});
|
|
2700
|
+
})();
|
|
2701
|
+
const abortable = task;
|
|
2702
|
+
abortable.abort = () => {
|
|
2703
|
+
if (abortFn) {
|
|
2704
|
+
abortFn();
|
|
2705
|
+
}
|
|
2706
|
+
};
|
|
2707
|
+
return abortable;
|
|
2708
|
+
}
|
|
2709
|
+
isAuthenticated() {
|
|
2710
|
+
return this.accessToken !== null;
|
|
2711
|
+
}
|
|
2712
|
+
getAccessToken() {
|
|
2713
|
+
return this.accessToken;
|
|
2714
|
+
}
|
|
2715
|
+
getPublicKey() {
|
|
2716
|
+
return this.client.getPublicKey();
|
|
2717
|
+
}
|
|
2718
|
+
getAppearance() {
|
|
2719
|
+
return this.options.appearance;
|
|
2720
|
+
}
|
|
2721
|
+
async hydrateCustomerData() {
|
|
2722
|
+
if (!this.accessToken || this.customerData) {
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2725
|
+
if (!this.client.getAccessToken()) {
|
|
2726
|
+
this.client.setAccessToken(this.accessToken);
|
|
2727
|
+
}
|
|
2728
|
+
const linkDataResult = await this.client.link.getLinkData();
|
|
2729
|
+
if (!linkDataResult.ok || !linkDataResult.value?.customer) {
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
const customer = linkDataResult.value.customer;
|
|
2733
|
+
this.customerData = {
|
|
2734
|
+
name: customer.name || "",
|
|
2735
|
+
email: customer.email || null,
|
|
2736
|
+
phone: customer.phone || null
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2739
|
+
handleMessage(event) {
|
|
2740
|
+
if (!isAllowedOrigin(event.origin)) {
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
const message = parseIframeMessage(event.data);
|
|
2744
|
+
if (!message) return;
|
|
2745
|
+
switch (message.type) {
|
|
2746
|
+
case MESSAGE_TYPES.AUTHENTICATED:
|
|
2747
|
+
const customer = message.customer ?? {
|
|
2748
|
+
name: "",
|
|
2749
|
+
email: null,
|
|
2750
|
+
phone: null
|
|
2751
|
+
};
|
|
2752
|
+
this.accessToken = message.token;
|
|
2753
|
+
this.accountId = message.accountId;
|
|
2754
|
+
this.customerId = message.customerId;
|
|
2755
|
+
this.customerData = customer;
|
|
2756
|
+
this.client.setAccessToken(message.token);
|
|
1448
2757
|
this.elements.forEach((element, type) => {
|
|
1449
2758
|
if (type !== ELEMENT_TYPES.AUTH) {
|
|
1450
2759
|
element.sendMessage({ type: MESSAGE_TYPES.SET_TOKEN, token: message.token });
|
|
@@ -1462,11 +2771,16 @@ var CimplifyElements = class {
|
|
|
1462
2771
|
this.paymentData = message.method;
|
|
1463
2772
|
break;
|
|
1464
2773
|
case MESSAGE_TYPES.LOGOUT_COMPLETE:
|
|
2774
|
+
if (this.checkoutInProgress && this.activeCheckoutAbort) {
|
|
2775
|
+
this.activeCheckoutAbort();
|
|
2776
|
+
}
|
|
1465
2777
|
this.accessToken = null;
|
|
1466
2778
|
this.accountId = null;
|
|
1467
2779
|
this.customerId = null;
|
|
2780
|
+
this.customerData = null;
|
|
1468
2781
|
this.addressData = null;
|
|
1469
2782
|
this.paymentData = null;
|
|
2783
|
+
this.client.clearSession();
|
|
1470
2784
|
break;
|
|
1471
2785
|
}
|
|
1472
2786
|
}
|
|
@@ -1516,6 +2830,8 @@ var CimplifyElement = class {
|
|
|
1516
2830
|
this.container = null;
|
|
1517
2831
|
this.mounted = false;
|
|
1518
2832
|
this.eventHandlers.clear();
|
|
2833
|
+
this.resolvers.forEach((entry) => clearTimeout(entry.timeoutId));
|
|
2834
|
+
this.resolvers.clear();
|
|
1519
2835
|
if (typeof window !== "undefined") {
|
|
1520
2836
|
window.removeEventListener("message", this.boundHandleMessage);
|
|
1521
2837
|
}
|
|
@@ -1530,16 +2846,21 @@ var CimplifyElement = class {
|
|
|
1530
2846
|
this.eventHandlers.get(event)?.delete(handler);
|
|
1531
2847
|
}
|
|
1532
2848
|
async getData() {
|
|
2849
|
+
if (!this.isMounted()) {
|
|
2850
|
+
return null;
|
|
2851
|
+
}
|
|
1533
2852
|
return new Promise((resolve) => {
|
|
1534
2853
|
const id = Math.random().toString(36).slice(2);
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
this.resolvers.delete(id);
|
|
1540
|
-
resolve(null);
|
|
2854
|
+
const timeoutId = setTimeout(() => {
|
|
2855
|
+
const entry = this.resolvers.get(id);
|
|
2856
|
+
if (!entry) {
|
|
2857
|
+
return;
|
|
1541
2858
|
}
|
|
2859
|
+
this.resolvers.delete(id);
|
|
2860
|
+
entry.resolve(null);
|
|
1542
2861
|
}, 5e3);
|
|
2862
|
+
this.resolvers.set(id, { resolve, timeoutId });
|
|
2863
|
+
this.sendMessage({ type: MESSAGE_TYPES.GET_DATA });
|
|
1543
2864
|
});
|
|
1544
2865
|
}
|
|
1545
2866
|
sendMessage(message) {
|
|
@@ -1547,6 +2868,12 @@ var CimplifyElement = class {
|
|
|
1547
2868
|
this.iframe.contentWindow.postMessage(message, this.linkUrl);
|
|
1548
2869
|
}
|
|
1549
2870
|
}
|
|
2871
|
+
getContentWindow() {
|
|
2872
|
+
return this.iframe?.contentWindow ?? null;
|
|
2873
|
+
}
|
|
2874
|
+
isMounted() {
|
|
2875
|
+
return this.mounted && Boolean(this.iframe) && this.iframe?.isConnected === true;
|
|
2876
|
+
}
|
|
1550
2877
|
createIframe() {
|
|
1551
2878
|
if (!this.container) return;
|
|
1552
2879
|
const iframe = document.createElement("iframe");
|
|
@@ -1561,14 +2888,21 @@ var CimplifyElement = class {
|
|
|
1561
2888
|
iframe.style.overflow = "hidden";
|
|
1562
2889
|
iframe.setAttribute("allowtransparency", "true");
|
|
1563
2890
|
iframe.setAttribute("frameborder", "0");
|
|
1564
|
-
iframe.setAttribute(
|
|
2891
|
+
iframe.setAttribute(
|
|
2892
|
+
"sandbox",
|
|
2893
|
+
"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
|
|
2894
|
+
);
|
|
1565
2895
|
this.iframe = iframe;
|
|
1566
2896
|
this.container.appendChild(iframe);
|
|
1567
2897
|
iframe.onload = () => {
|
|
2898
|
+
const publicKey = this.parent.getPublicKey();
|
|
1568
2899
|
this.sendMessage({
|
|
1569
2900
|
type: MESSAGE_TYPES.INIT,
|
|
1570
2901
|
businessId: this.businessId,
|
|
1571
|
-
|
|
2902
|
+
publicKey,
|
|
2903
|
+
demoMode: publicKey.length === 0,
|
|
2904
|
+
prefillEmail: this.options.prefillEmail,
|
|
2905
|
+
appearance: this.parent.getAppearance()
|
|
1572
2906
|
});
|
|
1573
2907
|
const token = this.parent.getAccessToken();
|
|
1574
2908
|
if (token && this.type !== ELEMENT_TYPES.AUTH) {
|
|
@@ -1580,8 +2914,8 @@ var CimplifyElement = class {
|
|
|
1580
2914
|
if (!isAllowedOrigin(event.origin)) {
|
|
1581
2915
|
return;
|
|
1582
2916
|
}
|
|
1583
|
-
const message = event.data;
|
|
1584
|
-
if (!message
|
|
2917
|
+
const message = parseIframeMessage(event.data);
|
|
2918
|
+
if (!message) return;
|
|
1585
2919
|
switch (message.type) {
|
|
1586
2920
|
case MESSAGE_TYPES.READY:
|
|
1587
2921
|
this.emit(EVENT_TYPES.READY, { height: message.height });
|
|
@@ -1590,10 +2924,16 @@ var CimplifyElement = class {
|
|
|
1590
2924
|
if (this.iframe) this.iframe.style.height = `${message.height}px`;
|
|
1591
2925
|
break;
|
|
1592
2926
|
case MESSAGE_TYPES.AUTHENTICATED:
|
|
2927
|
+
const customer = message.customer ?? {
|
|
2928
|
+
name: "",
|
|
2929
|
+
email: null,
|
|
2930
|
+
phone: null
|
|
2931
|
+
};
|
|
1593
2932
|
this.emit(EVENT_TYPES.AUTHENTICATED, {
|
|
1594
2933
|
accountId: message.accountId,
|
|
1595
2934
|
customerId: message.customerId,
|
|
1596
|
-
token: message.token
|
|
2935
|
+
token: message.token,
|
|
2936
|
+
customer
|
|
1597
2937
|
});
|
|
1598
2938
|
break;
|
|
1599
2939
|
case MESSAGE_TYPES.REQUIRES_OTP:
|
|
@@ -1619,7 +2959,10 @@ var CimplifyElement = class {
|
|
|
1619
2959
|
this.eventHandlers.get(event)?.forEach((handler) => handler(data));
|
|
1620
2960
|
}
|
|
1621
2961
|
resolveData(data) {
|
|
1622
|
-
this.resolvers.forEach((
|
|
2962
|
+
this.resolvers.forEach((entry) => {
|
|
2963
|
+
clearTimeout(entry.timeoutId);
|
|
2964
|
+
entry.resolve(data);
|
|
2965
|
+
});
|
|
1623
2966
|
this.resolvers.clear();
|
|
1624
2967
|
}
|
|
1625
2968
|
};
|
|
@@ -1728,6 +3071,9 @@ var CimplifyClient = class {
|
|
|
1728
3071
|
getAccessToken() {
|
|
1729
3072
|
return this.accessToken;
|
|
1730
3073
|
}
|
|
3074
|
+
getPublicKey() {
|
|
3075
|
+
return this.publicKey;
|
|
3076
|
+
}
|
|
1731
3077
|
setAccessToken(token) {
|
|
1732
3078
|
const previous = this.accessToken;
|
|
1733
3079
|
this.accessToken = token;
|
|
@@ -2034,463 +3380,119 @@ var CimplifyClient = class {
|
|
|
2034
3380
|
}
|
|
2035
3381
|
get business() {
|
|
2036
3382
|
if (!this._business) {
|
|
2037
|
-
this._business = new BusinessService(this);
|
|
2038
|
-
}
|
|
2039
|
-
return this._business;
|
|
2040
|
-
}
|
|
2041
|
-
get inventory() {
|
|
2042
|
-
if (!this._inventory) {
|
|
2043
|
-
this._inventory = new InventoryService(this);
|
|
2044
|
-
}
|
|
2045
|
-
return this._inventory;
|
|
2046
|
-
}
|
|
2047
|
-
get scheduling() {
|
|
2048
|
-
if (!this._scheduling) {
|
|
2049
|
-
this._scheduling = new SchedulingService(this);
|
|
2050
|
-
}
|
|
2051
|
-
return this._scheduling;
|
|
2052
|
-
}
|
|
2053
|
-
get lite() {
|
|
2054
|
-
if (!this._lite) {
|
|
2055
|
-
this._lite = new LiteService(this);
|
|
2056
|
-
}
|
|
2057
|
-
return this._lite;
|
|
2058
|
-
}
|
|
2059
|
-
get fx() {
|
|
2060
|
-
if (!this._fx) {
|
|
2061
|
-
this._fx = new FxService(this);
|
|
2062
|
-
}
|
|
2063
|
-
return this._fx;
|
|
2064
|
-
}
|
|
2065
|
-
/**
|
|
2066
|
-
* Create a CimplifyElements instance for embedding checkout components.
|
|
2067
|
-
* Like Stripe's stripe.elements().
|
|
2068
|
-
*
|
|
2069
|
-
* @param businessId - The business ID for checkout context
|
|
2070
|
-
* @param options - Optional configuration for elements
|
|
2071
|
-
*
|
|
2072
|
-
* @example
|
|
2073
|
-
* ```ts
|
|
2074
|
-
* const elements = client.elements('bus_xxx');
|
|
2075
|
-
* const authElement = elements.create('auth');
|
|
2076
|
-
* authElement.mount('#auth-container');
|
|
2077
|
-
* ```
|
|
2078
|
-
*/
|
|
2079
|
-
elements(businessId, options) {
|
|
2080
|
-
return createElements(this, businessId, options);
|
|
2081
|
-
}
|
|
2082
|
-
};
|
|
2083
|
-
function createCimplifyClient(config = {}) {
|
|
2084
|
-
return new CimplifyClient(config);
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
|
-
// src/query/builder.ts
|
|
2088
|
-
var QueryBuilder = class {
|
|
2089
|
-
constructor(entity) {
|
|
2090
|
-
this.filters = [];
|
|
2091
|
-
this.modifiers = [];
|
|
2092
|
-
this.pathSegments = [];
|
|
2093
|
-
this.entity = entity;
|
|
2094
|
-
}
|
|
2095
|
-
path(segment) {
|
|
2096
|
-
this.pathSegments.push(segment);
|
|
2097
|
-
return this;
|
|
2098
|
-
}
|
|
2099
|
-
where(field, op, value) {
|
|
2100
|
-
const v = typeof value === "string" ? `'${value}'` : value;
|
|
2101
|
-
if (op === "contains" || op === "startsWith") {
|
|
2102
|
-
this.filters.push(`@.${field} ${op} ${v}`);
|
|
2103
|
-
} else {
|
|
2104
|
-
this.filters.push(`@.${field}${op}${v}`);
|
|
2105
|
-
}
|
|
2106
|
-
return this;
|
|
2107
|
-
}
|
|
2108
|
-
and(field, op, value) {
|
|
2109
|
-
return this.where(field, op, value);
|
|
2110
|
-
}
|
|
2111
|
-
sort(field, order = "asc") {
|
|
2112
|
-
this.modifiers.push(`sort(${field},${order})`);
|
|
2113
|
-
return this;
|
|
2114
|
-
}
|
|
2115
|
-
limit(n) {
|
|
2116
|
-
this.modifiers.push(`limit(${n})`);
|
|
2117
|
-
return this;
|
|
2118
|
-
}
|
|
2119
|
-
offset(n) {
|
|
2120
|
-
this.modifiers.push(`offset(${n})`);
|
|
2121
|
-
return this;
|
|
2122
|
-
}
|
|
2123
|
-
count() {
|
|
2124
|
-
this.modifiers.push("count");
|
|
2125
|
-
return this;
|
|
2126
|
-
}
|
|
2127
|
-
enriched() {
|
|
2128
|
-
this.modifiers.push("enriched");
|
|
2129
|
-
return this;
|
|
2130
|
-
}
|
|
2131
|
-
build() {
|
|
2132
|
-
let query2 = this.entity;
|
|
2133
|
-
if (this.pathSegments.length > 0) {
|
|
2134
|
-
query2 += "." + this.pathSegments.join(".");
|
|
2135
|
-
}
|
|
2136
|
-
if (this.filters.length > 0) {
|
|
2137
|
-
query2 += `[?(${this.filters.join(" && ")})]`;
|
|
2138
|
-
}
|
|
2139
|
-
for (const mod of this.modifiers) {
|
|
2140
|
-
query2 += `#${mod}`;
|
|
2141
|
-
}
|
|
2142
|
-
return query2;
|
|
2143
|
-
}
|
|
2144
|
-
toString() {
|
|
2145
|
-
return this.build();
|
|
2146
|
-
}
|
|
2147
|
-
};
|
|
2148
|
-
function query(entity) {
|
|
2149
|
-
return new QueryBuilder(entity);
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
// src/utils/price.ts
|
|
2153
|
-
var CURRENCY_SYMBOLS = {
|
|
2154
|
-
// Major world currencies
|
|
2155
|
-
USD: "$",
|
|
2156
|
-
EUR: "\u20AC",
|
|
2157
|
-
GBP: "\xA3",
|
|
2158
|
-
JPY: "\xA5",
|
|
2159
|
-
CNY: "\xA5",
|
|
2160
|
-
CHF: "CHF",
|
|
2161
|
-
CAD: "C$",
|
|
2162
|
-
AUD: "A$",
|
|
2163
|
-
NZD: "NZ$",
|
|
2164
|
-
HKD: "HK$",
|
|
2165
|
-
SGD: "S$",
|
|
2166
|
-
INR: "\u20B9",
|
|
2167
|
-
BRL: "R$",
|
|
2168
|
-
MXN: "MX$",
|
|
2169
|
-
KRW: "\u20A9",
|
|
2170
|
-
RUB: "\u20BD",
|
|
2171
|
-
TRY: "\u20BA",
|
|
2172
|
-
THB: "\u0E3F",
|
|
2173
|
-
PLN: "z\u0142",
|
|
2174
|
-
SEK: "kr",
|
|
2175
|
-
NOK: "kr",
|
|
2176
|
-
DKK: "kr",
|
|
2177
|
-
CZK: "K\u010D",
|
|
2178
|
-
HUF: "Ft",
|
|
2179
|
-
ILS: "\u20AA",
|
|
2180
|
-
AED: "\u062F.\u0625",
|
|
2181
|
-
SAR: "\uFDFC",
|
|
2182
|
-
MYR: "RM",
|
|
2183
|
-
PHP: "\u20B1",
|
|
2184
|
-
IDR: "Rp",
|
|
2185
|
-
VND: "\u20AB",
|
|
2186
|
-
TWD: "NT$",
|
|
2187
|
-
// African currencies
|
|
2188
|
-
GHS: "GH\u20B5",
|
|
2189
|
-
NGN: "\u20A6",
|
|
2190
|
-
KES: "KSh",
|
|
2191
|
-
ZAR: "R",
|
|
2192
|
-
XOF: "CFA",
|
|
2193
|
-
XAF: "FCFA",
|
|
2194
|
-
EGP: "E\xA3",
|
|
2195
|
-
MAD: "MAD",
|
|
2196
|
-
TZS: "TSh",
|
|
2197
|
-
UGX: "USh",
|
|
2198
|
-
RWF: "FRw",
|
|
2199
|
-
ETB: "Br",
|
|
2200
|
-
ZMW: "ZK",
|
|
2201
|
-
BWP: "P",
|
|
2202
|
-
MUR: "\u20A8",
|
|
2203
|
-
SCR: "\u20A8",
|
|
2204
|
-
NAD: "N$",
|
|
2205
|
-
SZL: "E",
|
|
2206
|
-
LSL: "L",
|
|
2207
|
-
MWK: "MK",
|
|
2208
|
-
AOA: "Kz",
|
|
2209
|
-
CDF: "FC",
|
|
2210
|
-
GMD: "D",
|
|
2211
|
-
GNF: "FG",
|
|
2212
|
-
LRD: "L$",
|
|
2213
|
-
SLL: "Le",
|
|
2214
|
-
MZN: "MT",
|
|
2215
|
-
SDG: "SDG",
|
|
2216
|
-
SSP: "SSP",
|
|
2217
|
-
SOS: "Sh.So.",
|
|
2218
|
-
DJF: "Fdj",
|
|
2219
|
-
ERN: "Nfk",
|
|
2220
|
-
CVE: "$",
|
|
2221
|
-
STN: "Db",
|
|
2222
|
-
KMF: "CF",
|
|
2223
|
-
BIF: "FBu"
|
|
2224
|
-
};
|
|
2225
|
-
function getCurrencySymbol(currencyCode) {
|
|
2226
|
-
return CURRENCY_SYMBOLS[currencyCode.toUpperCase()] || currencyCode;
|
|
2227
|
-
}
|
|
2228
|
-
function formatNumberCompact(value, decimals = 1) {
|
|
2229
|
-
const absValue = Math.abs(value);
|
|
2230
|
-
const sign = value < 0 ? "-" : "";
|
|
2231
|
-
if (absValue >= 1e9) {
|
|
2232
|
-
return `${sign}${(absValue / 1e9).toFixed(decimals)}B`;
|
|
2233
|
-
}
|
|
2234
|
-
if (absValue >= 1e6) {
|
|
2235
|
-
return `${sign}${(absValue / 1e6).toFixed(decimals)}M`;
|
|
2236
|
-
}
|
|
2237
|
-
if (absValue >= 1e3) {
|
|
2238
|
-
return `${sign}${(absValue / 1e3).toFixed(decimals)}K`;
|
|
2239
|
-
}
|
|
2240
|
-
return `${sign}${absValue.toFixed(decimals)}`;
|
|
2241
|
-
}
|
|
2242
|
-
function formatPrice(amount, currency = "GHS", locale = "en-US") {
|
|
2243
|
-
const numAmount = typeof amount === "string" ? parseFloat(amount) : amount;
|
|
2244
|
-
if (isNaN(numAmount)) {
|
|
2245
|
-
return `${getCurrencySymbol(currency)}0.00`;
|
|
2246
|
-
}
|
|
2247
|
-
try {
|
|
2248
|
-
return new Intl.NumberFormat(locale, {
|
|
2249
|
-
style: "currency",
|
|
2250
|
-
currency: currency.toUpperCase(),
|
|
2251
|
-
minimumFractionDigits: 2,
|
|
2252
|
-
maximumFractionDigits: 2
|
|
2253
|
-
}).format(numAmount);
|
|
2254
|
-
} catch {
|
|
2255
|
-
return `${getCurrencySymbol(currency)}${numAmount.toFixed(2)}`;
|
|
2256
|
-
}
|
|
2257
|
-
}
|
|
2258
|
-
function formatPriceAdjustment(amount, currency = "GHS", locale = "en-US") {
|
|
2259
|
-
const formatted = formatPrice(Math.abs(amount), currency, locale);
|
|
2260
|
-
if (amount > 0) {
|
|
2261
|
-
return `+${formatted}`;
|
|
2262
|
-
} else if (amount < 0) {
|
|
2263
|
-
return `-${formatted}`;
|
|
2264
|
-
}
|
|
2265
|
-
return formatted;
|
|
2266
|
-
}
|
|
2267
|
-
function formatPriceCompact(amount, currency = "GHS", decimals = 1) {
|
|
2268
|
-
const numAmount = typeof amount === "string" ? parseFloat(amount) : amount;
|
|
2269
|
-
if (isNaN(numAmount)) {
|
|
2270
|
-
return `${getCurrencySymbol(currency)}0`;
|
|
2271
|
-
}
|
|
2272
|
-
const symbol = getCurrencySymbol(currency);
|
|
2273
|
-
if (Math.abs(numAmount) < 1e3) {
|
|
2274
|
-
return `${symbol}${numAmount.toFixed(2)}`;
|
|
2275
|
-
}
|
|
2276
|
-
return `${symbol}${formatNumberCompact(numAmount, decimals)}`;
|
|
2277
|
-
}
|
|
2278
|
-
function formatMoney(amount, currency = "GHS") {
|
|
2279
|
-
const symbol = getCurrencySymbol(currency);
|
|
2280
|
-
const numAmount = typeof amount === "string" ? parseFloat(amount) : amount;
|
|
2281
|
-
if (isNaN(numAmount)) {
|
|
2282
|
-
return `${symbol}0.00`;
|
|
2283
|
-
}
|
|
2284
|
-
return `${symbol}${numAmount.toFixed(2)}`;
|
|
2285
|
-
}
|
|
2286
|
-
function parsePrice(value) {
|
|
2287
|
-
if (value === void 0 || value === null) {
|
|
2288
|
-
return 0;
|
|
2289
|
-
}
|
|
2290
|
-
if (typeof value === "number") {
|
|
2291
|
-
return isNaN(value) ? 0 : value;
|
|
2292
|
-
}
|
|
2293
|
-
const cleaned = value.replace(/[^\d.-]/g, "");
|
|
2294
|
-
const parsed = parseFloat(cleaned);
|
|
2295
|
-
return isNaN(parsed) ? 0 : parsed;
|
|
2296
|
-
}
|
|
2297
|
-
function getDisplayPrice(product) {
|
|
2298
|
-
if (product.price_info) {
|
|
2299
|
-
return parsePrice(product.price_info.final_price);
|
|
2300
|
-
}
|
|
2301
|
-
if (product.final_price !== void 0 && product.final_price !== null) {
|
|
2302
|
-
return parsePrice(product.final_price);
|
|
3383
|
+
this._business = new BusinessService(this);
|
|
3384
|
+
}
|
|
3385
|
+
return this._business;
|
|
2303
3386
|
}
|
|
2304
|
-
|
|
2305
|
-
|
|
3387
|
+
get inventory() {
|
|
3388
|
+
if (!this._inventory) {
|
|
3389
|
+
this._inventory = new InventoryService(this);
|
|
3390
|
+
}
|
|
3391
|
+
return this._inventory;
|
|
2306
3392
|
}
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
return
|
|
3393
|
+
get scheduling() {
|
|
3394
|
+
if (!this._scheduling) {
|
|
3395
|
+
this._scheduling = new SchedulingService(this);
|
|
3396
|
+
}
|
|
3397
|
+
return this._scheduling;
|
|
2312
3398
|
}
|
|
2313
|
-
|
|
2314
|
-
|
|
3399
|
+
get lite() {
|
|
3400
|
+
if (!this._lite) {
|
|
3401
|
+
this._lite = new LiteService(this);
|
|
3402
|
+
}
|
|
3403
|
+
return this._lite;
|
|
2315
3404
|
}
|
|
2316
|
-
|
|
2317
|
-
|
|
3405
|
+
get fx() {
|
|
3406
|
+
if (!this._fx) {
|
|
3407
|
+
this._fx = new FxService(this);
|
|
3408
|
+
}
|
|
3409
|
+
return this._fx;
|
|
2318
3410
|
}
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
3411
|
+
/**
|
|
3412
|
+
* Create a CimplifyElements instance for embedding checkout components.
|
|
3413
|
+
* Like Stripe's stripe.elements().
|
|
3414
|
+
*
|
|
3415
|
+
* @param businessId - The business ID for checkout context
|
|
3416
|
+
* @param options - Optional configuration for elements
|
|
3417
|
+
*
|
|
3418
|
+
* @example
|
|
3419
|
+
* ```ts
|
|
3420
|
+
* const elements = client.elements('bus_xxx');
|
|
3421
|
+
* const authElement = elements.create('auth');
|
|
3422
|
+
* authElement.mount('#auth-container');
|
|
3423
|
+
* ```
|
|
3424
|
+
*/
|
|
3425
|
+
elements(businessId, options) {
|
|
3426
|
+
return createElements(this, businessId, options);
|
|
2331
3427
|
}
|
|
2332
|
-
|
|
3428
|
+
};
|
|
3429
|
+
function createCimplifyClient(config = {}) {
|
|
3430
|
+
return new CimplifyClient(config);
|
|
2333
3431
|
}
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
3432
|
+
|
|
3433
|
+
// src/query/builder.ts
|
|
3434
|
+
var QueryBuilder = class {
|
|
3435
|
+
constructor(entity) {
|
|
3436
|
+
this.filters = [];
|
|
3437
|
+
this.modifiers = [];
|
|
3438
|
+
this.pathSegments = [];
|
|
3439
|
+
this.entity = entity;
|
|
2339
3440
|
}
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
if (product.price_info?.currency) {
|
|
2344
|
-
return product.price_info.currency;
|
|
3441
|
+
path(segment) {
|
|
3442
|
+
this.pathSegments.push(segment);
|
|
3443
|
+
return this;
|
|
2345
3444
|
}
|
|
2346
|
-
|
|
2347
|
-
|
|
3445
|
+
where(field, op, value) {
|
|
3446
|
+
const v = typeof value === "string" ? `'${value}'` : value;
|
|
3447
|
+
if (op === "contains" || op === "startsWith") {
|
|
3448
|
+
this.filters.push(`@.${field} ${op} ${v}`);
|
|
3449
|
+
} else {
|
|
3450
|
+
this.filters.push(`@.${field}${op}${v}`);
|
|
3451
|
+
}
|
|
3452
|
+
return this;
|
|
2348
3453
|
}
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
function formatProductPrice(product, locale = "en-US") {
|
|
2352
|
-
const price = getDisplayPrice(product);
|
|
2353
|
-
const currency = getProductCurrency(product);
|
|
2354
|
-
return formatPrice(price, currency, locale);
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
// src/utils/payment.ts
|
|
2358
|
-
function categorizePaymentError(error, errorCode) {
|
|
2359
|
-
let message = "An unexpected error occurred during payment processing. Please try again or contact support.";
|
|
2360
|
-
let recoverable = true;
|
|
2361
|
-
let code = errorCode || "PAYMENT_ERROR";
|
|
2362
|
-
const technical = error.stack;
|
|
2363
|
-
const errorMessage = error.message?.toLowerCase() || "";
|
|
2364
|
-
if (errorCode === "INSUFFICIENT_FUNDS" || errorMessage.includes("insufficient") || errorMessage.includes("funds")) {
|
|
2365
|
-
code = "INSUFFICIENT_FUNDS";
|
|
2366
|
-
message = "Your payment method has insufficient funds. Please try another payment method.";
|
|
2367
|
-
} else if (errorCode === "CARD_DECLINED" || errorMessage.includes("declined")) {
|
|
2368
|
-
code = "CARD_DECLINED";
|
|
2369
|
-
message = "Your card was declined. Please try another card or payment method.";
|
|
2370
|
-
} else if (errorMessage.includes("cancelled") || errorMessage.includes("canceled") || errorCode === "PAYMENT_CANCELLED") {
|
|
2371
|
-
code = "PAYMENT_CANCELLED";
|
|
2372
|
-
message = "Payment was cancelled. You can try again when ready.";
|
|
2373
|
-
} else if (errorMessage.includes("network") || errorMessage.includes("connection") || errorCode === "NETWORK_ERROR") {
|
|
2374
|
-
code = "NETWORK_ERROR";
|
|
2375
|
-
message = "Network connection issue. Please check your internet connection and try again.";
|
|
2376
|
-
} else if (errorMessage.includes("timeout") || errorCode === "TIMEOUT") {
|
|
2377
|
-
code = "TIMEOUT";
|
|
2378
|
-
message = "Payment processing timed out. Please try again.";
|
|
2379
|
-
} else if (errorCode === "PAYMENT_ACTION_NOT_COMPLETED") {
|
|
2380
|
-
code = "PAYMENT_ACTION_NOT_COMPLETED";
|
|
2381
|
-
message = "Payment action was not completed. Please try again.";
|
|
2382
|
-
} else if (errorCode === "AUTHORIZATION_FAILED") {
|
|
2383
|
-
code = "AUTHORIZATION_FAILED";
|
|
2384
|
-
message = "Authorization failed. Please check your code and try again.";
|
|
2385
|
-
} else if (errorCode === "INVALID_OTP") {
|
|
2386
|
-
code = "INVALID_OTP";
|
|
2387
|
-
message = "Invalid verification code. Please check and try again.";
|
|
3454
|
+
and(field, op, value) {
|
|
3455
|
+
return this.where(field, op, value);
|
|
2388
3456
|
}
|
|
2389
|
-
|
|
2390
|
-
}
|
|
2391
|
-
|
|
2392
|
-
return response !== null && typeof response === "object" && "payment" in response && typeof response.payment === "object";
|
|
2393
|
-
}
|
|
2394
|
-
function isWebPaymentResponse(response) {
|
|
2395
|
-
return response !== null && typeof response === "object" && "transaction" in response && typeof response.transaction === "object";
|
|
2396
|
-
}
|
|
2397
|
-
function normalizePaymentResponse(response) {
|
|
2398
|
-
if (!response) {
|
|
2399
|
-
return {
|
|
2400
|
-
method: "unknown",
|
|
2401
|
-
provider: "unknown",
|
|
2402
|
-
requires_action: false,
|
|
2403
|
-
metadata: {}
|
|
2404
|
-
};
|
|
3457
|
+
sort(field, order = "asc") {
|
|
3458
|
+
this.modifiers.push(`sort(${field},${order})`);
|
|
3459
|
+
return this;
|
|
2405
3460
|
}
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
provider: response.payment.provider?.toLowerCase() || "unknown",
|
|
2410
|
-
requires_action: !!response.payment.redirect_url || !!response.payment.access_code,
|
|
2411
|
-
public_key: response.payment.public_key,
|
|
2412
|
-
client_secret: response.payment.access_code,
|
|
2413
|
-
access_code: response.payment.access_code,
|
|
2414
|
-
redirect_url: response.payment.redirect_url,
|
|
2415
|
-
transaction_id: response.payment.reference,
|
|
2416
|
-
order_id: response.order_id,
|
|
2417
|
-
reference: response.payment.reference,
|
|
2418
|
-
instructions: response.payment.instructions,
|
|
2419
|
-
metadata: response.payment.metadata
|
|
2420
|
-
};
|
|
3461
|
+
limit(n) {
|
|
3462
|
+
this.modifiers.push(`limit(${n})`);
|
|
3463
|
+
return this;
|
|
2421
3464
|
}
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
const safeAuthType = authType && validAuthTypes.includes(authType) ? authType : void 0;
|
|
2426
|
-
return {
|
|
2427
|
-
provider: response.transaction.provider_type?.toLowerCase() || "unknown",
|
|
2428
|
-
requires_action: response.requires_action || false,
|
|
2429
|
-
public_key: response.public_key,
|
|
2430
|
-
client_secret: response.client_secret,
|
|
2431
|
-
redirect_url: response.authorization_url,
|
|
2432
|
-
transaction_id: response.transaction.id,
|
|
2433
|
-
order_id: response.transaction.order_id,
|
|
2434
|
-
reference: response.transaction.provider_reference,
|
|
2435
|
-
metadata: response.transaction.metadata,
|
|
2436
|
-
method: response.transaction.payment_method?.toLowerCase() || "unknown",
|
|
2437
|
-
instructions: response.display_text,
|
|
2438
|
-
display_text: response.display_text,
|
|
2439
|
-
requires_authorization: response.requires_authorization,
|
|
2440
|
-
authorization_type: safeAuthType,
|
|
2441
|
-
provider_payment_id: response.provider_payment_id || response.transaction.provider_reference
|
|
2442
|
-
};
|
|
3465
|
+
offset(n) {
|
|
3466
|
+
this.modifiers.push(`offset(${n})`);
|
|
3467
|
+
return this;
|
|
2443
3468
|
}
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
requires_action: false,
|
|
2448
|
-
metadata: {}
|
|
2449
|
-
};
|
|
2450
|
-
}
|
|
2451
|
-
function normalizeStatusResponse(response) {
|
|
2452
|
-
if (!response || typeof response !== "object") {
|
|
2453
|
-
return {
|
|
2454
|
-
status: "pending",
|
|
2455
|
-
paid: false,
|
|
2456
|
-
message: "No status available"
|
|
2457
|
-
};
|
|
3469
|
+
count() {
|
|
3470
|
+
this.modifiers.push("count");
|
|
3471
|
+
return this;
|
|
2458
3472
|
}
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
if (status === "success" || status === "completed" || res.paid === true) {
|
|
2463
|
-
standardStatus = "success";
|
|
2464
|
-
} else if (status === "failed" || status === "declined" || status === "cancelled") {
|
|
2465
|
-
standardStatus = "failed";
|
|
2466
|
-
} else if (status === "processing" || status === "pending_confirmation") {
|
|
2467
|
-
standardStatus = "processing";
|
|
2468
|
-
} else {
|
|
2469
|
-
standardStatus = "pending";
|
|
3473
|
+
enriched() {
|
|
3474
|
+
this.modifiers.push("enriched");
|
|
3475
|
+
return this;
|
|
2470
3476
|
}
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
mtn: { name: "MTN Mobile Money", prefix: ["024", "054", "055", "059"] },
|
|
2482
|
-
vodafone: { name: "Vodafone Cash", prefix: ["020", "050"] },
|
|
2483
|
-
airtel: { name: "AirtelTigo Money", prefix: ["027", "057", "026", "056"] }
|
|
2484
|
-
};
|
|
2485
|
-
function detectMobileMoneyProvider(phoneNumber) {
|
|
2486
|
-
const cleaned = phoneNumber.replace(/\D/g, "");
|
|
2487
|
-
const prefix = cleaned.slice(-9, -6);
|
|
2488
|
-
for (const [provider, info] of Object.entries(MOBILE_MONEY_PROVIDERS)) {
|
|
2489
|
-
if (info.prefix.some((p) => prefix.startsWith(p.slice(1)))) {
|
|
2490
|
-
return provider;
|
|
3477
|
+
build() {
|
|
3478
|
+
let query2 = this.entity;
|
|
3479
|
+
if (this.pathSegments.length > 0) {
|
|
3480
|
+
query2 += "." + this.pathSegments.join(".");
|
|
3481
|
+
}
|
|
3482
|
+
if (this.filters.length > 0) {
|
|
3483
|
+
query2 += `[?(${this.filters.join(" && ")})]`;
|
|
3484
|
+
}
|
|
3485
|
+
for (const mod of this.modifiers) {
|
|
3486
|
+
query2 += `#${mod}`;
|
|
2491
3487
|
}
|
|
3488
|
+
return query2;
|
|
2492
3489
|
}
|
|
2493
|
-
|
|
3490
|
+
toString() {
|
|
3491
|
+
return this.build();
|
|
3492
|
+
}
|
|
3493
|
+
};
|
|
3494
|
+
function query(entity) {
|
|
3495
|
+
return new QueryBuilder(entity);
|
|
2494
3496
|
}
|
|
2495
3497
|
|
|
2496
|
-
export { AUTHORIZATION_TYPE, AUTH_MUTATION, AuthService, BusinessService, CHECKOUT_MODE, CHECKOUT_MUTATION, CHECKOUT_STEP, CONTACT_TYPE, CURRENCY_SYMBOLS, CartOperations, CatalogueQueries, CheckoutService as CheckoutOperations, CheckoutService, CimplifyClient, CimplifyElement, CimplifyElements, CimplifyError, DEFAULT_COUNTRY, DEFAULT_CURRENCY, DEVICE_TYPE, ELEMENT_TYPES, EVENT_TYPES, ErrorCode, FxService, InventoryService, LINK_MUTATION, LINK_QUERY, LinkService, LiteService, MESSAGE_TYPES, MOBILE_MONEY_PROVIDER, MOBILE_MONEY_PROVIDERS, ORDER_MUTATION, ORDER_TYPE, OrderQueries, PAYMENT_METHOD, PAYMENT_MUTATION, PAYMENT_STATE, PICKUP_TIME_TYPE, QueryBuilder, SchedulingService, categorizePaymentError, combine, combineObject, createCimplifyClient, createElements, detectMobileMoneyProvider, err, flatMap, formatMoney, formatNumberCompact, formatPrice, formatPriceAdjustment, formatPriceCompact, formatProductPrice, fromPromise, generateIdempotencyKey, getBasePrice, getCurrencySymbol, getDiscountPercentage, getDisplayPrice, getMarkupPercentage, getOrElse, getProductCurrency, isCimplifyError, isErr, isOk, isOnSale, isRetryableError, mapError, mapResult, normalizePaymentResponse, normalizeStatusResponse, ok, parsePrice, query, toNullable, tryCatch, unwrap };
|
|
3498
|
+
export { AUTHORIZATION_TYPE, AUTH_MUTATION, AuthService, BusinessService, CHECKOUT_MODE, CHECKOUT_MUTATION, CHECKOUT_STEP, CONTACT_TYPE, CURRENCY_SYMBOLS, CartOperations, CatalogueQueries, CheckoutService as CheckoutOperations, CheckoutService, CimplifyClient, CimplifyElement, CimplifyElements, CimplifyError, DEFAULT_COUNTRY, DEFAULT_CURRENCY, DEVICE_TYPE, ELEMENT_TYPES, EVENT_TYPES, ErrorCode, FxService, InventoryService, LINK_MUTATION, LINK_QUERY, LinkService, LiteService, MESSAGE_TYPES, MOBILE_MONEY_PROVIDER, MOBILE_MONEY_PROVIDERS, ORDER_MUTATION, ORDER_TYPE, OrderQueries, PAYMENT_METHOD, PAYMENT_MUTATION, PAYMENT_STATE, PICKUP_TIME_TYPE, QueryBuilder, SchedulingService, categorizePaymentError, combine, combineObject, createCimplifyClient, createElements, detectMobileMoneyProvider, err, flatMap, formatMoney, formatNumberCompact, formatPrice, formatPriceAdjustment, formatPriceCompact, formatProductPrice, fromPromise, generateIdempotencyKey, getBasePrice, getCurrencySymbol, getDiscountPercentage, getDisplayPrice, getMarkupPercentage, getOrElse, getProductCurrency, isCimplifyError, isErr, isOk, isOnSale, isPaymentStatusFailure, isPaymentStatusRequiresAction, isPaymentStatusSuccess, isRetryableError, mapError, mapResult, normalizePaymentResponse, normalizeStatusResponse, ok, parsePrice, query, toNullable, tryCatch, unwrap };
|