@djangocfg/ext-payments 1.0.17 → 1.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.cjs +1 -1
- package/dist/config.js +1 -1
- package/dist/index.cjs +1175 -290
- package/dist/index.d.cts +226 -80
- package/dist/index.d.ts +226 -80
- package/dist/index.js +1157 -255
- package/package.json +9 -9
- package/src/WalletPage.tsx +100 -0
- package/src/api/generated/ext_payments/CLAUDE.md +6 -4
- package/src/api/generated/ext_payments/_utils/fetchers/ext_payments__payments.ts +37 -6
- package/src/api/generated/ext_payments/_utils/hooks/ext_payments__payments.ts +34 -3
- package/src/api/generated/ext_payments/_utils/schemas/Balance.schema.ts +1 -1
- package/src/api/generated/ext_payments/_utils/schemas/PaymentCreateResponse.schema.ts +22 -0
- package/src/api/generated/ext_payments/_utils/schemas/PaymentDetail.schema.ts +3 -3
- package/src/api/generated/ext_payments/_utils/schemas/PaymentList.schema.ts +2 -2
- package/src/api/generated/ext_payments/_utils/schemas/Transaction.schema.ts +1 -1
- package/src/api/generated/ext_payments/_utils/schemas/WithdrawalCancelResponse.schema.ts +22 -0
- package/src/api/generated/ext_payments/_utils/schemas/WithdrawalCreateResponse.schema.ts +22 -0
- package/src/api/generated/ext_payments/_utils/schemas/WithdrawalDetail.schema.ts +5 -5
- package/src/api/generated/ext_payments/_utils/schemas/WithdrawalList.schema.ts +2 -2
- package/src/api/generated/ext_payments/_utils/schemas/index.ts +3 -0
- package/src/api/generated/ext_payments/client.ts +1 -1
- package/src/api/generated/ext_payments/ext_payments__payments/client.ts +49 -4
- package/src/api/generated/ext_payments/ext_payments__payments/models.ts +33 -14
- package/src/api/generated/ext_payments/index.ts +1 -1
- package/src/api/generated/ext_payments/schema.json +167 -33
- package/src/components/AddFundsSheet.tsx +157 -73
- package/src/components/CurrencyCombobox.tsx +49 -0
- package/src/components/PaymentSheet.tsx +94 -32
- package/src/components/WithdrawSheet.tsx +121 -95
- package/src/components/WithdrawalSheet.tsx +332 -0
- package/src/components/index.ts +1 -8
- package/src/config.ts +1 -0
- package/src/contexts/WalletContext.tsx +10 -9
- package/src/contexts/index.ts +5 -1
- package/src/contexts/types.ts +46 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useCurrencyOptions.ts +79 -0
- package/src/hooks/useEstimate.ts +113 -0
- package/src/hooks/useWithdrawalEstimate.ts +117 -0
- package/src/index.ts +3 -0
- package/src/types/index.ts +78 -0
- package/src/utils/errors.ts +36 -0
- package/src/utils/format.ts +65 -0
- package/src/utils/index.ts +3 -0
- package/src/components/ResponsiveSheet.tsx +0 -151
package/src/config.ts
CHANGED
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
import React, { createContext, ReactNode, useContext, useMemo, useCallback } from 'react';
|
|
14
14
|
|
|
15
15
|
import { apiPayments } from '../api';
|
|
16
|
-
import * as Fetchers from '../api/generated/ext_payments/_utils/fetchers';
|
|
17
16
|
import {
|
|
18
17
|
usePaymentsBalanceRetrieve,
|
|
19
18
|
usePaymentsPaymentsList,
|
|
@@ -61,8 +60,8 @@ export interface ActivityItem {
|
|
|
61
60
|
export interface Currency {
|
|
62
61
|
code: string;
|
|
63
62
|
name: string;
|
|
63
|
+
token: string;
|
|
64
64
|
network?: string;
|
|
65
|
-
rate: number;
|
|
66
65
|
enabled: boolean;
|
|
67
66
|
}
|
|
68
67
|
|
|
@@ -228,25 +227,27 @@ export function WalletProvider({ children }: { children: ReactNode }) {
|
|
|
228
227
|
.map((c: any) => ({
|
|
229
228
|
code: c.code || c.currency_code || c.symbol,
|
|
230
229
|
name: c.name || c.code,
|
|
230
|
+
token: c.token || c.code, // Token symbol (e.g., USDT, WBTC) with fallback to code
|
|
231
231
|
network: c.network || undefined,
|
|
232
|
-
rate: parseFloat(c.usd_rate || c.rate) || 1,
|
|
233
232
|
enabled: c.is_enabled !== false,
|
|
234
233
|
}));
|
|
235
234
|
}, [currenciesData]);
|
|
236
235
|
|
|
237
236
|
// Operations
|
|
238
237
|
const addFunds = useCallback(async (data: PaymentCreateRequest): Promise<PaymentDetail> => {
|
|
239
|
-
const
|
|
238
|
+
const response = await createPaymentMutation(data, apiPayments);
|
|
240
239
|
// Refresh all data
|
|
241
240
|
await Promise.all([mutateBalance(), mutatePayments(), mutateTransactions()]);
|
|
242
|
-
|
|
241
|
+
// API returns { success, payment, qr_code_url } - extract payment
|
|
242
|
+
return response.payment;
|
|
243
243
|
}, [createPaymentMutation, mutateBalance, mutatePayments, mutateTransactions]);
|
|
244
244
|
|
|
245
245
|
const withdraw = useCallback(async (data: WithdrawalCreateRequest): Promise<WithdrawalDetail> => {
|
|
246
|
-
const
|
|
246
|
+
const response = await createWithdrawalMutation(data, apiPayments);
|
|
247
247
|
// Refresh all data
|
|
248
248
|
await Promise.all([mutateBalance(), mutateWithdrawals(), mutateTransactions()]);
|
|
249
|
-
|
|
249
|
+
// API returns { success, withdrawal, message } - extract withdrawal
|
|
250
|
+
return response.withdrawal;
|
|
250
251
|
}, [createWithdrawalMutation, mutateBalance, mutateWithdrawals, mutateTransactions]);
|
|
251
252
|
|
|
252
253
|
const cancelWithdrawal = useCallback(async (id: string): Promise<void> => {
|
|
@@ -256,11 +257,11 @@ export function WalletProvider({ children }: { children: ReactNode }) {
|
|
|
256
257
|
}, [cancelWithdrawalMutation, mutateBalance, mutateWithdrawals, mutateTransactions]);
|
|
257
258
|
|
|
258
259
|
const getPaymentDetails = useCallback(async (id: string): Promise<PaymentDetail | undefined> => {
|
|
259
|
-
return
|
|
260
|
+
return apiPayments.ext_payments_payments.paymentsRetrieve(id);
|
|
260
261
|
}, []);
|
|
261
262
|
|
|
262
263
|
const getWithdrawalDetails = useCallback(async (id: string): Promise<WithdrawalDetail | undefined> => {
|
|
263
|
-
return
|
|
264
|
+
return apiPayments.ext_payments_payments.withdrawalsRetrieve(id);
|
|
264
265
|
}, []);
|
|
265
266
|
|
|
266
267
|
const refreshWallet = useCallback(async () => {
|
package/src/contexts/index.ts
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payments Context Types
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all types and enums from generated API for payments.
|
|
5
|
+
* This is the single source of truth for payment types.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Import and re-export enums
|
|
9
|
+
import * as PaymentsEnums from '../api/generated/ext_payments/enums';
|
|
10
|
+
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Payment Enums
|
|
13
|
+
// =============================================================================
|
|
14
|
+
export const PaymentStatus = PaymentsEnums.PaymentDetailStatus;
|
|
15
|
+
export const PaymentListStatus = PaymentsEnums.PaymentListStatus;
|
|
16
|
+
export const TransactionType = PaymentsEnums.TransactionTransactionType;
|
|
17
|
+
export const WithdrawalStatus = PaymentsEnums.WithdrawalDetailStatus;
|
|
18
|
+
export const WithdrawalListStatus = PaymentsEnums.WithdrawalListStatus;
|
|
19
|
+
|
|
20
|
+
// Type exports for TypeScript
|
|
21
|
+
export type PaymentStatus = PaymentsEnums.PaymentDetailStatus;
|
|
22
|
+
export type PaymentListStatus = PaymentsEnums.PaymentListStatus;
|
|
23
|
+
export type TransactionType = PaymentsEnums.TransactionTransactionType;
|
|
24
|
+
export type WithdrawalStatus = PaymentsEnums.WithdrawalDetailStatus;
|
|
25
|
+
export type WithdrawalListStatus = PaymentsEnums.WithdrawalListStatus;
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Payment Schemas
|
|
29
|
+
// =============================================================================
|
|
30
|
+
export type { Balance } from '../api/generated/ext_payments/_utils/schemas/Balance.schema';
|
|
31
|
+
export type { Currency } from '../api/generated/ext_payments/_utils/schemas/Currency.schema';
|
|
32
|
+
export type { PaymentDetail } from '../api/generated/ext_payments/_utils/schemas/PaymentDetail.schema';
|
|
33
|
+
export type { PaymentList } from '../api/generated/ext_payments/_utils/schemas/PaymentList.schema';
|
|
34
|
+
export type { PaymentCreateRequest } from '../api/generated/ext_payments/_utils/schemas/PaymentCreateRequest.schema';
|
|
35
|
+
export type { PaymentCreateResponse } from '../api/generated/ext_payments/_utils/schemas/PaymentCreateResponse.schema';
|
|
36
|
+
export type { PaginatedPaymentListList } from '../api/generated/ext_payments/_utils/schemas/PaginatedPaymentListList.schema';
|
|
37
|
+
export type { Transaction } from '../api/generated/ext_payments/_utils/schemas/Transaction.schema';
|
|
38
|
+
|
|
39
|
+
// =============================================================================
|
|
40
|
+
// Withdrawal Schemas
|
|
41
|
+
// =============================================================================
|
|
42
|
+
export type { WithdrawalDetail } from '../api/generated/ext_payments/_utils/schemas/WithdrawalDetail.schema';
|
|
43
|
+
export type { WithdrawalList } from '../api/generated/ext_payments/_utils/schemas/WithdrawalList.schema';
|
|
44
|
+
export type { WithdrawalCreateRequest } from '../api/generated/ext_payments/_utils/schemas/WithdrawalCreateRequest.schema';
|
|
45
|
+
export type { WithdrawalCreateResponse } from '../api/generated/ext_payments/_utils/schemas/WithdrawalCreateResponse.schema';
|
|
46
|
+
export type { PaginatedWithdrawalListList } from '../api/generated/ext_payments/_utils/schemas/PaginatedWithdrawalListList.schema';
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hooks for currency selection
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useMemo, useEffect, useCallback, useRef } from 'react';
|
|
8
|
+
import type { Currency } from '../contexts/WalletContext';
|
|
9
|
+
import type { CurrencyOption } from '../types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Map currencies to combobox options
|
|
13
|
+
*/
|
|
14
|
+
export function useCurrencyOptions(currencies: Currency[]): CurrencyOption[] {
|
|
15
|
+
return useMemo(() => {
|
|
16
|
+
return currencies.map((c) => ({
|
|
17
|
+
value: c.code,
|
|
18
|
+
label: c.name,
|
|
19
|
+
token: c.token,
|
|
20
|
+
network: c.network,
|
|
21
|
+
}));
|
|
22
|
+
}, [currencies]);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface UseDefaultCurrencyOptions {
|
|
26
|
+
currencyOptions: CurrencyOption[];
|
|
27
|
+
savedCurrency: string;
|
|
28
|
+
currentValue: string;
|
|
29
|
+
setValue: (value: string) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Set default currency when options load (saved or USDT fallback)
|
|
34
|
+
*/
|
|
35
|
+
export function useDefaultCurrency({
|
|
36
|
+
currencyOptions,
|
|
37
|
+
savedCurrency,
|
|
38
|
+
currentValue,
|
|
39
|
+
setValue,
|
|
40
|
+
}: UseDefaultCurrencyOptions): void {
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (currencyOptions.length > 0 && !currentValue) {
|
|
43
|
+
// Prefer saved currency if it exists in options
|
|
44
|
+
const savedOption = savedCurrency && currencyOptions.find(c => c.value === savedCurrency);
|
|
45
|
+
if (savedOption) {
|
|
46
|
+
setValue(savedOption.value);
|
|
47
|
+
} else {
|
|
48
|
+
// Fallback to USDT or first available
|
|
49
|
+
const usdt = currencyOptions.find(c => c.value.includes('USDT'));
|
|
50
|
+
setValue(usdt?.value || currencyOptions[0].value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}, [currencyOptions, savedCurrency, currentValue, setValue]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Auto-save value to localStorage when it changes
|
|
58
|
+
*/
|
|
59
|
+
export function useAutoSave<T>(
|
|
60
|
+
value: T,
|
|
61
|
+
save: (value: T) => void,
|
|
62
|
+
validate?: (value: T) => boolean
|
|
63
|
+
): void {
|
|
64
|
+
// Use refs to avoid infinite loops from unstable function references
|
|
65
|
+
const saveRef = useRef(save);
|
|
66
|
+
const validateRef = useRef(validate);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
saveRef.current = save;
|
|
70
|
+
validateRef.current = validate;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const isValid = validateRef.current ? validateRef.current(value) : Boolean(value);
|
|
75
|
+
if (isValid) {
|
|
76
|
+
saveRef.current(value);
|
|
77
|
+
}
|
|
78
|
+
}, [value]);
|
|
79
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook for fetching currency estimates with debounce
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useEffect } from 'react';
|
|
8
|
+
import { apiPayments } from '../api';
|
|
9
|
+
import type { DepositEstimate } from '../types';
|
|
10
|
+
|
|
11
|
+
interface UseEstimateOptions {
|
|
12
|
+
/** Currency code to get estimate for */
|
|
13
|
+
currencyCode: string | undefined;
|
|
14
|
+
/** Amount in USD */
|
|
15
|
+
amountUsd: number;
|
|
16
|
+
/** Minimum amount required (default: 0) */
|
|
17
|
+
minAmount?: number;
|
|
18
|
+
/** Debounce delay in ms (default: 300) */
|
|
19
|
+
debounceMs?: number;
|
|
20
|
+
/** Whether to skip fetching (e.g., when conditions not met) */
|
|
21
|
+
skip?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Parsed deposit estimate data for UI */
|
|
25
|
+
interface DepositEstimateData {
|
|
26
|
+
estimatedAmount: number;
|
|
27
|
+
usdRate: number;
|
|
28
|
+
minAmountUsd: number | null;
|
|
29
|
+
isStablecoin: boolean;
|
|
30
|
+
amountToReceive: number;
|
|
31
|
+
serviceFeeUsd: number;
|
|
32
|
+
serviceFeePercent: number;
|
|
33
|
+
totalToPayUsd: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface UseEstimateResult {
|
|
37
|
+
estimate: DepositEstimateData | null;
|
|
38
|
+
isLoading: boolean;
|
|
39
|
+
error: Error | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Fetch currency estimate with debouncing
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* const { estimate, isLoading } = useEstimate({
|
|
48
|
+
* currencyCode: 'USDTTRC20',
|
|
49
|
+
* amountUsd: 100,
|
|
50
|
+
* minAmount: 1,
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function useEstimate({
|
|
55
|
+
currencyCode,
|
|
56
|
+
amountUsd,
|
|
57
|
+
minAmount = 0,
|
|
58
|
+
debounceMs = 300,
|
|
59
|
+
skip = false,
|
|
60
|
+
}: UseEstimateOptions): UseEstimateResult {
|
|
61
|
+
const [estimate, setEstimate] = useState<DepositEstimateData | null>(null);
|
|
62
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
63
|
+
const [error, setError] = useState<Error | null>(null);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
// Skip if conditions not met
|
|
67
|
+
if (skip || !currencyCode || amountUsd < minAmount) {
|
|
68
|
+
setEstimate(null);
|
|
69
|
+
setError(null);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const fetchEstimate = async () => {
|
|
74
|
+
setIsLoading(true);
|
|
75
|
+
setError(null);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const response = await apiPayments.ext_payments_payments.currenciesEstimateRetrieve(
|
|
79
|
+
currencyCode,
|
|
80
|
+
{ amount: amountUsd }
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (response?.success && response?.estimated_amount) {
|
|
84
|
+
setEstimate({
|
|
85
|
+
estimatedAmount: parseFloat(response.estimated_amount),
|
|
86
|
+
usdRate: parseFloat(response.usd_rate) || 0,
|
|
87
|
+
minAmountUsd: response.min_amount_usd ? parseFloat(response.min_amount_usd) : null,
|
|
88
|
+
isStablecoin: response.is_stablecoin || false,
|
|
89
|
+
// New fee fields
|
|
90
|
+
amountToReceive: parseFloat(response.amount_to_receive) || amountUsd,
|
|
91
|
+
serviceFeeUsd: parseFloat(response.service_fee_usd) || 0,
|
|
92
|
+
serviceFeePercent: parseFloat(response.service_fee_percent) || 0,
|
|
93
|
+
totalToPayUsd: parseFloat(response.total_to_pay_usd) || amountUsd,
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
setEstimate(null);
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error('Failed to fetch estimate:', err);
|
|
100
|
+
setEstimate(null);
|
|
101
|
+
setError(err instanceof Error ? err : new Error('Failed to fetch estimate'));
|
|
102
|
+
} finally {
|
|
103
|
+
setIsLoading(false);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Debounce the fetch
|
|
108
|
+
const timeoutId = setTimeout(fetchEstimate, debounceMs);
|
|
109
|
+
return () => clearTimeout(timeoutId);
|
|
110
|
+
}, [currencyCode, amountUsd, minAmount, debounceMs, skip]);
|
|
111
|
+
|
|
112
|
+
return { estimate, isLoading, error };
|
|
113
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook for fetching withdrawal estimates with debounce
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useEffect } from 'react';
|
|
8
|
+
import { apiPayments } from '../api';
|
|
9
|
+
import type { WithdrawalEstimate } from '../types';
|
|
10
|
+
|
|
11
|
+
interface UseWithdrawalEstimateOptions {
|
|
12
|
+
/** Currency code to get estimate for */
|
|
13
|
+
currencyCode: string | undefined;
|
|
14
|
+
/** Amount in USD to withdraw */
|
|
15
|
+
amountUsd: number;
|
|
16
|
+
/** Minimum amount required (default: 10) */
|
|
17
|
+
minAmount?: number;
|
|
18
|
+
/** Debounce delay in ms (default: 300) */
|
|
19
|
+
debounceMs?: number;
|
|
20
|
+
/** Whether to skip fetching */
|
|
21
|
+
skip?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Parsed withdrawal estimate data for UI */
|
|
25
|
+
interface WithdrawalEstimateData {
|
|
26
|
+
estimatedAmount: number;
|
|
27
|
+
usdRate: number;
|
|
28
|
+
minAmountUsd: number | null;
|
|
29
|
+
isStablecoin: boolean;
|
|
30
|
+
amountRequested: number;
|
|
31
|
+
serviceFeeUsd: number;
|
|
32
|
+
serviceFeePercent: number;
|
|
33
|
+
networkFeeUsd: number;
|
|
34
|
+
totalFeesUsd: number;
|
|
35
|
+
amountToReceive: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface UseWithdrawalEstimateResult {
|
|
39
|
+
estimate: WithdrawalEstimateData | null;
|
|
40
|
+
isLoading: boolean;
|
|
41
|
+
error: Error | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Fetch withdrawal estimate with debouncing
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* const { estimate, isLoading } = useWithdrawalEstimate({
|
|
50
|
+
* currencyCode: 'USDTTRC20',
|
|
51
|
+
* amountUsd: 100,
|
|
52
|
+
* minAmount: 10,
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export function useWithdrawalEstimate({
|
|
57
|
+
currencyCode,
|
|
58
|
+
amountUsd,
|
|
59
|
+
minAmount = 10,
|
|
60
|
+
debounceMs = 300,
|
|
61
|
+
skip = false,
|
|
62
|
+
}: UseWithdrawalEstimateOptions): UseWithdrawalEstimateResult {
|
|
63
|
+
const [estimate, setEstimate] = useState<WithdrawalEstimateData | null>(null);
|
|
64
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
65
|
+
const [error, setError] = useState<Error | null>(null);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
// Skip if conditions not met
|
|
69
|
+
if (skip || !currencyCode || amountUsd < minAmount) {
|
|
70
|
+
setEstimate(null);
|
|
71
|
+
setError(null);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const fetchEstimate = async () => {
|
|
76
|
+
setIsLoading(true);
|
|
77
|
+
setError(null);
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const response = await apiPayments.ext_payments_payments.currenciesWithdrawalEstimateRetrieve(
|
|
81
|
+
currencyCode,
|
|
82
|
+
{ amount: amountUsd }
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (response?.success && response?.estimated_amount) {
|
|
86
|
+
setEstimate({
|
|
87
|
+
estimatedAmount: parseFloat(response.estimated_amount),
|
|
88
|
+
usdRate: parseFloat(response.usd_rate) || 0,
|
|
89
|
+
minAmountUsd: response.min_amount_usd ? parseFloat(response.min_amount_usd) : null,
|
|
90
|
+
isStablecoin: response.is_stablecoin || false,
|
|
91
|
+
// Withdrawal-specific fields
|
|
92
|
+
amountRequested: parseFloat(response.amount_requested) || amountUsd,
|
|
93
|
+
serviceFeeUsd: parseFloat(response.service_fee_usd) || 0,
|
|
94
|
+
serviceFeePercent: parseFloat(response.service_fee_percent) || 0,
|
|
95
|
+
networkFeeUsd: parseFloat(response.network_fee_usd) || 0,
|
|
96
|
+
totalFeesUsd: parseFloat(response.total_fees_usd) || 0,
|
|
97
|
+
amountToReceive: parseFloat(response.amount_to_receive) || 0,
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
setEstimate(null);
|
|
101
|
+
}
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error('Failed to fetch withdrawal estimate:', err);
|
|
104
|
+
setEstimate(null);
|
|
105
|
+
setError(err instanceof Error ? err : new Error('Failed to fetch withdrawal estimate'));
|
|
106
|
+
} finally {
|
|
107
|
+
setIsLoading(false);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Debounce the fetch
|
|
112
|
+
const timeoutId = setTimeout(fetchEstimate, debounceMs);
|
|
113
|
+
return () => clearTimeout(timeoutId);
|
|
114
|
+
}, [currencyCode, amountUsd, minAmount, debounceMs, skip]);
|
|
115
|
+
|
|
116
|
+
return { estimate, isLoading, error };
|
|
117
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Estimate Types (not generated - defined manually for estimate endpoints)
|
|
4
|
+
// =============================================================================
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Deposit estimate response from API
|
|
8
|
+
*/
|
|
9
|
+
export interface DepositEstimate {
|
|
10
|
+
success: boolean;
|
|
11
|
+
/** Crypto amount to send (includes fees) */
|
|
12
|
+
estimated_amount: string;
|
|
13
|
+
/** USD rate (how much USD per 1 crypto) */
|
|
14
|
+
usd_rate: string;
|
|
15
|
+
/** Minimum amount in USD */
|
|
16
|
+
min_amount_usd: string | null;
|
|
17
|
+
/** Whether the currency is a stablecoin */
|
|
18
|
+
is_stablecoin: boolean;
|
|
19
|
+
/** Amount user will receive on balance (USD) */
|
|
20
|
+
amount_to_receive: string;
|
|
21
|
+
/** Service fee in USD */
|
|
22
|
+
service_fee_usd: string;
|
|
23
|
+
/** Service fee percentage (e.g., 2 for 2%) */
|
|
24
|
+
service_fee_percent: string;
|
|
25
|
+
/** Total USD amount to pay (including fees) */
|
|
26
|
+
total_to_pay_usd: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Withdrawal estimate response from API
|
|
31
|
+
*/
|
|
32
|
+
export interface WithdrawalEstimate {
|
|
33
|
+
success: boolean;
|
|
34
|
+
/** Crypto amount user will receive */
|
|
35
|
+
estimated_amount: string;
|
|
36
|
+
/** USD rate (how much USD per 1 crypto) */
|
|
37
|
+
usd_rate: string;
|
|
38
|
+
/** Minimum amount in USD */
|
|
39
|
+
min_amount_usd: string | null;
|
|
40
|
+
/** Whether the currency is a stablecoin */
|
|
41
|
+
is_stablecoin: boolean;
|
|
42
|
+
/** Amount user requested to withdraw */
|
|
43
|
+
amount_requested: string;
|
|
44
|
+
/** Service fee in USD */
|
|
45
|
+
service_fee_usd: string;
|
|
46
|
+
/** Service fee percentage (e.g., 1 for 1%) */
|
|
47
|
+
service_fee_percent: string;
|
|
48
|
+
/** Network fee in USD */
|
|
49
|
+
network_fee_usd: string;
|
|
50
|
+
/** Total fees in USD */
|
|
51
|
+
total_fees_usd: string;
|
|
52
|
+
/** Amount user will receive after fees (USD) */
|
|
53
|
+
amount_to_receive: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// =============================================================================
|
|
57
|
+
// UI Helper Types
|
|
58
|
+
// =============================================================================
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Currency option for combobox
|
|
62
|
+
*/
|
|
63
|
+
export interface CurrencyOption {
|
|
64
|
+
value: string;
|
|
65
|
+
label: string;
|
|
66
|
+
token: string;
|
|
67
|
+
network?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Formatted display data for crypto transactions
|
|
72
|
+
*/
|
|
73
|
+
export interface CryptoDisplayData {
|
|
74
|
+
token: string;
|
|
75
|
+
amount: string;
|
|
76
|
+
rate: string;
|
|
77
|
+
showRate: boolean;
|
|
78
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error handling utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extract user-friendly error message from API error response
|
|
7
|
+
*
|
|
8
|
+
* Handles various error formats:
|
|
9
|
+
* - APIError with response.data
|
|
10
|
+
* - Axios-style errors
|
|
11
|
+
* - Plain Error objects
|
|
12
|
+
*/
|
|
13
|
+
export function extractErrorMessage(err: unknown, fallback = 'An error occurred'): string {
|
|
14
|
+
if (!err) return fallback;
|
|
15
|
+
|
|
16
|
+
// Type guard for error-like objects
|
|
17
|
+
const error = err as Record<string, unknown>;
|
|
18
|
+
|
|
19
|
+
// APIError / Axios style: err.response.data.error
|
|
20
|
+
const responseData = (error.response as Record<string, unknown>)?.data as Record<string, unknown> | undefined;
|
|
21
|
+
const response = responseData || error.response as Record<string, unknown> | undefined;
|
|
22
|
+
|
|
23
|
+
if (response) {
|
|
24
|
+
if (typeof response.error === 'string') return response.error;
|
|
25
|
+
if (typeof response.message === 'string') return response.message;
|
|
26
|
+
if (typeof response.detail === 'string') return response.detail;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// APIError getter style
|
|
30
|
+
if (typeof error.errorMessage === 'string') return error.errorMessage;
|
|
31
|
+
|
|
32
|
+
// Plain Error
|
|
33
|
+
if (typeof error.message === 'string') return error.message;
|
|
34
|
+
|
|
35
|
+
return fallback;
|
|
36
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatting utilities for Payments module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Format USD rate with adaptive decimal places based on value
|
|
7
|
+
*
|
|
8
|
+
* - >= $1: 2 decimals (e.g., $95,503.90)
|
|
9
|
+
* - $0.01-$0.99: up to 4 decimals (e.g., $0.0123)
|
|
10
|
+
* - $0.0001-$0.0099: up to 6 decimals (e.g., $0.000253)
|
|
11
|
+
* - < $0.0001: up to 8 decimals (e.g., $0.00000025)
|
|
12
|
+
*/
|
|
13
|
+
export function formatUsdRate(rate: number): string {
|
|
14
|
+
if (rate >= 1) {
|
|
15
|
+
return rate.toLocaleString(undefined, {
|
|
16
|
+
minimumFractionDigits: 2,
|
|
17
|
+
maximumFractionDigits: 2,
|
|
18
|
+
});
|
|
19
|
+
} else if (rate >= 0.01) {
|
|
20
|
+
return rate.toLocaleString(undefined, {
|
|
21
|
+
minimumFractionDigits: 2,
|
|
22
|
+
maximumFractionDigits: 4,
|
|
23
|
+
});
|
|
24
|
+
} else if (rate >= 0.0001) {
|
|
25
|
+
return rate.toLocaleString(undefined, {
|
|
26
|
+
minimumFractionDigits: 4,
|
|
27
|
+
maximumFractionDigits: 6,
|
|
28
|
+
});
|
|
29
|
+
} else {
|
|
30
|
+
return rate.toLocaleString(undefined, {
|
|
31
|
+
minimumFractionDigits: 6,
|
|
32
|
+
maximumFractionDigits: 8,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Format crypto amount with appropriate decimal places
|
|
39
|
+
*
|
|
40
|
+
* @param amount - The crypto amount to format
|
|
41
|
+
* @param isStablecoin - Whether the currency is a stablecoin (affects decimal places)
|
|
42
|
+
*/
|
|
43
|
+
export function formatCryptoAmount(amount: number, isStablecoin: boolean): string {
|
|
44
|
+
const decimals = isStablecoin ? 2 : 8;
|
|
45
|
+
return amount.toFixed(decimals);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Format USD amount for display
|
|
50
|
+
* Shows cents only if they exist (e.g., $100 not $100.00, but $99.50)
|
|
51
|
+
*/
|
|
52
|
+
export function formatUsdAmount(amount: number, alwaysShowCents = false): string {
|
|
53
|
+
if (alwaysShowCents) {
|
|
54
|
+
return amount.toFixed(2);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check if amount has cents
|
|
58
|
+
const hasCents = amount % 1 !== 0;
|
|
59
|
+
|
|
60
|
+
if (hasCents) {
|
|
61
|
+
return amount.toFixed(2);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return amount.toFixed(0);
|
|
65
|
+
}
|