payaza-storefront-layouts 1.1.21 → 1.1.23

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.
@@ -0,0 +1,54 @@
1
+ import type { ShippingQuoteAddress } from '../../lib/shipping-address.util';
2
+ export interface DeliveryRate {
3
+ providerKey: string;
4
+ providerDisplayName: string;
5
+ methodCode: string | null;
6
+ methodName: string;
7
+ etaTo: number | null;
8
+ etaFrom: number | null;
9
+ price: number;
10
+ currency: string;
11
+ quoteToken: string | null;
12
+ serviceCode?: string;
13
+ courierId?: string;
14
+ notes: string | null;
15
+ isFastest?: boolean;
16
+ isCheapest?: boolean;
17
+ isRecommended?: boolean;
18
+ serviceType?: 'pickup' | 'dropoff';
19
+ supportsCOD?: boolean;
20
+ trackingLevel?: number;
21
+ }
22
+ /** @deprecated use DeliveryRate */
23
+ export type ShipBubbleRate = DeliveryRate;
24
+ export interface SelectedShipping {
25
+ providerKey: string;
26
+ serviceCode: string;
27
+ courierId: string;
28
+ requestToken: string;
29
+ quoteId: string;
30
+ price: number;
31
+ currency: string;
32
+ courierName: string;
33
+ }
34
+ interface ShippingOptionsPanelProps {
35
+ storeId: string;
36
+ address: ShippingQuoteAddress;
37
+ packages: Array<{
38
+ weight: number;
39
+ value?: number;
40
+ name?: string;
41
+ quantity?: number;
42
+ }>;
43
+ onSelect: (selection: SelectedShipping | null) => void;
44
+ selected: SelectedShipping | null;
45
+ customerInfo?: {
46
+ firstName?: string;
47
+ lastName?: string;
48
+ phone?: string;
49
+ email?: string;
50
+ };
51
+ }
52
+ export declare function ShippingOptionsPanel({ storeId, address, packages, onSelect, selected, customerInfo, }: ShippingOptionsPanelProps): import("react/jsx-runtime").JSX.Element;
53
+ export {};
54
+ //# sourceMappingURL=ShippingOptionsPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShippingOptionsPanel.d.ts","sourceRoot":"","sources":["../../../src/components/checkout/ShippingOptionsPanel.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAExE,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,mCAAmC;AACnC,MAAM,MAAM,cAAc,GAAG,YAAY,CAAC;AAE1C,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,QAAQ,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtF,QAAQ,EAAE,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,CAAC;IACvD,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAClC,YAAY,CAAC,EAAE;QACb,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAkBD,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,YAAY,GACb,EAAE,yBAAyB,2CAuK3B"}
@@ -0,0 +1,115 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect, useCallback } from 'react';
4
+ import { api } from '../../lib/api.js';
5
+ import { Skeleton } from '../../components/ui/skeleton.js';
6
+ import { buildDeliveryQuotePayload } from '../../lib/shipping-address.util.js';
7
+ function toSelectedShipping(rate) {
8
+ const serviceCode = rate.serviceCode || rate.methodCode || 'standard';
9
+ const courierId = rate.courierId || '';
10
+ const quoteToken = rate.quoteToken || '';
11
+ return {
12
+ providerKey: rate.providerKey,
13
+ serviceCode,
14
+ courierId,
15
+ requestToken: quoteToken,
16
+ quoteId: rate.providerKey === 'shipbubble' ? `${quoteToken}|${courierId}` : quoteToken,
17
+ price: rate.price,
18
+ currency: rate.currency,
19
+ courierName: rate.methodName,
20
+ };
21
+ }
22
+ export function ShippingOptionsPanel({ storeId, address, packages, onSelect, selected, customerInfo, }) {
23
+ const [rates, setRates] = useState([]);
24
+ const [loading, setLoading] = useState(false);
25
+ const [error, setError] = useState(null);
26
+ const [addressIncomplete, setAddressIncomplete] = useState(false);
27
+ const fetchRates = useCallback(async () => {
28
+ if (!address.city || !address.state || !address.country) {
29
+ setAddressIncomplete(true);
30
+ setRates([]);
31
+ return;
32
+ }
33
+ const street = (address.street || address.address1 || '').trim();
34
+ if (!street) {
35
+ setAddressIncomplete(true);
36
+ setRates([]);
37
+ return;
38
+ }
39
+ setAddressIncomplete(false);
40
+ setLoading(true);
41
+ setError(null);
42
+ try {
43
+ const payload = buildDeliveryQuotePayload({
44
+ storeId,
45
+ address,
46
+ packages,
47
+ customerInfo,
48
+ });
49
+ const response = await api.post('/delivery/quote', payload);
50
+ const quotes = response.data?.data?.quotes || response.data?.data || [];
51
+ setRates(quotes);
52
+ if (quotes.length > 0 && !selected) {
53
+ const recommended = quotes.find((q) => q.isRecommended || q.isFastest) || quotes[0];
54
+ onSelect(toSelectedShipping(recommended));
55
+ }
56
+ }
57
+ catch (err) {
58
+ const message = err?.response?.data?.message ||
59
+ err?.message ||
60
+ 'Could not load shipping options. You can continue and shipping will be calculated at fulfilment.';
61
+ setError(message);
62
+ setRates([]);
63
+ }
64
+ finally {
65
+ setLoading(false);
66
+ }
67
+ }, [
68
+ storeId,
69
+ address.city,
70
+ address.state,
71
+ address.country,
72
+ address.street,
73
+ address.address1,
74
+ address.zipCode,
75
+ packages,
76
+ customerInfo,
77
+ selected,
78
+ onSelect,
79
+ ]);
80
+ useEffect(() => {
81
+ fetchRates();
82
+ }, [fetchRates]);
83
+ const handleSelect = (rate) => {
84
+ onSelect(toSelectedShipping(rate));
85
+ };
86
+ if (addressIncomplete) {
87
+ return (_jsx("p", { className: "text-xs text-gray-500 bg-gray-50 border border-gray-200 rounded px-3 py-2", children: "Complete your address (street, city, state, country) to see shipping rates." }));
88
+ }
89
+ if (loading) {
90
+ return (_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium text-gray-700", children: "Shipping options" }), _jsx(Skeleton, { className: "h-16 w-full" }), _jsx(Skeleton, { className: "h-16 w-full" })] }));
91
+ }
92
+ if (error) {
93
+ return (_jsx("p", { className: "text-xs text-amber-600 bg-amber-50 border border-amber-200 rounded px-3 py-2", children: error }));
94
+ }
95
+ if (rates.length === 0) {
96
+ return (_jsx("p", { className: "text-xs text-gray-500 bg-gray-50 border border-gray-200 rounded px-3 py-2", children: "No courier rates available for this address. Try a different address or continue \u2014 shipping may be arranged after checkout." }));
97
+ }
98
+ return (_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium text-gray-700", children: "Shipping options" }), _jsx("div", { className: "space-y-2", children: rates.map((rate, idx) => {
99
+ const serviceCode = rate.serviceCode || rate.methodCode || 'standard';
100
+ const courierId = rate.courierId || '';
101
+ const isSelected = selected?.providerKey === rate.providerKey &&
102
+ selected?.serviceCode === serviceCode &&
103
+ selected?.courierId === courierId;
104
+ const etaDays = rate.etaTo;
105
+ return (_jsxs("button", { type: "button", onClick: () => handleSelect(rate), className: [
106
+ 'w-full flex items-start gap-3 p-3 rounded-lg border text-left transition-colors',
107
+ isSelected
108
+ ? 'border-blue-500 bg-blue-50'
109
+ : 'border-gray-200 hover:border-gray-300 bg-white',
110
+ ].join(' '), children: [_jsx("span", { className: [
111
+ 'mt-0.5 flex-shrink-0 w-4 h-4 rounded-full border-2 flex items-center justify-center',
112
+ isSelected ? 'border-blue-500' : 'border-gray-300',
113
+ ].join(' '), children: isSelected && _jsx("span", { className: "w-2 h-2 rounded-full bg-blue-500" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("span", { className: "text-sm font-medium text-gray-900", children: rate.methodName }), rate.isFastest && (_jsx("span", { className: "text-[10px] font-semibold bg-blue-100 text-blue-700 px-1.5 py-0.5 rounded-full uppercase tracking-wide", children: "Fastest" })), rate.isCheapest && (_jsx("span", { className: "text-[10px] font-semibold bg-green-100 text-green-700 px-1.5 py-0.5 rounded-full uppercase tracking-wide", children: "Cheapest" }))] }), _jsxs("div", { className: "flex items-center gap-2 mt-0.5 text-xs text-gray-500 flex-wrap", children: [etaDays && _jsxs("span", { children: ["Arrives in ~", etaDays, " day", etaDays !== 1 ? 's' : ''] }), rate.providerKey === 'shipbubble' && rate.serviceType === 'dropoff' && _jsx("span", { children: "\u00B7 Drop-off at courier station" }), rate.trackingLevel != null && _jsxs("span", { children: ["\u00B7 Tracking ", rate.trackingLevel, "/10"] })] })] }), _jsx("div", { className: "flex-shrink-0 text-right", children: _jsxs("p", { className: "text-sm font-semibold text-gray-900", children: [rate.currency, " ", rate.price.toLocaleString()] }) })] }, `${rate.providerKey}-${serviceCode}-${courierId}-${idx}`));
114
+ }) })] }));
115
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"guest-checkout-modal.d.ts","sourceRoot":"","sources":["../../../src/components/ui/guest-checkout-modal.tsx"],"names":[],"mappings":"AAQA,UAAU,uBAAuB;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;KACf,KAAK,IAAI,CAAC;IACX,WAAW,CAAC,EAAE;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,OAAO,EACP,QAAQ,EACR,WAAW,GACZ,EAAE,uBAAuB,2CAiRzB"}
1
+ {"version":3,"file":"guest-checkout-modal.d.ts","sourceRoot":"","sources":["../../../src/components/ui/guest-checkout-modal.tsx"],"names":[],"mappings":"AAQA,UAAU,uBAAuB;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;KACf,KAAK,IAAI,CAAC;IACX,WAAW,CAAC,EAAE;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,OAAO,EACP,QAAQ,EACR,WAAW,GACZ,EAAE,uBAAuB,2CAoRzB"}
@@ -35,8 +35,11 @@ export function GuestCheckoutModal({ isOpen, onClose, onSubmit, initialData, })
35
35
  if (!formData.phone.trim()) {
36
36
  newErrors.phone = 'Phone number is required';
37
37
  }
