@viu/emporix-sdk 2.21.0 → 2.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +146 -0
- package/dist/availability.cjs +118 -0
- package/dist/availability.cjs.map +1 -1
- package/dist/availability.d.cts +178 -2
- package/dist/availability.d.ts +178 -2
- package/dist/availability.js +1 -1
- package/dist/cart.cjs +215 -9
- package/dist/cart.cjs.map +1 -1
- package/dist/cart.d.cts +281 -3
- package/dist/cart.d.ts +281 -3
- package/dist/cart.js +1 -1
- package/dist/category.cjs +220 -2
- package/dist/category.cjs.map +1 -1
- package/dist/category.d.cts +408 -1
- package/dist/category.d.ts +408 -1
- package/dist/category.js +1 -1
- package/dist/{chunk-HD5RBCM6.js → chunk-27NLS646.js} +10 -2
- package/dist/chunk-27NLS646.js.map +1 -0
- package/dist/{chunk-PYLWZZC4.js → chunk-6PJFOX3A.js} +126 -2
- package/dist/chunk-6PJFOX3A.js.map +1 -0
- package/dist/chunk-HWVFF6B2.js +378 -0
- package/dist/chunk-HWVFF6B2.js.map +1 -0
- package/dist/chunk-LQFRJSVT.js +224 -0
- package/dist/chunk-LQFRJSVT.js.map +1 -0
- package/dist/chunk-P7G4LOOV.js +264 -0
- package/dist/chunk-P7G4LOOV.js.map +1 -0
- package/dist/{chunk-HBEQEIT6.js → chunk-PRN3T6TY.js} +63 -3
- package/dist/chunk-PRN3T6TY.js.map +1 -0
- package/dist/{chunk-44F52KRE.js → chunk-QQMLN76U.js} +27 -2
- package/dist/chunk-QQMLN76U.js.map +1 -0
- package/dist/chunk-UNW6SFSB.js +363 -0
- package/dist/chunk-UNW6SFSB.js.map +1 -0
- package/dist/chunk-V3275BA7.js +181 -0
- package/dist/chunk-V3275BA7.js.map +1 -0
- package/dist/chunk-W2KBVWJ2.js +323 -0
- package/dist/chunk-W2KBVWJ2.js.map +1 -0
- package/dist/chunk-YVZ6GOLE.js +399 -0
- package/dist/chunk-YVZ6GOLE.js.map +1 -0
- package/dist/chunk-ZOFGDNSX.js +305 -0
- package/dist/chunk-ZOFGDNSX.js.map +1 -0
- package/dist/companies.cjs +25 -0
- package/dist/companies.cjs.map +1 -1
- package/dist/companies.d.cts +14 -0
- package/dist/companies.d.ts +14 -0
- package/dist/companies.js +1 -1
- package/dist/contacts.cjs +8 -0
- package/dist/contacts.cjs.map +1 -1
- package/dist/contacts.d.cts +2 -0
- package/dist/contacts.d.ts +2 -0
- package/dist/contacts.js +1 -1
- package/dist/customer-groups-BxSNFHq1.d.cts +706 -0
- package/dist/customer-groups-GlzWhDsi.d.ts +706 -0
- package/dist/customer-groups.cjs +200 -17
- package/dist/customer-groups.cjs.map +1 -1
- package/dist/customer-groups.d.cts +2 -110
- package/dist/customer-groups.d.ts +2 -110
- package/dist/customer-groups.js +1 -1
- package/dist/customer.cjs +61 -1
- package/dist/customer.cjs.map +1 -1
- package/dist/customer.d.cts +48 -1
- package/dist/customer.d.ts +48 -1
- package/dist/customer.js +1 -1
- package/dist/index.cjs +2566 -554
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +455 -12
- package/dist/index.d.ts +455 -12
- package/dist/index.js +687 -342
- package/dist/index.js.map +1 -1
- package/dist/orders.cjs +160 -2
- package/dist/orders.cjs.map +1 -1
- package/dist/orders.d.cts +221 -1
- package/dist/orders.d.ts +221 -1
- package/dist/orders.js +1 -1
- package/dist/payment.cjs +124 -0
- package/dist/payment.cjs.map +1 -1
- package/dist/payment.d.cts +335 -1
- package/dist/payment.d.ts +335 -1
- package/dist/payment.js +1 -1
- package/dist/price.cjs +197 -0
- package/dist/price.cjs.map +1 -1
- package/dist/price.d.cts +577 -1
- package/dist/price.d.ts +577 -1
- package/dist/price.js +1 -1
- package/dist/product.cjs +190 -0
- package/dist/product.cjs.map +1 -1
- package/dist/product.d.cts +503 -1
- package/dist/product.d.ts +503 -1
- package/dist/product.js +1 -1
- package/dist/segment.cjs +179 -0
- package/dist/segment.cjs.map +1 -1
- package/dist/segment.d.cts +181 -1
- package/dist/segment.d.ts +181 -1
- package/dist/segment.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-36T4YPQJ.js +0 -106
- package/dist/chunk-36T4YPQJ.js.map +0 -1
- package/dist/chunk-3BTPX7IJ.js +0 -41
- package/dist/chunk-3BTPX7IJ.js.map +0 -1
- package/dist/chunk-44F52KRE.js.map +0 -1
- package/dist/chunk-A3LYJUD4.js +0 -108
- package/dist/chunk-A3LYJUD4.js.map +0 -1
- package/dist/chunk-FZMP5NQZ.js +0 -173
- package/dist/chunk-FZMP5NQZ.js.map +0 -1
- package/dist/chunk-HBEQEIT6.js.map +0 -1
- package/dist/chunk-HD5RBCM6.js.map +0 -1
- package/dist/chunk-HUEMYP2L.js +0 -193
- package/dist/chunk-HUEMYP2L.js.map +0 -1
- package/dist/chunk-IVCCGJN5.js +0 -63
- package/dist/chunk-IVCCGJN5.js.map +0 -1
- package/dist/chunk-NE7MIZPR.js +0 -144
- package/dist/chunk-NE7MIZPR.js.map +0 -1
- package/dist/chunk-PYLWZZC4.js.map +0 -1
- package/dist/chunk-ZNLHLFQM.js +0 -160
- package/dist/chunk-ZNLHLFQM.js.map +0 -1
package/dist/orders.d.ts
CHANGED
|
@@ -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
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;
|
|
@@ -88,6 +89,129 @@ var PaymentGatewayService = class {
|
|
|
88
89
|
body: input
|
|
89
90
|
});
|
|
90
91
|
}
|
|
92
|
+
/** Retrieves a single frontend payment mode by id. Defaults to anonymous (no scope required). */
|
|
93
|
+
async getMode(id, authCtx = ANON) {
|
|
94
|
+
return this.ctx.http.request({
|
|
95
|
+
method: "GET",
|
|
96
|
+
path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/frontend/${id}`,
|
|
97
|
+
auth: authCtx
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/** Initializes a payment from the frontend. Defaults to anonymous (no scope required). */
|
|
101
|
+
async initialize(input, authCtx = ANON) {
|
|
102
|
+
return this.ctx.http.request({
|
|
103
|
+
method: "POST",
|
|
104
|
+
path: `/payment-gateway/${this.ctx.tenant}/payment/frontend/initialize`,
|
|
105
|
+
auth: authCtx,
|
|
106
|
+
body: input
|
|
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
|
+
};
|
|
91
215
|
};
|
|
92
216
|
|
|
93
217
|
exports.PaymentGatewayService = PaymentGatewayService;
|
package/dist/payment.cjs.map
CHANGED
|
@@ -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":"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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} 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/** 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"]}
|
|
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"]}
|