diff --git a/src/App.tsx b/src/App.tsx index aff238b..de183fc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,19 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom'; import Cashier from '@/features/cashier/Cashier'; import { CashierProvider } from '@/features/cashier/context/CashierContext'; +import CashierStateHandler from '@/features/cashier/CashierStateHandler/CashierStateHandler'; import PaymentStatus from '@/pages/PaymentStatus/PaymentStatus'; import '@/App.scss'; +import Status from './components/Status/Status'; function App() { return (
+ - } /> + } errorElement={} /> } /> diff --git a/src/components/Loading/Loading.scss b/src/components/Loading/Loading.scss index 810a9b2..309ef1f 100644 --- a/src/components/Loading/Loading.scss +++ b/src/components/Loading/Loading.scss @@ -8,12 +8,28 @@ } .loading__spinner { - width: 40px; - height: 40px; - border: 4px solid #f3f3f3; + width: 48px; + height: 48px; + border: 4px solid rgba(100, 108, 255, 0.1); border-top: 4px solid #646cff; + border-right: 4px solid #646cff; border-radius: 50%; - animation: spin 1s linear infinite; + animation: spin 0.8s linear infinite; + position: relative; +} + +.loading__spinner::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 24px; + height: 24px; + margin: -12px 0 0 -12px; + border: 3px solid rgba(100, 108, 255, 0.2); + border-top: 3px solid #646cff; + border-radius: 50%; + animation: spin 0.6s linear infinite reverse; } @keyframes spin { diff --git a/src/components/Loading/Loading.tsx b/src/components/Loading/Loading.tsx index 26bbc10..feb0edc 100644 --- a/src/components/Loading/Loading.tsx +++ b/src/components/Loading/Loading.tsx @@ -1,10 +1,14 @@ import './Loading.scss'; -function Loading() { +interface ILoadingProps { + message?: string; +} + +function Loading({ message = 'Loading payment methods...' }: ILoadingProps) { return (
-

Loading payment methods...

+

{message}

); } diff --git a/src/config/api.ts b/src/config/api.ts index abf795b..a8ff1d2 100644 --- a/src/config/api.ts +++ b/src/config/api.ts @@ -12,13 +12,6 @@ export const TMerchant = { export type TMerchant = (typeof TMerchant)[keyof typeof TMerchant]; -export const MERCHANT_API_KEYS: Record = { - [TMerchant.DATA_SPIN]: '10000000-0000-0000-0000-000000000001', - [TMerchant.WIN_BOT]: '20000000-0000-0000-0000-000000000002', - [TMerchant.VARK_SERVICES]: '30000000-0000-0000-0000-000000000003', - [TMerchant.BETRISE]: '40000000-0000-0000-0000-000000000004', -}; - export const MERCHANT_IDS: Record = { [TMerchant.DATA_SPIN]: '10000000-0000-0000-0000-000000000001', [TMerchant.WIN_BOT]: '20000000-0000-0000-0000-000000000002', @@ -27,4 +20,4 @@ export const MERCHANT_IDS: Record = { }; // Default merchant (can be changed based on requirements) -export const DEFAULT_MERCHANT = TMerchant.WIN_BOT; +export const DEFAULT_MERCHANT = TMerchant.BETRISE; diff --git a/src/config/cashierConfig.ts b/src/config/cashierConfig.ts index 24510b9..c0113d1 100644 --- a/src/config/cashierConfig.ts +++ b/src/config/cashierConfig.ts @@ -41,10 +41,13 @@ export interface ICashierConfig { export function getCashierConfig(): ICashierConfig { const params = new URLSearchParams(window.location.search); - const config: ICashierConfig = {}; + const config: ICashierConfig = { + paymentType: (params.get('payment_type') as 'deposit' | 'withdrawal') || 'deposit', + }; // Payment type from URL (support both 'method' and 'payment_type' for backward compatibility) const method = params.get('method'); + const paymentType = params.get('payment_type') || method; if (paymentType === 'deposit' || paymentType === 'withdrawal') { config.paymentType = paymentType; @@ -61,10 +64,13 @@ export function getCashierConfig(): ICashierConfig { if (amount && amount !== 'undefined') { const parsedAmount = parseFloat(amount); if (!isNaN(parsedAmount)) { - config.amount = parsedAmount; + config.amount = 200; } } + // TODO: remove this default value + config.amount = 200; + // Account from URL (for withdrawal) const account = params.get('account'); if (account) { @@ -81,6 +87,9 @@ export function getCashierConfig(): ICashierConfig { if (userId) { config.userId = userId; } + // TODO: remove this default value + config.userId = '12345'; + const sessionId = params.get('sessionId'); if (sessionId) { diff --git a/src/features/cashier/Cashier.tsx b/src/features/cashier/Cashier.tsx index 687aa9c..d14ab1e 100644 --- a/src/features/cashier/Cashier.tsx +++ b/src/features/cashier/Cashier.tsx @@ -1,9 +1,6 @@ -import { useEffect } from 'react'; import PaymentMethodsList from '@/features/payment-methods/PaymentMethodsList/PaymentMethodsList'; import EmptyPaymentMethods from '@/features/payment-methods/EmptyPaymentMethods/EmptyPaymentMethods'; import PaymentForm from './PaymentForm/PaymentForm'; -import Loading from '@/components/Loading/Loading'; -import Status from '@/components/Status/Status'; import type { IPaymentMethod } from '@/features/payment-methods/types'; import type { IPaymentRequest } from './types'; import { useCashier } from './context/CashierContext'; @@ -16,26 +13,22 @@ function Cashier() { currencies, selectedMethod, formMetadata, - error, - paymentUrl, - goBack, setSelectedMethod, initiatePayment, } = useCashier(); - // Redirect to payment URL when state is redirecting - useEffect(() => { - if (state === 'redirecting' && paymentUrl) { - window.location.href = paymentUrl; - } - }, [state, paymentUrl]); - const handleMethodSelect = (method: IPaymentMethod) => { setSelectedMethod(method); }; const handleFormSubmit = async (formData: Record) => { try { + const config = getCashierConfig(); + + if (!config.userId) { + return; + } + // Transform form data to match IPaymentRequest structure const additionalFields: Record = {}; if (formData.account) { @@ -59,13 +52,14 @@ function Cashier() { } }); + const paymentRequest: Omit = { payment_type: (formData.payment_type as 'deposit' | 'withdrawal') || 'deposit', method: (formData.method as string) || '', currency: (formData.currency as string) || '', amount: (formData.amount as number) || 0, customer: (formData.customer as IPaymentRequest['customer']) || { - id: '', + id: config.userId || '', first_name: '', last_name: '', email: '', @@ -83,19 +77,7 @@ function Cashier() { setSelectedMethod(null); }; - if (state === 'loading') { - return ; - } - - if (state === 'error' && error) { - return goBack()} />; - } - - if (state === 'redirecting') { - return
Redirecting to payment...
; - } - - if (selectedMethod) { + if (selectedMethod && state === 'ready' && methods.length > 0) { return ( ); } diff --git a/src/features/cashier/CashierStateHandler/CashierStateHandler.tsx b/src/features/cashier/CashierStateHandler/CashierStateHandler.tsx new file mode 100644 index 0000000..6bc5238 --- /dev/null +++ b/src/features/cashier/CashierStateHandler/CashierStateHandler.tsx @@ -0,0 +1,43 @@ +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; +import Loading from '@/components/Loading/Loading'; +import Status from '@/components/Status/Status'; +import { useCashier } from '../context/CashierContext'; + +function CashierStateHandler() { + const { state, error, paymentUrl, goBack } = useCashier(); + const location = useLocation(); + const isCashierRoute = location.pathname === '/'; + + // Redirect to payment URL when state is redirecting + useEffect(() => { + if (state === 'redirecting' && paymentUrl) { + window.location.href = paymentUrl; + } + }, [state, paymentUrl]); + + if (!isCashierRoute) { + return null; + } + + if (state === 'loading') { + return ; + } + + if (state === 'submitting') { + return ; + } + + if (state === 'error' && error) { + return goBack()} />; + } + + if (state === 'redirecting') { + return ; + } + + return null; +} + +export default CashierStateHandler; + diff --git a/src/features/cashier/PaymentForm/PaymentForm.tsx b/src/features/cashier/PaymentForm/PaymentForm.tsx index 575550b..08be8e0 100644 --- a/src/features/cashier/PaymentForm/PaymentForm.tsx +++ b/src/features/cashier/PaymentForm/PaymentForm.tsx @@ -41,7 +41,7 @@ function PaymentForm({ type: config?.paymentType || 'deposit', method: method.code, currency: currencies.length > 0 ? currencies[0]?.code : '', - amount: '', + amount: config?.amount || 0, }; // Initialize customer fields from config @@ -52,7 +52,6 @@ function PaymentForm({ return initial; }); - console.log('currencies', currencies); const parsedFields = useMemo(() => { if (formMetadata?.length === 0) return []; return parseFormFields(formMetadata); @@ -80,7 +79,15 @@ function PaymentForm({ return ''; } } - return String(current || ''); + // Handle numeric values including 0, and null/undefined + if (current === null || current === undefined) { + return ''; + } + // For number inputs, preserve numeric values including 0 + if (field.inputType === 'number' && typeof current === 'number') { + return String(current); + } + return String(current); }; const setFieldValue = (field: IParsedField, value: string): void => { @@ -92,31 +99,6 @@ function PaymentForm({ const value = getFieldValue(field); const fieldId = `field-${field.code}`; - if (field.inputType === 'radio' && field.code === 'type') { - return ( -
- - -
- ); - } - if (field.inputType === 'select' && field.code === 'currency') { return (
@@ -202,8 +184,6 @@ function PaymentForm({ onSubmit(submitData); }; - const showPaymentType = !config.paymentType && groupedFields.payment.some(f => f.code === 'type'); - return (
@@ -215,13 +195,6 @@ function PaymentForm({
- {showPaymentType && groupedFields.payment.some(f => f.code === 'type') && ( -
-

Payment Type

- {groupedFields.payment.filter(f => f.code === 'type').map(renderField)} -
- )} - {groupedFields.payment.filter(f => f.code !== 'type' && f.code !== 'method').length > 0 && (

Payment Information

diff --git a/src/features/cashier/services/CashierService.ts b/src/features/cashier/services/CashierService.ts index c76d1a2..1bc69a9 100644 --- a/src/features/cashier/services/CashierService.ts +++ b/src/features/cashier/services/CashierService.ts @@ -59,7 +59,7 @@ class CashierService { if (method) { try { const config = getCashierConfig(); - const paymentType = config.paymentType || 'deposit'; + const paymentType = config.paymentType || 'withdrawal'; // Fetch form metadata and currencies for the selected method in parallel const [formMetadataData, currenciesData] = await Promise.all([ diff --git a/src/features/cashier/utils/formBuilder.ts b/src/features/cashier/utils/formBuilder.ts index bc09637..31c0c5e 100644 --- a/src/features/cashier/utils/formBuilder.ts +++ b/src/features/cashier/utils/formBuilder.ts @@ -53,6 +53,11 @@ export function shouldShowField( if (code === 'currency' && !config.currency) return true; if (code === 'currency' && config.currency) return false; + // Hide customer.id field - it's set programmatically from config + if (code === 'customer.id' || (field.path[0] === 'customer' && field.path[1] === 'id')) { + return false; + } + // Show all other fields return true; } diff --git a/src/services/api.ts b/src/services/api.ts index 5f897ef..976aa70 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -1,4 +1,4 @@ -import { API_BASE_URL, MERCHANT_API_KEYS, TMerchant, DEFAULT_MERCHANT } from '@/config/api'; +import { API_BASE_URL, MERCHANT_IDS, TMerchant, DEFAULT_MERCHANT } from '@/config/api'; import type { IApiMethodsResponse, IApiCurrency, @@ -16,7 +16,7 @@ class ApiService { private currencies: IApiCurrency[] = []; // Cache for currencies without method filter private getAuthHeader(merchant: TMerchant = DEFAULT_MERCHANT): HeadersInit { - const apiKey = MERCHANT_API_KEYS[merchant]; + const apiKey = MERCHANT_IDS[merchant]; return { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', @@ -88,6 +88,7 @@ class ApiService { type, }); + console.log('params', params.toString()); const response = await fetch(`${API_BASE_URL}/form?${params.toString()}`, { method: 'GET', headers: this.getAuthHeader(merchant),