@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.
Files changed (46) hide show
  1. package/dist/config.cjs +1 -1
  2. package/dist/config.js +1 -1
  3. package/dist/index.cjs +1175 -290
  4. package/dist/index.d.cts +226 -80
  5. package/dist/index.d.ts +226 -80
  6. package/dist/index.js +1157 -255
  7. package/package.json +9 -9
  8. package/src/WalletPage.tsx +100 -0
  9. package/src/api/generated/ext_payments/CLAUDE.md +6 -4
  10. package/src/api/generated/ext_payments/_utils/fetchers/ext_payments__payments.ts +37 -6
  11. package/src/api/generated/ext_payments/_utils/hooks/ext_payments__payments.ts +34 -3
  12. package/src/api/generated/ext_payments/_utils/schemas/Balance.schema.ts +1 -1
  13. package/src/api/generated/ext_payments/_utils/schemas/PaymentCreateResponse.schema.ts +22 -0
  14. package/src/api/generated/ext_payments/_utils/schemas/PaymentDetail.schema.ts +3 -3
  15. package/src/api/generated/ext_payments/_utils/schemas/PaymentList.schema.ts +2 -2
  16. package/src/api/generated/ext_payments/_utils/schemas/Transaction.schema.ts +1 -1
  17. package/src/api/generated/ext_payments/_utils/schemas/WithdrawalCancelResponse.schema.ts +22 -0
  18. package/src/api/generated/ext_payments/_utils/schemas/WithdrawalCreateResponse.schema.ts +22 -0
  19. package/src/api/generated/ext_payments/_utils/schemas/WithdrawalDetail.schema.ts +5 -5
  20. package/src/api/generated/ext_payments/_utils/schemas/WithdrawalList.schema.ts +2 -2
  21. package/src/api/generated/ext_payments/_utils/schemas/index.ts +3 -0
  22. package/src/api/generated/ext_payments/client.ts +1 -1
  23. package/src/api/generated/ext_payments/ext_payments__payments/client.ts +49 -4
  24. package/src/api/generated/ext_payments/ext_payments__payments/models.ts +33 -14
  25. package/src/api/generated/ext_payments/index.ts +1 -1
  26. package/src/api/generated/ext_payments/schema.json +167 -33
  27. package/src/components/AddFundsSheet.tsx +157 -73
  28. package/src/components/CurrencyCombobox.tsx +49 -0
  29. package/src/components/PaymentSheet.tsx +94 -32
  30. package/src/components/WithdrawSheet.tsx +121 -95
  31. package/src/components/WithdrawalSheet.tsx +332 -0
  32. package/src/components/index.ts +1 -8
  33. package/src/config.ts +1 -0
  34. package/src/contexts/WalletContext.tsx +10 -9
  35. package/src/contexts/index.ts +5 -1
  36. package/src/contexts/types.ts +46 -0
  37. package/src/hooks/index.ts +3 -0
  38. package/src/hooks/useCurrencyOptions.ts +79 -0
  39. package/src/hooks/useEstimate.ts +113 -0
  40. package/src/hooks/useWithdrawalEstimate.ts +117 -0
  41. package/src/index.ts +3 -0
  42. package/src/types/index.ts +78 -0
  43. package/src/utils/errors.ts +36 -0
  44. package/src/utils/format.ts +65 -0
  45. package/src/utils/index.ts +3 -0
  46. package/src/components/ResponsiveSheet.tsx +0 -151
package/src/config.ts CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { createExtensionConfig } from '@djangocfg/ext-base';
6
6
 
7
+ // @ts-ignore - package.json is not typed
7
8
  import packageJson from '../package.json';
8
9
 
9
10
  export const extensionConfig = createExtensionConfig(packageJson, {
@@ -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 result = await createPaymentMutation(data, apiPayments);
238
+ const response = await createPaymentMutation(data, apiPayments);
240
239
  // Refresh all data
241
240
  await Promise.all([mutateBalance(), mutatePayments(), mutateTransactions()]);
242
- return result;
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 result = await createWithdrawalMutation(data, apiPayments);
246
+ const response = await createWithdrawalMutation(data, apiPayments);
247
247
  // Refresh all data
248
248
  await Promise.all([mutateBalance(), mutateWithdrawals(), mutateTransactions()]);
249
- return result;
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 Fetchers.getPaymentsPaymentsRetrieve(id, apiPayments);
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 Fetchers.getPaymentsWithdrawalsRetrieve(id, apiPayments);
264
+ return apiPayments.ext_payments_payments.withdrawalsRetrieve(id);
264
265
  }, []);
265
266
 
266
267
  const refreshWallet = useCallback(async () => {
@@ -1,7 +1,11 @@
1
1
  /**
2
- * Wallet Context Exports
2
+ * Payments Context Exports
3
3
  */
4
4
 
5
+ // Types (single source of truth)
6
+ export * from './types';
7
+
8
+ // Context
5
9
  export {
6
10
  WalletProvider,
7
11
  useWallet,
@@ -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,3 @@
1
+ export * from './useEstimate';
2
+ export * from './useWithdrawalEstimate';
3
+ export * from './useCurrencyOptions';
@@ -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
@@ -4,6 +4,9 @@
4
4
  * Simplified payments system with single page UX.
5
5
  */
6
6
 
7
+ // Page component
8
+ export { WalletPage } from './WalletPage';
9
+
7
10
  // API client (server-safe)
8
11
  export * from './api/generated/ext_payments';
9
12
  export { API } from './api/generated/ext_payments';
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ export * from './format';
2
+ export * from './errors';
3
+ export * from './logger';