38
- else if (!/^[0-9+\-\s()]+$/.test(formData.phone.trim())) {
39
- newErrors.phone = 'Please enter a valid phone number';
38
+ else {
39
+ const digits = formData.phone.trim().replace(/\D/g, '');
40
+ if (digits.length < 7 || digits.length > 15) {
41
+ newErrors.phone = 'Please enter a valid phone number';
42
+ }
40
43
  }
41
44
  if (!formData.email.trim()) {
42
45
  newErrors.email = 'Email is required';
@@ -1 +1 @@
1
- {"version":3,"file":"use-payaza-checkout.d.ts","sourceRoot":"","sources":["../../src/hooks/use-payaza-checkout.ts"],"names":[],"mappings":"AAGA,OAAO,EAA0B,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE7G,UAAU,wBAAwB;IAChC,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACvD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,EAAE,SAAc,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,wBAAwB;uBAiBzE,IAAI,CAAC,oBAAoB,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;;;EAsHhG"}
1
+ {"version":3,"file":"use-payaza-checkout.d.ts","sourceRoot":"","sources":["../../src/hooks/use-payaza-checkout.ts"],"names":[],"mappings":"AAGA,OAAO,EAA0B,oBAAoB,EAAE,sBAAsB,EAA6B,MAAM,uBAAuB,CAAC;AAExI,UAAU,wBAAwB;IAChC,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACvD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,EAAE,SAAc,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,wBAAwB;uBAiBzE,IAAI,CAAC,oBAAoB,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;;;EAqGhG"}
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { useState, useRef, useEffect } from 'react';
3
- import { initiatePayazaCheckout } from '../lib/payaza-checkout.js';
3
+ import { initiatePayazaCheckout, teardownPayazaCheckoutDom } from '../lib/payaza-checkout.js';
4
4
  export function usePayazaCheckout({ publicKey = '', onSuccess, onError, onClose }) {
5
5
  const [isLoading, setIsLoading] = useState(false);
6
6
  const [error, setError] = useState(null);
@@ -29,29 +29,7 @@ export function usePayazaCheckout({ publicKey = '', onSuccess, onError, onClose
29
29
  };
30
30
  const PAYAZA_TIMEOUT_MS = 45000;
31
31
  const removePayazaOverlay = () => {
32
- if (typeof document === 'undefined' || !document.body || !document.querySelector)
33
- return;
34
- const iframe = document.querySelector('iframe[src*="payaza.africa"]');
35
- let node = iframe;
36
- while (node?.parentElement && node.parentElement !== document.body) {
37
- node = node.parentElement;
38
- }
39
- if (node?.parentElement === document.body) {
40
- node.remove();
41
- }
42
- else if (iframe?.parentElement) {
43
- iframe.parentElement.remove();
44
- }
45
- document.querySelectorAll('style').forEach((styleEl) => {
46
- if (styleEl.textContent?.includes('payaza-loading-spinner')) {
47
- styleEl.remove();
48
- }
49
- });
50
- document.querySelectorAll('div').forEach((div) => {
51
- if (div.innerHTML.includes('payaza-loading-spinner')) {
52
- div.remove();
53
- }
54
- });
32
+ teardownPayazaCheckoutDom();
55
33
  };
56
34
  const setLoadingTimeout = () => {
57
35
  timeoutRef.current = setTimeout(() => {
@@ -87,12 +65,14 @@ export function usePayazaCheckout({ publicKey = '', onSuccess, onError, onClose
87
65
  clearLoadingWithTimeout();
88
66
  setIsLoading(false);
89
67
  setError(errorMessage);
68
+ removePayazaOverlay();
90
69
  onError?.(errorMessage);
91
70
  },
92
71
  onClose: () => {
93
72
  callbackFired = true;
94
73
  clearLoadingWithTimeout();
95
74
  setIsLoading(false);
75
+ removePayazaOverlay();
96
76
  onClose?.();
97
77
  },
98
78
  });
@@ -121,6 +101,7 @@ export function usePayazaCheckout({ publicKey = '', onSuccess, onError, onClose
121
101
  const errorMessage = err instanceof Error ? err.message : 'An unexpected error occurred';
122
102
  setIsLoading(false);
123
103
  setError(errorMessage);
104
+ removePayazaOverlay();
124
105
  onError?.(errorMessage);
125
106
  }
126
107
  };
@@ -1 +1 @@
1
- {"version":3,"file":"CheckoutPage.d.ts","sourceRoot":"","sources":["../../../../src/layouts/shared/pages/CheckoutPage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA8BhD,UAAU,iBAAiB;IACzB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAUD,wBAAgB,YAAY,CAAC,EAAE,WAAW,EAAE,EAAE,iBAAiB,2CAwsD9D"}
1
+ {"version":3,"file":"CheckoutPage.d.ts","sourceRoot":"","sources":["../../../../src/layouts/shared/pages/CheckoutPage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA+BhD,UAAU,iBAAiB;IACzB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAUD,wBAAgB,YAAY,CAAC,EAAE,WAAW,EAAE,EAAE,iBAAiB,2CA2xD9D"}
@@ -4,7 +4,7 @@ import { Button } from '../../../components/ui/button.js';
4
4
  import { ArrowLeft, ShieldCheck, CheckCircle, MapPin, Lock, ChevronRight, Tag, Truck, ChevronDown } from 'lucide-react';
5
5
  import Link from 'next/link';
6
6
  import Image from 'next/image';
7
- import { useState, useEffect, useRef } from 'react';
7
+ import { useState, useEffect, useRef, useCallback } from 'react';
8
8
  import { cn } from '../../../lib/utils/index.js';
9
9
  import { usePayazaCheckout } from '../../../hooks/use-payaza-checkout.js';
10
10
  import { generateCheckoutConfig } from '../../../lib/payaza-checkout.js';
@@ -20,10 +20,11 @@ import { AddressSelector } from '../../../components/ui/address-selector.js';
20
20
  import { countries, getCountryByName, getCitiesByCountry } from '../../../lib/countries.js';
21
21
  import { calculateVAT, getVATLabel } from '../../../lib/utils/fee-calculations.js';
22
22
  import { shippingService } from '../../../lib/services/shipping.service.js';
23
- import { enrichShippingQuoteAddress, buildShippingCalculatePayload, isNigeria, deriveNigerianStateFromCity, NIGERIAN_STATES } from '../../../lib/shipping-address.util.js';
23
+ import { enrichShippingQuoteAddress, buildShippingCalculatePayload, canRequestShippingQuote, isNigeria, deriveNigerianStateFromCity, NIGERIAN_STATES } from '../../../lib/shipping-address.util.js';
24
+ import { ShippingOptionsPanel } from '../../../components/checkout/ShippingOptionsPanel.js';
24
25
  import { CheckoutSkeleton } from '../../../components/ui/skeletons/checkout-skeleton.js';
25
26
  import { useAnalytics } from '../../../hooks/use-analytics.js';
26
- import { formatPhoneInput, validatePhone, getPhonePlaceholder, toCountryCode } from '../../../lib/utils/phone-format.js';
27
+ import { formatPhoneInput, validatePhone, getPhonePlaceholder, normalizePhoneForPayaza } from '../../../lib/utils/phone-format.js';
27
28
  const PROMO_CODE_ENABLED = false;
28
29
  function RequiredLabel({ children }) {
29
30
  return (_jsxs("label", { className: "text-sm font-bold text-gray-700 ml-1", children: [children, " ", _jsx("span", { className: "text-red-500", children: "*" })] }));
@@ -104,6 +105,9 @@ export function CheckoutPage({ storeConfig }) {
104
105
  const [shippingError, setShippingError] = useState(null);
105
106
  const [freeShippingEligible, setFreeShippingEligible] = useState(false);
106
107
  const [freeShippingThreshold, setFreeShippingThreshold] = useState(undefined);
108
+ const [isCourierMode, setIsCourierMode] = useState(false);
109
+ const [checkingDeliveryStatus, setCheckingDeliveryStatus] = useState(false);
110
+ const [selectedCourier, setSelectedCourier] = useState(null);
107
111
  // Payaza Configuration
108
112
  // Fallback key only — the authoritative key is resolved per-currency by the
109
113
  // backend from the database-managed registry and returned by /checkout/process
@@ -235,8 +239,6 @@ export function CheckoutPage({ storeConfig }) {
235
239
  setLoading(false);
236
240
  },
237
241
  onClose: () => {
238
- // onClose is only called when payment was not successful (user canceled)
239
- // This is now handled correctly by payaza-checkout.ts tracking payment success state
240
242
  addToast('Payment canceled', 'info');
241
243
  setLoading(false);
242
244
  },
@@ -380,9 +382,19 @@ export function CheckoutPage({ storeConfig }) {
380
382
  }
381
383
  // eslint-disable-next-line react-hooks/exhaustive-deps
382
384
  }, [isAuthenticated, user, selectedAddressId, savedAddresses.length, loadingAddresses]);
383
- // Calculate shipping when address is filled
385
+ // Calculate legacy shipping when address is filled (flat-rate / zone methods)
384
386
  const calculateShipping = async () => {
385
- if (!formData.city || !formData.country || checkoutItems.length === 0) {
387
+ const quoteAddress = enrichShippingQuoteAddress({
388
+ street: formData.address,
389
+ address1: formData.address,
390
+ city: formData.city,
391
+ state: formData.state || formData.city,
392
+ zipCode: formData.postalCode,
393
+ country: formData.country,
394
+ phone: formData.phone,
395
+ email: formData.email,
396
+ });
397
+ if (!canRequestShippingQuote(quoteAddress) || checkoutItems.length === 0) {
386
398
  setShippingMethods([]);
387
399
  setSelectedShippingMethod(null);
388
400
  return;
@@ -392,16 +404,7 @@ export function CheckoutPage({ storeConfig }) {
392
404
  try {
393
405
  const result = await shippingService.calculateShipping(buildShippingCalculatePayload({
394
406
  storeId: storeConfig.id,
395
- address: enrichShippingQuoteAddress({
396
- street: formData.address,
397
- address1: formData.address,
398
- city: formData.city,
399
- state: formData.state || formData.city,
400
- zipCode: formData.postalCode,
401
- country: formData.country,
402
- phone: formData.phone,
403
- email: formData.email,
404
- }),
407
+ address: quoteAddress,
405
408
  customerInfo: {
406
409
  firstName: formData.firstName,
407
410
  lastName: formData.lastName,
@@ -510,20 +513,78 @@ export function CheckoutPage({ storeConfig }) {
510
513
  const [countrySearch, setCountrySearch] = useState('');
511
514
  const [citySearch, setCitySearch] = useState('');
512
515
  const deliveryEnabled = storeConfig.features?.delivery !== false;
513
- // Debounced shipping calculation when address changes (skip when delivery is globally disabled)
516
+ const buildQuoteAddress = () => enrichShippingQuoteAddress({
517
+ street: formData.address,
518
+ address1: formData.address,
519
+ city: formData.city,
520
+ state: formData.state || formData.city,
521
+ zipCode: formData.postalCode,
522
+ country: formData.country,
523
+ phone: formData.phone,
524
+ email: formData.email,
525
+ });
526
+ const handleCourierSelect = useCallback((selection) => {
527
+ setSelectedCourier(selection);
528
+ if (!selection) {
529
+ setSelectedShippingMethod(null);
530
+ return;
531
+ }
532
+ setSelectedShippingMethod({
533
+ code: selection.serviceCode,
534
+ name: selection.courierName,
535
+ cost: selection.price,
536
+ estimatedDays: 0,
537
+ providerKey: selection.providerKey,
538
+ deliveryMethodId: selection.serviceCode,
539
+ deliveryQuoteId: selection.quoteId,
540
+ });
541
+ }, []);
542
+ // Hybrid shipping: courier quotes when providers enabled, else legacy calculate
514
543
  useEffect(() => {
515
- if (!deliveryEnabled || !formData.city || !formData.country || checkoutItems.length === 0) {
544
+ if (!deliveryEnabled || checkoutItems.length === 0) {
545
+ setIsCourierMode(false);
546
+ setShippingMethods([]);
547
+ setSelectedShippingMethod(null);
548
+ setSelectedCourier(null);
516
549
  return;
517
550
  }
518
- const timer = setTimeout(() => {
519
- calculateShipping();
520
- }, 500); // Debounce 500ms
551
+ const quoteAddress = buildQuoteAddress();
552
+ if (!canRequestShippingQuote(quoteAddress)) {
553
+ setIsCourierMode(false);
554
+ setShippingMethods([]);
555
+ setSelectedShippingMethod(null);
556
+ setSelectedCourier(null);
557
+ return;
558
+ }
559
+ const timer = setTimeout(async () => {
560
+ setCheckingDeliveryStatus(true);
561
+ setShippingError(null);
562
+ try {
563
+ const status = await shippingService.getDeliveryStatus(storeConfig.id);
564
+ const anyProvider = status.enabled && Object.values(status.providers || {}).some(Boolean);
565
+ setIsCourierMode(anyProvider);
566
+ if (anyProvider) {
567
+ setShippingMethods([]);
568
+ setSelectedShippingMethod(null);
569
+ setSelectedCourier(null);
570
+ }
571
+ else {
572
+ await calculateShipping();
573
+ }
574
+ }
575
+ catch {
576
+ setIsCourierMode(false);
577
+ await calculateShipping();
578
+ }
579
+ finally {
580
+ setCheckingDeliveryStatus(false);
581
+ }
582
+ }, 500);
521
583
  return () => clearTimeout(timer);
522
584
  // eslint-disable-next-line react-hooks/exhaustive-deps
523
- }, [deliveryEnabled, formData.address, formData.city, formData.state, formData.country, formData.postalCode, formData.email, formData.phone, formData.firstName, formData.lastName, checkoutItems.length, subtotal, currency]);
585
+ }, [deliveryEnabled, formData.address, formData.city, formData.state, formData.country, formData.postalCode, formData.email, formData.phone, formData.firstName, formData.lastName, checkoutItems.length, subtotal, currency, storeConfig.id]);
524
586
  const validateForm = () => {
525
587
  const newErrors = {};
526
- const phoneCountryCode = toCountryCode(getCountryByName(formData.country)?.code);
527
588
  if (!formData.email)
528
589
  newErrors.email = 'Email is required';
529
590
  else if (!/\S+@\S+\.\S+/.test(formData.email))
@@ -543,7 +604,7 @@ export function CheckoutPage({ storeConfig }) {
543
604
  }
544
605
  if (!formData.phone)
545
606
  newErrors.phone = 'Phone is required';
546
- else if (!validatePhone(formData.phone, phoneCountryCode)) {
607
+ else if (!validatePhone(formData.phone)) {
547
608
  newErrors.phone = 'Please enter a valid phone number';
548
609
  }
549
610
  setErrors(newErrors);
@@ -557,10 +618,9 @@ export function CheckoutPage({ storeConfig }) {
557
618
  setIsPromoApplied(true);
558
619
  addToast('Promo code applied successfully!', 'success');
559
620
  };
560
- const phoneCountryCode = toCountryCode(getCountryByName(formData.country)?.code);
561
- const phonePlaceholder = getPhonePlaceholder(phoneCountryCode);
621
+ const phonePlaceholder = getPhonePlaceholder();
562
622
  const handlePhoneChange = (value) => {
563
- setFormData({ ...formData, phone: formatPhoneInput(value, phoneCountryCode) });
623
+ setFormData({ ...formData, phone: formatPhoneInput(value) });
564
624
  };
565
625
  // Handle address selection
566
626
  const handleAddressSelect = (addressId) => {
@@ -630,6 +690,12 @@ export function CheckoutPage({ storeConfig }) {
630
690
  if (!formData.phone) {
631
691
  return { isValid: false, error: 'Phone is required' };
632
692
  }
693
+ if (!validatePhone(formData.phone)) {
694
+ return { isValid: false, error: 'Please enter a valid phone number' };
695
+ }
696
+ if (isNigeria(formData.country) && !formData.state) {
697
+ return { isValid: false, error: 'State is required for Nigeria' };
698
+ }
633
699
  // Validate currency availability
634
700
  const hasCurrency = storeConfig.settings?.currency || checkoutItems.some(item => item.product.currency);
635
701
  if (!hasCurrency) {
@@ -766,7 +832,7 @@ export function CheckoutPage({ storeConfig }) {
766
832
  checkoutConfig = generateCheckoutConfig(payazaItems, formData.email, effectivePayazaPublicKey, {
767
833
  firstName: formData.firstName,
768
834
  lastName: formData.lastName,
769
- phone: formData.phone,
835
+ phone: normalizePhoneForPayaza(formData.phone),
770
836
  callbackUrl: `${typeof window !== 'undefined' ? window.location.origin : ''}/${storeConfig.slug}/payment/callback?storeId=${storeConfig.id}`,
771
837
  currency: checkoutResponse.currency || storeConfig.settings?.currency,
772
838
  storeId: storeConfig.id,
@@ -920,9 +986,6 @@ export function CheckoutPage({ storeConfig }) {
920
986
  country: country.name,
921
987
  city: '',
922
988
  state: '',
923
- phone: prev.phone
924
- ? formatPhoneInput(prev.phone, toCountryCode(country.code))
925
- : prev.phone,
926
989
  }));
927
990
  setIsCountryDropdownOpen(false);
928
991
  setCountrySearch('');
@@ -951,9 +1014,19 @@ export function CheckoutPage({ storeConfig }) {
951
1014
  }, className: cn("w-full px-5 py-3 text-left hover:bg-gray-50 transition-colors", formData.city === city && "bg-gray-50 font-medium"), children: city }, city))), getCitiesByCountry(formData.country).filter(c => c.toLowerCase().includes(citySearch.toLowerCase())).length === 0 && (_jsx("div", { className: "px-5 py-3 text-gray-400 text-sm", children: "No cities found" }))] })] })) : (_jsx("div", { className: "px-5 py-3 text-gray-500 text-sm", children: "No cities available. Please type your city in the field above." })) })] }))] }), errors.city && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.city })] }), isNigeria(formData.country) && (_jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "State" }), _jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: () => setIsStateDropdownOpen(!isStateDropdownOpen), className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-left flex items-center justify-between text-base", errors.state ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), children: [_jsx("span", { className: formData.state ? "text-gray-900" : "text-gray-400", children: formData.state || 'Select state' }), _jsx(ChevronDown, { className: cn("w-5 h-5 text-gray-400 transition-transform", isStateDropdownOpen && "transform rotate-180") })] }), isStateDropdownOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () => setIsStateDropdownOpen(false) }), _jsx("div", { className: "absolute z-30 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-xl max-h-52 overflow-y-auto", children: NIGERIAN_STATES.map((state) => (_jsx("button", { type: "button", onClick: () => {
