@djangocfg/ext-payments 1.0.0
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/README.md +206 -0
- package/dist/chunk-5KY6HVXF.js +2593 -0
- package/dist/hooks.cjs +2666 -0
- package/dist/hooks.d.cts +186 -0
- package/dist/hooks.d.ts +186 -0
- package/dist/hooks.js +1 -0
- package/dist/index.cjs +2590 -0
- package/dist/index.d.cts +1287 -0
- package/dist/index.d.ts +1287 -0
- package/dist/index.js +1 -0
- package/package.json +79 -0
- package/src/api/generated/ext_payments/_utils/fetchers/ext_payments__payments.ts +408 -0
- package/src/api/generated/ext_payments/_utils/fetchers/index.ts +28 -0
- package/src/api/generated/ext_payments/_utils/hooks/ext_payments__payments.ts +147 -0
- package/src/api/generated/ext_payments/_utils/hooks/index.ts +28 -0
- package/src/api/generated/ext_payments/_utils/schemas/Balance.schema.ts +23 -0
- package/src/api/generated/ext_payments/_utils/schemas/Currency.schema.ts +28 -0
- package/src/api/generated/ext_payments/_utils/schemas/PaginatedPaymentListList.schema.ts +24 -0
- package/src/api/generated/ext_payments/_utils/schemas/PaymentDetail.schema.ts +44 -0
- package/src/api/generated/ext_payments/_utils/schemas/PaymentList.schema.ts +28 -0
- package/src/api/generated/ext_payments/_utils/schemas/Transaction.schema.ts +28 -0
- package/src/api/generated/ext_payments/_utils/schemas/index.ts +24 -0
- package/src/api/generated/ext_payments/api-instance.ts +131 -0
- package/src/api/generated/ext_payments/client.ts +301 -0
- package/src/api/generated/ext_payments/enums.ts +64 -0
- package/src/api/generated/ext_payments/errors.ts +116 -0
- package/src/api/generated/ext_payments/ext_payments__payments/client.ts +118 -0
- package/src/api/generated/ext_payments/ext_payments__payments/index.ts +2 -0
- package/src/api/generated/ext_payments/ext_payments__payments/models.ts +135 -0
- package/src/api/generated/ext_payments/http.ts +103 -0
- package/src/api/generated/ext_payments/index.ts +273 -0
- package/src/api/generated/ext_payments/logger.ts +259 -0
- package/src/api/generated/ext_payments/retry.ts +175 -0
- package/src/api/generated/ext_payments/schema.json +850 -0
- package/src/api/generated/ext_payments/storage.ts +161 -0
- package/src/api/generated/ext_payments/validation-events.ts +133 -0
- package/src/api/index.ts +9 -0
- package/src/config.ts +20 -0
- package/src/contexts/BalancesContext.tsx +62 -0
- package/src/contexts/CurrenciesContext.tsx +63 -0
- package/src/contexts/OverviewContext.tsx +173 -0
- package/src/contexts/PaymentsContext.tsx +121 -0
- package/src/contexts/PaymentsExtensionProvider.tsx +55 -0
- package/src/contexts/README.md +201 -0
- package/src/contexts/RootPaymentsContext.tsx +65 -0
- package/src/contexts/index.ts +45 -0
- package/src/contexts/types.ts +40 -0
- package/src/hooks/index.ts +20 -0
- package/src/index.ts +36 -0
- package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +92 -0
- package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +291 -0
- package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +290 -0
- package/src/layouts/PaymentsLayout/components/index.ts +2 -0
- package/src/layouts/PaymentsLayout/events.ts +47 -0
- package/src/layouts/PaymentsLayout/index.ts +16 -0
- package/src/layouts/PaymentsLayout/types.ts +6 -0
- package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +128 -0
- package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +142 -0
- package/src/layouts/PaymentsLayout/views/overview/components/index.ts +2 -0
- package/src/layouts/PaymentsLayout/views/overview/index.tsx +20 -0
- package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +277 -0
- package/src/layouts/PaymentsLayout/views/payments/components/index.ts +1 -0
- package/src/layouts/PaymentsLayout/views/payments/index.tsx +17 -0
- package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +273 -0
- package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +1 -0
- package/src/layouts/PaymentsLayout/views/transactions/index.tsx +17 -0
- package/src/utils/logger.ts +9 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Error Classes
|
|
3
|
+
*
|
|
4
|
+
* Typed error classes with Django REST Framework support.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* HTTP API Error with DRF field-specific validation errors.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* ```typescript
|
|
12
|
+
* try {
|
|
13
|
+
* await api.users.create(userData);
|
|
14
|
+
* } catch (error) {
|
|
15
|
+
* if (error instanceof APIError) {
|
|
16
|
+
* if (error.isValidationError) {
|
|
17
|
+
* console.log('Field errors:', error.fieldErrors);
|
|
18
|
+
* // { "email": ["Email already exists"], "username": ["Required"] }
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export class APIError extends Error {
|
|
25
|
+
constructor(
|
|
26
|
+
public statusCode: number,
|
|
27
|
+
public statusText: string,
|
|
28
|
+
public response: any,
|
|
29
|
+
public url: string,
|
|
30
|
+
message?: string
|
|
31
|
+
) {
|
|
32
|
+
super(message || `HTTP ${statusCode}: ${statusText}`);
|
|
33
|
+
this.name = 'APIError';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get error details from response.
|
|
38
|
+
* DRF typically returns: { "detail": "Error message" } or { "field": ["error1", "error2"] }
|
|
39
|
+
*/
|
|
40
|
+
get details(): Record<string, any> | null {
|
|
41
|
+
if (typeof this.response === 'object' && this.response !== null) {
|
|
42
|
+
return this.response;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get field-specific validation errors from DRF.
|
|
49
|
+
* Returns: { "field_name": ["error1", "error2"], ... }
|
|
50
|
+
*/
|
|
51
|
+
get fieldErrors(): Record<string, string[]> | null {
|
|
52
|
+
const details = this.details;
|
|
53
|
+
if (!details) return null;
|
|
54
|
+
|
|
55
|
+
// DRF typically returns: { "field": ["error1", "error2"] }
|
|
56
|
+
const fieldErrors: Record<string, string[]> = {};
|
|
57
|
+
for (const [key, value] of Object.entries(details)) {
|
|
58
|
+
if (Array.isArray(value)) {
|
|
59
|
+
fieldErrors[key] = value;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return Object.keys(fieldErrors).length > 0 ? fieldErrors : null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get single error message from DRF.
|
|
68
|
+
* Checks for "detail", "message", or first field error.
|
|
69
|
+
*/
|
|
70
|
+
get errorMessage(): string {
|
|
71
|
+
const details = this.details;
|
|
72
|
+
if (!details) return this.message;
|
|
73
|
+
|
|
74
|
+
// Check for "detail" field (common in DRF)
|
|
75
|
+
if (details.detail) {
|
|
76
|
+
return Array.isArray(details.detail) ? details.detail.join(', ') : String(details.detail);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check for "message" field
|
|
80
|
+
if (details.message) {
|
|
81
|
+
return String(details.message);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Return first field error
|
|
85
|
+
const fieldErrors = this.fieldErrors;
|
|
86
|
+
if (fieldErrors) {
|
|
87
|
+
const firstField = Object.keys(fieldErrors)[0];
|
|
88
|
+
if (firstField) {
|
|
89
|
+
return `${firstField}: ${fieldErrors[firstField]?.join(', ')}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return this.message;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Helper methods for common HTTP status codes
|
|
97
|
+
get isValidationError(): boolean { return this.statusCode === 400; }
|
|
98
|
+
get isAuthError(): boolean { return this.statusCode === 401; }
|
|
99
|
+
get isPermissionError(): boolean { return this.statusCode === 403; }
|
|
100
|
+
get isNotFoundError(): boolean { return this.statusCode === 404; }
|
|
101
|
+
get isServerError(): boolean { return this.statusCode >= 500 && this.statusCode < 600; }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Network Error (connection failed, timeout, etc.)
|
|
106
|
+
*/
|
|
107
|
+
export class NetworkError extends Error {
|
|
108
|
+
constructor(
|
|
109
|
+
message: string,
|
|
110
|
+
public url: string,
|
|
111
|
+
public originalError?: Error
|
|
112
|
+
) {
|
|
113
|
+
super(message);
|
|
114
|
+
this.name = 'NetworkError';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import * as Models from "./models";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* API endpoints for Payments.
|
|
6
|
+
*/
|
|
7
|
+
export class ExtPaymentsPayments {
|
|
8
|
+
private client: any;
|
|
9
|
+
|
|
10
|
+
constructor(client: any) {
|
|
11
|
+
this.client = client;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get user balance
|
|
16
|
+
*
|
|
17
|
+
* Get current user balance and transaction statistics
|
|
18
|
+
*/
|
|
19
|
+
async balanceRetrieve(): Promise<Models.Balance> {
|
|
20
|
+
const response = await this.client.request('GET', "/cfg/payments/balance/");
|
|
21
|
+
return response;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get available currencies
|
|
26
|
+
*
|
|
27
|
+
* Returns list of available currencies with token+network info
|
|
28
|
+
*/
|
|
29
|
+
async currenciesList(): Promise<any> {
|
|
30
|
+
const response = await this.client.request('GET', "/cfg/payments/currencies/");
|
|
31
|
+
return response;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async paymentsList(page?: number, page_size?: number): Promise<Models.PaginatedPaymentListList>;
|
|
35
|
+
async paymentsList(params?: { page?: number; page_size?: number }): Promise<Models.PaginatedPaymentListList>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* ViewSet for payment operations. Endpoints: - GET /payments/ - List
|
|
39
|
+
* user's payments - GET /payments/{id}/ - Get payment details - POST
|
|
40
|
+
* /payments/create/ - Create new payment - GET /payments/{id}/status/ -
|
|
41
|
+
* Check payment status - POST /payments/{id}/confirm/ - Confirm payment
|
|
42
|
+
*/
|
|
43
|
+
async paymentsList(...args: any[]): Promise<Models.PaginatedPaymentListList> {
|
|
44
|
+
const isParamsObject = args.length === 1 && typeof args[0] === 'object' && args[0] !== null && !Array.isArray(args[0]);
|
|
45
|
+
|
|
46
|
+
let params;
|
|
47
|
+
if (isParamsObject) {
|
|
48
|
+
params = args[0];
|
|
49
|
+
} else {
|
|
50
|
+
params = { page: args[0], page_size: args[1] };
|
|
51
|
+
}
|
|
52
|
+
const response = await this.client.request('GET', "/cfg/payments/payments/", { params });
|
|
53
|
+
return response;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* ViewSet for payment operations. Endpoints: - GET /payments/ - List
|
|
58
|
+
* user's payments - GET /payments/{id}/ - Get payment details - POST
|
|
59
|
+
* /payments/create/ - Create new payment - GET /payments/{id}/status/ -
|
|
60
|
+
* Check payment status - POST /payments/{id}/confirm/ - Confirm payment
|
|
61
|
+
*/
|
|
62
|
+
async paymentsRetrieve(id: string): Promise<Models.PaymentDetail> {
|
|
63
|
+
const response = await this.client.request('GET', `/cfg/payments/payments/${id}/`);
|
|
64
|
+
return response;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* POST /api/v1/payments/{id}/confirm/ Confirm payment (user clicked "I
|
|
69
|
+
* have paid"). Checks status with provider and creates transaction if
|
|
70
|
+
* completed.
|
|
71
|
+
*/
|
|
72
|
+
async paymentsConfirmCreate(id: string): Promise<Models.PaymentList> {
|
|
73
|
+
const response = await this.client.request('POST', `/cfg/payments/payments/${id}/confirm/`);
|
|
74
|
+
return response;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* GET /api/v1/payments/{id}/status/?refresh=true Check payment status
|
|
79
|
+
* (with optional refresh from provider). Query params: - refresh: boolean
|
|
80
|
+
* (default: false) - Force refresh from provider
|
|
81
|
+
*/
|
|
82
|
+
async paymentsStatusRetrieve(id: string): Promise<Models.PaymentList[]> {
|
|
83
|
+
const response = await this.client.request('GET', `/cfg/payments/payments/${id}/status/`);
|
|
84
|
+
return (response as any).results || response;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* POST /api/v1/payments/create/ Create new payment. Request body: {
|
|
89
|
+
* "amount_usd": "100.00", "currency_code": "USDTTRC20", "description":
|
|
90
|
+
* "Optional description" }
|
|
91
|
+
*/
|
|
92
|
+
async paymentsCreateCreate(): Promise<Models.PaymentList> {
|
|
93
|
+
const response = await this.client.request('POST', "/cfg/payments/payments/create/");
|
|
94
|
+
return response;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async transactionsList(limit?: number, offset?: number, type?: string): Promise<any>;
|
|
98
|
+
async transactionsList(params?: { limit?: number; offset?: number; type?: string }): Promise<any>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get user transactions
|
|
102
|
+
*
|
|
103
|
+
* Get user transactions with pagination and filtering
|
|
104
|
+
*/
|
|
105
|
+
async transactionsList(...args: any[]): Promise<any> {
|
|
106
|
+
const isParamsObject = args.length === 1 && typeof args[0] === 'object' && args[0] !== null && !Array.isArray(args[0]);
|
|
107
|
+
|
|
108
|
+
let params;
|
|
109
|
+
if (isParamsObject) {
|
|
110
|
+
params = args[0];
|
|
111
|
+
} else {
|
|
112
|
+
params = { limit: args[0], offset: args[1], type: args[2] };
|
|
113
|
+
}
|
|
114
|
+
const response = await this.client.request('GET', "/cfg/payments/transactions/", { params });
|
|
115
|
+
return response;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as Enums from "../enums";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* User balance serializer.
|
|
5
|
+
*
|
|
6
|
+
* Response model (includes read-only fields).
|
|
7
|
+
*/
|
|
8
|
+
export interface Balance {
|
|
9
|
+
/** Current balance in USD */
|
|
10
|
+
balance_usd: string;
|
|
11
|
+
balance_display: string;
|
|
12
|
+
/** Total amount deposited (lifetime) */
|
|
13
|
+
total_deposited: string;
|
|
14
|
+
/** Total amount withdrawn (lifetime) */
|
|
15
|
+
total_withdrawn: string;
|
|
16
|
+
/** When the last transaction occurred */
|
|
17
|
+
last_transaction_at?: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* Response model (includes read-only fields).
|
|
23
|
+
*/
|
|
24
|
+
export interface PaginatedPaymentListList {
|
|
25
|
+
/** Total number of items across all pages */
|
|
26
|
+
count: number;
|
|
27
|
+
/** Current page number (1-based) */
|
|
28
|
+
page: number;
|
|
29
|
+
/** Total number of pages */
|
|
30
|
+
pages: number;
|
|
31
|
+
/** Number of items per page */
|
|
32
|
+
page_size: number;
|
|
33
|
+
/** Whether there is a next page */
|
|
34
|
+
has_next: boolean;
|
|
35
|
+
/** Whether there is a previous page */
|
|
36
|
+
has_previous: boolean;
|
|
37
|
+
/** Next page number (null if no next page) */
|
|
38
|
+
next_page?: number | null;
|
|
39
|
+
/** Previous page number (null if no previous page) */
|
|
40
|
+
previous_page?: number | null;
|
|
41
|
+
/** Array of items for current page */
|
|
42
|
+
results: Array<PaymentList>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Detailed payment information.
|
|
47
|
+
*
|
|
48
|
+
* Response model (includes read-only fields).
|
|
49
|
+
*/
|
|
50
|
+
export interface PaymentDetail {
|
|
51
|
+
/** Unique identifier for this record */
|
|
52
|
+
id: string;
|
|
53
|
+
/** Internal payment identifier (PAY_YYYYMMDDHHMMSS_UUID) */
|
|
54
|
+
internal_payment_id: string;
|
|
55
|
+
/** Payment amount in USD */
|
|
56
|
+
amount_usd: string;
|
|
57
|
+
currency_code: string;
|
|
58
|
+
currency_name: string;
|
|
59
|
+
currency_token: string;
|
|
60
|
+
currency_network: string;
|
|
61
|
+
/** Amount to pay in cryptocurrency */
|
|
62
|
+
pay_amount?: string | null;
|
|
63
|
+
/** Actual amount received in cryptocurrency */
|
|
64
|
+
actual_amount?: string | null;
|
|
65
|
+
/** Actual amount received in USD */
|
|
66
|
+
actual_amount_usd?: string | null;
|
|
67
|
+
/** Current payment status
|
|
68
|
+
|
|
69
|
+
* `pending` - Pending
|
|
70
|
+
* `confirming` - Confirming
|
|
71
|
+
* `confirmed` - Confirmed
|
|
72
|
+
* `completed` - Completed
|
|
73
|
+
* `partially_paid` - Partially Paid
|
|
74
|
+
* `failed` - Failed
|
|
75
|
+
* `expired` - Expired
|
|
76
|
+
* `cancelled` - Cancelled */
|
|
77
|
+
status: Enums.PaymentDetailStatus;
|
|
78
|
+
status_display: string;
|
|
79
|
+
/** Cryptocurrency payment address */
|
|
80
|
+
pay_address?: string | null;
|
|
81
|
+
/** Get QR code URL. */
|
|
82
|
+
qr_code_url?: string | null;
|
|
83
|
+
/** Payment page URL (if provided by provider) */
|
|
84
|
+
payment_url?: string | null;
|
|
85
|
+
/** Blockchain transaction hash */
|
|
86
|
+
transaction_hash?: string | null;
|
|
87
|
+
/** Get blockchain explorer link. */
|
|
88
|
+
explorer_link?: string | null;
|
|
89
|
+
/** Number of blockchain confirmations */
|
|
90
|
+
confirmations_count: number;
|
|
91
|
+
/** When this payment expires (typically 30 minutes) */
|
|
92
|
+
expires_at?: string | null;
|
|
93
|
+
/** When this payment was completed */
|
|
94
|
+
completed_at?: string | null;
|
|
95
|
+
/** When this record was created */
|
|
96
|
+
created_at: string;
|
|
97
|
+
is_completed: boolean;
|
|
98
|
+
is_failed: boolean;
|
|
99
|
+
is_expired: boolean;
|
|
100
|
+
/** Payment description */
|
|
101
|
+
description: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Payment list item (lighter than detail).
|
|
106
|
+
*
|
|
107
|
+
* Response model (includes read-only fields).
|
|
108
|
+
*/
|
|
109
|
+
export interface PaymentList {
|
|
110
|
+
/** Unique identifier for this record */
|
|
111
|
+
id: string;
|
|
112
|
+
/** Internal payment identifier (PAY_YYYYMMDDHHMMSS_UUID) */
|
|
113
|
+
internal_payment_id: string;
|
|
114
|
+
/** Payment amount in USD */
|
|
115
|
+
amount_usd: string;
|
|
116
|
+
currency_code: string;
|
|
117
|
+
currency_token: string;
|
|
118
|
+
/** Current payment status
|
|
119
|
+
|
|
120
|
+
* `pending` - Pending
|
|
121
|
+
* `confirming` - Confirming
|
|
122
|
+
* `confirmed` - Confirmed
|
|
123
|
+
* `completed` - Completed
|
|
124
|
+
* `partially_paid` - Partially Paid
|
|
125
|
+
* `failed` - Failed
|
|
126
|
+
* `expired` - Expired
|
|
127
|
+
* `cancelled` - Cancelled */
|
|
128
|
+
status: Enums.PaymentListStatus;
|
|
129
|
+
status_display: string;
|
|
130
|
+
/** When this record was created */
|
|
131
|
+
created_at: string;
|
|
132
|
+
/** When this payment was completed */
|
|
133
|
+
completed_at?: string | null;
|
|
134
|
+
}
|
|
135
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Client Adapter Pattern
|
|
3
|
+
*
|
|
4
|
+
* Allows switching between fetch/axios/httpx without changing generated code.
|
|
5
|
+
* Provides unified interface for making HTTP requests.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface HttpRequest {
|
|
9
|
+
method: string;
|
|
10
|
+
url: string;
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
body?: any;
|
|
13
|
+
params?: Record<string, any>;
|
|
14
|
+
/** FormData for file uploads (multipart/form-data) */
|
|
15
|
+
formData?: FormData;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface HttpResponse<T = any> {
|
|
19
|
+
data: T;
|
|
20
|
+
status: number;
|
|
21
|
+
statusText: string;
|
|
22
|
+
headers: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* HTTP Client Adapter Interface.
|
|
27
|
+
* Implement this to use custom HTTP clients (axios, httpx, etc.)
|
|
28
|
+
*/
|
|
29
|
+
export interface HttpClientAdapter {
|
|
30
|
+
request<T = any>(request: HttpRequest): Promise<HttpResponse<T>>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Default Fetch API adapter.
|
|
35
|
+
* Uses native browser fetch() with proper error handling.
|
|
36
|
+
*/
|
|
37
|
+
export class FetchAdapter implements HttpClientAdapter {
|
|
38
|
+
async request<T = any>(request: HttpRequest): Promise<HttpResponse<T>> {
|
|
39
|
+
const { method, url, headers, body, params, formData } = request;
|
|
40
|
+
|
|
41
|
+
// Build URL with query params
|
|
42
|
+
let finalUrl = url;
|
|
43
|
+
if (params) {
|
|
44
|
+
const searchParams = new URLSearchParams();
|
|
45
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
46
|
+
if (value !== null && value !== undefined) {
|
|
47
|
+
searchParams.append(key, String(value));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
const queryString = searchParams.toString();
|
|
51
|
+
if (queryString) {
|
|
52
|
+
finalUrl = url.includes('?') ? `${url}&${queryString}` : `${url}?${queryString}`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Build headers
|
|
57
|
+
const finalHeaders: Record<string, string> = { ...headers };
|
|
58
|
+
|
|
59
|
+
// Determine body and content-type
|
|
60
|
+
let requestBody: string | FormData | undefined;
|
|
61
|
+
|
|
62
|
+
if (formData) {
|
|
63
|
+
// For multipart/form-data, let browser set Content-Type with boundary
|
|
64
|
+
requestBody = formData;
|
|
65
|
+
// Don't set Content-Type - browser will set it with boundary
|
|
66
|
+
} else if (body) {
|
|
67
|
+
// JSON request
|
|
68
|
+
finalHeaders['Content-Type'] = 'application/json';
|
|
69
|
+
requestBody = JSON.stringify(body);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Make request
|
|
73
|
+
const response = await fetch(finalUrl, {
|
|
74
|
+
method,
|
|
75
|
+
headers: finalHeaders,
|
|
76
|
+
body: requestBody,
|
|
77
|
+
credentials: 'include', // Include Django session cookies
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Parse response
|
|
81
|
+
let data: any = null;
|
|
82
|
+
const contentType = response.headers.get('content-type');
|
|
83
|
+
|
|
84
|
+
if (response.status !== 204 && contentType?.includes('application/json')) {
|
|
85
|
+
data = await response.json();
|
|
86
|
+
} else if (response.status !== 204) {
|
|
87
|
+
data = await response.text();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Convert Headers to plain object
|
|
91
|
+
const responseHeaders: Record<string, string> = {};
|
|
92
|
+
response.headers.forEach((value, key) => {
|
|
93
|
+
responseHeaders[key] = value;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
data,
|
|
98
|
+
status: response.status,
|
|
99
|
+
statusText: response.statusText,
|
|
100
|
+
headers: responseHeaders,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|