@viu/emporix-sdk 2.22.0 → 2.25.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.
Files changed (116) hide show
  1. package/CHANGELOG.md +144 -0
  2. package/README.md +16 -6
  3. package/dist/availability.cjs +118 -0
  4. package/dist/availability.cjs.map +1 -1
  5. package/dist/availability.d.cts +178 -2
  6. package/dist/availability.d.ts +178 -2
  7. package/dist/availability.js +1 -1
  8. package/dist/cart.cjs +156 -11
  9. package/dist/cart.cjs.map +1 -1
  10. package/dist/cart.d.cts +177 -4
  11. package/dist/cart.d.ts +177 -4
  12. package/dist/cart.js +1 -1
  13. package/dist/category.cjs +192 -2
  14. package/dist/category.cjs.map +1 -1
  15. package/dist/category.d.cts +399 -2
  16. package/dist/category.d.ts +399 -2
  17. package/dist/category.js +1 -1
  18. package/dist/checkout.d.cts +1 -1
  19. package/dist/checkout.d.ts +1 -1
  20. package/dist/{chunk-HD5RBCM6.js → chunk-27NLS646.js} +10 -2
  21. package/dist/chunk-27NLS646.js.map +1 -0
  22. package/dist/{chunk-MMX5FGMI.js → chunk-6PJFOX3A.js} +109 -2
  23. package/dist/chunk-6PJFOX3A.js.map +1 -0
  24. package/dist/chunk-HWVFF6B2.js +378 -0
  25. package/dist/chunk-HWVFF6B2.js.map +1 -0
  26. package/dist/chunk-LQFRJSVT.js +224 -0
  27. package/dist/chunk-LQFRJSVT.js.map +1 -0
  28. package/dist/chunk-P7G4LOOV.js +264 -0
  29. package/dist/chunk-P7G4LOOV.js.map +1 -0
  30. package/dist/{chunk-44F52KRE.js → chunk-QQMLN76U.js} +27 -2
  31. package/dist/chunk-QQMLN76U.js.map +1 -0
  32. package/dist/chunk-UNW6SFSB.js +363 -0
  33. package/dist/chunk-UNW6SFSB.js.map +1 -0
  34. package/dist/chunk-V3275BA7.js +181 -0
  35. package/dist/chunk-V3275BA7.js.map +1 -0
  36. package/dist/chunk-W2KBVWJ2.js +323 -0
  37. package/dist/chunk-W2KBVWJ2.js.map +1 -0
  38. package/dist/{chunk-5DWCYDQD.js → chunk-YVZ6GOLE.js} +158 -13
  39. package/dist/chunk-YVZ6GOLE.js.map +1 -0
  40. package/dist/chunk-ZOFGDNSX.js +305 -0
  41. package/dist/chunk-ZOFGDNSX.js.map +1 -0
  42. package/dist/companies.cjs +25 -0
  43. package/dist/companies.cjs.map +1 -1
  44. package/dist/companies.d.cts +15 -1
  45. package/dist/companies.d.ts +15 -1
  46. package/dist/companies.js +1 -1
  47. package/dist/contacts.cjs +8 -0
  48. package/dist/contacts.cjs.map +1 -1
  49. package/dist/contacts.d.cts +3 -1
  50. package/dist/contacts.d.ts +3 -1
  51. package/dist/contacts.js +1 -1
  52. package/dist/{context-CfXJcYKP.d.cts → context-DC5gSgKK.d.cts} +18 -0
  53. package/dist/{context-CfXJcYKP.d.ts → context-DC5gSgKK.d.ts} +18 -0
  54. package/dist/customer-groups-BqYsPCA6.d.cts +706 -0
  55. package/dist/customer-groups-CLm6Gu9U.d.ts +706 -0
  56. package/dist/customer-groups.cjs +200 -17
  57. package/dist/customer-groups.cjs.map +1 -1
  58. package/dist/customer-groups.d.cts +2 -110
  59. package/dist/customer-groups.d.ts +2 -110
  60. package/dist/customer-groups.js +1 -1
  61. package/dist/customer.d.cts +1 -1
  62. package/dist/customer.d.ts +1 -1
  63. package/dist/index.cjs +2231 -395
  64. package/dist/index.cjs.map +1 -1
  65. package/dist/index.d.cts +435 -13
  66. package/dist/index.d.ts +435 -13
  67. package/dist/index.js +679 -344
  68. package/dist/index.js.map +1 -1
  69. package/dist/locations.d.cts +1 -1
  70. package/dist/locations.d.ts +1 -1
  71. package/dist/media.d.cts +1 -1
  72. package/dist/media.d.ts +1 -1
  73. package/dist/orders.cjs +160 -2
  74. package/dist/orders.cjs.map +1 -1
  75. package/dist/orders.d.cts +222 -2
  76. package/dist/orders.d.ts +222 -2
  77. package/dist/orders.js +1 -1
  78. package/dist/payment.cjs +107 -0
  79. package/dist/payment.cjs.map +1 -1
  80. package/dist/payment.d.cts +300 -2
  81. package/dist/payment.d.ts +300 -2
  82. package/dist/payment.js +1 -1
  83. package/dist/price.cjs +197 -0
  84. package/dist/price.cjs.map +1 -1
  85. package/dist/price.d.cts +578 -2
  86. package/dist/price.d.ts +578 -2
  87. package/dist/price.js +1 -1
  88. package/dist/product.cjs +190 -0
  89. package/dist/product.cjs.map +1 -1
  90. package/dist/product.d.cts +504 -2
  91. package/dist/product.d.ts +504 -2
  92. package/dist/product.js +1 -1
  93. package/dist/segment.cjs +179 -0
  94. package/dist/segment.cjs.map +1 -1
  95. package/dist/segment.d.cts +182 -2
  96. package/dist/segment.d.ts +182 -2
  97. package/dist/segment.js +1 -1
  98. package/package.json +1 -1
  99. package/dist/chunk-36T4YPQJ.js +0 -106
  100. package/dist/chunk-36T4YPQJ.js.map +0 -1
  101. package/dist/chunk-3BTPX7IJ.js +0 -41
  102. package/dist/chunk-3BTPX7IJ.js.map +0 -1
  103. package/dist/chunk-44F52KRE.js.map +0 -1
  104. package/dist/chunk-5DWCYDQD.js.map +0 -1
  105. package/dist/chunk-A3LYJUD4.js +0 -108
  106. package/dist/chunk-A3LYJUD4.js.map +0 -1
  107. package/dist/chunk-DXOLWAOY.js +0 -188
  108. package/dist/chunk-DXOLWAOY.js.map +0 -1
  109. package/dist/chunk-FZMP5NQZ.js +0 -173
  110. package/dist/chunk-FZMP5NQZ.js.map +0 -1
  111. package/dist/chunk-HD5RBCM6.js.map +0 -1
  112. package/dist/chunk-IVCCGJN5.js +0 -63
  113. package/dist/chunk-IVCCGJN5.js.map +0 -1
  114. package/dist/chunk-MMX5FGMI.js.map +0 -1
  115. package/dist/chunk-NE7MIZPR.js +0 -144
  116. package/dist/chunk-NE7MIZPR.js.map +0 -1
package/dist/orders.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext, P as PaginatedItems } from './context-CfXJcYKP.js';
1
+ import { C as ClientContext, b as AuthContext, P as PaginatedItems } from './context-DC5gSgKK.js';
2
2
  import { a as QueryFor } from './query-mLQs2WFr.js';
3
3
 
