Added Method currency fetching
This commit is contained in:
parent
e57f170470
commit
2de17b8675
24
src/components/Icons/ApprovedIcon.tsx
Normal file
24
src/components/Icons/ApprovedIcon.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
interface IApprovedIconProps {
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ApprovedIcon({ size = 20 }: IApprovedIconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
aria-label="Approved"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<path d="M9 12l2 2 4-4" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ApprovedIcon;
|
||||||
25
src/components/Icons/CancelledIcon.tsx
Normal file
25
src/components/Icons/CancelledIcon.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
interface ICancelledIconProps {
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CancelledIcon({ size = 20 }: ICancelledIconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
aria-label="Cancelled"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<line x1="15" y1="9" x2="9" y2="15" />
|
||||||
|
<line x1="9" y1="9" x2="15" y2="15" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CancelledIcon;
|
||||||
25
src/components/Icons/ErrorIcon.tsx
Normal file
25
src/components/Icons/ErrorIcon.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
interface IErrorIconProps {
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ErrorIcon({ size = 20 }: IErrorIconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
aria-label="Error"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<line x1="12" y1="7" x2="12" y2="13" />
|
||||||
|
<circle cx="12" cy="17" r="1" fill="currentColor" stroke="none" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorIcon;
|
||||||
@ -23,3 +23,22 @@
|
|||||||
.status--cancel .status__message {
|
.status--cancel .status__message {
|
||||||
color: #f57c00;
|
color: #f57c00;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status__icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status--cancel .status__icon {
|
||||||
|
color: #f57c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status--success .status__icon {
|
||||||
|
color: #2e7d32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status--error .status__icon {
|
||||||
|
color: #d32f2f;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import './Status.scss';
|
import './Status.scss';
|
||||||
import Button from '../Button/Button';
|
import Button from '../Button/Button';
|
||||||
|
import CancelledIcon from '../Icons/CancelledIcon';
|
||||||
|
import ApprovedIcon from '../Icons/ApprovedIcon';
|
||||||
|
import ErrorIcon from '../Icons/ErrorIcon';
|
||||||
|
|
||||||
type TStatusType = 'error' | 'success' | 'cancel';
|
type TStatusType = 'error' | 'success' | 'cancel';
|
||||||
|
|
||||||
@ -18,6 +21,21 @@ function Status({ type, message, onAction, actionLabel }: IStatusProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`status status--${type}`}>
|
<div className={`status status--${type}`}>
|
||||||
|
{type === 'cancel' && (
|
||||||
|
<div className="status__icon">
|
||||||
|
<CancelledIcon size={48} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{type === 'success' && (
|
||||||
|
<div className="status__icon">
|
||||||
|
<ApprovedIcon size={48} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{type === 'error' && (
|
||||||
|
<div className="status__icon">
|
||||||
|
<ErrorIcon size={48} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<p className="status__message">{message}</p>
|
<p className="status__message">{message}</p>
|
||||||
{onAction && <Button onClick={onAction} label={getDefaultActionLabel()} />}
|
{onAction && <Button onClick={onAction} label={getDefaultActionLabel()} />}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import type { IPaymentMethod } from '@/features/payment-methods/types';
|
|||||||
import type { IPaymentRequest } from './types';
|
import type { IPaymentRequest } from './types';
|
||||||
import { useCashier } from './context/CashierContext';
|
import { useCashier } from './context/CashierContext';
|
||||||
import { getCashierConfig } from '@/config/cashierConfig';
|
import { getCashierConfig } from '@/config/cashierConfig';
|
||||||
import { cashierService } from './services/CashierService';
|
|
||||||
|
|
||||||
function Cashier() {
|
function Cashier() {
|
||||||
const {
|
const {
|
||||||
@ -42,11 +41,19 @@ function Cashier() {
|
|||||||
if (formData.account) {
|
if (formData.account) {
|
||||||
additionalFields.account = formData.account as string;
|
additionalFields.account = formData.account as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve any additional fields
|
// Preserve any additional fields
|
||||||
Object.entries(formData).forEach(([key, value]) => {
|
Object.entries(formData).forEach(([key, value]) => {
|
||||||
if (
|
if (
|
||||||
!['payment_type', 'method', 'currency', 'amount', 'customer', 'redirect', 'account'].includes(key)
|
![
|
||||||
|
'payment_type',
|
||||||
|
'method',
|
||||||
|
'currency',
|
||||||
|
'amount',
|
||||||
|
'customer',
|
||||||
|
'redirect',
|
||||||
|
'account',
|
||||||
|
].includes(key)
|
||||||
) {
|
) {
|
||||||
additionalFields[key] = value;
|
additionalFields[key] = value;
|
||||||
}
|
}
|
||||||
@ -81,7 +88,7 @@ function Cashier() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state === 'error' && error) {
|
if (state === 'error' && error) {
|
||||||
return <Status type="error" message={`Error: ${error.message}`} onAction={()=> goBack()}/>;
|
return <Status type="error" message={`Error: ${error.message}`} onAction={() => goBack()} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state === 'redirecting') {
|
if (state === 'redirecting') {
|
||||||
@ -91,6 +98,7 @@ function Cashier() {
|
|||||||
if (selectedMethod) {
|
if (selectedMethod) {
|
||||||
return (
|
return (
|
||||||
<PaymentForm
|
<PaymentForm
|
||||||
|
key={selectedMethod.code}
|
||||||
method={selectedMethod}
|
method={selectedMethod}
|
||||||
currencies={currencies}
|
currencies={currencies}
|
||||||
formMetadata={formMetadata}
|
formMetadata={formMetadata}
|
||||||
|
|||||||
@ -40,7 +40,7 @@ function PaymentForm({
|
|||||||
const initial: Record<string, unknown> = {
|
const initial: Record<string, unknown> = {
|
||||||
type: config?.paymentType || 'deposit',
|
type: config?.paymentType || 'deposit',
|
||||||
method: method.code,
|
method: method.code,
|
||||||
currency: config?.currency || currencies[0]?.code || '',
|
currency: currencies.length > 0 ? currencies[0]?.code : '',
|
||||||
amount: '',
|
amount: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -52,8 +52,9 @@ function PaymentForm({
|
|||||||
return initial;
|
return initial;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('currencies', currencies);
|
||||||
const parsedFields = useMemo(() => {
|
const parsedFields = useMemo(() => {
|
||||||
if (formMetadata?. length === 0) return [];
|
if (formMetadata?.length === 0) return [];
|
||||||
return parseFormFields(formMetadata);
|
return parseFormFields(formMetadata);
|
||||||
}, [formMetadata]);
|
}, [formMetadata]);
|
||||||
|
|
||||||
|
|||||||
@ -69,9 +69,7 @@ export function CashierProvider({ children }: ICashierProviderProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Initial load
|
// Initial load
|
||||||
cashierService.loadData(DEFAULT_MERCHANT).catch(() => {
|
cashierService.loadData(DEFAULT_MERCHANT);
|
||||||
// Error is handled by state
|
|
||||||
});
|
|
||||||
|
|
||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@ -60,12 +60,24 @@ class CashierService {
|
|||||||
try {
|
try {
|
||||||
const config = getCashierConfig();
|
const config = getCashierConfig();
|
||||||
const paymentType = config.paymentType || 'deposit';
|
const paymentType = config.paymentType || 'deposit';
|
||||||
this.formMetadata = await apiService.getFormMetadata(method.code, paymentType, merchant);
|
|
||||||
|
// Fetch form metadata and currencies for the selected method in parallel
|
||||||
|
const [formMetadataData, currenciesData] = await Promise.all([
|
||||||
|
apiService.getFormMetadata(method.code, paymentType, merchant),
|
||||||
|
apiService.getCurrencies(merchant, method.code),
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.formMetadata = formMetadataData;
|
||||||
|
this.currencies = currenciesData;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// If form metadata fetch fails, we still allow method selection
|
// If form metadata or currencies fetch fails, we still allow method selection
|
||||||
// Error can be handled by the UI if needed
|
// Error can be handled by the UI if needed
|
||||||
console.error('Failed to fetch form metadata:', err);
|
console.error('Failed to fetch form metadata or currencies:', err);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Reset currencies when method is deselected
|
||||||
|
// Optionally reload all currencies or keep empty
|
||||||
|
this.currencies = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.notifyListeners();
|
this.notifyListeners();
|
||||||
@ -94,13 +106,11 @@ class CashierService {
|
|||||||
this.setState('loading');
|
this.setState('loading');
|
||||||
this.error = null;
|
this.error = null;
|
||||||
|
|
||||||
const [methodsData, currenciesData] = await Promise.all([
|
// Only fetch methods, currencies will be fetched when a method is selected
|
||||||
apiService.getMethods(merchant),
|
const methodsData = await apiService.getMethods(merchant);
|
||||||
apiService.getCurrencies(merchant),
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.methods = methodsData;
|
this.methods = methodsData;
|
||||||
this.currencies = currenciesData;
|
this.currencies = []; // Currencies will be loaded when method is selected
|
||||||
this.setState('ready');
|
this.setState('ready');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.error = err instanceof Error ? err : new Error('Failed to load cashier data');
|
this.error = err instanceof Error ? err : new Error('Failed to load cashier data');
|
||||||
@ -137,6 +147,7 @@ class CashierService {
|
|||||||
this.state = 'loading';
|
this.state = 'loading';
|
||||||
this.selectedMethod = null;
|
this.selectedMethod = null;
|
||||||
this.formMetadata = [];
|
this.formMetadata = [];
|
||||||
|
this.currencies = [];
|
||||||
this.error = null;
|
this.error = null;
|
||||||
this.paymentUrl = null;
|
this.paymentUrl = null;
|
||||||
this.notifyListeners();
|
this.notifyListeners();
|
||||||
@ -145,6 +156,7 @@ class CashierService {
|
|||||||
goBack(): void {
|
goBack(): void {
|
||||||
this.selectedMethod = null;
|
this.selectedMethod = null;
|
||||||
this.formMetadata = [];
|
this.formMetadata = [];
|
||||||
|
this.currencies = [];
|
||||||
this.error = null;
|
this.error = null;
|
||||||
// Reset to ready state if we have methods available, otherwise keep current state
|
// Reset to ready state if we have methods available, otherwise keep current state
|
||||||
if (this.methods.length > 0) {
|
if (this.methods.length > 0) {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export function parseFieldCode(code: string): string[] {
|
|||||||
|
|
||||||
export function getInputType(code: string): string {
|
export function getInputType(code: string): string {
|
||||||
const lowerCode = code.toLowerCase();
|
const lowerCode = code.toLowerCase();
|
||||||
|
|
||||||
if (lowerCode.includes('email')) return 'email';
|
if (lowerCode.includes('email')) return 'email';
|
||||||
if (lowerCode.includes('phone')) return 'tel';
|
if (lowerCode.includes('phone')) return 'tel';
|
||||||
if (lowerCode.includes('birthdate') || lowerCode.includes('date')) return 'date';
|
if (lowerCode.includes('birthdate') || lowerCode.includes('date')) return 'date';
|
||||||
@ -21,7 +21,7 @@ export function getInputType(code: string): string {
|
|||||||
if (lowerCode === 'type') return 'radio';
|
if (lowerCode === 'type') return 'radio';
|
||||||
if (lowerCode === 'method') return 'select';
|
if (lowerCode === 'method') return 'select';
|
||||||
if (lowerCode === 'currency') return 'select';
|
if (lowerCode === 'currency') return 'select';
|
||||||
|
|
||||||
return 'text';
|
return 'text';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,18 +41,18 @@ export function shouldShowField(
|
|||||||
config: { paymentType?: 'deposit' | 'withdrawal'; currency?: string }
|
config: { paymentType?: 'deposit' | 'withdrawal'; currency?: string }
|
||||||
): boolean {
|
): boolean {
|
||||||
const code = field.code.toLowerCase();
|
const code = field.code.toLowerCase();
|
||||||
|
|
||||||
// Always show type if not in config
|
// Always show type if not in config
|
||||||
if (code === 'type' && !config.paymentType) return true;
|
if (code === 'type' && !config.paymentType) return true;
|
||||||
if (code === 'type' && config.paymentType) return false;
|
if (code === 'type' && config.paymentType) return false;
|
||||||
|
|
||||||
// Always show method (but we might not render it as it's already selected)
|
// Always show method (but we might not render it as it's already selected)
|
||||||
if (code === 'method') return false; // Method is already selected, don't show in form
|
if (code === 'method') return false; // Method is already selected, don't show in form
|
||||||
|
|
||||||
// Show currency if not in config
|
// Show currency if not in config
|
||||||
if (code === 'currency' && !config.currency) return true;
|
if (code === 'currency' && !config.currency) return true;
|
||||||
if (code === 'currency' && config.currency) return false;
|
if (code === 'currency' && config.currency) return false;
|
||||||
|
|
||||||
// Show all other fields
|
// Show all other fields
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -60,12 +60,12 @@ export function shouldShowField(
|
|||||||
export function buildNestedObject(path: string[], value: unknown): Record<string, unknown> {
|
export function buildNestedObject(path: string[], value: unknown): Record<string, unknown> {
|
||||||
const result: Record<string, unknown> = {};
|
const result: Record<string, unknown> = {};
|
||||||
let current = result;
|
let current = result;
|
||||||
|
|
||||||
for (let i = 0; i < path.length - 1; i++) {
|
for (let i = 0; i < path.length - 1; i++) {
|
||||||
current[path[i]] = {};
|
current[path[i]] = {};
|
||||||
current = current[path[i]] as Record<string, unknown>;
|
current = current[path[i]] as Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
current[path[path.length - 1]] = value;
|
current[path[path.length - 1]] = value;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -75,7 +75,7 @@ export function mergeNestedObjects(
|
|||||||
obj2: Record<string, unknown>
|
obj2: Record<string, unknown>
|
||||||
): Record<string, unknown> {
|
): Record<string, unknown> {
|
||||||
const result = { ...obj1 };
|
const result = { ...obj1 };
|
||||||
|
|
||||||
for (const key in obj2) {
|
for (const key in obj2) {
|
||||||
if (typeof obj2[key] === 'object' && obj2[key] !== null && !Array.isArray(obj2[key])) {
|
if (typeof obj2[key] === 'object' && obj2[key] !== null && !Array.isArray(obj2[key])) {
|
||||||
result[key] = mergeNestedObjects(
|
result[key] = mergeNestedObjects(
|
||||||
@ -86,7 +86,7 @@ export function mergeNestedObjects(
|
|||||||
result[key] = obj2[key];
|
result[key] = obj2[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ export function groupFieldsBySection(fields: IParsedField[]): {
|
|||||||
const payment: IParsedField[] = [];
|
const payment: IParsedField[] = [];
|
||||||
const customer: IParsedField[] = [];
|
const customer: IParsedField[] = [];
|
||||||
const account: IParsedField[] = [];
|
const account: IParsedField[] = [];
|
||||||
|
|
||||||
fields?.forEach(field => {
|
fields?.forEach(field => {
|
||||||
if (field.path[0] === 'customer' && field.path[1] === 'account') {
|
if (field.path[0] === 'customer' && field.path[1] === 'account') {
|
||||||
account.push(field);
|
account.push(field);
|
||||||
@ -108,7 +108,6 @@ export function groupFieldsBySection(fields: IParsedField[]): {
|
|||||||
payment.push(field);
|
payment.push(field);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { payment, customer, account };
|
return { payment, customer, account };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -37,6 +37,18 @@ function PaymentStatus() {
|
|||||||
return 'Close';
|
return 'Close';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getDefaultMessage(type: TCashierResultStatus): string {
|
||||||
|
switch (type) {
|
||||||
|
case 'success':
|
||||||
|
return 'Payment completed successfully!';
|
||||||
|
case 'cancel':
|
||||||
|
return 'Payment was cancelled.';
|
||||||
|
case 'error':
|
||||||
|
return 'An error occurred during payment processing.';
|
||||||
|
default:
|
||||||
|
return 'Payment status unknown.';
|
||||||
|
}
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="payment-status">
|
<div className="payment-status">
|
||||||
<Status
|
<Status
|
||||||
@ -49,17 +61,4 @@ function PaymentStatus() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultMessage(type: TCashierResultStatus): string {
|
|
||||||
switch (type) {
|
|
||||||
case 'success':
|
|
||||||
return 'Payment completed successfully!';
|
|
||||||
case 'cancel':
|
|
||||||
return 'Payment was cancelled.';
|
|
||||||
case 'error':
|
|
||||||
return 'An error occurred during payment processing.';
|
|
||||||
default:
|
|
||||||
return 'Payment status unknown.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PaymentStatus;
|
export default PaymentStatus;
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import type { IPaymentMethod } from '@/features/payment-methods/types';
|
|||||||
|
|
||||||
class ApiService {
|
class ApiService {
|
||||||
private methods: IPaymentMethod[] = [];
|
private methods: IPaymentMethod[] = [];
|
||||||
private currencies: IApiCurrency[] = [];
|
private currencies: IApiCurrency[] = []; // Cache for currencies without method filter
|
||||||
|
|
||||||
private getAuthHeader(merchant: TMerchant = DEFAULT_MERCHANT): HeadersInit {
|
private getAuthHeader(merchant: TMerchant = DEFAULT_MERCHANT): HeadersInit {
|
||||||
const apiKey = MERCHANT_API_KEYS[merchant];
|
const apiKey = MERCHANT_API_KEYS[merchant];
|
||||||
@ -43,11 +43,23 @@ class ApiService {
|
|||||||
return this.methods;
|
return this.methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCurrencies(merchant: TMerchant = DEFAULT_MERCHANT): Promise<IApiCurrency[]> {
|
async getCurrencies(
|
||||||
if (this.currencies.length > 0) {
|
merchant: TMerchant = DEFAULT_MERCHANT,
|
||||||
|
method?: string
|
||||||
|
): Promise<IApiCurrency[]> {
|
||||||
|
// If method is provided, always fetch fresh data (don't use cache)
|
||||||
|
// If no method and we have cached currencies, return cached
|
||||||
|
if (!method && this.currencies.length > 0) {
|
||||||
return this.currencies;
|
return this.currencies;
|
||||||
}
|
}
|
||||||
const response = await fetch(`${API_BASE_URL}/currencies`, {
|
|
||||||
|
let url = `${API_BASE_URL}/currencies`;
|
||||||
|
if (method) {
|
||||||
|
const separator = url.includes('?') ? '&' : '?';
|
||||||
|
url = `${url}${separator}method=${encodeURIComponent(method)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: this.getAuthHeader(merchant),
|
headers: this.getAuthHeader(merchant),
|
||||||
});
|
});
|
||||||
@ -57,6 +69,12 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data: IApiCurrenciesResponse = await response.json();
|
const data: IApiCurrenciesResponse = await response.json();
|
||||||
|
|
||||||
|
// Only cache if no method filter was applied
|
||||||
|
if (!method) {
|
||||||
|
this.currencies = data.data;
|
||||||
|
}
|
||||||
|
|
||||||
return data.data;
|
return data.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +113,9 @@ class ApiService {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
throw new Error(`Failed to initiate payment: ${response.statusText} - ${data?.error?.detail}`);
|
throw new Error(
|
||||||
|
`Failed to initiate payment: ${response.statusText} - ${data?.error?.detail}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user