952
1015
  setFormData({ ...formData, state });
953
1016
  setIsStateDropdownOpen(false);
954
- }, className: cn("w-full px-5 py-3 text-left hover:bg-gray-50 transition-colors", formData.state === state && "bg-gray-50 font-medium"), children: state }, state))) })] }))] }), errors.state && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.state })] })), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Postal/Zip Code" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.postalCode ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: "Optional", value: formData.postalCode, onChange: e => setFormData({ ...formData, postalCode: e.target.value }) }), errors.postalCode && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.postalCode })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "Phone" }), _jsx("input", { type: "tel", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.phone ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: phonePlaceholder, value: formData.phone, onChange: e => handlePhoneChange(e.target.value) }), errors.phone && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.phone })] })] }), isAuthenticated && !selectedAddressId && (_jsxs("div", { className: "mt-6 space-y-3", children: [_jsxs("label", { className: "flex items-center gap-3 cursor-pointer group", children: [_jsx("input", { type: "checkbox", checked: saveNewAddress, onChange: (e) => setSaveNewAddress(e.target.checked), className: "w-5 h-5 rounded border-2 border-gray-300 text-black focus:ring-2 focus:ring-black cursor-pointer" }), _jsx("span", { className: "text-sm font-medium text-gray-700 group-hover:text-black transition-colors", children: "Save this address for future use" })] }), saveNewAddress && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Address Label (optional)" }), _jsx("input", { type: "text", value: addressLabel, onChange: (e) => setAddressLabel(e.target.value), placeholder: "e.g., Home, Work, Office", className: "w-full px-5 py-3.5 bg-white border-2 border-gray-100 rounded-xl focus:outline-none focus:ring-0 focus:border-black transition-all font-medium" })] }))] }))] }), deliveryEnabled && storeConfig.settings?.shippingEnabled !== false && (formData.city && formData.country) && (_jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-purple-50 text-purple-600 rounded-xl", children: _jsx(Truck, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Shipping Method" })] }), freeShippingThreshold !== undefined && !freeShippingEligible && (_jsxs("div", { className: "mb-4 p-3 bg-blue-50 border border-blue-200 rounded-lg", children: [_jsxs("p", { className: "text-xs font-medium text-blue-900 mb-1", children: ["Spend ", formatCurrency(freeShippingThreshold - subtotal, currency), " more for free shipping!"] }), _jsx("div", { className: "w-full bg-blue-200 rounded-full h-2", children: _jsx("div", { className: "bg-blue-600 h-2 rounded-full transition-all", style: {
1017
+ }, className: cn("w-full px-5 py-3 text-left hover:bg-gray-50 transition-colors", formData.state === state && "bg-gray-50 font-medium"), children: state }, state))) })] }))] }), errors.state && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.state })] })), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Postal/Zip Code" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.postalCode ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: "Optional", value: formData.postalCode, onChange: e => setFormData({ ...formData, postalCode: e.target.value }) }), errors.postalCode && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.postalCode })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "Phone" }), _jsx("input", { type: "tel", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.phone ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: phonePlaceholder, value: formData.phone, onChange: e => handlePhoneChange(e.target.value) }), errors.phone && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.phone })] })] }), isAuthenticated && !selectedAddressId && (_jsxs("div", { className: "mt-6 space-y-3", children: [_jsxs("label", { className: "flex items-center gap-3 cursor-pointer group", children: [_jsx("input", { type: "checkbox", checked: saveNewAddress, onChange: (e) => setSaveNewAddress(e.target.checked), className: "w-5 h-5 rounded border-2 border-gray-300 text-black focus:ring-2 focus:ring-black cursor-pointer" }), _jsx("span", { className: "text-sm font-medium text-gray-700 group-hover:text-black transition-colors", children: "Save this address for future use" })] }), saveNewAddress && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Address Label (optional)" }), _jsx("input", { type: "text", value: addressLabel, onChange: (e) => setAddressLabel(e.target.value), placeholder: "e.g., Home, Work, Office", className: "w-full px-5 py-3.5 bg-white border-2 border-gray-100 rounded-xl focus:outline-none focus:ring-0 focus:border-black transition-all font-medium" })] }))] }))] }), deliveryEnabled && storeConfig.settings?.shippingEnabled !== false && canRequestShippingQuote(buildQuoteAddress()) && (_jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-purple-50 text-purple-600 rounded-xl", children: _jsx(Truck, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Shipping Method" })] }), !isCourierMode && freeShippingThreshold !== undefined && !freeShippingEligible && (_jsxs("div", { className: "mb-4 p-3 bg-blue-50 border border-blue-200 rounded-lg", children: [_jsxs("p", { className: "text-xs font-medium text-blue-900 mb-1", children: ["Spend ", formatCurrency(freeShippingThreshold - subtotal, currency), " more for free shipping!"] }), _jsx("div", { className: "w-full bg-blue-200 rounded-full h-2", children: _jsx("div", { className: "bg-blue-600 h-2 rounded-full transition-all", style: {
955
1018
  width: `${Math.min(100, (subtotal / freeShippingThreshold) * 100)}%`,
956
- } }) })] })), isCalculatingShipping ? (_jsx("div", { className: "flex items-center justify-center p-8", children: _jsxs("div", { className: "flex items-center gap-2 text-gray-600", children: [_jsx("div", { className: "animate-spin rounded-full h-5 w-5 border-b-2 border-gray-900" }), _jsx("span", { className: "text-sm font-medium", children: "Calculating shipping options..." })] }) })) : shippingError ? (_jsx("div", { className: "p-4 bg-red-50 border border-red-200 rounded-lg", children: _jsx("p", { className: "text-sm text-red-600", children: shippingError }) })) : shippingMethods.length > 0 ? (_jsx("div", { className: "space-y-3", children: shippingMethods.map((method) => (_jsxs("label", { className: cn("flex items-center justify-between p-4 border-2 rounded-xl cursor-pointer transition-all", selectedShippingMethod?.code === method.code
1019
+ } }) })] })), checkingDeliveryStatus || (!isCourierMode && isCalculatingShipping) ? (_jsx("div", { className: "flex items-center justify-center p-8", children: _jsxs("div", { className: "flex items-center gap-2 text-gray-600", children: [_jsx("div", { className: "animate-spin rounded-full h-5 w-5 border-b-2 border-gray-900" }), _jsx("span", { className: "text-sm font-medium", children: "Calculating shipping options..." })] }) })) : isCourierMode ? (_jsx(ShippingOptionsPanel, { storeId: storeConfig.id, address: buildQuoteAddress(), packages: checkoutItems.map((item) => ({
1020
+ weight: item.product?.weight || 0.5,
1021
+ value: item.price,
1022
+ name: item.product?.name,
1023
+ quantity: item.quantity,
1024
+ })), customerInfo: {
1025
+ firstName: formData.firstName,
1026
+ lastName: formData.lastName,
1027
+ phone: formData.phone,
1028
+ email: formData.email,
1029
+ }, onSelect: handleCourierSelect, selected: selectedCourier })) : shippingError ? (_jsx("div", { className: "p-4 bg-red-50 border border-red-200 rounded-lg", children: _jsx("p", { className: "text-sm text-red-600", children: shippingError }) })) : shippingMethods.length > 0 ? (_jsx("div", { className: "space-y-3", children: shippingMethods.map((method) => (_jsxs("label", { className: cn("flex items-center justify-between p-4 border-2 rounded-xl cursor-pointer transition-all", selectedShippingMethod?.code === method.code
957
1030
  ? "border-black bg-gray-50"
958
1031
  : "border-gray-200 hover:border-gray-300 bg-white"), children: [_jsxs("div", { className: "flex items-center gap-4 flex-1", children: [_jsx("input", { type: "radio", name: "shippingMethod", value: method.code, checked: selectedShippingMethod?.code === method.code, onChange: () => setSelectedShippingMethod(method), className: "w-5 h-5 text-black focus:ring-2 focus:ring-black cursor-pointer" }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("p", { className: "font-bold text-gray-900", children: method.name }), freeShippingEligible && method.cost === 0 && (_jsx("span", { className: "text-xs font-normal text-green-700 bg-green-100 px-2 py-0.5 rounded-full", children: "FREE" }))] }), method.description && (_jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: method.description })), method.estimatedDays && (_jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: method.estimatedDaysMin && method.estimatedDaysMax
959
1032
  ? `Estimated delivery: ${method.estimatedDaysMin}-${method.estimatedDaysMax} business days`
@@ -88,6 +88,12 @@ export declare function getCurrencyFromItems(items: Array<{
88
88
  currency?: string;
89
89
  };
90
90
  }>, fallbackCurrency?: string): string;
91
+ /**
92
+ * Remove all Payaza SDK DOM nodes (loader overlay + invisible iframe).
93
+ * The SDK appends a fullscreen fixed iframe at max z-index; if checkout never
94
+ * loads, only the spinner is removed and the invisible iframe blocks all clicks.
95
+ */
96
+ export declare function teardownPayazaCheckoutDom(): void;
91
97
  /**
92
98
  * Initialize Payaza checkout using SDK
93
99
  * Uses setup() function from payaza-web-sdk npm package as per official documentation
@@ -1 +1 @@
1
- {"version":3,"file":"payaza-checkout.d.ts","sourceRoot":"","sources":["../../src/lib/payaza-checkout.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"payaza-checkout.d.ts","sourceRoot":"","sources":["../../src/lib/payaza-checkout.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,7 @@
6
6
  * Method: setup(config).showPopup()
7
7
  */
8
8
  import PayazaCheckout from 'payaza-web-sdk';
9
+ import { normalizePhoneForPayaza } from '../lib/utils/phone-format.js';
9
10
  export function validateAndNormalizeCurrency(currency) {
10
11
  if (!currency || typeof currency !== 'string') {
11
12
  throw new Error('Currency is required and must be a string');
@@ -78,6 +79,31 @@ export function getCurrencyFromItems(items, fallbackCurrency) {
78
79
  const mostCommonCurrency = Object.entries(currencyCounts).reduce((a, b) => currencyCounts[a[0]] > currencyCounts[b[0]] ? a : b)[0];
79
80
  return mostCommonCurrency;
80
81
  }
82
+ /**
83
+ * Remove all Payaza SDK DOM nodes (loader overlay + invisible iframe).
84
+ * The SDK appends a fullscreen fixed iframe at max z-index; if checkout never
85
+ * loads, only the spinner is removed and the invisible iframe blocks all clicks.
86
+ */
87
+ export function teardownPayazaCheckoutDom() {
88
+ if (typeof document === 'undefined' || !document.body)
89
+ return;
90
+ document.querySelectorAll('style').forEach((styleEl) => {
91
+ if (styleEl.textContent?.includes('payaza-loading-spinner')) {
92
+ styleEl.remove();
93
+ }
94
+ });
95
+ document.querySelectorAll('div').forEach((div) => {
96
+ if (div.innerHTML.includes('payaza-loading-spinner')) {
97
+ div.remove();
98
+ }
99
+ });
100
+ document.querySelectorAll('iframe[src*="payaza.africa"], iframe[src*="shopaza.africa"]').forEach((iframe) => {
101
+ iframe.remove();
102
+ });
103
+ if (document.body.style.overflow === 'hidden') {
104
+ document.body.style.overflow = '';
105
+ }
106
+ }
81
107
  /**
82
108
  * Initialize Payaza checkout using SDK
83
109
  * Uses setup() function from payaza-web-sdk npm package as per official documentation
@@ -98,6 +124,19 @@ export const initiatePayazaCheckout = async (config) => {
98
124
  }
99
125
  console.log('[Payaza] Initiating checkout with npm package...');
100
126
  console.log('[Payaza] SDK verified ready, proceeding with checkout setup');
127
+ if (!Number.isFinite(config.amount) || config.amount <= 0) {
128
+ throw new Error('Invalid payment amount. Please refresh and try again.');
129
+ }
130
+ if (!config.email?.trim()) {
131
+ throw new Error('Email is required for payment.');
132
+ }
133
+ if (!config.firstName?.trim()) {
134
+ throw new Error('First name is required for payment.');
135
+ }
136
+ if (!config.lastName?.trim()) {
137
+ throw new Error('Last name is required for payment.');
138
+ }
139
+ const normalizedPhone = config.phone ? normalizePhoneForPayaza(config.phone) : '';
101
140
  // Generate transaction reference
102
141
  const reference = config.reference || `PL${Math.floor((Math.random() * 10000000) + 1)}`;
103
142
  console.log('[Payaza] Transaction reference:', reference);
@@ -141,8 +180,8 @@ export const initiatePayazaCheckout = async (config) => {
141
180
  if (config.lastName) {
142
181
  additionalDetails.customerLastName = config.lastName;
143
182
  }
144
- if (config.phone) {
145
- additionalDetails.customerPhone = config.phone;
183
+ if (normalizedPhone) {
184
+ additionalDetails.customerPhone = normalizedPhone;
146
185
  }
147
186
  // Shipping address information
148
187
  if (config.shippingAddress) {
@@ -176,7 +215,7 @@ export const initiatePayazaCheckout = async (config) => {
176
215
  email_address: config.email,
177
216
  first_name: config.firstName || '', // Required field
178
217
  last_name: config.lastName || '', // Required field
179
- phone_number: config.phone || '',
218
+ phone_number: normalizedPhone,
180
219
  transaction_reference: reference,
181
220
  // Required by interface, but we'll override with setCallback/setOnClose
182
221
  onClose: () => { },
@@ -198,18 +237,7 @@ export const initiatePayazaCheckout = async (config) => {
198
237
  let loaderTimeoutId = null;
199
238
  let checkoutLoadedFired = false;
200
239
  const removePayazaLoaderDom = () => {
201
- if (typeof document === 'undefined' || !document.body)
202
- return;
203
- document.querySelectorAll('style').forEach((styleEl) => {
204
- if (styleEl.textContent?.includes('payaza-loading-spinner')) {
205
- styleEl.remove();
206
- }
207
- });
208
- document.querySelectorAll('div').forEach((div) => {
209
- if (div.innerHTML.includes('payaza-loading-spinner')) {
210
- div.remove();
211
- }
212
- });
240
+ teardownPayazaCheckoutDom();
213
241
  };
214
242
  const clearLoaderTimeout = () => {
215
243
  if (loaderTimeoutId) {
@@ -297,12 +325,13 @@ export const initiatePayazaCheckout = async (config) => {
297
325
  message: errorMessage,
298
326
  data: errorData,
299
327
  });
300
- // Call the error handler and clear close-check interval
301
328
  clearCloseCheckInterval();
329
+ clearLoaderTimeout();
330
+ removePayazaLoaderDom();
302
331
  if (config.onError) {
303
332
  config.onError(errorMessage);
304
333
  }
305
- return; // Exit early on error
334
+ return;
306
335
  }
307
336
  // Handle success responses
308
337
  if (isSuccessResponse && callbackResponse.data) {
@@ -371,6 +400,8 @@ export const initiatePayazaCheckout = async (config) => {
371
400
  data: callbackResponse.data,
372
401
  });
373
402
  clearCloseCheckInterval();
403
+ clearLoaderTimeout();
404
+ removePayazaLoaderDom();
374
405
  if (config.onError) {
375
406
  config.onError('Received unexpected response from payment gateway. Please try again.');
376
407
  }
@@ -407,14 +438,29 @@ export const initiatePayazaCheckout = async (config) => {
407
438
  }, PAYAZA_CLOSE_CHECK_MS);
408
439
  closeCheckIntervalId = checkLoadedAndClose;
409
440
  loaderTimeoutId = setTimeout(() => {
410
- if (closeHandled)
441
+ if (closeHandled || checkoutLoadedFired)
411
442
  return;
412
- if (!hasPayazaLoaderInDom())
443
+ const loaderStillVisible = hasPayazaLoaderInDom();
444
+ const iframePresent = Boolean(typeof document !== 'undefined' &&
445
+ document.querySelector('iframe[src*="payaza.africa"], iframe[src*="shopaza.africa"]'));
446
+ if (!loaderStillVisible && !iframePresent)
413
447
  return;
414
- console.error('[Payaza] Loader timeout: checkout overlay did not clear');
415
- removePayazaLoaderDom();
448
+ console.error('[Payaza] Loader timeout: checkout overlay did not clear', {
449
+ keyPrefix: config.publicKey?.substring(0, 8),
450
+ amount: config.amount,
451
+ currency: validatedCurrency,
452
+ connectionMode,
453
+ email: config.email,
454
+ reference,
455
+ loaderStillVisible,
456
+ iframePresent,
457
+ });
458
+ teardownPayazaCheckoutDom();
459
+ closeHandled = true;
416
460
  clearCloseCheckInterval();
417
461
  clearLoaderTimeout();
462
+ checkoutLoadedFired = true;
463
+ config.onCheckoutLoaded?.();
418
464
  if (config.onError) {
419
465
  config.onError('Payment window failed to load. This may be caused by browser security settings or a blocked connection to Payaza. Please try again.');
420
466
  }
@@ -1,6 +1,6 @@
1
- import type { CountryCode } from 'libphonenumber-js';
2
- export declare function toCountryCode(isoCode: string | undefined): CountryCode;
3
- export declare function formatPhoneInput(value: string, countryCode: CountryCode): string;
4
- export declare function validatePhone(value: string, countryCode: CountryCode): boolean;
5
- export declare function getPhonePlaceholder(countryCode: CountryCode): string;
1
+ export declare function validatePhone(value: string): boolean;
2
+ export declare function formatPhoneInput(value: string): string;
3
+ export declare function getPhonePlaceholder(): string;
4
+ /** Strip spaces/dashes/parens before sending to Payaza SDK. Keeps + and digits. */
5
+ export declare function normalizePhoneForPayaza(value: string): string;
6
6
  //# sourceMappingURL=phone-format.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"phone-format.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/phone-format.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CAGtE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,GAAG,MAAM,CAGhF;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,GAAG,OAAO,CAQ9E;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,CAOpE"}
1
+ {"version":3,"file":"phone-format.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/phone-format.ts"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAKpD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,mFAAmF;AACnF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7D"}
@@ -1,31 +1,17 @@
1
- import { AsYouType, isValidPhoneNumber, getExampleNumber } from 'libphonenumber-js';
2
- import examples from 'libphonenumber-js/mobile/examples';
3
- export function toCountryCode(isoCode) {
4
- if (!isoCode || isoCode.length !== 2)
5
- return 'NG';
6
- return isoCode.toUpperCase();
7
- }
8
- export function formatPhoneInput(value, countryCode) {
9
- const formatter = new AsYouType(countryCode);
10
- return formatter.input(value);
11
- }
12
- export function validatePhone(value, countryCode) {
1
+ export function validatePhone(value) {
13
2
  const trimmed = value.trim();
14
3
  if (!trimmed)
15
4
  return false;
16
- try {
17
- return isValidPhoneNumber(trimmed, countryCode);
18
- }
19
- catch {
20
- return false;
21
- }
5
+ const digits = trimmed.replace(/\D/g, '');
6
+ return digits.length >= 7 && digits.length <= 15;
7
+ }
8
+ export function formatPhoneInput(value) {
9
+ return value;
10
+ }
11
+ export function getPhonePlaceholder() {
12
+ return 'e.g. +234 801 234 5678';
22
13
  }
23
- export function getPhonePlaceholder(countryCode) {
24
- try {
25
- const example = getExampleNumber(countryCode, examples);
26
- return example?.formatInternational() ?? '+234 801 234 5678';
27
- }
28
- catch {
29
- return '+234 801 234 5678';
30
- }
14
+ /** Strip spaces/dashes/parens before sending to Payaza SDK. Keeps + and digits. */
15
+ export function normalizePhoneForPayaza(value) {
16
+ return value.trim().replace(/[\s\-\(\)]/g, '');
31
17
  }
@@ -1,4 +1,4 @@
1
- /*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
1
+ /*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
2
2
  @layer properties;
3
3
  @layer theme, base, components, utilities;
4
4
  @layer theme {
@@ -344,6 +344,9 @@
344
344
  .\!visible {
345
345
  visibility: visible !important;
346
346
  }
347
+ .invisible {
348
+ visibility: hidden;
349
+ }
347
350
  .visible {
348
351
  visibility: visible;
349
352
  }
@@ -428,10 +431,10 @@
428
431
  top: calc(var(--spacing) * 1.5);
429
432
  }
430
433
  .top-1\/2 {
431
- top: calc(1/2 * 100%);
434
+ top: calc(1 / 2 * 100%);
432
435
  }
433
436
  .top-1\/4 {
434
- top: calc(1/4 * 100%);
437
+ top: calc(1 / 4 * 100%);
435
438
  }
436
439
  .top-2 {
437
440
  top: calc(var(--spacing) * 2);
@@ -518,7 +521,7 @@
518
521
  right: calc(var(--spacing) * 1);
519
522
  }
520
523
  .right-1\/4 {
521
- right: calc(1/4 * 100%);
524
+ right: calc(1 / 4 * 100%);
522
525
  }
523
526
  .right-2 {
524
527
  right: calc(var(--spacing) * 2);
@@ -578,7 +581,7 @@
578
581
  bottom: calc(var(--spacing) * 1.5);
579
582
  }
580
583
  .bottom-1\/4 {
581
- bottom: calc(1/4 * 100%);
584
+ bottom: calc(1 / 4 * 100%);
582
585
  }
583
586
  .bottom-2 {
584
587
  bottom: calc(var(--spacing) * 2);
@@ -626,10 +629,10 @@
626
629
  left: calc(var(--spacing) * 1);
627
630
  }
628
631
  .left-1\/2 {
629
- left: calc(1/2 * 100%);
632
+ left: calc(1 / 2 * 100%);
630
633
  }
631
634
  .left-1\/4 {
632
- left: calc(1/4 * 100%);
635
+ left: calc(1 / 4 * 100%);
633
636
  }
634
637
  .left-2 {
635
638
  left: calc(var(--spacing) * 2);
@@ -1085,7 +1088,7 @@
1085
1088
  height: calc(var(--spacing) * 1.5);
1086
1089
  }
1087
1090
  .h-1\/4 {
1088
- height: calc(1/4 * 100%);
1091
+ height: calc(1 / 4 * 100%);
1089
1092
  }
1090
1093
  .h-2 {
1091
1094
  height: calc(var(--spacing) * 2);
@@ -1100,7 +1103,7 @@
1100
1103
  height: calc(var(--spacing) * 3.5);
1101
1104
  }
1102
1105
  .h-3\/4 {
1103
- height: calc(3/4 * 100%);
1106
+ height: calc(3 / 4 * 100%);
1104
1107
  }
1105
1108
  .h-4 {
1106
1109
  height: calc(var(--spacing) * 4);
@@ -1349,13 +1352,13 @@
1349
1352
  width: calc(var(--spacing) * 1.5);
1350
1353
  }
1351
1354
  .w-1\/2 {
1352
- width: calc(1/2 * 100%);
1355
+ width: calc(1 / 2 * 100%);
1353
1356
  }
1354
1357
  .w-1\/3 {
1355
- width: calc(1/3 * 100%);
1358
+ width: calc(1 / 3 * 100%);
1356
1359
  }
1357
1360
  .w-1\/4 {
1358
- width: calc(1/4 * 100%);
1361
+ width: calc(1 / 4 * 100%);
1359
1362
  }
1360
1363
  .w-2 {
1361
1364
  width: calc(var(--spacing) * 2);
@@ -1364,10 +1367,10 @@
1364
1367
  width: calc(var(--spacing) * 2.5);
1365
1368
  }
1366
1369
  .w-2\/3 {
1367
- width: calc(2/3 * 100%);
1370
+ width: calc(2 / 3 * 100%);
1368
1371
  }
1369
1372
  .w-2\/5 {
1370
- width: calc(2/5 * 100%);
1373
+ width: calc(2 / 5 * 100%);
1371
1374
  }
1372
1375
  .w-3 {
1373
1376
  width: calc(var(--spacing) * 3);
@@ -1376,7 +1379,7 @@
1376
1379
  width: calc(var(--spacing) * 3.5);
1377
1380
  }
1378
1381
  .w-3\/4 {
1379
- width: calc(3/4 * 100%);
1382
+ width: calc(3 / 4 * 100%);
1380
1383
  }
1381
1384
  .w-4 {
1382
1385
  width: calc(var(--spacing) * 4);
@@ -1385,13 +1388,13 @@
1385
1388
  width: calc(var(--spacing) * 4.5);
1386
1389
  }
1387
1390
  .w-4\/6 {
1388
- width: calc(4/6 * 100%);
1391
+ width: calc(4 / 6 * 100%);
1389
1392
  }
1390
1393
  .w-5 {
1391
1394
  width: calc(var(--spacing) * 5);
1392
1395
  }
1393
1396
  .w-5\/6 {
1394
- width: calc(5/6 * 100%);
1397
+ width: calc(5 / 6 * 100%);
1395
1398
  }
1396
1399
  .w-6 {
1397
1400
  width: calc(var(--spacing) * 6);
@@ -1652,7 +1655,7 @@
1652
1655
  transform-origin: 0;
1653
1656
  }
1654
1657
  .-translate-x-1\/2 {
1655
- --tw-translate-x: calc(calc(1/2 * 100%) * -1);
1658
+ --tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
1656
1659
  translate: var(--tw-translate-x) var(--tw-translate-y);
1657
1660
  }
1658
1661
  .-translate-x-2 {
@@ -1672,7 +1675,7 @@
1672
1675
  translate: var(--tw-translate-x) var(--tw-translate-y);
1673
1676
  }
1674
1677
  .translate-x-1\/2 {
1675
- --tw-translate-x: calc(1/2 * 100%);
1678
+ --tw-translate-x: calc(1 / 2 * 100%);
1676
1679
  translate: var(--tw-translate-x) var(--tw-translate-y);
1677
1680
  }
1678
1681
  .translate-x-2 {
@@ -1708,7 +1711,7 @@
1708
1711
  translate: var(--tw-translate-x) var(--tw-translate-y);
1709
1712
  }
1710
1713
  .-translate-y-1\/2 {
1711
- --tw-translate-y: calc(calc(1/2 * 100%) * -1);
1714
+ --tw-translate-y: calc(calc(1 / 2 * 100%) * -1);
1712
1715
  translate: var(--tw-translate-x) var(--tw-translate-y);
1713
1716
  }
1714
1717
  .-translate-y-4 {
@@ -1875,6 +1878,24 @@
1875
1878
  .scroll-pt-1 {
1876
1879
  scroll-padding-top: calc(var(--spacing) * 1);
1877
1880
  }
1881
+ .scrollbar-thin {
1882
+ scrollbar-width: thin;
1883
+ }
1884
+ .scrollbar-thumb-gray-200 {
1885
+ --tw-scrollbar-thumb: var(--color-gray-200);
1886
+ scrollbar-color: var(--tw-scrollbar-thumb) var(--tw-scrollbar-track);
1887
+ }
1888
+ .scrollbar-thumb-white\/10 {
1889
+ --tw-scrollbar-thumb: color-mix(in srgb, #fff 10%, transparent);
1890
+ @supports (color: color-mix(in lab, red, red)) {
1891
+ --tw-scrollbar-thumb: color-mix(in oklab, var(--color-white) 10%, transparent);
1892
+ }
1893
+ scrollbar-color: var(--tw-scrollbar-thumb) var(--tw-scrollbar-track);
1894
+ }
1895
+ .scrollbar-track-transparent {
1896
+ --tw-scrollbar-track: transparent;
1897
+ scrollbar-color: var(--tw-scrollbar-thumb) var(--tw-scrollbar-track);
1898
+ }
1878
1899
  .list-decimal {
1879
1900
  list-style-type: decimal;
1880
1901
  }
@@ -2458,6 +2479,9 @@
2458
2479
  .border-amber-100 {
2459
2480
  border-color: var(--color-amber-100);
2460
2481
  }
2482
+ .border-amber-200 {
2483
+ border-color: var(--color-amber-200);
2484
+ }
2461
2485
  .border-black {
2462
2486
  border-color: var(--color-black);
2463
2487
  }
@@ -4469,6 +4493,9 @@
4469
4493
  color: color-mix(in oklab, var(--color-amber-500) 80%, transparent);
4470
4494
  }
4471
4495
  }
4496
+ .text-amber-600 {
4497
+ color: var(--color-amber-600);
4498
+ }
4472
4499
  .text-black {
4473
4500
  color: var(--color-black);
4474
4501
  }
@@ -8822,12 +8849,12 @@
8822
8849
  }
8823
8850
  .lg\:w-2\/5 {
8824
8851
  @media (width >= 64rem) {
8825
- width: calc(2/5 * 100%);
8852
+ width: calc(2 / 5 * 100%);
8826
8853
  }
8827
8854
  }
8828
8855
  .lg\:w-3\/5 {
8829
8856
  @media (width >= 64rem) {
8830
- width: calc(3/5 * 100%);
8857
+ width: calc(3 / 5 * 100%);
8831
8858
  }
8832
8859
  }
8833
8860
  .lg\:w-72 {
@@ -9682,6 +9709,16 @@
9682
9709
  inherits: false;
9683
9710
  initial-value: proximity;
9684
9711
  }
9712
+ @property --tw-scrollbar-thumb {
9713
+ syntax: "<color>";
9714
+ inherits: false;
9715
+ initial-value: #0000;
9716
+ }
9717
+ @property --tw-scrollbar-track {
9718
+ syntax: "<color>";
9719
+ inherits: false;
9720
+ initial-value: #0000;
9721
+ }
9685
9722
  @property --tw-space-y-reverse {
9686
9723
  syntax: "*";
9687
9724
  inherits: false;
@@ -10001,6 +10038,8 @@
10001
10038
  --tw-skew-x: initial;
10002
10039
  --tw-skew-y: initial;
10003
10040
  --tw-scroll-snap-strictness: proximity;
10041
+ --tw-scrollbar-thumb: #0000;
10042
+ --tw-scrollbar-track: #0000;
10004
10043
  --tw-space-y-reverse: 0;
10005
10044
  --tw-space-x-reverse: 0;
10006
10045
  --tw-divide-y-reverse: 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payaza-storefront-layouts",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
4
4
  "type": "module",
5
5
  "description": "Shared layout components for Storefront applications",
6
6
  "main": "dist/index.js",
@@ -123,7 +123,6 @@
123
123
  "class-variance-authority": "^0.7.1",
124
124
  "clsx": "^2.1.1",
125
125
  "isomorphic-dompurify": "^2.28.0",
126
- "libphonenumber-js": "^1.12.9",
127
126
  "lucide-react": "^0.554.0",
128
127
  "nprogress": "^0.2.0",
129
128
  "payaza-web-sdk": "^1.0.4",