4
4
  /**
@@ -61,6 +61,49 @@ type AggregateFee = {
61
61
  }>;
62
62
  }>;
63
63
  };
64
+ type OrderEntriesDto = {
65
+ entries: Array<{
66
+ /**
67
+ * Order entry id. Required if `productId` is not provided. Use for entry modification. Cannot be provided along with `productId`.
68
+ */
69
+ entryId?: string;
70
+ /**
71
+ * Id of the product added to the order. Required if `entryId` is not provided. Cannot be provided along with `entryId`.
72
+ */
73
+ productId?: string;
74
+ /**
75
+ * Quantity of the products added to the order. When set to `0` then such entries are removed from the order.
76
+ */
77
+ quantity: number;
78
+ }>;
79
+ };
80
+ type OrderCalculationDto = {
81
+ /**
82
+ * Order recalculation DTO contains a list of entries, where each entry contains entry ID and delivered amount of items.
83
+ */
84
+ entries?: Array<OrderCalculationEntryDto>;
85
+ };
86
+ /**
87
+ * Order Calculation DTO contains information to recalculate a single entry. Required information is ID of entry and (new) delivered amount of items.
88
+ */
89
+ type OrderCalculationEntryDto = {
90
+ /**
91
+ * Unique identifier of an entry.
92
+ */
93
+ entryId: string;
94
+ /**
95
+ * Delivered amount is the new amount of items for recalculation.
96
+ */
97
+ deliveredAmount: number;
98
+ /**
99
+ * Delivered amount in pieces
100
+ */
101
+ deliveredAmountInPc?: number;
102
+ /**
103
+ * The measurement unit used to present the price.
104
+ */
105
+ unit?: string;
106
+ };
64
107
  type Channel = {
65
108
  name?: string;
66
109
  source?: string;
@@ -757,6 +800,16 @@ type MeasurementUnit = {
757
800
  */
758
801
  value: number;
759
802
  };
803
+ /**
804
+ * Resource location is a response object which contains ID and link to created resource.
805
+ */
806
+ type ResourceLocation = {
807
+ /**
808
+ * Unique identifier of the created resource.
809
+ */
810
+ id?: string;
811
+ yrn?: Yrn;
812
+ };
760
813
  type Vendor = {
761
814
  /**
762
815
  * An identifier of a vendor
@@ -911,6 +964,32 @@ type SalesOrder = {
911
964
  */
912
965
  checkout?: boolean;
913
966
  } & OrderUpdateDto;
967
+ type SalesOrderCreationDto = {
968
+ /**
969
+ * Identifier of the quote from which the order has been created
970
+ */
971
+ quoteId?: string;
972
+ /**
973
+ * Indicates whether this order was created through checkout. Can only be provided when creating an order.
974
+ *
975
+ * When set to `true`, only one order can be created per cart ID — enforcing that a checkout cart results in a single order.
976
+ * Orders created through the checkout flow have this flag set to `true` by default.
977
+ *
978
+ */
979
+ checkout?: boolean;
980
+ } & OrderCreationDto;
981
+ type SearchRequest = {
982
+ /**
983
+ * A standard query parameter is used to search for specific values.
984
+ *
985
+ * See: [Standard practises - Query parameter](https://developer.emporix.io/api-references/standard-practices/q-param)
986
+ *
987
+ */
988
+ q?: string;
989
+ };
990
+ type OrderCreationDto = {
991
+ channel?: Channel;
992
+ } & OrderCommonDto;
914
993
  type OrderUpdateDto = {
915
994
  channel?: Channel;
916
995
  /**
@@ -952,6 +1031,30 @@ type SplitInfo = {
952
1031
  */
953
1032
  uniqueValue?: string;
954
1033
  };
1034
+ /**
1035
+ * Information about how the order was split from the master order.
1036
+ */
1037
+ type SplitItemResponse = {
1038
+ /**
1039
+ * An ID of a suborder.
1040
+ */
1041
+ id?: string;
1042
+ /**
1043
+ * A unique value used to split the entries of a master order into separate suborders.
1044
+ */
1045
+ uniqueValue?: string;
1046
+ };
1047
+ type OrderSplitRequest = {
1048
+ splitBy?: SplitBy;
1049
+ };
1050
+ type OrderSplitResponse = {
1051
+ /**
1052
+ * ID of the order that was split. The same as in the path.
1053
+ */
1054
+ masterOrder?: string;
1055
+ splitBy?: SplitBy;
1056
+ subOrders?: Array<SplitItemResponse>;
1057
+ };
955
1058
  /**
956
1059
  * Status of the payment.
957
1060
  */
@@ -962,6 +1065,43 @@ type PaymentStatus = 'PENDING' | 'AUTHORIZATION_SUCCESS' | 'AUTHORIZATION_FAILUR
962
1065
  type Transition = {
963
1066
  status?: OrderStatus;
964
1067
  };
1068
+ /**
1069
+ * A historical order status transition entry.
1070
+ */
1071
+ type HistoricalTransition = {
1072
+ status: OrderStatus;
1073
+ /**
1074
+ * Timestamp when the status transition happened.
1075
+ */
1076
+ timestamp: string;
1077
+ };
1078
+ /**
1079
+ * Metadata related to historical order status transitions.
1080
+ */
1081
+ type HistoricalTransitionsMetadata = {
1082
+ /**
1083
+ * Version of the transition history record.
1084
+ */
1085
+ version: number;
1086
+ /**
1087
+ * Timestamp when the transition history record was created.
1088
+ */
1089
+ createdAt: string;
1090
+ /**
1091
+ * Timestamp when the transition history record was last modified.
1092
+ */
1093
+ modifiedAt: string;
1094
+ };
1095
+ /**
1096
+ * Historical order status transitions with metadata.
1097
+ */
1098
+ type HistoricalTransitionsResponse = {
1099
+ /**
1100
+ * Ordered list of historical status transitions.
1101
+ */
1102
+ transitions: Array<HistoricalTransition>;
1103
+ metadata: HistoricalTransitionsMetadata;
1104
+ };
965
1105
  type MetadataMixins = {
966
1106
  mixins?: {
967
1107
  [key: string]: unknown;
@@ -1729,6 +1869,42 @@ type PriceMatchDetails = {
1729
1869
  * subset — so the SDK exposes a partial of it.
1730
1870
  */
1731
1871
  type SalesOrderPatch = Partial<OrderUpdateDto>;
1872
+ /** Body for creating a sales-order as an employee (`POST /salesorders`). */
1873
+ type SalesOrderCreateInput = SalesOrderCreationDto;
1874
+ /** Id/location envelope returned when a sales-order is created. */
1875
+ type SalesOrderCreated = ResourceLocation;
1876
+ /** Body for a full sales-order replace (`PUT /salesorders/{id}`). */
1877
+ type SalesOrderReplaceInput = OrderUpdateDto;
1878
+ /** Body for `salesOrders.search` (`POST /salesorders/search`). */
1879
+ type SalesOrderSearchInput = SearchRequest;
1880
+ /** Response of `salesOrders.listHistoricalTransitions`. */
1881
+ type SalesOrderHistoricalTransitions = HistoricalTransitionsResponse;
1882
+ /** Body for `salesOrders.calculate` (`POST /salesorders/{id}/calculations`). */
1883
+ type SalesOrderCalculationInput = OrderCalculationDto;
1884
+ /** Body for `salesOrders.updateEntries` (`POST /salesorders/{id}/entries`). */
1885
+ type SalesOrderEntriesInput = OrderEntriesDto;
1886
+ /** Body for `salesOrders.split` (`POST /salesorders/{id}/split`). */
1887
+ type SalesOrderSplitInput = OrderSplitRequest;
1888
+ /** Response of `salesOrders.split`. */
1889
+ type SalesOrderSplitResult = OrderSplitResponse;
1890
+ /** Options for `salesOrders.list`. */
1891
+ interface ListSalesOrdersOptions {
1892
+ pageNumber?: number;
1893
+ pageSize?: number;
1894
+ sort?: string;
1895
+ fields?: string;
1896
+ /** A `q` filter — raw DSL string or a built filter for entity "ORDER". */
1897
+ q?: QueryFor<"ORDER">;
1898
+ }
1899
+ /** Options for `orders.listForLegalEntity`. */
1900
+ interface ListLegalEntityOrdersOptions {
1901
+ pageNumber?: number;
1902
+ pageSize?: number;
1903
+ sort?: string;
1904
+ saasToken?: string;
1905
+ /** A `q` filter — raw DSL string or a built filter for entity "ORDER". */
1906
+ q?: QueryFor<"ORDER">;
1907
+ }
1732
1908
  /** Optional fields supported by the order-v2 list endpoint. */
1733
1909
  interface ListMyOrdersOptions {
1734
1910
  pageNumber?: number;
@@ -1777,6 +1953,19 @@ declare class OrdersService {
1777
1953
  cancel(orderId: string, auth: AuthContext, opts?: {
1778
1954
  saasToken?: string;
1779
1955
  }): Promise<void>;
1956
+ /**
1957
+ * Lists a legal entity's orders (`GET /legal-entity-orders/{legalEntityId}`),
1958
+ * wrapped into `PaginatedItems`. B2B storefront view — requires a customer
1959
+ * token with access to the legal entity.
1960
+ */
1961
+ listForLegalEntity(legalEntityId: string, auth: AuthContext, opts?: ListLegalEntityOrdersOptions): Promise<PaginatedItems<SalesOrder>>;
1962
+ /**
1963
+ * Fetches one order within a legal entity
1964
+ * (`GET /legal-entity-orders/{legalEntityId}/{orderId}`).
1965
+ */
1966
+ getForLegalEntity(legalEntityId: string, orderId: string, auth: AuthContext, opts?: {
1967
+ saasToken?: string;
1968
+ }): Promise<SalesOrder>;
1780
1969
  }
1781
1970
  /**
1782
1971
  * Backend / service-account access to the merchant-facing
@@ -1796,6 +1985,37 @@ declare class SalesOrdersService {
1796
1985
  get(orderId: string, auth: AuthContext): Promise<Order>;
1797
1986
  /** Patches an existing order (status, mixins, customAttributes, metadata). */
1798
1987
  update(orderId: string, patch: SalesOrderPatch, auth: AuthContext, opts?: UpdateSalesOrderOptions): Promise<Order>;
1988
+ /** Lists tenant sales-orders (`GET /salesorders`), wrapped into `PaginatedItems`. */
1989
+ list(auth: AuthContext, opts?: ListSalesOrdersOptions): Promise<PaginatedItems<SalesOrder>>;
1990
+ /** Searches tenant sales-orders (`POST /salesorders/search`, body carries the `q` filter). */
1991
+ search(query: SalesOrderSearchInput, auth: AuthContext, opts?: {
1992
+ pageNumber?: number;
1993
+ pageSize?: number;
1994
+ sort?: string;
1995
+ fields?: string;
1996
+ }): Promise<SalesOrder[]>;
1997
+ /** Creates a sales-order as an employee (`POST /salesorders`). Returns the created resource location. */
1998
+ create(input: SalesOrderCreateInput, auth: AuthContext): Promise<SalesOrderCreated>;
1999
+ /**
2000
+ * Full-replaces a sales-order (`PUT /salesorders/{orderId}`, body `OrderUpdateDto`).
2001
+ * The endpoint returns 204, so the order is re-fetched (direct GET) and returned.
2002
+ * Note: `update` on this class is the PATCH (partial); `replace` is the PUT (full).
2003
+ */
2004
+ replace(orderId: string, input: SalesOrderReplaceInput, auth: AuthContext): Promise<SalesOrder>;
2005
+ /** Deletes a sales-order (`DELETE /salesorders/{orderId}`). */
2006
+ delete(orderId: string, auth: AuthContext): Promise<void>;
2007
+ /** Lists the available transitions for a sales-order (`GET /salesorders/{orderId}/transitions`). */
2008
+ listTransitions(orderId: string, auth: AuthContext): Promise<Transition[]>;
2009
+ /** Transitions a sales-order to a new status (`POST /salesorders/{orderId}/transitions`). */
2010
+ transition(orderId: string, input: Transition, auth: AuthContext): Promise<void>;
2011
+ /** Lists the historical transitions of a sales-order (`GET /salesorders/{orderId}/historical-transitions`). */
2012
+ listHistoricalTransitions(orderId: string, auth: AuthContext): Promise<SalesOrderHistoricalTransitions>;
2013
+ /** Calculates order-entry totals for a sales-order (`POST /salesorders/{orderId}/calculations`). */
2014
+ calculate(orderId: string, input: SalesOrderCalculationInput, auth: AuthContext): Promise<unknown>;
2015
+ /** Updates a sales-order's entries (`POST /salesorders/{orderId}/entries`). Returns the updated order. */
2016
+ updateEntries(orderId: string, input: SalesOrderEntriesInput, auth: AuthContext): Promise<SalesOrder>;
2017
+ /** Splits a sales-order (`POST /salesorders/{orderId}/split`). */
2018
+ split(orderId: string, input: SalesOrderSplitInput, auth: AuthContext): Promise<SalesOrderSplitResult>;
1799
2019
  }
1800
2020
 
1801
- export { type GetOrderOptions, type ListMyOrdersOptions, type Order, type OrderEntry, type OrderStatus, type OrderTransitionOptions, OrdersService, type SalesOrder, type SalesOrderPatch, SalesOrdersService, type Transition, type UpdateSalesOrderOptions };
2021
+ export { type GetOrderOptions, type ListLegalEntityOrdersOptions, type ListMyOrdersOptions, type ListSalesOrdersOptions, type Order, type OrderEntry, type OrderStatus, type OrderTransitionOptions, OrdersService, type SalesOrder, type SalesOrderCalculationInput, type SalesOrderCreateInput, type SalesOrderCreated, type SalesOrderEntriesInput, type SalesOrderHistoricalTransitions, type SalesOrderPatch, type SalesOrderReplaceInput, type SalesOrderSearchInput, type SalesOrderSplitInput, type SalesOrderSplitResult, SalesOrdersService, type Transition, type UpdateSalesOrderOptions };
package/dist/orders.js CHANGED
@@ -1,4 +1,4 @@
1
- export { OrdersService, SalesOrdersService } from './chunk-36T4YPQJ.js';
1
+ export { OrdersService, SalesOrdersService } from './chunk-P7G4LOOV.js';
2
2
  import './chunk-S5HEIQDK.js';
3
3
  //# sourceMappingURL=orders.js.map
4
4
  //# sourceMappingURL=orders.js.map
package/dist/payment.cjs CHANGED
@@ -61,6 +61,7 @@ function requireCustomer(auth2) {
61
61
 
62
62
  // src/services/payment.ts
63
63
  var ANON = auth.anonymous();
64
+ var SERVICE = auth.service();
64
65
  var PaymentGatewayService = class {
65
66
  constructor(ctx) {
66
67
  this.ctx = ctx;
@@ -105,6 +106,112 @@ var PaymentGatewayService = class {
105
106
  body: input
106
107
  });
107
108
  }
109
+ /**
110
+ * Payment-mode **configuration** (`/paymentmodes/config`) — the admin view.
111
+ * For the storefront view use {@link listPaymentModes} / {@link getMode}.
112
+ * Defaults to service auth.
113
+ */
114
+ modes = {
115
+ /** Lists all configured payment modes. The endpoint takes no query parameters. */
116
+ list: async (authCtx = SERVICE) => this.ctx.http.request({
117
+ method: "GET",
118
+ path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/config`,
119
+ auth: authCtx
120
+ }),
121
+ /** Retrieves one payment-mode configuration by id. */
122
+ get: async (id, authCtx = SERVICE) => this.ctx.http.request({
123
+ method: "GET",
124
+ path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/config/${id}`,
125
+ auth: authCtx
126
+ }),
127
+ /** Creates a payment-mode configuration. Responds 200 with the created mode. */
128
+ create: async (input, authCtx = SERVICE) => this.ctx.http.request({
129
+ method: "POST",
130
+ path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/config`,
131
+ body: input,
132
+ auth: authCtx
133
+ }),
134
+ /** Updates a payment-mode configuration (PUT). Responds 200 with the updated mode. */
135
+ update: async (id, input, authCtx = SERVICE) => this.ctx.http.request({
136
+ method: "PUT",
137
+ path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/config/${id}`,
138
+ body: input,
139
+ auth: authCtx
140
+ }),
141
+ /** Deletes a payment-mode configuration. */
142
+ delete: async (id, authCtx = SERVICE) => {
143
+ await this.ctx.http.request({
144
+ method: "DELETE",
145
+ path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/config/${id}`,
146
+ auth: authCtx
147
+ });
148
+ }
149
+ };
150
+ /**
151
+ * Backend transaction lifecycle and reads. `authorize` creates the transaction
152
+ * that `capture`/`refund`/`cancel` then act on. Business failures are reported
153
+ * in the response body (`successful: false`), not as HTTP errors — always check
154
+ * `successful`. Defaults to service auth.
155
+ */
156
+ transactions = {
157
+ /** One page of payment transactions. */
158
+ list: async (params = {}, authCtx = SERVICE) => {
159
+ const query = {
160
+ ...params.pageNumber === void 0 ? {} : { pageNumber: params.pageNumber },
161
+ ...params.pageSize === void 0 ? {} : { pageSize: params.pageSize },
162
+ ...params.sort === void 0 ? {} : { sort: params.sort }
163
+ };
164
+ return this.ctx.http.request({
165
+ method: "GET",
166
+ path: `/payment-gateway/${this.ctx.tenant}/transactions`,
167
+ ...Object.keys(query).length > 0 ? { query } : {},
168
+ auth: authCtx
169
+ });
170
+ },
171
+ /** Retrieves one payment transaction by id. */
172
+ get: async (transactionId, authCtx = SERVICE) => this.ctx.http.request({
173
+ method: "GET",
174
+ path: `/payment-gateway/${this.ctx.tenant}/transactions/${transactionId}`,
175
+ auth: authCtx
176
+ }),
177
+ /**
178
+ * Authorizes a payment as a backend caller (`POST /payment/authorize`). The
179
+ * storefront equivalent is {@link PaymentGatewayService.authorize}, which
180
+ * targets `/payment/frontend/authorize` with a customer token.
181
+ */
182
+ authorize: async (input, authCtx = SERVICE) => this.ctx.http.request({
183
+ method: "POST",
184
+ path: `/payment-gateway/${this.ctx.tenant}/payment/authorize`,
185
+ body: input,
186
+ auth: authCtx
187
+ }),
188
+ /**
189
+ * Captures an authorized payment. Omit `input` to capture the full
190
+ * authorized amount. Returns the provider's `captureId` on success.
191
+ */
192
+ capture: async (transactionId, input, authCtx = SERVICE) => this.ctx.http.request({
193
+ method: "POST",
194
+ path: `/payment-gateway/${this.ctx.tenant}/payment/${transactionId}/capture`,
195
+ ...input === void 0 ? {} : { body: input },
196
+ auth: authCtx
197
+ }),
198
+ /**
199
+ * Refunds a captured payment. Omit `input` to refund the full captured
200
+ * amount; pass `captureId` when several captures exist for the payment.
201
+ */
202
+ refund: async (transactionId, input, authCtx = SERVICE) => this.ctx.http.request({
203
+ method: "POST",
204
+ path: `/payment-gateway/${this.ctx.tenant}/payment/${transactionId}/refund`,
205
+ ...input === void 0 ? {} : { body: input },
206
+ auth: authCtx
207
+ }),
208
+ /** Cancels an authorized payment. Takes no body. */
209
+ cancel: async (transactionId, authCtx = SERVICE) => this.ctx.http.request({
210
+ method: "POST",
211
+ path: `/payment-gateway/${this.ctx.tenant}/payment/${transactionId}/cancel`,
212
+ auth: authCtx
213
+ })
214
+ };
108
215
  };
109
216
 
110
217
  exports.PaymentGatewayService = PaymentGatewayService;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/errors.ts","../src/core/auth.ts","../src/core/require-customer.ts","../src/services/payment.ts"],"names":["auth"],"mappings":";;;AACA,IAAM,kBAAA,uBAAyB,GAAA,CAAI;AAAA,EACjC,cAAA;AAAA,EAAgB,eAAA;AAAA,EAAiB,OAAA;AAAA,EAAS,eAAA;AAAA,EAAiB,WAAA;AAAA,EAC3D,QAAA;AAAA,EAAU,eAAA;AAAA,EAAiB,eAAA;AAAA,EAAiB;AAC9C,CAAC,CAAA;AAED,SAAS,MAAM,IAAA,EAAwB;AACrC,EAAA,IAAI,MAAM,OAAA,CAAQ,IAAI,GAAG,OAAO,IAAA,CAAK,IAAI,KAAK,CAAA;AAC9C,EAAA,IAAI,IAAA,IAAQ,OAAO,IAAA,KAAS,QAAA,EAAU;AACpC,IAAA,MAAM,MAA+B,EAAC;AACtC,IAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,KAAK,MAAA,CAAO,OAAA,CAAQ,IAA+B,CAAA,EAAG;AACpE,MAAA,GAAA,CAAI,CAAC,CAAA,GAAI,kBAAA,CAAmB,GAAA,CAAI,CAAA,CAAE,aAAa,CAAA,GAAI,gBAAA,GAAmB,KAAA,CAAM,CAAC,CAAA;AAAA,IAC/E;AACA,IAAA,OAAO,GAAA;AAAA,EACT;AACA,EAAA,OAAO,IAAA;AACT;AAGO,IAAM,YAAA,GAAN,cAA2B,KAAA,CAAM;AAAA,EAC7B,MAAA;AAAA,EACA,IAAA;AAAA,EACT,WAAA,CAAY,OAAA,EAAiB,MAAA,EAAiB,IAAA,EAAgB;AAC5D,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,OAAO,GAAA,CAAA,MAAA,CAAW,IAAA;AACvB,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,GAAA,CAAA,MAAA,CAAW,SAAS,CAAA;AAAA,EAClD;AAAA;AAAA,EAEA,MAAA,GAAkC;AAChC,IAAA,OAAO,EAAE,IAAA,EAAM,IAAA,CAAK,IAAA,EAAM,SAAS,IAAA,CAAK,OAAA,EAAS,MAAA,EAAQ,IAAA,CAAK,MAAA,EAAQ,IAAA,EAAM,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA,EAAE;AAAA,EAC/F;AACF,CAAA;AAGO,IAAM,gBAAA,GAAN,cAA+B,YAAA,CAAa;AAAC,CAAA;;;ACwD7C,IAAM,IAAA,GAAO;AAAA;AAAA,EAElB,OAAA,EAAS,CAAC,WAAA,KACR,WAAA,KAAgB,MAAA,GAAY,EAAE,IAAA,EAAM,SAAA,EAAU,GAAI,EAAE,IAAA,EAAM,SAAA,EAAW,WAAA,EAAY;AAAA;AAAA,EAEnF,SAAA,EAAW,OAAoB,EAAE,IAAA,EAAM,WAAA,EAAY,CAAA;AAAA;AAAA,EAEnD,UAAU,CAAC,KAAA,MAAgC,EAAE,IAAA,EAAM,YAAY,KAAA,EAAM,CAAA;AAAA;AAAA,EAErE,KAAK,CAAC,KAAA,MAAgC,EAAE,IAAA,EAAM,OAAO,KAAA,EAAM;AAC7D,CAAA;;;AC7FO,SAAS,gBAAgBA,KAAAA,EAA4C;AAC1E,EAAA,IAAIA,UAASA,KAAAA,CAAK,IAAA,KAAS,cAAcA,KAAAA,CAAK,IAAA,KAAS,QAAQ,OAAOA,KAAAA;AACtE,EAAA,MAAM,IAAI,iBAAiB,uDAAuD,CAAA;AACpF;;;ACFA,IAAM,IAAA,GAAoB,KAAK,SAAA,EAAU;AA0BlC,IAAM,wBAAN,MAA4B;AAAA,EAEjC,YAA6B,GAAA,EAAoB;AAApB,IAAA,IAAA,CAAA,GAAA,GAAA,GAAA;AAAA,EAAqB;AAAA,EAArB,GAAA;AAAA,EAD7B,OAAgB,OAAA,GAAU,SAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ1B,MAAM,gBAAA,CAAiB,OAAA,GAAuB,IAAA,EAA8B;AAC1E,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,OAAA,CAAuB;AAAA,MAC1C,MAAA,EAAQ,KAAA;AAAA,MACR,IAAA,EAAM,CAAA,iBAAA,EAAoB,IAAA,CAAK,GAAA,CAAI,MAAM,CAAA,sBAAA,CAAA;AAAA,MACzC,IAAA,EAAM;AAAA,KACP,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,SAAA,CACJ,KAAA,EACA,OAAA,EACiC;AACjC,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,OAAA,CAAgC;AAAA,MACnD,MAAA,EAAQ,MAAA;AAAA,MACR,IAAA,EAAM,CAAA,iBAAA,EAAoB,IAAA,CAAK,GAAA,CAAI,MAAM,CAAA,2BAAA,CAAA;AAAA,MACzC,IAAA,EAAM,gBAAgB,OAAO,CAAA;AAAA,MAC7B,IAAA,EAAM;AAAA,KACP,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,OAAA,CAAQ,EAAA,EAAY,OAAA,GAAuB,IAAA,EAA4B;AAC3E,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,OAAA,CAAqB;AAAA,MACxC,MAAA,EAAQ,KAAA;AAAA,MACR,MAAM,CAAA,iBAAA,EAAoB,IAAA,CAAK,GAAA,CAAI,MAAM,0BAA0B,EAAE,CAAA,CAAA;AAAA,MACrE,IAAA,EAAM;AAAA,KACP,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,UAAA,CACJ,KAAA,EACA,OAAA,GAAuB,IAAA,EACW;AAClC,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,OAAA,CAAiC;AAAA,MACpD,MAAA,EAAQ,MAAA;AAAA,MACR,IAAA,EAAM,CAAA,iBAAA,EAAoB,IAAA,CAAK,GAAA,CAAI,MAAM,CAAA,4BAAA,CAAA;AAAA,MACzC,IAAA,EAAM,OAAA;AAAA,MACN,IAAA,EAAM;AAAA,KACP,CAAA;AAAA,EACH;AACF","file":"payment.cjs","sourcesContent":["/** Keys whose values are stripped from any serialised error body. */\nconst REDACTED_BODY_KEYS = new Set([\n \"access_token\", \"refresh_token\", \"token\", \"customertoken\", \"saastoken\",\n \"secret\", \"client_secret\", \"authorization\", \"password\",\n]);\n\nfunction scrub(body: unknown): unknown {\n if (Array.isArray(body)) return body.map(scrub);\n if (body && typeof body === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(body as Record<string, unknown>)) {\n out[k] = REDACTED_BODY_KEYS.has(k.toLowerCase()) ? \"***redacted***\" : scrub(v);\n }\n return out;\n }\n return body;\n}\n\n/** Base class for every error thrown by the SDK. */\nexport class EmporixError extends Error {\n readonly status: number | undefined;\n readonly body: unknown;\n constructor(message: string, status?: number, body?: unknown) {\n super(message);\n this.name = new.target.name;\n this.status = status;\n this.body = body;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n /** Safe serialisation — token-like body fields are redacted. */\n toJSON(): Record<string, unknown> {\n return { name: this.name, message: this.message, status: this.status, body: scrub(this.body) };\n }\n}\n\n/** 401 — authentication failed or token expired. */\nexport class EmporixAuthError extends EmporixError {}\n/** 403 — authenticated but not permitted. */\nexport class EmporixForbiddenError extends EmporixError {}\n/** 404 — resource not found. */\nexport class EmporixNotFoundError extends EmporixError {}\n/** 400/422 — request validation failed. */\nexport class EmporixValidationError extends EmporixError {}\n/** 5xx — server-side failure. */\nexport class EmporixServerError extends EmporixError {}\n/** Request aborted by the configured connect/read timeout. No HTTP status. */\nexport class EmporixTimeoutError extends EmporixError {}\n/** DNS/TLS/connection-level failure before or during the exchange. No HTTP status. */\nexport class EmporixNetworkError extends EmporixError {}\n\n/**\n * 403 with a `missing scope: <name>` hint in the body's `details` array.\n * Subclasses {@link EmporixForbiddenError} so existing 403 catches still work.\n */\nexport class EmporixInsufficientScopeError extends EmporixForbiddenError {\n readonly requiredScope: string | undefined;\n constructor(message: string, status: number, body: unknown, requiredScope?: string) {\n super(message, status, body);\n this.requiredScope = requiredScope;\n }\n}\n\nconst SCOPE_HINT_RE = /missing scope[: ]+([a-z0-9._-]+)/i;\n\nfunction extractRequiredScope(body: unknown): string | undefined {\n if (body && typeof body === \"object\" && \"details\" in body) {\n const details = (body as { details?: unknown }).details;\n if (Array.isArray(details)) {\n for (const d of details) {\n if (typeof d === \"string\") {\n const m = d.match(SCOPE_HINT_RE);\n if (m) return m[1];\n }\n }\n }\n }\n return undefined;\n}\n\n/** Maps an HTTP status to the matching {@link EmporixError} subclass. */\nexport function errorFromResponse(status: number, message: string, body: unknown): EmporixError {\n if (status === 401) return new EmporixAuthError(message, status, body);\n if (status === 403) {\n const scope = extractRequiredScope(body);\n if (scope) return new EmporixInsufficientScopeError(message, status, body, scope);\n return new EmporixForbiddenError(message, status, body);\n }\n if (status === 404) return new EmporixNotFoundError(message, status, body);\n if (status === 400 || status === 422) return new EmporixValidationError(message, status, body);\n if (status >= 500) return new EmporixServerError(message, status, body);\n return new EmporixError(message, status, body);\n}\n","import type { ResolvedConfig, ServiceCredentials } from \"./config\";\nimport { EmporixAuthError, EmporixTimeoutError, EmporixNetworkError } from \"./errors\";\n\n/** Which token a call should use. */\nexport type AuthKind = \"service\" | \"customer\" | \"anonymous\" | \"raw\";\n\n/** Caller-supplied, per-call auth selector. Never stored on the client. */\nexport type AuthContext =\n | { kind: \"service\"; credentials?: string }\n | { kind: \"anonymous\" }\n | { kind: \"customer\"; token: string }\n | { kind: \"raw\"; token: string };\n\n/**\n * Supplies a fresh customer token when a `customer`-kind request 401s. The host\n * (e.g. EmporixProvider) implements this; the SDK never refreshes the\n * caller-owned customer token unless a refresher is registered.\n */\nexport interface CustomerTokenRefresher {\n /**\n * Called on a `customer`-kind 401. Receives the token that just failed;\n * returns a fresh customer token to retry with, or `null` to give up (the\n * 401 then propagates as EmporixAuthError).\n */\n refresh(expiredToken: string): Promise<string | null>;\n}\n\n/** An obtained anonymous storefront session. */\nexport interface AnonymousSession {\n accessToken: string;\n refreshToken: string;\n sessionId: string;\n expiresIn: number;\n}\n\n/**\n * Persistence callback for anonymous sessions. `read` is called once on the\n * first need for an anonymous token to bootstrap a possibly-existing session;\n * `write` is called after every successful login or refresh. `write(null)`\n * means the SDK is invalidating the stored session.\n */\nexport interface AnonymousSessionStore {\n read(): { refreshToken: string; sessionId: string } | null;\n write(session: { refreshToken: string; sessionId: string } | null): void;\n}\n\n/** Supplies SDK-managed tokens (service/custom + anonymous). May be user-injected. */\nexport interface TokenProvider {\n /** Service/custom client-credentials token for the named credential set. */\n getToken(credentialSet: string): Promise<string>;\n /** Cached anonymous storefront session (preserves sessionId across refreshes). */\n getAnonymousToken(): Promise<AnonymousSession>;\n /** Refresh the anonymous session, preserving sessionId. */\n refreshAnonymous?(): Promise<AnonymousSession>;\n /** Invalidate a cached SDK-managed token so the next call re-auths. */\n invalidate?(credentialSet: string): void;\n /** Invalidate the cached anonymous session entirely (next call re-logs in). */\n invalidateAnonymous?(): void;\n /**\n * Mark the anonymous access token stale but keep the refresh token, so the\n * next {@link getAnonymousToken} refreshes (preserving sessionId) rather\n * than starting a brand-new session.\n */\n expireAnonymous?(): void;\n /**\n * Install a persistence adapter for the anonymous session. The host (e.g.\n * `EmporixProvider`) calls this at construction so the SDK can bootstrap\n * an existing session and persist refreshes. Idempotent: a later call\n * replaces the previous adapter.\n */\n attachAnonymousStore?(store: AnonymousSessionStore): void;\n /**\n * Override the storefront context (currency/site/country) used for the next\n * anonymous login, then invalidate the current anonymous session so it\n * re-mints with the new context. No-op for providers without anon support.\n */\n setAnonymousContext?(ctx: {\n currency?: string;\n siteCode?: string;\n targetLocation?: string;\n }): void;\n /**\n * Subscribe to token-refresh events. Optional — implementations may no-op.\n * Returns an unsubscribe function. `DefaultTokenProvider` emits for the\n * anonymous-login / anonymous-refresh paths.\n */\n onRefresh?(\n listener: (event: { kind: \"anonymous\" | \"customer\"; success: boolean }) => void,\n ): () => void;\n}\n\n/** Tiny constructors for {@link AuthContext}. */\nexport const auth = {\n /** Service/custom credential set (default `backend`). */\n service: (credentials?: string): AuthContext =>\n credentials === undefined ? { kind: \"service\" } : { kind: \"service\", credentials },\n /** Cached anonymous storefront token. */\n anonymous: (): AuthContext => ({ kind: \"anonymous\" }),\n /** Caller-owned customer bearer token. */\n customer: (token: string): AuthContext => ({ kind: \"customer\", token }),\n /** Exact token, no transformation (SSO / token-exchange). */\n raw: (token: string): AuthContext => ({ kind: \"raw\", token }),\n};\n\n/** Resolves an {@link AuthContext} to a concrete bearer token. */\nexport async function resolveToken(ctx: AuthContext, provider: TokenProvider): Promise<string> {\n switch (ctx.kind) {\n case \"service\":\n return provider.getToken(ctx.credentials ?? \"backend\");\n case \"anonymous\":\n return (await provider.getAnonymousToken()).accessToken;\n case \"customer\":\n case \"raw\":\n return ctx.token;\n }\n}\n\n/**\n * Late-bindable, single-flight holder for an optional\n * {@link CustomerTokenRefresher}. Single-flight is required because Emporix\n * rotates the refresh token on each refresh — concurrent refreshes would\n * invalidate each other. Off (returns `null`) until a refresher is set.\n */\nexport class CustomerRefreshRegistry {\n private refresher: CustomerTokenRefresher | null = null;\n private inflight: Promise<string | null> | null = null;\n\n set(refresher: CustomerTokenRefresher | null): void {\n this.refresher = refresher;\n }\n\n get enabled(): boolean {\n return this.refresher !== null;\n }\n\n /** Concurrent callers share one in-flight refresh. */\n refresh(expiredToken: string): Promise<string | null> {\n if (!this.refresher) return Promise.resolve(null);\n if (this.inflight) return this.inflight;\n const p = Promise.resolve(this.refresher.refresh(expiredToken)).finally(() => {\n this.inflight = null;\n });\n this.inflight = p;\n return p;\n }\n}\n\ninterface CacheEntry {\n token: string;\n expiresAt: number;\n obtainedAt: number;\n}\n\n/** SDK-owned token provider: client-credentials service tokens + anonymous session. */\nexport class DefaultTokenProvider implements TokenProvider {\n private readonly serviceCache = new Map<string, CacheEntry>();\n private readonly serviceLocks = new Map<string, Promise<string>>();\n private anon: (AnonymousSession & { expiresAt: number }) | undefined;\n private anonLock: Promise<AnonymousSession> | undefined;\n private anonStore?: AnonymousSessionStore;\n private contextOverride:\n | { currency?: string; siteCode?: string; targetLocation?: string }\n | undefined;\n private readonly refreshListeners = new Set<\n (event: { kind: \"anonymous\" | \"customer\"; success: boolean }) => void\n >();\n\n constructor(private readonly cfg: ResolvedConfig) {}\n\n onRefresh(\n listener: (event: { kind: \"anonymous\" | \"customer\"; success: boolean }) => void,\n ): () => void {\n this.refreshListeners.add(listener);\n return () => {\n this.refreshListeners.delete(listener);\n };\n }\n\n private notifyRefresh(kind: \"anonymous\" | \"customer\", success: boolean): void {\n for (const l of this.refreshListeners) {\n try {\n l({ kind, success });\n } catch {\n // Never let a telemetry listener break the auth path.\n }\n }\n }\n\n attachAnonymousStore(store: AnonymousSessionStore): void {\n this.anonStore = store;\n // Bootstrap `this.anon` from the store if we don't have it yet. The seeded\n // session has expiresAt = 0 so the next getAnonymousToken triggers a refresh\n // (which preserves sessionId) instead of a fresh login.\n if (!this.anon) {\n const persisted = store.read();\n if (persisted) {\n this.anon = {\n accessToken: \"\",\n refreshToken: persisted.refreshToken,\n sessionId: persisted.sessionId,\n expiresIn: 0,\n expiresAt: 0,\n };\n }\n }\n }\n\n private creds(set: string): ServiceCredentials {\n if (set === \"backend\") {\n const b = this.cfg.credentials.backend;\n if (!b?.clientId || !b.secret) {\n throw new EmporixAuthError(\n \"A 'service' AuthContext was used but credentials.backend is not configured\",\n );\n }\n return b;\n }\n const c = this.cfg.credentials.custom?.[set];\n if (!c) throw new Error(`Unknown credential set \"${set}\"`);\n return c;\n }\n\n private fresh(e: CacheEntry | undefined): boolean {\n if (!e) return false;\n const now = Date.now();\n if (now - e.obtainedAt >= this.cfg.cache.maxLifetimeSeconds * 1000) return false;\n return now < e.expiresAt;\n }\n\n async getToken(set: string): Promise<string> {\n // Surface an unknown credential set before entering the cache/lock path.\n this.creds(set);\n const cached = this.serviceCache.get(set);\n if (this.fresh(cached)) return cached!.token;\n const inflight = this.serviceLocks.get(set);\n if (inflight) return inflight;\n const p = this.requestServiceToken(set).finally(() => this.serviceLocks.delete(set));\n this.serviceLocks.set(set, p);\n return p;\n }\n\n /**\n * fetch with the configured read timeout. Token endpoints sit in front of\n * single-flight locks — one hung call would otherwise block every request\n * on this credential set forever.\n */\n private async boundedFetch(url: string | URL, init: RequestInit = {}): Promise<Response> {\n const ms = this.cfg.timeouts?.readMs ?? 60_000;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), ms);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n throw new EmporixTimeoutError(`token request timed out after ${ms}ms`);\n }\n throw new EmporixNetworkError(`token request network failure: ${(err as Error).message}`);\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async requestServiceToken(set: string): Promise<string> {\n const c = this.creds(set);\n const body = new URLSearchParams({\n grant_type: \"client_credentials\",\n client_id: c.clientId,\n client_secret: c.secret,\n });\n if (c.scope) body.set(\"scope\", c.scope);\n const res = await this.boundedFetch(`${this.cfg.host}/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n const json = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n if (!res.ok) {\n throw new EmporixAuthError(`Token request failed for \"${set}\"`, res.status, json);\n }\n const obtainedAt = Date.now();\n const ttl = Number((json.expires_in as number | undefined) ?? 3600);\n this.serviceCache.set(set, {\n token: json.access_token as string,\n obtainedAt,\n expiresAt: obtainedAt + (ttl - this.cfg.cache.expirationBufferSeconds) * 1000,\n });\n return this.serviceCache.get(set)!.token;\n }\n\n invalidate(set: string): void {\n this.serviceCache.delete(set);\n }\n\n private anonFresh(): boolean {\n return !!this.anon && Date.now() < this.anon.expiresAt;\n }\n\n async getAnonymousToken(): Promise<AnonymousSession> {\n if (this.anonFresh()) return this.stripExpiry(this.anon!);\n if (this.anonLock) return this.anonLock;\n // Token expired but a refresh token survives → refresh to keep the same\n // sessionId; only fall back to a brand-new login if the refresh fails.\n const canRefresh = !!this.anon?.refreshToken;\n const obtain = canRefresh\n ? this.fetchAnonymous(\"refresh\").catch(() => this.fetchAnonymous(\"login\"))\n : this.fetchAnonymous(\"login\");\n const p = obtain.finally(() => {\n this.anonLock = undefined;\n });\n this.anonLock = p;\n return p;\n }\n\n /** Refreshes the anonymous session, preserving its sessionId. */\n async refreshAnonymous(): Promise<AnonymousSession> {\n if (!this.anon) return this.getAnonymousToken();\n return this.fetchAnonymous(\"refresh\");\n }\n\n invalidateAnonymous(): void {\n this.anon = undefined;\n this.anonStore?.write(null);\n }\n\n setAnonymousContext(ctx: {\n currency?: string;\n siteCode?: string;\n targetLocation?: string;\n }): void {\n const base = this.contextOverride ?? this.cfg.credentials.storefront?.context ?? {};\n this.contextOverride = { ...base, ...ctx };\n this.invalidateAnonymous();\n }\n\n /** Force a stale access token while keeping the refresh token + sessionId. */\n expireAnonymous(): void {\n if (this.anon) this.anon = { ...this.anon, expiresAt: 0 };\n }\n\n private stripExpiry(s: AnonymousSession & { expiresAt: number }): AnonymousSession {\n const { expiresAt: _expiresAt, ...rest } = s;\n return rest;\n }\n\n private async fetchAnonymous(mode: \"login\" | \"refresh\"): Promise<AnonymousSession> {\n const sf = this.cfg.credentials.storefront;\n if (!sf?.clientId) {\n throw new Error(\"credentials.storefront.clientId is required for anonymous tokens\");\n }\n const url = new URL(`${this.cfg.host}/customerlogin/auth/anonymous/${mode}`);\n url.searchParams.set(\"tenant\", this.cfg.tenant);\n url.searchParams.set(\"client_id\", sf.clientId);\n // Context is the runtime override (set via setAnonymousContext) if present,\n // else the config-fixed context.\n const c = this.contextOverride ?? sf.context;\n if (c?.currency) url.searchParams.set(\"currency\", c.currency);\n if (c?.siteCode) url.searchParams.set(\"siteCode\", c.siteCode);\n if (c?.targetLocation) url.searchParams.set(\"targetLocation\", c.targetLocation);\n if (mode === \"refresh\" && this.anon) {\n url.searchParams.set(\"refresh_token\", this.anon.refreshToken);\n }\n try {\n const res = await this.boundedFetch(url, { method: \"GET\" });\n const json = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n if (!res.ok) {\n this.notifyRefresh(\"anonymous\", false);\n throw new EmporixAuthError(`Anonymous token ${mode} failed`, res.status, json);\n }\n const obtainedAt = Date.now();\n this.anon = {\n accessToken: json.access_token as string,\n refreshToken: json.refresh_token as string,\n sessionId: json.sessionId as string,\n expiresIn: json.expires_in as number,\n expiresAt:\n obtainedAt +\n ((json.expires_in as number) - this.cfg.cache.expirationBufferSeconds) * 1000,\n };\n this.anonStore?.write({\n refreshToken: this.anon.refreshToken,\n sessionId: this.anon.sessionId,\n });\n this.notifyRefresh(\"anonymous\", true);\n return this.stripExpiry(this.anon);\n } catch (err) {\n // Re-throw EmporixAuthError as-is; for non-Emporix errors (network), also notify.\n if (!(err instanceof EmporixAuthError)) {\n this.notifyRefresh(\"anonymous\", false);\n }\n throw err;\n }\n }\n}\n// Customer-token refreshes happen via client.customers.refresh() and don't\n// route through this TokenProvider — only the anonymous flow notifies.\n// React-side useCustomerSession.refresh emits its own telemetry event in a\n// follow-up if needed.\n","import type { AuthContext } from \"./auth\";\nimport { EmporixAuthError } from \"./errors\";\n\n/**\n * Enforces a caller-owned customer or raw token. Throws `EmporixAuthError`\n * for any other `AuthContext` kind (or when `auth` is missing). Shared by\n * services whose endpoints require a customer scope (`/me`, payments,\n * customer-segments…).\n */\nexport function requireCustomer(auth: AuthContext | undefined): AuthContext {\n if (auth && (auth.kind === \"customer\" || auth.kind === \"raw\")) return auth;\n throw new EmporixAuthError(\"This operation requires a customer or raw AuthContext\");\n}\n","import type { ClientContext } from \"../core/context\";\nimport { auth, type AuthContext } from \"../core/auth\";\nimport { requireCustomer } from \"../core/require-customer\";\nimport type {\n PaymentModeFrontendResponse,\n AuthorizePaymentRequest,\n InitializePaymentRequest,\n InitializePaymentResponse,\n} from \"../generated/payment\";\n\nconst ANON: AuthContext = auth.anonymous();\n\n/** A frontend payment mode (generated). */\nexport type PaymentMode = PaymentModeFrontendResponse;\n\n/** Post-checkout authorize request (generated; caller sends the exact wire shape). */\nexport type AuthorizePaymentInput = AuthorizePaymentRequest;\n\n/** Frontend payment-initialize request (generated). */\nexport type InitializePaymentInput = InitializePaymentRequest;\n\n/** Frontend payment-initialize response (generated). */\nexport type InitializePaymentResult = InitializePaymentResponse;\n\n/** Post-checkout authorize result. */\nexport interface AuthorizePaymentResult {\n successful: boolean;\n paymentTransactionId?: string;\n authorizationToken?: string;\n requiresExternalPayment?: boolean;\n externalPaymentRedirectURL?: string;\n externalPaymentHttpMethod?: string;\n [k: string]: unknown;\n}\n\n/** Payment-Gateway: list frontend payment modes, authorize deferred payments. */\nexport class PaymentGatewayService {\n static readonly channel = \"payment\" as const;\n constructor(private readonly ctx: ClientContext) {}\n\n /**\n * Lists configured frontend payment modes. The endpoint requires a bearer\n * token but no customer scope (\"No scope required\"), so it defaults to an\n * anonymous context and works for guests and logged-in customers alike.\n */\n async listPaymentModes(authCtx: AuthContext = ANON): Promise<PaymentMode[]> {\n return this.ctx.http.request<PaymentMode[]>({\n method: \"GET\",\n path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/frontend`,\n auth: authCtx,\n });\n }\n\n /** Authorizes a post-checkout (deferred) payment for an existing order. */\n async authorize(\n input: AuthorizePaymentInput,\n authCtx?: AuthContext,\n ): Promise<AuthorizePaymentResult> {\n return this.ctx.http.request<AuthorizePaymentResult>({\n method: \"POST\",\n path: `/payment-gateway/${this.ctx.tenant}/payment/frontend/authorize`,\n auth: requireCustomer(authCtx),\n body: input,\n });\n }\n\n /** Retrieves a single frontend payment mode by id. Defaults to anonymous (no scope required). */\n async getMode(id: string, authCtx: AuthContext = ANON): Promise<PaymentMode> {\n return this.ctx.http.request<PaymentMode>({\n method: \"GET\",\n path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/frontend/${id}`,\n auth: authCtx,\n });\n }\n\n /** Initializes a payment from the frontend. Defaults to anonymous (no scope required). */\n async initialize(\n input: InitializePaymentInput,\n authCtx: AuthContext = ANON,\n ): Promise<InitializePaymentResult> {\n return this.ctx.http.request<InitializePaymentResult>({\n method: \"POST\",\n path: `/payment-gateway/${this.ctx.tenant}/payment/frontend/initialize`,\n auth: authCtx,\n body: input,\n });\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/core/errors.ts","../src/core/auth.ts","../src/core/require-customer.ts","../src/services/payment.ts"],"names":["auth"],"mappings":"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","file":"payment.cjs","sourcesContent":["/** Keys whose values are stripped from any serialised error body. */\nconst REDACTED_BODY_KEYS = new Set([\n \"access_token\", \"refresh_token\", \"token\", \"customertoken\", \"saastoken\",\n \"secret\", \"client_secret\", \"authorization\", \"password\",\n]);\n\nfunction scrub(body: unknown): unknown {\n if (Array.isArray(body)) return body.map(scrub);\n if (body && typeof body === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(body as Record<string, unknown>)) {\n out[k] = REDACTED_BODY_KEYS.has(k.toLowerCase()) ? \"***redacted***\" : scrub(v);\n }\n return out;\n }\n return body;\n}\n\n/** Base class for every error thrown by the SDK. */\nexport class EmporixError extends Error {\n readonly status: number | undefined;\n readonly body: unknown;\n constructor(message: string, status?: number, body?: unknown) {\n super(message);\n this.name = new.target.name;\n this.status = status;\n this.body = body;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n /** Safe serialisation — token-like body fields are redacted. */\n toJSON(): Record<string, unknown> {\n return { name: this.name, message: this.message, status: this.status, body: scrub(this.body) };\n }\n}\n\n/** 401 — authentication failed or token expired. */\nexport class EmporixAuthError extends EmporixError {}\n/** 403 — authenticated but not permitted. */\nexport class EmporixForbiddenError extends EmporixError {}\n/** 404 — resource not found. */\nexport class EmporixNotFoundError extends EmporixError {}\n/** 400/422 — request validation failed. */\nexport class EmporixValidationError extends EmporixError {}\n/** 5xx — server-side failure. */\nexport class EmporixServerError extends EmporixError {}\n/** Request aborted by the configured connect/read timeout. No HTTP status. */\nexport class EmporixTimeoutError extends EmporixError {}\n/** DNS/TLS/connection-level failure before or during the exchange. No HTTP status. */\nexport class EmporixNetworkError extends EmporixError {}\n\n/**\n * 403 with a `missing scope: <name>` hint in the body's `details` array.\n * Subclasses {@link EmporixForbiddenError} so existing 403 catches still work.\n */\nexport class EmporixInsufficientScopeError extends EmporixForbiddenError {\n readonly requiredScope: string | undefined;\n constructor(message: string, status: number, body: unknown, requiredScope?: string) {\n super(message, status, body);\n this.requiredScope = requiredScope;\n }\n}\n\nconst SCOPE_HINT_RE = /missing scope[: ]+([a-z0-9._-]+)/i;\n\nfunction extractRequiredScope(body: unknown): string | undefined {\n if (body && typeof body === \"object\" && \"details\" in body) {\n const details = (body as { details?: unknown }).details;\n if (Array.isArray(details)) {\n for (const d of details) {\n if (typeof d === \"string\") {\n const m = d.match(SCOPE_HINT_RE);\n if (m) return m[1];\n }\n }\n }\n }\n return undefined;\n}\n\n/** Maps an HTTP status to the matching {@link EmporixError} subclass. */\nexport function errorFromResponse(status: number, message: string, body: unknown): EmporixError {\n if (status === 401) return new EmporixAuthError(message, status, body);\n if (status === 403) {\n const scope = extractRequiredScope(body);\n if (scope) return new EmporixInsufficientScopeError(message, status, body, scope);\n return new EmporixForbiddenError(message, status, body);\n }\n if (status === 404) return new EmporixNotFoundError(message, status, body);\n if (status === 400 || status === 422) return new EmporixValidationError(message, status, body);\n if (status >= 500) return new EmporixServerError(message, status, body);\n return new EmporixError(message, status, body);\n}\n","import type { ResolvedConfig, ServiceCredentials } from \"./config\";\nimport { EmporixAuthError, EmporixTimeoutError, EmporixNetworkError } from \"./errors\";\n\n/** Which token a call should use. */\nexport type AuthKind = \"service\" | \"customer\" | \"anonymous\" | \"raw\";\n\n/** Caller-supplied, per-call auth selector. Never stored on the client. */\nexport type AuthContext =\n | { kind: \"service\"; credentials?: string }\n | { kind: \"anonymous\" }\n | { kind: \"customer\"; token: string }\n | { kind: \"raw\"; token: string };\n\n/**\n * Supplies a fresh customer token when a `customer`-kind request 401s. The host\n * (e.g. EmporixProvider) implements this; the SDK never refreshes the\n * caller-owned customer token unless a refresher is registered.\n */\nexport interface CustomerTokenRefresher {\n /**\n * Called on a `customer`-kind 401. Receives the token that just failed;\n * returns a fresh customer token to retry with, or `null` to give up (the\n * 401 then propagates as EmporixAuthError).\n */\n refresh(expiredToken: string): Promise<string | null>;\n}\n\n/** An obtained anonymous storefront session. */\nexport interface AnonymousSession {\n accessToken: string;\n refreshToken: string;\n sessionId: string;\n expiresIn: number;\n}\n\n/**\n * Persistence callback for anonymous sessions. `read` is called once on the\n * first need for an anonymous token to bootstrap a possibly-existing session;\n * `write` is called after every successful login or refresh. `write(null)`\n * means the SDK is invalidating the stored session.\n */\nexport interface AnonymousSessionStore {\n read(): { refreshToken: string; sessionId: string } | null;\n write(session: { refreshToken: string; sessionId: string } | null): void;\n}\n\n/** Supplies SDK-managed tokens (service/custom + anonymous). May be user-injected. */\nexport interface TokenProvider {\n /** Service/custom client-credentials token for the named credential set. */\n getToken(credentialSet: string): Promise<string>;\n /** Cached anonymous storefront session (preserves sessionId across refreshes). */\n getAnonymousToken(): Promise<AnonymousSession>;\n /** Refresh the anonymous session, preserving sessionId. */\n refreshAnonymous?(): Promise<AnonymousSession>;\n /** Invalidate a cached SDK-managed token so the next call re-auths. */\n invalidate?(credentialSet: string): void;\n /** Invalidate the cached anonymous session entirely (next call re-logs in). */\n invalidateAnonymous?(): void;\n /**\n * Mark the anonymous access token stale but keep the refresh token, so the\n * next {@link getAnonymousToken} refreshes (preserving sessionId) rather\n * than starting a brand-new session.\n */\n expireAnonymous?(): void;\n /**\n * Install a persistence adapter for the anonymous session. The host (e.g.\n * `EmporixProvider`) calls this at construction so the SDK can bootstrap\n * an existing session and persist refreshes. Idempotent: a later call\n * replaces the previous adapter.\n */\n attachAnonymousStore?(store: AnonymousSessionStore): void;\n /**\n * Override the storefront context (currency/site/country) used for the next\n * anonymous login, then invalidate the current anonymous session so it\n * re-mints with the new context. No-op for providers without anon support.\n */\n setAnonymousContext?(ctx: {\n currency?: string;\n siteCode?: string;\n targetLocation?: string;\n }): void;\n /**\n * Subscribe to token-refresh events. Optional — implementations may no-op.\n * Returns an unsubscribe function. `DefaultTokenProvider` emits for the\n * anonymous-login / anonymous-refresh paths.\n */\n onRefresh?(\n listener: (event: { kind: \"anonymous\" | \"customer\"; success: boolean }) => void,\n ): () => void;\n}\n\n/** Tiny constructors for {@link AuthContext}. */\nexport const auth = {\n /** Service/custom credential set (default `backend`). */\n service: (credentials?: string): AuthContext =>\n credentials === undefined ? { kind: \"service\" } : { kind: \"service\", credentials },\n /** Cached anonymous storefront token. */\n anonymous: (): AuthContext => ({ kind: \"anonymous\" }),\n /** Caller-owned customer bearer token. */\n customer: (token: string): AuthContext => ({ kind: \"customer\", token }),\n /** Exact token, no transformation (SSO / token-exchange). */\n raw: (token: string): AuthContext => ({ kind: \"raw\", token }),\n};\n\n/** Resolves an {@link AuthContext} to a concrete bearer token. */\nexport async function resolveToken(ctx: AuthContext, provider: TokenProvider): Promise<string> {\n switch (ctx.kind) {\n case \"service\":\n return provider.getToken(ctx.credentials ?? \"backend\");\n case \"anonymous\":\n return (await provider.getAnonymousToken()).accessToken;\n case \"customer\":\n case \"raw\":\n return ctx.token;\n }\n}\n\n/**\n * Late-bindable, single-flight holder for an optional\n * {@link CustomerTokenRefresher}. Single-flight is required because Emporix\n * rotates the refresh token on each refresh — concurrent refreshes would\n * invalidate each other. Off (returns `null`) until a refresher is set.\n */\nexport class CustomerRefreshRegistry {\n private refresher: CustomerTokenRefresher | null = null;\n private inflight: Promise<string | null> | null = null;\n\n set(refresher: CustomerTokenRefresher | null): void {\n this.refresher = refresher;\n }\n\n get enabled(): boolean {\n return this.refresher !== null;\n }\n\n /** Concurrent callers share one in-flight refresh. */\n refresh(expiredToken: string): Promise<string | null> {\n if (!this.refresher) return Promise.resolve(null);\n if (this.inflight) return this.inflight;\n const p = Promise.resolve(this.refresher.refresh(expiredToken)).finally(() => {\n this.inflight = null;\n });\n this.inflight = p;\n return p;\n }\n}\n\ninterface CacheEntry {\n token: string;\n expiresAt: number;\n obtainedAt: number;\n}\n\n/** SDK-owned token provider: client-credentials service tokens + anonymous session. */\nexport class DefaultTokenProvider implements TokenProvider {\n private readonly serviceCache = new Map<string, CacheEntry>();\n private readonly serviceLocks = new Map<string, Promise<string>>();\n private anon: (AnonymousSession & { expiresAt: number }) | undefined;\n private anonLock: Promise<AnonymousSession> | undefined;\n private anonStore?: AnonymousSessionStore;\n private contextOverride:\n | { currency?: string; siteCode?: string; targetLocation?: string }\n | undefined;\n private readonly refreshListeners = new Set<\n (event: { kind: \"anonymous\" | \"customer\"; success: boolean }) => void\n >();\n\n constructor(private readonly cfg: ResolvedConfig) {}\n\n onRefresh(\n listener: (event: { kind: \"anonymous\" | \"customer\"; success: boolean }) => void,\n ): () => void {\n this.refreshListeners.add(listener);\n return () => {\n this.refreshListeners.delete(listener);\n };\n }\n\n private notifyRefresh(kind: \"anonymous\" | \"customer\", success: boolean): void {\n for (const l of this.refreshListeners) {\n try {\n l({ kind, success });\n } catch {\n // Never let a telemetry listener break the auth path.\n }\n }\n }\n\n attachAnonymousStore(store: AnonymousSessionStore): void {\n this.anonStore = store;\n // Bootstrap `this.anon` from the store if we don't have it yet. The seeded\n // session has expiresAt = 0 so the next getAnonymousToken triggers a refresh\n // (which preserves sessionId) instead of a fresh login.\n if (!this.anon) {\n const persisted = store.read();\n if (persisted) {\n this.anon = {\n accessToken: \"\",\n refreshToken: persisted.refreshToken,\n sessionId: persisted.sessionId,\n expiresIn: 0,\n expiresAt: 0,\n };\n }\n }\n }\n\n private creds(set: string): ServiceCredentials {\n if (set === \"backend\") {\n const b = this.cfg.credentials.backend;\n if (!b?.clientId || !b.secret) {\n throw new EmporixAuthError(\n \"A 'service' AuthContext was used but credentials.backend is not configured\",\n );\n }\n return b;\n }\n const c = this.cfg.credentials.custom?.[set];\n if (!c) throw new Error(`Unknown credential set \"${set}\"`);\n return c;\n }\n\n private fresh(e: CacheEntry | undefined): boolean {\n if (!e) return false;\n const now = Date.now();\n if (now - e.obtainedAt >= this.cfg.cache.maxLifetimeSeconds * 1000) return false;\n return now < e.expiresAt;\n }\n\n async getToken(set: string): Promise<string> {\n // Surface an unknown credential set before entering the cache/lock path.\n this.creds(set);\n const cached = this.serviceCache.get(set);\n if (this.fresh(cached)) return cached!.token;\n const inflight = this.serviceLocks.get(set);\n if (inflight) return inflight;\n const p = this.requestServiceToken(set).finally(() => this.serviceLocks.delete(set));\n this.serviceLocks.set(set, p);\n return p;\n }\n\n /**\n * fetch with the configured read timeout. Token endpoints sit in front of\n * single-flight locks — one hung call would otherwise block every request\n * on this credential set forever.\n */\n private async boundedFetch(url: string | URL, init: RequestInit = {}): Promise<Response> {\n const ms = this.cfg.timeouts?.readMs ?? 60_000;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), ms);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n throw new EmporixTimeoutError(`token request timed out after ${ms}ms`);\n }\n throw new EmporixNetworkError(`token request network failure: ${(err as Error).message}`);\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async requestServiceToken(set: string): Promise<string> {\n const c = this.creds(set);\n const body = new URLSearchParams({\n grant_type: \"client_credentials\",\n client_id: c.clientId,\n client_secret: c.secret,\n });\n if (c.scope) body.set(\"scope\", c.scope);\n const res = await this.boundedFetch(`${this.cfg.host}/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n const json = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n if (!res.ok) {\n throw new EmporixAuthError(`Token request failed for \"${set}\"`, res.status, json);\n }\n const obtainedAt = Date.now();\n const ttl = Number((json.expires_in as number | undefined) ?? 3600);\n this.serviceCache.set(set, {\n token: json.access_token as string,\n obtainedAt,\n expiresAt: obtainedAt + (ttl - this.cfg.cache.expirationBufferSeconds) * 1000,\n });\n return this.serviceCache.get(set)!.token;\n }\n\n invalidate(set: string): void {\n this.serviceCache.delete(set);\n }\n\n private anonFresh(): boolean {\n return !!this.anon && Date.now() < this.anon.expiresAt;\n }\n\n async getAnonymousToken(): Promise<AnonymousSession> {\n if (this.anonFresh()) return this.stripExpiry(this.anon!);\n if (this.anonLock) return this.anonLock;\n // Token expired but a refresh token survives → refresh to keep the same\n // sessionId; only fall back to a brand-new login if the refresh fails.\n const canRefresh = !!this.anon?.refreshToken;\n const obtain = canRefresh\n ? this.fetchAnonymous(\"refresh\").catch(() => this.fetchAnonymous(\"login\"))\n : this.fetchAnonymous(\"login\");\n const p = obtain.finally(() => {\n this.anonLock = undefined;\n });\n this.anonLock = p;\n return p;\n }\n\n /** Refreshes the anonymous session, preserving its sessionId. */\n async refreshAnonymous(): Promise<AnonymousSession> {\n if (!this.anon) return this.getAnonymousToken();\n return this.fetchAnonymous(\"refresh\");\n }\n\n invalidateAnonymous(): void {\n this.anon = undefined;\n this.anonStore?.write(null);\n }\n\n setAnonymousContext(ctx: {\n currency?: string;\n siteCode?: string;\n targetLocation?: string;\n }): void {\n const base = this.contextOverride ?? this.cfg.credentials.storefront?.context ?? {};\n this.contextOverride = { ...base, ...ctx };\n this.invalidateAnonymous();\n }\n\n /** Force a stale access token while keeping the refresh token + sessionId. */\n expireAnonymous(): void {\n if (this.anon) this.anon = { ...this.anon, expiresAt: 0 };\n }\n\n private stripExpiry(s: AnonymousSession & { expiresAt: number }): AnonymousSession {\n const { expiresAt: _expiresAt, ...rest } = s;\n return rest;\n }\n\n private async fetchAnonymous(mode: \"login\" | \"refresh\"): Promise<AnonymousSession> {\n const sf = this.cfg.credentials.storefront;\n if (!sf?.clientId) {\n throw new Error(\"credentials.storefront.clientId is required for anonymous tokens\");\n }\n const url = new URL(`${this.cfg.host}/customerlogin/auth/anonymous/${mode}`);\n url.searchParams.set(\"tenant\", this.cfg.tenant);\n url.searchParams.set(\"client_id\", sf.clientId);\n // Context is the runtime override (set via setAnonymousContext) if present,\n // else the config-fixed context.\n const c = this.contextOverride ?? sf.context;\n if (c?.currency) url.searchParams.set(\"currency\", c.currency);\n if (c?.siteCode) url.searchParams.set(\"siteCode\", c.siteCode);\n if (c?.targetLocation) url.searchParams.set(\"targetLocation\", c.targetLocation);\n if (mode === \"refresh\" && this.anon) {\n url.searchParams.set(\"refresh_token\", this.anon.refreshToken);\n }\n try {\n const res = await this.boundedFetch(url, { method: \"GET\" });\n const json = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n if (!res.ok) {\n this.notifyRefresh(\"anonymous\", false);\n throw new EmporixAuthError(`Anonymous token ${mode} failed`, res.status, json);\n }\n const obtainedAt = Date.now();\n this.anon = {\n accessToken: json.access_token as string,\n refreshToken: json.refresh_token as string,\n sessionId: json.sessionId as string,\n expiresIn: json.expires_in as number,\n expiresAt:\n obtainedAt +\n ((json.expires_in as number) - this.cfg.cache.expirationBufferSeconds) * 1000,\n };\n this.anonStore?.write({\n refreshToken: this.anon.refreshToken,\n sessionId: this.anon.sessionId,\n });\n this.notifyRefresh(\"anonymous\", true);\n return this.stripExpiry(this.anon);\n } catch (err) {\n // Re-throw EmporixAuthError as-is; for non-Emporix errors (network), also notify.\n if (!(err instanceof EmporixAuthError)) {\n this.notifyRefresh(\"anonymous\", false);\n }\n throw err;\n }\n }\n}\n// Customer-token refreshes happen via client.customers.refresh() and don't\n// route through this TokenProvider — only the anonymous flow notifies.\n// React-side useCustomerSession.refresh emits its own telemetry event in a\n// follow-up if needed.\n","import type { AuthContext } from \"./auth\";\nimport { EmporixAuthError } from \"./errors\";\n\n/**\n * Enforces a caller-owned customer or raw token. Throws `EmporixAuthError`\n * for any other `AuthContext` kind (or when `auth` is missing). Shared by\n * services whose endpoints require a customer scope (`/me`, payments,\n * customer-segments…).\n */\nexport function requireCustomer(auth: AuthContext | undefined): AuthContext {\n if (auth && (auth.kind === \"customer\" || auth.kind === \"raw\")) return auth;\n throw new EmporixAuthError(\"This operation requires a customer or raw AuthContext\");\n}\n","import type { ClientContext } from \"../core/context\";\nimport { auth, type AuthContext } from \"../core/auth\";\nimport { requireCustomer } from \"../core/require-customer\";\nimport type {\n PaymentModeFrontendResponse,\n AuthorizePaymentRequest,\n InitializePaymentRequest,\n InitializePaymentResponse,\n PaymentModeResponse,\n PaymentModeRequest,\n PaymentMethodUpdateRequest,\n PaymentTransactionResponse,\n AuthorizePaymentResponse,\n CaptureRequest,\n RefundRequest,\n CommonPaymentResponse,\n} from \"../generated/payment\";\n\nconst ANON: AuthContext = auth.anonymous();\nconst SERVICE: AuthContext = auth.service();\n\n/** A frontend payment mode (generated). */\nexport type PaymentMode = PaymentModeFrontendResponse;\n\n/** Post-checkout authorize request (generated; caller sends the exact wire shape). */\nexport type AuthorizePaymentInput = AuthorizePaymentRequest;\n\n/** Frontend payment-initialize request (generated). */\nexport type InitializePaymentInput = InitializePaymentRequest;\n\n/** Frontend payment-initialize response (generated). */\nexport type InitializePaymentResult = InitializePaymentResponse;\n\n/** Post-checkout authorize result. */\nexport interface AuthorizePaymentResult {\n successful: boolean;\n paymentTransactionId?: string;\n authorizationToken?: string;\n requiresExternalPayment?: boolean;\n externalPaymentRedirectURL?: string;\n externalPaymentHttpMethod?: string;\n [k: string]: unknown;\n}\n\n/** A configured payment mode (admin view, `/paymentmodes/config`). */\nexport type PaymentModeConfig = PaymentModeResponse;\n\n/** Body for creating a payment-mode configuration. */\nexport type PaymentModeCreateInput = PaymentModeRequest;\n\n/** Body for updating a payment-mode configuration. */\nexport type PaymentModeUpdateInput = PaymentMethodUpdateRequest;\n\n/** A payment transaction (generated). */\nexport type PaymentTransaction = PaymentTransactionResponse;\n\n/** Result of a backend payment authorization. */\nexport type PaymentAuthorizeResult = AuthorizePaymentResponse;\n\n/** Body for capturing an authorized payment. */\nexport type PaymentCaptureInput = CaptureRequest;\n\n/** Body for refunding a captured payment. */\nexport type PaymentRefundInput = RefundRequest;\n\n/**\n * Result of a refund/cancel operation. Business failures are reported in the\n * body (`successful: false` + `message`), not via HTTP status.\n */\nexport type PaymentActionResult = CommonPaymentResponse;\n\n/** Result of a capture — like {@link PaymentActionResult} plus the provider's capture id. */\nexport type PaymentCaptureResult = {\n successful?: boolean;\n message?: string;\n captureId?: string;\n};\n\n/** Payment-Gateway: list frontend payment modes, authorize deferred payments. */\nexport class PaymentGatewayService {\n static readonly channel = \"payment\" as const;\n constructor(private readonly ctx: ClientContext) {}\n\n /**\n * Lists configured frontend payment modes. The endpoint requires a bearer\n * token but no customer scope (\"No scope required\"), so it defaults to an\n * anonymous context and works for guests and logged-in customers alike.\n */\n async listPaymentModes(authCtx: AuthContext = ANON): Promise<PaymentMode[]> {\n return this.ctx.http.request<PaymentMode[]>({\n method: \"GET\",\n path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/frontend`,\n auth: authCtx,\n });\n }\n\n /** Authorizes a post-checkout (deferred) payment for an existing order. */\n async authorize(\n input: AuthorizePaymentInput,\n authCtx?: AuthContext,\n ): Promise<AuthorizePaymentResult> {\n return this.ctx.http.request<AuthorizePaymentResult>({\n method: \"POST\",\n path: `/payment-gateway/${this.ctx.tenant}/payment/frontend/authorize`,\n auth: requireCustomer(authCtx),\n body: input,\n });\n }\n\n /** Retrieves a single frontend payment mode by id. Defaults to anonymous (no scope required). */\n async getMode(id: string, authCtx: AuthContext = ANON): Promise<PaymentMode> {\n return this.ctx.http.request<PaymentMode>({\n method: \"GET\",\n path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/frontend/${id}`,\n auth: authCtx,\n });\n }\n\n /** Initializes a payment from the frontend. Defaults to anonymous (no scope required). */\n async initialize(\n input: InitializePaymentInput,\n authCtx: AuthContext = ANON,\n ): Promise<InitializePaymentResult> {\n return this.ctx.http.request<InitializePaymentResult>({\n method: \"POST\",\n path: `/payment-gateway/${this.ctx.tenant}/payment/frontend/initialize`,\n auth: authCtx,\n body: input,\n });\n }\n\n /**\n * Payment-mode **configuration** (`/paymentmodes/config`) — the admin view.\n * For the storefront view use {@link listPaymentModes} / {@link getMode}.\n * Defaults to service auth.\n */\n readonly modes = {\n /** Lists all configured payment modes. The endpoint takes no query parameters. */\n list: async (authCtx: AuthContext = SERVICE): Promise<PaymentModeConfig[]> =>\n this.ctx.http.request<PaymentModeConfig[]>({\n method: \"GET\",\n path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/config`,\n auth: authCtx,\n }),\n\n /** Retrieves one payment-mode configuration by id. */\n get: async (id: string, authCtx: AuthContext = SERVICE): Promise<PaymentModeConfig> =>\n this.ctx.http.request<PaymentModeConfig>({\n method: \"GET\",\n path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/config/${id}`,\n auth: authCtx,\n }),\n\n /** Creates a payment-mode configuration. Responds 200 with the created mode. */\n create: async (\n input: PaymentModeCreateInput,\n authCtx: AuthContext = SERVICE,\n ): Promise<PaymentModeConfig> =>\n this.ctx.http.request<PaymentModeConfig>({\n method: \"POST\",\n path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/config`,\n body: input,\n auth: authCtx,\n }),\n\n /** Updates a payment-mode configuration (PUT). Responds 200 with the updated mode. */\n update: async (\n id: string,\n input: PaymentModeUpdateInput,\n authCtx: AuthContext = SERVICE,\n ): Promise<PaymentModeConfig> =>\n this.ctx.http.request<PaymentModeConfig>({\n method: \"PUT\",\n path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/config/${id}`,\n body: input,\n auth: authCtx,\n }),\n\n /** Deletes a payment-mode configuration. */\n delete: async (id: string, authCtx: AuthContext = SERVICE): Promise<void> => {\n await this.ctx.http.request<void>({\n method: \"DELETE\",\n path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/config/${id}`,\n auth: authCtx,\n });\n },\n };\n\n /**\n * Backend transaction lifecycle and reads. `authorize` creates the transaction\n * that `capture`/`refund`/`cancel` then act on. Business failures are reported\n * in the response body (`successful: false`), not as HTTP errors — always check\n * `successful`. Defaults to service auth.\n */\n readonly transactions = {\n /** One page of payment transactions. */\n list: async (\n params: { pageNumber?: number; pageSize?: number; sort?: string } = {},\n authCtx: AuthContext = SERVICE,\n ): Promise<PaymentTransaction[]> => {\n const query = {\n ...(params.pageNumber === undefined ? {} : { pageNumber: params.pageNumber }),\n ...(params.pageSize === undefined ? {} : { pageSize: params.pageSize }),\n ...(params.sort === undefined ? {} : { sort: params.sort }),\n };\n return this.ctx.http.request<PaymentTransaction[]>({\n method: \"GET\",\n path: `/payment-gateway/${this.ctx.tenant}/transactions`,\n ...(Object.keys(query).length > 0 ? { query } : {}),\n auth: authCtx,\n });\n },\n\n /** Retrieves one payment transaction by id. */\n get: async (transactionId: string, authCtx: AuthContext = SERVICE): Promise<PaymentTransaction> =>\n this.ctx.http.request<PaymentTransaction>({\n method: \"GET\",\n path: `/payment-gateway/${this.ctx.tenant}/transactions/${transactionId}`,\n auth: authCtx,\n }),\n\n /**\n * Authorizes a payment as a backend caller (`POST /payment/authorize`). The\n * storefront equivalent is {@link PaymentGatewayService.authorize}, which\n * targets `/payment/frontend/authorize` with a customer token.\n */\n authorize: async (\n input: AuthorizePaymentInput,\n authCtx: AuthContext = SERVICE,\n ): Promise<PaymentAuthorizeResult> =>\n this.ctx.http.request<PaymentAuthorizeResult>({\n method: \"POST\",\n path: `/payment-gateway/${this.ctx.tenant}/payment/authorize`,\n body: input,\n auth: authCtx,\n }),\n\n /**\n * Captures an authorized payment. Omit `input` to capture the full\n * authorized amount. Returns the provider's `captureId` on success.\n */\n capture: async (\n transactionId: string,\n input?: PaymentCaptureInput,\n authCtx: AuthContext = SERVICE,\n ): Promise<PaymentCaptureResult> =>\n this.ctx.http.request<PaymentCaptureResult>({\n method: \"POST\",\n path: `/payment-gateway/${this.ctx.tenant}/payment/${transactionId}/capture`,\n ...(input === undefined ? {} : { body: input }),\n auth: authCtx,\n }),\n\n /**\n * Refunds a captured payment. Omit `input` to refund the full captured\n * amount; pass `captureId` when several captures exist for the payment.\n */\n refund: async (\n transactionId: string,\n input?: PaymentRefundInput,\n authCtx: AuthContext = SERVICE,\n ): Promise<PaymentActionResult> =>\n this.ctx.http.request<PaymentActionResult>({\n method: \"POST\",\n path: `/payment-gateway/${this.ctx.tenant}/payment/${transactionId}/refund`,\n ...(input === undefined ? {} : { body: input }),\n auth: authCtx,\n }),\n\n /** Cancels an authorized payment. Takes no body. */\n cancel: async (transactionId: string, authCtx: AuthContext = SERVICE): Promise<PaymentActionResult> =>\n this.ctx.http.request<PaymentActionResult>({\n method: \"POST\",\n path: `/payment-gateway/${this.ctx.tenant}/payment/${transactionId}/cancel`,\n auth: authCtx,\n }),\n };\n}\n"]}