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