@djangocfg/ext-payments 1.0.9 → 1.0.11

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 CHANGED
@@ -27,7 +27,7 @@ var import_ext_base = require("@djangocfg/ext-base");
27
27
  // package.json
28
28
  var package_default = {
29
29
  name: "@djangocfg/ext-payments",
30
- version: "1.0.9",
30
+ version: "1.0.11",
31
31
  description: "Payments system extension for DjangoCFG",
32
32
  keywords: [
33
33
  "django",
package/dist/config.js CHANGED
@@ -4,7 +4,7 @@ import { createExtensionConfig } from "@djangocfg/ext-base";
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "@djangocfg/ext-payments",
7
- version: "1.0.9",
7
+ version: "1.0.11",
8
8
  description: "Payments system extension for DjangoCFG",
9
9
  keywords: [
10
10
  "django",
package/dist/hooks.cjs CHANGED
@@ -129,7 +129,7 @@ var models_exports = {};
129
129
  // src/api/generated/ext_payments/http.ts
130
130
  var FetchAdapter = class {
131
131
  async request(request) {
132
- const { method, url, headers, body, params, formData } = request;
132
+ const { method, url, headers, body, params, formData, binaryBody } = request;
133
133
  let finalUrl = url;
134
134
  if (params) {
135
135
  const searchParams = new URLSearchParams();
@@ -147,6 +147,9 @@ var FetchAdapter = class {
147
147
  let requestBody;
148
148
  if (formData) {
149
149
  requestBody = formData;
150
+ } else if (binaryBody) {
151
+ finalHeaders["Content-Type"] = "application/octet-stream";
152
+ requestBody = binaryBody;
150
153
  } else if (body) {
151
154
  finalHeaders["Content-Type"] = "application/json";
152
155
  requestBody = JSON.stringify(body);
@@ -471,11 +474,13 @@ var APIClient = class {
471
474
  httpClient;
472
475
  logger = null;
473
476
  retryConfig = null;
477
+ tokenGetter = null;
474
478
  // Sub-clients
475
479
  ext_payments_payments;
476
480
  constructor(baseUrl, options) {
477
481
  this.baseUrl = baseUrl.replace(/\/$/, "");
478
482
  this.httpClient = options?.httpClient || new FetchAdapter();
483
+ this.tokenGetter = options?.tokenGetter || null;
479
484
  if (options?.loggerConfig !== void 0) {
480
485
  this.logger = new APILogger(options.loggerConfig);
481
486
  }
@@ -498,6 +503,19 @@ var APIClient = class {
498
503
  }
499
504
  return null;
500
505
  }
506
+ /**
507
+ * Get the base URL for building streaming/download URLs.
508
+ */
509
+ getBaseUrl() {
510
+ return this.baseUrl;
511
+ }
512
+ /**
513
+ * Get JWT token for URL authentication (used in streaming endpoints).
514
+ * Returns null if no token getter is configured or no token is available.
515
+ */
516
+ getToken() {
517
+ return this.tokenGetter ? this.tokenGetter() : null;
518
+ }
501
519
  /**
502
520
  * Make HTTP request with Django CSRF and session handling.
503
521
  * Automatically retries on network errors and 5xx server errors.
@@ -528,7 +546,7 @@ var APIClient = class {
528
546
  const headers = {
529
547
  ...options?.headers || {}
530
548
  };
531
- if (!options?.formData && !headers["Content-Type"]) {
549
+ if (!options?.formData && !options?.binaryBody && !headers["Content-Type"]) {
532
550
  headers["Content-Type"] = "application/json";
533
551
  }
534
552
  if (this.logger) {
@@ -547,7 +565,8 @@ var APIClient = class {
547
565
  headers,
548
566
  params: options?.params,
549
567
  body: options?.body,
550
- formData: options?.formData
568
+ formData: options?.formData,
569
+ binaryBody: options?.binaryBody
551
570
  });
552
571
  const duration = Date.now() - startTime;
553
572
  if (response.status >= 400) {
@@ -883,7 +902,7 @@ var PaymentDetailSchema = zod.z.object({
883
902
  status_display: zod.z.string(),
884
903
  pay_address: zod.z.string().nullable(),
885
904
  qr_code_url: zod.z.string().nullable(),
886
- payment_url: zod.z.url().nullable(),
905
+ payment_url: zod.z.union([zod.z.url(), zod.z.literal("")]).nullable(),
887
906
  transaction_hash: zod.z.string().nullable(),
888
907
  explorer_link: zod.z.string().nullable(),
889
908
  confirmations_count: zod.z.int(),
@@ -968,15 +987,28 @@ __export(fetchers_exports, {
968
987
 
969
988
  // src/api/generated/ext_payments/api-instance.ts
970
989
  var globalAPI = null;
990
+ var autoConfigAttempted = false;
991
+ function tryAutoConfigureFromEnv() {
992
+ if (autoConfigAttempted) return;
993
+ autoConfigAttempted = true;
994
+ if (globalAPI) return;
995
+ if (typeof process === "undefined" || !process.env) return;
996
+ const baseUrl = process.env.NEXT_PUBLIC_API_URL || process.env.VITE_API_URL || process.env.REACT_APP_API_URL || process.env.API_URL;
997
+ if (baseUrl) {
998
+ globalAPI = new API(baseUrl);
999
+ }
1000
+ }
971
1001
  function getAPIInstance() {
1002
+ tryAutoConfigureFromEnv();
972
1003
  if (!globalAPI) {
973
1004
  throw new Error(
974
- 'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\nExample:\n import { configureAPI } from "./api-instance"\n configureAPI({ baseUrl: "https://api.example.com" })'
1005
+ 'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\nExample:\n import { configureAPI } from "./api-instance"\n configureAPI({ baseUrl: "https://api.example.com" })\n\nOr set environment variable: NEXT_PUBLIC_API_URL, VITE_API_URL, or REACT_APP_API_URL'
975
1006
  );
976
1007
  }
977
1008
  return globalAPI;
978
1009
  }
979
1010
  function isAPIConfigured() {
1011
+ tryAutoConfigureFromEnv();
980
1012
  return globalAPI !== null;
981
1013
  }
982
1014
  function configureAPI(config) {
@@ -1291,7 +1323,8 @@ var API = class {
1291
1323
  this._loadTokensFromStorage();
1292
1324
  this._client = new APIClient(this.baseUrl, {
1293
1325
  retryConfig: this.options?.retryConfig,
1294
- loggerConfig: this.options?.loggerConfig
1326
+ loggerConfig: this.options?.loggerConfig,
1327
+ tokenGetter: () => this.getToken()
1295
1328
  });
1296
1329
  this._injectAuthHeader();
1297
1330
  this.ext_payments_payments = this._client.ext_payments_payments;
@@ -1303,7 +1336,8 @@ var API = class {
1303
1336
  _reinitClients() {
1304
1337
  this._client = new APIClient(this.baseUrl, {
1305
1338
  retryConfig: this.options?.retryConfig,
1306
- loggerConfig: this.options?.loggerConfig
1339
+ loggerConfig: this.options?.loggerConfig,
1340
+ tokenGetter: () => this.getToken()
1307
1341
  });
1308
1342
  this._injectAuthHeader();
1309
1343
  this.ext_payments_payments = this._client.ext_payments_payments;
@@ -1394,6 +1428,7 @@ var API = class {
1394
1428
  return "./schema.json";
1395
1429
  }
1396
1430
  };
1431
+ api.initializeExtensionAPI(configureAPI);
1397
1432
  var apiPayments = api.createExtensionAPI(API);
1398
1433
  function usePaymentsBalanceRetrieve(client) {
1399
1434
  return useSWR__default.default(
@@ -2617,7 +2652,7 @@ var PaymentsLayout = () => {
2617
2652
  // package.json
2618
2653
  var package_default = {
2619
2654
  name: "@djangocfg/ext-payments",
2620
- version: "1.0.9",
2655
+ version: "1.0.11",
2621
2656
  description: "Payments system extension for DjangoCFG",
2622
2657
  keywords: [
2623
2658
  "django",
package/dist/hooks.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createConsola, consola } from 'consola';
2
2
  import pRetry, { AbortError } from 'p-retry';
3
3
  import { z } from 'zod';
4
- import { createExtensionAPI } from '@djangocfg/ext-base/api';
4
+ import { initializeExtensionAPI, createExtensionAPI } from '@djangocfg/ext-base/api';
5
5
  import { RefreshCw, Plus, XCircle, ExternalLink, Wallet, CreditCard, History, Clock, AlertCircle, CheckCircle2, Search, Filter, ArrowDownLeft, ArrowUpRight } from 'lucide-react';
6
6
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Form, FormField, FormItem, FormLabel, FormControl, Input, FormDescription, FormMessage, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, TokenIcon, DialogFooter, Button, CopyButton, Tabs, TabsList, TabsTrigger, TabsContent, Card, CardHeader, CardTitle, Skeleton, CardContent, Badge, Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '@djangocfg/ui-core';
7
7
  import { createContext, useContext, useState, useMemo, useEffect } from 'react';
@@ -121,7 +121,7 @@ var models_exports = {};
121
121
  // src/api/generated/ext_payments/http.ts
122
122
  var FetchAdapter = class {
123
123
  async request(request) {
124
- const { method, url, headers, body, params, formData } = request;
124
+ const { method, url, headers, body, params, formData, binaryBody } = request;
125
125
  let finalUrl = url;
126
126
  if (params) {
127
127
  const searchParams = new URLSearchParams();
@@ -139,6 +139,9 @@ var FetchAdapter = class {
139
139
  let requestBody;
140
140
  if (formData) {
141
141
  requestBody = formData;
142
+ } else if (binaryBody) {
143
+ finalHeaders["Content-Type"] = "application/octet-stream";
144
+ requestBody = binaryBody;
142
145
  } else if (body) {
143
146
  finalHeaders["Content-Type"] = "application/json";
144
147
  requestBody = JSON.stringify(body);
@@ -463,11 +466,13 @@ var APIClient = class {
463
466
  httpClient;
464
467
  logger = null;
465
468
  retryConfig = null;
469
+ tokenGetter = null;
466
470
  // Sub-clients
467
471
  ext_payments_payments;
468
472
  constructor(baseUrl, options) {
469
473
  this.baseUrl = baseUrl.replace(/\/$/, "");
470
474
  this.httpClient = options?.httpClient || new FetchAdapter();
475
+ this.tokenGetter = options?.tokenGetter || null;
471
476
  if (options?.loggerConfig !== void 0) {
472
477
  this.logger = new APILogger(options.loggerConfig);
473
478
  }
@@ -490,6 +495,19 @@ var APIClient = class {
490
495
  }
491
496
  return null;
492
497
  }
498
+ /**
499
+ * Get the base URL for building streaming/download URLs.
500
+ */
501
+ getBaseUrl() {
502
+ return this.baseUrl;
503
+ }
504
+ /**
505
+ * Get JWT token for URL authentication (used in streaming endpoints).
506
+ * Returns null if no token getter is configured or no token is available.
507
+ */
508
+ getToken() {
509
+ return this.tokenGetter ? this.tokenGetter() : null;
510
+ }
493
511
  /**
494
512
  * Make HTTP request with Django CSRF and session handling.
495
513
  * Automatically retries on network errors and 5xx server errors.
@@ -520,7 +538,7 @@ var APIClient = class {
520
538
  const headers = {
521
539
  ...options?.headers || {}
522
540
  };
523
- if (!options?.formData && !headers["Content-Type"]) {
541
+ if (!options?.formData && !options?.binaryBody && !headers["Content-Type"]) {
524
542
  headers["Content-Type"] = "application/json";
525
543
  }
526
544
  if (this.logger) {
@@ -539,7 +557,8 @@ var APIClient = class {
539
557
  headers,
540
558
  params: options?.params,
541
559
  body: options?.body,
542
- formData: options?.formData
560
+ formData: options?.formData,
561
+ binaryBody: options?.binaryBody
543
562
  });
544
563
  const duration = Date.now() - startTime;
545
564
  if (response.status >= 400) {
@@ -875,7 +894,7 @@ var PaymentDetailSchema = z.object({
875
894
  status_display: z.string(),
876
895
  pay_address: z.string().nullable(),
877
896
  qr_code_url: z.string().nullable(),
878
- payment_url: z.url().nullable(),
897
+ payment_url: z.union([z.url(), z.literal("")]).nullable(),
879
898
  transaction_hash: z.string().nullable(),
880
899
  explorer_link: z.string().nullable(),
881
900
  confirmations_count: z.int(),
@@ -960,15 +979,28 @@ __export(fetchers_exports, {
960
979
 
961
980
  // src/api/generated/ext_payments/api-instance.ts
962
981
  var globalAPI = null;
982
+ var autoConfigAttempted = false;
983
+ function tryAutoConfigureFromEnv() {
984
+ if (autoConfigAttempted) return;
985
+ autoConfigAttempted = true;
986
+ if (globalAPI) return;
987
+ if (typeof process === "undefined" || !process.env) return;
988
+ const baseUrl = process.env.NEXT_PUBLIC_API_URL || process.env.VITE_API_URL || process.env.REACT_APP_API_URL || process.env.API_URL;
989
+ if (baseUrl) {
990
+ globalAPI = new API(baseUrl);
991
+ }
992
+ }
963
993
  function getAPIInstance() {
994
+ tryAutoConfigureFromEnv();
964
995
  if (!globalAPI) {
965
996
  throw new Error(
966
- 'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\nExample:\n import { configureAPI } from "./api-instance"\n configureAPI({ baseUrl: "https://api.example.com" })'
997
+ 'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\nExample:\n import { configureAPI } from "./api-instance"\n configureAPI({ baseUrl: "https://api.example.com" })\n\nOr set environment variable: NEXT_PUBLIC_API_URL, VITE_API_URL, or REACT_APP_API_URL'
967
998
  );
968
999
  }
969
1000
  return globalAPI;
970
1001
  }
971
1002
  function isAPIConfigured() {
1003
+ tryAutoConfigureFromEnv();
972
1004
  return globalAPI !== null;
973
1005
  }
974
1006
  function configureAPI(config) {
@@ -1283,7 +1315,8 @@ var API = class {
1283
1315
  this._loadTokensFromStorage();
1284
1316
  this._client = new APIClient(this.baseUrl, {
1285
1317
  retryConfig: this.options?.retryConfig,
1286
- loggerConfig: this.options?.loggerConfig
1318
+ loggerConfig: this.options?.loggerConfig,
1319
+ tokenGetter: () => this.getToken()
1287
1320
  });
1288
1321
  this._injectAuthHeader();
1289
1322
  this.ext_payments_payments = this._client.ext_payments_payments;
@@ -1295,7 +1328,8 @@ var API = class {
1295
1328
  _reinitClients() {
1296
1329
  this._client = new APIClient(this.baseUrl, {
1297
1330
  retryConfig: this.options?.retryConfig,
1298
- loggerConfig: this.options?.loggerConfig
1331
+ loggerConfig: this.options?.loggerConfig,
1332
+ tokenGetter: () => this.getToken()
1299
1333
  });
1300
1334
  this._injectAuthHeader();
1301
1335
  this.ext_payments_payments = this._client.ext_payments_payments;
@@ -1386,6 +1420,7 @@ var API = class {
1386
1420
  return "./schema.json";
1387
1421
  }
1388
1422
  };
1423
+ initializeExtensionAPI(configureAPI);
1389
1424
  var apiPayments = createExtensionAPI(API);
1390
1425
  function usePaymentsBalanceRetrieve(client) {
1391
1426
  return useSWR(
@@ -2609,7 +2644,7 @@ var PaymentsLayout = () => {
2609
2644
  // package.json
2610
2645
  var package_default = {
2611
2646
  name: "@djangocfg/ext-payments",
2612
- version: "1.0.9",
2647
+ version: "1.0.11",
2613
2648
  description: "Payments system extension for DjangoCFG",
2614
2649
  keywords: [
2615
2650
  "django",
package/dist/index.cjs CHANGED
@@ -129,7 +129,7 @@ var models_exports = {};
129
129
  // src/api/generated/ext_payments/http.ts
130
130
  var FetchAdapter = class {
131
131
  async request(request) {
132
- const { method, url, headers, body, params, formData } = request;
132
+ const { method, url, headers, body, params, formData, binaryBody } = request;
133
133
  let finalUrl = url;
134
134
  if (params) {
135
135
  const searchParams = new URLSearchParams();
@@ -147,6 +147,9 @@ var FetchAdapter = class {
147
147
  let requestBody;
148
148
  if (formData) {
149
149
  requestBody = formData;
150
+ } else if (binaryBody) {
151
+ finalHeaders["Content-Type"] = "application/octet-stream";
152
+ requestBody = binaryBody;
150
153
  } else if (body) {
151
154
  finalHeaders["Content-Type"] = "application/json";
152
155
  requestBody = JSON.stringify(body);
@@ -471,11 +474,13 @@ var APIClient = class {
471
474
  httpClient;
472
475
  logger = null;
473
476
  retryConfig = null;
477
+ tokenGetter = null;
474
478
  // Sub-clients
475
479
  ext_payments_payments;
476
480
  constructor(baseUrl, options) {
477
481
  this.baseUrl = baseUrl.replace(/\/$/, "");
478
482
  this.httpClient = options?.httpClient || new FetchAdapter();
483
+ this.tokenGetter = options?.tokenGetter || null;
479
484
  if (options?.loggerConfig !== void 0) {
480
485
  this.logger = new APILogger(options.loggerConfig);
481
486
  }
@@ -498,6 +503,19 @@ var APIClient = class {
498
503
  }
499
504
  return null;
500
505
  }
506
+ /**
507
+ * Get the base URL for building streaming/download URLs.
508
+ */
509
+ getBaseUrl() {
510
+ return this.baseUrl;
511
+ }
512
+ /**
513
+ * Get JWT token for URL authentication (used in streaming endpoints).
514
+ * Returns null if no token getter is configured or no token is available.
515
+ */
516
+ getToken() {
517
+ return this.tokenGetter ? this.tokenGetter() : null;
518
+ }
501
519
  /**
502
520
  * Make HTTP request with Django CSRF and session handling.
503
521
  * Automatically retries on network errors and 5xx server errors.
@@ -528,7 +546,7 @@ var APIClient = class {
528
546
  const headers = {
529
547
  ...options?.headers || {}
530
548
  };
531
- if (!options?.formData && !headers["Content-Type"]) {
549
+ if (!options?.formData && !options?.binaryBody && !headers["Content-Type"]) {
532
550
  headers["Content-Type"] = "application/json";
533
551
  }
534
552
  if (this.logger) {
@@ -547,7 +565,8 @@ var APIClient = class {
547
565
  headers,
548
566
  params: options?.params,
549
567
  body: options?.body,
550
- formData: options?.formData
568
+ formData: options?.formData,
569
+ binaryBody: options?.binaryBody
551
570
  });
552
571
  const duration = Date.now() - startTime;
553
572
  if (response.status >= 400) {
@@ -883,7 +902,7 @@ var PaymentDetailSchema = zod.z.object({
883
902
  status_display: zod.z.string(),
884
903
  pay_address: zod.z.string().nullable(),
885
904
  qr_code_url: zod.z.string().nullable(),
886
- payment_url: zod.z.url().nullable(),
905
+ payment_url: zod.z.union([zod.z.url(), zod.z.literal("")]).nullable(),
887
906
  transaction_hash: zod.z.string().nullable(),
888
907
  explorer_link: zod.z.string().nullable(),
889
908
  confirmations_count: zod.z.int(),
@@ -968,15 +987,28 @@ __export(fetchers_exports, {
968
987
 
969
988
  // src/api/generated/ext_payments/api-instance.ts
970
989
  var globalAPI = null;
990
+ var autoConfigAttempted = false;
991
+ function tryAutoConfigureFromEnv() {
992
+ if (autoConfigAttempted) return;
993
+ autoConfigAttempted = true;
994
+ if (globalAPI) return;
995
+ if (typeof process === "undefined" || !process.env) return;
996
+ const baseUrl = process.env.NEXT_PUBLIC_API_URL || process.env.VITE_API_URL || process.env.REACT_APP_API_URL || process.env.API_URL;
997
+ if (baseUrl) {
998
+ globalAPI = new API(baseUrl);
999
+ }
1000
+ }
971
1001
  function getAPIInstance() {
1002
+ tryAutoConfigureFromEnv();
972
1003
  if (!globalAPI) {
973
1004
  throw new Error(
974
- 'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\nExample:\n import { configureAPI } from "./api-instance"\n configureAPI({ baseUrl: "https://api.example.com" })'
1005
+ 'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\nExample:\n import { configureAPI } from "./api-instance"\n configureAPI({ baseUrl: "https://api.example.com" })\n\nOr set environment variable: NEXT_PUBLIC_API_URL, VITE_API_URL, or REACT_APP_API_URL'
975
1006
  );
976
1007
  }
977
1008
  return globalAPI;
978
1009
  }
979
1010
  function isAPIConfigured() {
1011
+ tryAutoConfigureFromEnv();
980
1012
  return globalAPI !== null;
981
1013
  }
982
1014
  function configureAPI(config) {
@@ -1291,7 +1323,8 @@ var API = class {
1291
1323
  this._loadTokensFromStorage();
1292
1324
  this._client = new APIClient(this.baseUrl, {
1293
1325
  retryConfig: this.options?.retryConfig,
1294
- loggerConfig: this.options?.loggerConfig
1326
+ loggerConfig: this.options?.loggerConfig,
1327
+ tokenGetter: () => this.getToken()
1295
1328
  });
1296
1329
  this._injectAuthHeader();
1297
1330
  this.ext_payments_payments = this._client.ext_payments_payments;
@@ -1303,7 +1336,8 @@ var API = class {
1303
1336
  _reinitClients() {
1304
1337
  this._client = new APIClient(this.baseUrl, {
1305
1338
  retryConfig: this.options?.retryConfig,
1306
- loggerConfig: this.options?.loggerConfig
1339
+ loggerConfig: this.options?.loggerConfig,
1340
+ tokenGetter: () => this.getToken()
1307
1341
  });
1308
1342
  this._injectAuthHeader();
1309
1343
  this.ext_payments_payments = this._client.ext_payments_payments;
@@ -1394,6 +1428,7 @@ var API = class {
1394
1428
  return "./schema.json";
1395
1429
  }
1396
1430
  };
1431
+ api.initializeExtensionAPI(configureAPI);
1397
1432
  var apiPayments = api.createExtensionAPI(API);
1398
1433
  function usePaymentsBalanceRetrieve(client) {
1399
1434
  return useSWR__default.default(
@@ -2561,7 +2596,7 @@ var PaymentsLayout = () => {
2561
2596
  // package.json
2562
2597
  var package_default = {
2563
2598
  name: "@djangocfg/ext-payments",
2564
- version: "1.0.9",
2599
+ version: "1.0.11",
2565
2600
  description: "Payments system extension for DjangoCFG",
2566
2601
  keywords: [
2567
2602
  "django",
package/dist/index.d.cts CHANGED
@@ -279,6 +279,8 @@ interface HttpRequest {
279
279
  params?: Record<string, any>;
280
280
  /** FormData for file uploads (multipart/form-data) */
281
281
  formData?: FormData;
282
+ /** Binary data for octet-stream uploads */
283
+ binaryBody?: Blob | ArrayBuffer;
282
284
  }
283
285
  interface HttpResponse<T = any> {
284
286
  data: T;
@@ -531,11 +533,13 @@ declare class APIClient {
531
533
  private httpClient;
532
534
  private logger;
533
535
  private retryConfig;
536
+ private tokenGetter;
534
537
  ext_payments_payments: ExtPaymentsPayments;
535
538
  constructor(baseUrl: string, options?: {
536
539
  httpClient?: HttpClientAdapter;
537
540
  loggerConfig?: Partial<LoggerConfig>;
538
541
  retryConfig?: RetryConfig;
542
+ tokenGetter?: () => string | null;
539
543
  });
540
544
  /**
541
545
  * Get CSRF token from cookies (for SessionAuthentication).
@@ -543,6 +547,15 @@ declare class APIClient {
543
547
  * Returns null if cookie doesn't exist (JWT-only auth).
544
548
  */
545
549
  getCsrfToken(): string | null;
550
+ /**
551
+ * Get the base URL for building streaming/download URLs.
552
+ */
553
+ getBaseUrl(): string;
554
+ /**
555
+ * Get JWT token for URL authentication (used in streaming endpoints).
556
+ * Returns null if no token getter is configured or no token is available.
557
+ */
558
+ getToken(): string | null;
546
559
  /**
547
560
  * Make HTTP request with Django CSRF and session handling.
548
561
  * Automatically retries on network errors and 5xx server errors.
@@ -551,6 +564,7 @@ declare class APIClient {
551
564
  params?: Record<string, any>;
552
565
  body?: any;
553
566
  formData?: FormData;
567
+ binaryBody?: Blob | ArrayBuffer;
554
568
  headers?: Record<string, string>;
555
569
  }): Promise<T>;
556
570
  /**
@@ -715,7 +729,7 @@ declare const PaymentDetailSchema: z.ZodObject<{
715
729
  status_display: z.ZodString;
716
730
  pay_address: z.ZodNullable<z.ZodString>;
717
731
  qr_code_url: z.ZodNullable<z.ZodString>;
718
- payment_url: z.ZodNullable<z.ZodURL>;
732
+ payment_url: z.ZodNullable<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>>;
719
733
  transaction_hash: z.ZodNullable<z.ZodString>;
720
734
  explorer_link: z.ZodNullable<z.ZodString>;
721
735
  confirmations_count: z.ZodInt;
@@ -995,30 +1009,38 @@ declare namespace index {
995
1009
  }
996
1010
 
997
1011
  /**
998
- * Global API Instance - Singleton configuration
1012
+ * Global API Instance - Singleton configuration with auto-configuration support
999
1013
  *
1000
- * This module provides a global API instance that can be configured once
1001
- * and used throughout your application.
1014
+ * This module provides a global API instance that auto-configures from
1015
+ * environment variables or can be configured manually.
1002
1016
  *
1003
- * Usage:
1017
+ * AUTO-CONFIGURATION (recommended):
1018
+ * Set one of these environment variables and the API will auto-configure:
1019
+ * - NEXT_PUBLIC_API_URL (Next.js)
1020
+ * - VITE_API_URL (Vite)
1021
+ * - REACT_APP_API_URL (Create React App)
1022
+ * - API_URL (generic)
1023
+ *
1024
+ * Then just use fetchers and hooks directly:
1025
+ * ```typescript
1026
+ * import { getUsers } from './_utils/fetchers'
1027
+ * const users = await getUsers({ page: 1 })
1028
+ * ```
1029
+ *
1030
+ * MANUAL CONFIGURATION:
1004
1031
  * ```typescript
1005
- * // Configure once (e.g., in your app entry point)
1006
1032
  * import { configureAPI } from './api-instance'
1007
1033
  *
1008
1034
  * configureAPI({
1009
1035
  * baseUrl: 'https://api.example.com',
1010
1036
  * token: 'your-jwt-token'
1011
1037
  * })
1012
- *
1013
- * // Then use fetchers and hooks anywhere without configuration
1014
- * import { getUsers } from './fetchers'
1015
- * const users = await getUsers({ page: 1 })
1016
1038
  * ```
1017
1039
  *
1018
1040
  * For SSR or multiple instances:
1019
1041
  * ```typescript
1020
1042
  * import { API } from './index'
1021
- * import { getUsers } from './fetchers'
1043
+ * import { getUsers } from './_utils/fetchers'
1022
1044
  *
1023
1045
  * const api = new API('https://api.example.com')
1024
1046
  * const users = await getUsers({ page: 1 }, api)
@@ -1027,11 +1049,12 @@ declare namespace index {
1027
1049
 
1028
1050
  /**
1029
1051
  * Get the global API instance
1030
- * @throws Error if API is not configured
1052
+ * Auto-configures from environment variables on first call if not manually configured.
1053
+ * @throws Error if API is not configured and no env variable is set
1031
1054
  */
1032
1055
  declare function getAPIInstance(): API;
1033
1056
  /**
1034
- * Check if API is configured
1057
+ * Check if API is configured (or can be auto-configured)
1035
1058
  */
1036
1059
  declare function isAPIConfigured(): boolean;
1037
1060
  /**
package/dist/index.d.ts CHANGED
@@ -279,6 +279,8 @@ interface HttpRequest {
279
279
  params?: Record<string, any>;
280
280
  /** FormData for file uploads (multipart/form-data) */
281
281
  formData?: FormData;
282
+ /** Binary data for octet-stream uploads */
283
+ binaryBody?: Blob | ArrayBuffer;
282
284
  }
283
285
  interface HttpResponse<T = any> {
284
286
  data: T;
@@ -531,11 +533,13 @@ declare class APIClient {
531
533
  private httpClient;
532
534
  private logger;
533
535
  private retryConfig;
536
+ private tokenGetter;
534
537
  ext_payments_payments: ExtPaymentsPayments;
535
538
  constructor(baseUrl: string, options?: {
536
539
  httpClient?: HttpClientAdapter;
537
540
  loggerConfig?: Partial<LoggerConfig>;
538
541
  retryConfig?: RetryConfig;
542
+ tokenGetter?: () => string | null;
539
543
  });
540
544
  /**
541
545
  * Get CSRF token from cookies (for SessionAuthentication).
@@ -543,6 +547,15 @@ declare class APIClient {
543
547
  * Returns null if cookie doesn't exist (JWT-only auth).
544
548
  */
545
549
  getCsrfToken(): string | null;
550
+ /**
551
+ * Get the base URL for building streaming/download URLs.
552
+ */
553
+ getBaseUrl(): string;
554
+ /**
555
+ * Get JWT token for URL authentication (used in streaming endpoints).
556
+ * Returns null if no token getter is configured or no token is available.
557
+ */
558
+ getToken(): string | null;
546
559
  /**
547
560
  * Make HTTP request with Django CSRF and session handling.
548
561
  * Automatically retries on network errors and 5xx server errors.
@@ -551,6 +564,7 @@ declare class APIClient {
551
564
  params?: Record<string, any>;
552
565
  body?: any;
553
566
  formData?: FormData;
567
+ binaryBody?: Blob | ArrayBuffer;
554
568
  headers?: Record<string, string>;
555
569
  }): Promise<T>;
556
570
  /**
@@ -715,7 +729,7 @@ declare const PaymentDetailSchema: z.ZodObject<{
715
729
  status_display: z.ZodString;
716
730
  pay_address: z.ZodNullable<z.ZodString>;
717
731
  qr_code_url: z.ZodNullable<z.ZodString>;
718
- payment_url: z.ZodNullable<z.ZodURL>;
732
+ payment_url: z.ZodNullable<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>>;
719
733
  transaction_hash: z.ZodNullable<z.ZodString>;
720
734
  explorer_link: z.ZodNullable<z.ZodString>;
721
735
  confirmations_count: z.ZodInt;
@@ -995,30 +1009,38 @@ declare namespace index {
995
1009
  }
996
1010
 
997
1011
  /**
998
- * Global API Instance - Singleton configuration
1012
+ * Global API Instance - Singleton configuration with auto-configuration support
999
1013
  *
1000
- * This module provides a global API instance that can be configured once
1001
- * and used throughout your application.
1014
+ * This module provides a global API instance that auto-configures from
1015
+ * environment variables or can be configured manually.
1002
1016
  *
1003
- * Usage:
1017
+ * AUTO-CONFIGURATION (recommended):
1018
+ * Set one of these environment variables and the API will auto-configure:
1019
+ * - NEXT_PUBLIC_API_URL (Next.js)
1020
+ * - VITE_API_URL (Vite)
1021
+ * - REACT_APP_API_URL (Create React App)
1022
+ * - API_URL (generic)
1023
+ *
1024
+ * Then just use fetchers and hooks directly:
1025
+ * ```typescript
1026
+ * import { getUsers } from './_utils/fetchers'
1027
+ * const users = await getUsers({ page: 1 })
1028
+ * ```
1029
+ *
1030
+ * MANUAL CONFIGURATION:
1004
1031
  * ```typescript
1005
- * // Configure once (e.g., in your app entry point)
1006
1032
  * import { configureAPI } from './api-instance'
1007
1033
  *
1008
1034
  * configureAPI({
1009
1035
  * baseUrl: 'https://api.example.com',
1010
1036
  * token: 'your-jwt-token'
1011
1037
  * })
1012
- *
1013
- * // Then use fetchers and hooks anywhere without configuration
1014
- * import { getUsers } from './fetchers'
1015
- * const users = await getUsers({ page: 1 })
1016
1038
  * ```
1017
1039
  *
1018
1040
  * For SSR or multiple instances:
1019
1041
  * ```typescript
1020
1042
  * import { API } from './index'
1021
- * import { getUsers } from './fetchers'
1043
+ * import { getUsers } from './_utils/fetchers'
1022
1044
  *
1023
1045
  * const api = new API('https://api.example.com')
1024
1046
  * const users = await getUsers({ page: 1 }, api)
@@ -1027,11 +1049,12 @@ declare namespace index {
1027
1049
 
1028
1050
  /**
1029
1051
  * Get the global API instance
1030
- * @throws Error if API is not configured
1052
+ * Auto-configures from environment variables on first call if not manually configured.
1053
+ * @throws Error if API is not configured and no env variable is set
1031
1054
  */
1032
1055
  declare function getAPIInstance(): API;
1033
1056
  /**
1034
- * Check if API is configured
1057
+ * Check if API is configured (or can be auto-configured)
1035
1058
  */
1036
1059
  declare function isAPIConfigured(): boolean;
1037
1060
  /**
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createConsola, consola } from 'consola';
2
2
  import pRetry, { AbortError } from 'p-retry';
3
3
  import { z } from 'zod';
4
- import { createExtensionAPI } from '@djangocfg/ext-base/api';
4
+ import { initializeExtensionAPI, createExtensionAPI } from '@djangocfg/ext-base/api';
5
5
  import { Wallet, CreditCard, History, RefreshCw, Plus, XCircle, ExternalLink, Search, Filter, Clock, AlertCircle, CheckCircle2, ArrowDownLeft, ArrowUpRight } from 'lucide-react';
6
6
  import { Tabs, TabsList, TabsTrigger, TabsContent, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Form, FormField, FormItem, FormLabel, FormControl, Input, FormDescription, FormMessage, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, TokenIcon, DialogFooter, Button, CopyButton, Card, CardHeader, CardTitle, Skeleton, CardContent, Badge, Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '@djangocfg/ui-core';
7
7
  import { createContext, useState, useMemo, useEffect, useContext } from 'react';
@@ -121,7 +121,7 @@ var models_exports = {};
121
121
  // src/api/generated/ext_payments/http.ts
122
122
  var FetchAdapter = class {
123
123
  async request(request) {
124
- const { method, url, headers, body, params, formData } = request;
124
+ const { method, url, headers, body, params, formData, binaryBody } = request;
125
125
  let finalUrl = url;
126
126
  if (params) {
127
127
  const searchParams = new URLSearchParams();
@@ -139,6 +139,9 @@ var FetchAdapter = class {
139
139
  let requestBody;
140
140
  if (formData) {
141
141
  requestBody = formData;
142
+ } else if (binaryBody) {
143
+ finalHeaders["Content-Type"] = "application/octet-stream";
144
+ requestBody = binaryBody;
142
145
  } else if (body) {
143
146
  finalHeaders["Content-Type"] = "application/json";
144
147
  requestBody = JSON.stringify(body);
@@ -463,11 +466,13 @@ var APIClient = class {
463
466
  httpClient;
464
467
  logger = null;
465
468
  retryConfig = null;
469
+ tokenGetter = null;
466
470
  // Sub-clients
467
471
  ext_payments_payments;
468
472
  constructor(baseUrl, options) {
469
473
  this.baseUrl = baseUrl.replace(/\/$/, "");
470
474
  this.httpClient = options?.httpClient || new FetchAdapter();
475
+ this.tokenGetter = options?.tokenGetter || null;
471
476
  if (options?.loggerConfig !== void 0) {
472
477
  this.logger = new APILogger(options.loggerConfig);
473
478
  }
@@ -490,6 +495,19 @@ var APIClient = class {
490
495
  }
491
496
  return null;
492
497
  }
498
+ /**
499
+ * Get the base URL for building streaming/download URLs.
500
+ */
501
+ getBaseUrl() {
502
+ return this.baseUrl;
503
+ }
504
+ /**
505
+ * Get JWT token for URL authentication (used in streaming endpoints).
506
+ * Returns null if no token getter is configured or no token is available.
507
+ */
508
+ getToken() {
509
+ return this.tokenGetter ? this.tokenGetter() : null;
510
+ }
493
511
  /**
494
512
  * Make HTTP request with Django CSRF and session handling.
495
513
  * Automatically retries on network errors and 5xx server errors.
@@ -520,7 +538,7 @@ var APIClient = class {
520
538
  const headers = {
521
539
  ...options?.headers || {}
522
540
  };
523
- if (!options?.formData && !headers["Content-Type"]) {
541
+ if (!options?.formData && !options?.binaryBody && !headers["Content-Type"]) {
524
542
  headers["Content-Type"] = "application/json";
525
543
  }
526
544
  if (this.logger) {
@@ -539,7 +557,8 @@ var APIClient = class {
539
557
  headers,
540
558
  params: options?.params,
541
559
  body: options?.body,
542
- formData: options?.formData
560
+ formData: options?.formData,
561
+ binaryBody: options?.binaryBody
543
562
  });
544
563
  const duration = Date.now() - startTime;
545
564
  if (response.status >= 400) {
@@ -875,7 +894,7 @@ var PaymentDetailSchema = z.object({
875
894
  status_display: z.string(),
876
895
  pay_address: z.string().nullable(),
877
896
  qr_code_url: z.string().nullable(),
878
- payment_url: z.url().nullable(),
897
+ payment_url: z.union([z.url(), z.literal("")]).nullable(),
879
898
  transaction_hash: z.string().nullable(),
880
899
  explorer_link: z.string().nullable(),
881
900
  confirmations_count: z.int(),
@@ -960,15 +979,28 @@ __export(fetchers_exports, {
960
979
 
961
980
  // src/api/generated/ext_payments/api-instance.ts
962
981
  var globalAPI = null;
982
+ var autoConfigAttempted = false;
983
+ function tryAutoConfigureFromEnv() {
984
+ if (autoConfigAttempted) return;
985
+ autoConfigAttempted = true;
986
+ if (globalAPI) return;
987
+ if (typeof process === "undefined" || !process.env) return;
988
+ const baseUrl = process.env.NEXT_PUBLIC_API_URL || process.env.VITE_API_URL || process.env.REACT_APP_API_URL || process.env.API_URL;
989
+ if (baseUrl) {
990
+ globalAPI = new API(baseUrl);
991
+ }
992
+ }
963
993
  function getAPIInstance() {
994
+ tryAutoConfigureFromEnv();
964
995
  if (!globalAPI) {
965
996
  throw new Error(
966
- 'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\nExample:\n import { configureAPI } from "./api-instance"\n configureAPI({ baseUrl: "https://api.example.com" })'
997
+ 'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\nExample:\n import { configureAPI } from "./api-instance"\n configureAPI({ baseUrl: "https://api.example.com" })\n\nOr set environment variable: NEXT_PUBLIC_API_URL, VITE_API_URL, or REACT_APP_API_URL'
967
998
  );
968
999
  }
969
1000
  return globalAPI;
970
1001
  }
971
1002
  function isAPIConfigured() {
1003
+ tryAutoConfigureFromEnv();
972
1004
  return globalAPI !== null;
973
1005
  }
974
1006
  function configureAPI(config) {
@@ -1283,7 +1315,8 @@ var API = class {
1283
1315
  this._loadTokensFromStorage();
1284
1316
  this._client = new APIClient(this.baseUrl, {
1285
1317
  retryConfig: this.options?.retryConfig,
1286
- loggerConfig: this.options?.loggerConfig
1318
+ loggerConfig: this.options?.loggerConfig,
1319
+ tokenGetter: () => this.getToken()
1287
1320
  });
1288
1321
  this._injectAuthHeader();
1289
1322
  this.ext_payments_payments = this._client.ext_payments_payments;
@@ -1295,7 +1328,8 @@ var API = class {
1295
1328
  _reinitClients() {
1296
1329
  this._client = new APIClient(this.baseUrl, {
1297
1330
  retryConfig: this.options?.retryConfig,
1298
- loggerConfig: this.options?.loggerConfig
1331
+ loggerConfig: this.options?.loggerConfig,
1332
+ tokenGetter: () => this.getToken()
1299
1333
  });
1300
1334
  this._injectAuthHeader();
1301
1335
  this.ext_payments_payments = this._client.ext_payments_payments;
@@ -1386,6 +1420,7 @@ var API = class {
1386
1420
  return "./schema.json";
1387
1421
  }
1388
1422
  };
1423
+ initializeExtensionAPI(configureAPI);
1389
1424
  var apiPayments = createExtensionAPI(API);
1390
1425
  function usePaymentsBalanceRetrieve(client) {
1391
1426
  return useSWR(
@@ -2553,7 +2588,7 @@ var PaymentsLayout = () => {
2553
2588
  // package.json
2554
2589
  var package_default = {
2555
2590
  name: "@djangocfg/ext-payments",
2556
- version: "1.0.9",
2591
+ version: "1.0.11",
2557
2592
  description: "Payments system extension for DjangoCFG",
2558
2593
  keywords: [
2559
2594
  "django",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ext-payments",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Payments system extension for DjangoCFG",
5
5
  "keywords": [
6
6
  "django",
@@ -59,10 +59,10 @@
59
59
  "check": "tsc --noEmit"
60
60
  },
61
61
  "peerDependencies": {
62
- "@djangocfg/api": "^2.1.104",
62
+ "@djangocfg/api": "^2.1.107",
63
63
  "@djangocfg/ext-base": "^1.0.8",
64
- "@djangocfg/ui-core": "^2.1.104",
65
- "@djangocfg/ui-nextjs": "^2.1.104",
64
+ "@djangocfg/ui-core": "^2.1.107",
65
+ "@djangocfg/ui-nextjs": "^2.1.107",
66
66
  "consola": "^3.4.2",
67
67
  "lucide-react": "^0.545.0",
68
68
  "next": "^15.5.7",
@@ -73,10 +73,10 @@
73
73
  "moment": "^2.30.1"
74
74
  },
75
75
  "devDependencies": {
76
- "@djangocfg/api": "^2.1.104",
76
+ "@djangocfg/api": "^2.1.107",
77
77
  "@djangocfg/ext-base": "^1.0.8",
78
- "@djangocfg/typescript-config": "^2.1.104",
79
- "@djangocfg/ui-nextjs": "^2.1.104",
78
+ "@djangocfg/typescript-config": "^2.1.107",
79
+ "@djangocfg/ui-nextjs": "^2.1.107",
80
80
  "@types/node": "^24.7.2",
81
81
  "@types/react": "^19.0.0",
82
82
  "consola": "^3.4.2",
@@ -65,12 +65,5 @@ openapi_client = OpenAPIClientConfig(
65
65
  )
66
66
  ```
67
67
 
68
- **Copy to Next.js** (if `nextjs_admin` configured):
69
- ```python
70
- nextjs_admin = NextJsAdminConfig(
71
- project_path="../frontend/apps/...",
72
- api_output_path="app/_lib/api/generated",
73
- )
74
- ```
75
-
76
68
  @see https://djangocfg.com/docs/features/api-generation
69
+
@@ -25,7 +25,7 @@ export const PaymentDetailSchema = z.object({
25
25
  status_display: z.string(),
26
26
  pay_address: z.string().nullable(),
27
27
  qr_code_url: z.string().nullable(),
28
- payment_url: z.url().nullable(),
28
+ payment_url: z.union([z.url(), z.literal('')]).nullable(),
29
29
  transaction_hash: z.string().nullable(),
30
30
  explorer_link: z.string().nullable(),
31
31
  confirmations_count: z.int(),
@@ -1,29 +1,37 @@
1
1
  // Auto-generated by DjangoCFG - see CLAUDE.md
2
2
  /**
3
- * Global API Instance - Singleton configuration
3
+ * Global API Instance - Singleton configuration with auto-configuration support
4
4
  *
5
- * This module provides a global API instance that can be configured once
6
- * and used throughout your application.
5
+ * This module provides a global API instance that auto-configures from
6
+ * environment variables or can be configured manually.
7
7
  *
8
- * Usage:
8
+ * AUTO-CONFIGURATION (recommended):
9
+ * Set one of these environment variables and the API will auto-configure:
10
+ * - NEXT_PUBLIC_API_URL (Next.js)
11
+ * - VITE_API_URL (Vite)
12
+ * - REACT_APP_API_URL (Create React App)
13
+ * - API_URL (generic)
14
+ *
15
+ * Then just use fetchers and hooks directly:
16
+ * ```typescript
17
+ * import { getUsers } from './_utils/fetchers'
18
+ * const users = await getUsers({ page: 1 })
19
+ * ```
20
+ *
21
+ * MANUAL CONFIGURATION:
9
22
  * ```typescript
10
- * // Configure once (e.g., in your app entry point)
11
23
  * import { configureAPI } from './api-instance'
12
24
  *
13
25
  * configureAPI({
14
26
  * baseUrl: 'https://api.example.com',
15
27
  * token: 'your-jwt-token'
16
28
  * })
17
- *
18
- * // Then use fetchers and hooks anywhere without configuration
19
- * import { getUsers } from './fetchers'
20
- * const users = await getUsers({ page: 1 })
21
29
  * ```
22
30
  *
23
31
  * For SSR or multiple instances:
24
32
  * ```typescript
25
33
  * import { API } from './index'
26
- * import { getUsers } from './fetchers'
34
+ * import { getUsers } from './_utils/fetchers'
27
35
  *
28
36
  * const api = new API('https://api.example.com')
29
37
  * const users = await getUsers({ page: 1 }, api)
@@ -33,27 +41,67 @@
33
41
  import { API, type APIOptions } from './index'
34
42
 
35
43
  let globalAPI: API | null = null
44
+ let autoConfigAttempted = false
45
+
46
+ /**
47
+ * Auto-configure from environment variable if available (Next.js pattern)
48
+ * This allows hooks and fetchers to work without explicit configureAPI() call
49
+ *
50
+ * Supported environment variables:
51
+ * - NEXT_PUBLIC_API_URL (Next.js)
52
+ * - VITE_API_URL (Vite)
53
+ * - REACT_APP_API_URL (Create React App)
54
+ * - API_URL (generic)
55
+ */
56
+ function tryAutoConfigureFromEnv(): void {
57
+ // Only attempt once
58
+ if (autoConfigAttempted) return
59
+ autoConfigAttempted = true
60
+
61
+ // Skip if already configured
62
+ if (globalAPI) return
63
+
64
+ // Skip if process is not available (pure browser without bundler)
65
+ if (typeof process === 'undefined' || !process.env) return
66
+
67
+ // Try different environment variable patterns
68
+ const baseUrl =
69
+ process.env.NEXT_PUBLIC_API_URL ||
70
+ process.env.VITE_API_URL ||
71
+ process.env.REACT_APP_API_URL ||
72
+ process.env.API_URL
73
+
74
+ if (baseUrl) {
75
+ globalAPI = new API(baseUrl)
76
+ }
77
+ }
36
78
 
37
79
  /**
38
80
  * Get the global API instance
39
- * @throws Error if API is not configured
81
+ * Auto-configures from environment variables on first call if not manually configured.
82
+ * @throws Error if API is not configured and no env variable is set
40
83
  */
41
84
  export function getAPIInstance(): API {
85
+ // Try auto-configuration on first access (lazy initialization)
86
+ tryAutoConfigureFromEnv()
87
+
42
88
  if (!globalAPI) {
43
89
  throw new Error(
44
90
  'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\n' +
45
91
  'Example:\n' +
46
92
  ' import { configureAPI } from "./api-instance"\n' +
47
- ' configureAPI({ baseUrl: "https://api.example.com" })'
93
+ ' configureAPI({ baseUrl: "https://api.example.com" })\n\n' +
94
+ 'Or set environment variable: NEXT_PUBLIC_API_URL, VITE_API_URL, or REACT_APP_API_URL'
48
95
  )
49
96
  }
50
97
  return globalAPI
51
98
  }
52
99
 
53
100
  /**
54
- * Check if API is configured
101
+ * Check if API is configured (or can be auto-configured)
55
102
  */
56
103
  export function isAPIConfigured(): boolean {
104
+ tryAutoConfigureFromEnv()
57
105
  return globalAPI !== null
58
106
  }
59
107
 
@@ -25,6 +25,7 @@ export class APIClient {
25
25
  private httpClient: HttpClientAdapter;
26
26
  private logger: APILogger | null = null;
27
27
  private retryConfig: RetryConfig | null = null;
28
+ private tokenGetter: (() => string | null) | null = null;
28
29
 
29
30
  // Sub-clients
30
31
  public ext_payments_payments: ExtPaymentsPayments;
@@ -35,10 +36,12 @@ export class APIClient {
35
36
  httpClient?: HttpClientAdapter;
36
37
  loggerConfig?: Partial<LoggerConfig>;
37
38
  retryConfig?: RetryConfig;
39
+ tokenGetter?: () => string | null;
38
40
  }
39
41
  ) {
40
42
  this.baseUrl = baseUrl.replace(/\/$/, '');
41
43
  this.httpClient = options?.httpClient || new FetchAdapter();
44
+ this.tokenGetter = options?.tokenGetter || null;
42
45
 
43
46
  // Initialize logger if config provided
44
47
  if (options?.loggerConfig !== undefined) {
@@ -69,6 +72,21 @@ export class APIClient {
69
72
  return null;
70
73
  }
71
74
 
75
+ /**
76
+ * Get the base URL for building streaming/download URLs.
77
+ */
78
+ getBaseUrl(): string {
79
+ return this.baseUrl;
80
+ }
81
+
82
+ /**
83
+ * Get JWT token for URL authentication (used in streaming endpoints).
84
+ * Returns null if no token getter is configured or no token is available.
85
+ */
86
+ getToken(): string | null {
87
+ return this.tokenGetter ? this.tokenGetter() : null;
88
+ }
89
+
72
90
  /**
73
91
  * Make HTTP request with Django CSRF and session handling.
74
92
  * Automatically retries on network errors and 5xx server errors.
@@ -80,6 +98,7 @@ export class APIClient {
80
98
  params?: Record<string, any>;
81
99
  body?: any;
82
100
  formData?: FormData;
101
+ binaryBody?: Blob | ArrayBuffer;
83
102
  headers?: Record<string, string>;
84
103
  }
85
104
  ): Promise<T> {
@@ -116,6 +135,7 @@ export class APIClient {
116
135
  params?: Record<string, any>;
117
136
  body?: any;
118
137
  formData?: FormData;
138
+ binaryBody?: Blob | ArrayBuffer;
119
139
  headers?: Record<string, string>;
120
140
  }
121
141
  ): Promise<T> {
@@ -129,8 +149,8 @@ export class APIClient {
129
149
  ...(options?.headers || {})
130
150
  };
131
151
 
132
- // Don't set Content-Type for FormData (browser will set it with boundary)
133
- if (!options?.formData && !headers['Content-Type']) {
152
+ // Don't set Content-Type for FormData/binaryBody (browser will set it with boundary)
153
+ if (!options?.formData && !options?.binaryBody && !headers['Content-Type']) {
134
154
  headers['Content-Type'] = 'application/json';
135
155
  }
136
156
 
@@ -157,6 +177,7 @@ export class APIClient {
157
177
  params: options?.params,
158
178
  body: options?.body,
159
179
  formData: options?.formData,
180
+ binaryBody: options?.binaryBody,
160
181
  });
161
182
 
162
183
  const duration = Date.now() - startTime;
@@ -14,6 +14,8 @@ export interface HttpRequest {
14
14
  params?: Record<string, any>;
15
15
  /** FormData for file uploads (multipart/form-data) */
16
16
  formData?: FormData;
17
+ /** Binary data for octet-stream uploads */
18
+ binaryBody?: Blob | ArrayBuffer;
17
19
  }
18
20
 
19
21
  export interface HttpResponse<T = any> {
@@ -37,7 +39,7 @@ export interface HttpClientAdapter {
37
39
  */
38
40
  export class FetchAdapter implements HttpClientAdapter {
39
41
  async request<T = any>(request: HttpRequest): Promise<HttpResponse<T>> {
40
- const { method, url, headers, body, params, formData } = request;
42
+ const { method, url, headers, body, params, formData, binaryBody } = request;
41
43
 
42
44
  // Build URL with query params
43
45
  let finalUrl = url;
@@ -58,12 +60,16 @@ export class FetchAdapter implements HttpClientAdapter {
58
60
  const finalHeaders: Record<string, string> = { ...headers };
59
61
 
60
62
  // Determine body and content-type
61
- let requestBody: string | FormData | undefined;
63
+ let requestBody: string | FormData | Blob | ArrayBuffer | undefined;
62
64
 
63
65
  if (formData) {
64
66
  // For multipart/form-data, let browser set Content-Type with boundary
65
67
  requestBody = formData;
66
68
  // Don't set Content-Type - browser will set it with boundary
69
+ } else if (binaryBody) {
70
+ // Binary upload (application/octet-stream)
71
+ finalHeaders['Content-Type'] = 'application/octet-stream';
72
+ requestBody = binaryBody;
67
73
  } else if (body) {
68
74
  // JSON request
69
75
  finalHeaders['Content-Type'] = 'application/json';
@@ -133,10 +133,11 @@ export class API {
133
133
 
134
134
  this._loadTokensFromStorage();
135
135
 
136
- // Initialize APIClient
136
+ // Initialize APIClient with token getter for URL authentication
137
137
  this._client = new APIClient(this.baseUrl, {
138
138
  retryConfig: this.options?.retryConfig,
139
139
  loggerConfig: this.options?.loggerConfig,
140
+ tokenGetter: () => this.getToken(),
140
141
  });
141
142
 
142
143
  // Always inject auth header wrapper (reads token dynamically from storage)
@@ -155,6 +156,7 @@ export class API {
155
156
  this._client = new APIClient(this.baseUrl, {
156
157
  retryConfig: this.options?.retryConfig,
157
158
  loggerConfig: this.options?.loggerConfig,
159
+ tokenGetter: () => this.getToken(),
158
160
  });
159
161
 
160
162
  // Always inject auth header wrapper (reads token dynamically from storage)
package/src/api/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { createExtensionAPI } from '@djangocfg/ext-base/api';
1
+ import { createExtensionAPI, initializeExtensionAPI } from '@djangocfg/ext-base/api';
2
2
 
3
3
  /**
4
4
  * Payments Extension API
@@ -6,5 +6,10 @@ import { createExtensionAPI } from '@djangocfg/ext-base/api';
6
6
  * Pre-configured API instance with shared authentication
7
7
  */
8
8
  import { API } from './generated/ext_payments';
9
+ import { configureAPI } from './generated/ext_payments/api-instance';
9
10
 
11
+ // Initialize global API singleton for hooks and fetchers
12
+ initializeExtensionAPI(configureAPI);
13
+
14
+ // Create instance for direct usage
10
15
  export const apiPayments = createExtensionAPI(API);