@sakeetech/viva-payments-core 0.2.1

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 (203) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +413 -0
  3. package/dist/auth/http.d.ts +44 -0
  4. package/dist/auth/http.d.ts.map +1 -0
  5. package/dist/auth/http.js +80 -0
  6. package/dist/auth/http.js.map +1 -0
  7. package/dist/auth/index.d.ts +19 -0
  8. package/dist/auth/index.d.ts.map +1 -0
  9. package/dist/auth/index.js +18 -0
  10. package/dist/auth/index.js.map +1 -0
  11. package/dist/auth/oauth2-strategy.d.ts +117 -0
  12. package/dist/auth/oauth2-strategy.d.ts.map +1 -0
  13. package/dist/auth/oauth2-strategy.js +217 -0
  14. package/dist/auth/oauth2-strategy.js.map +1 -0
  15. package/dist/auth/reseller-strategy.d.ts +65 -0
  16. package/dist/auth/reseller-strategy.d.ts.map +1 -0
  17. package/dist/auth/reseller-strategy.js +68 -0
  18. package/dist/auth/reseller-strategy.js.map +1 -0
  19. package/dist/auth/single-flight.d.ts +81 -0
  20. package/dist/auth/single-flight.d.ts.map +1 -0
  21. package/dist/auth/single-flight.js +160 -0
  22. package/dist/auth/single-flight.js.map +1 -0
  23. package/dist/auth/token-cache.d.ts +50 -0
  24. package/dist/auth/token-cache.d.ts.map +1 -0
  25. package/dist/auth/token-cache.js +59 -0
  26. package/dist/auth/token-cache.js.map +1 -0
  27. package/dist/errors/api-error.d.ts +15 -0
  28. package/dist/errors/api-error.d.ts.map +1 -0
  29. package/dist/errors/api-error.js +18 -0
  30. package/dist/errors/api-error.js.map +1 -0
  31. package/dist/errors/auth-error.d.ts +14 -0
  32. package/dist/errors/auth-error.d.ts.map +1 -0
  33. package/dist/errors/auth-error.js +17 -0
  34. package/dist/errors/auth-error.js.map +1 -0
  35. package/dist/errors/base.d.ts +59 -0
  36. package/dist/errors/base.d.ts.map +1 -0
  37. package/dist/errors/base.js +51 -0
  38. package/dist/errors/base.js.map +1 -0
  39. package/dist/errors/index.d.ts +18 -0
  40. package/dist/errors/index.d.ts.map +1 -0
  41. package/dist/errors/index.js +16 -0
  42. package/dist/errors/index.js.map +1 -0
  43. package/dist/errors/mode-mismatch-error.d.ts +19 -0
  44. package/dist/errors/mode-mismatch-error.d.ts.map +1 -0
  45. package/dist/errors/mode-mismatch-error.js +22 -0
  46. package/dist/errors/mode-mismatch-error.js.map +1 -0
  47. package/dist/errors/rate-limit-error.d.ts +20 -0
  48. package/dist/errors/rate-limit-error.d.ts.map +1 -0
  49. package/dist/errors/rate-limit-error.js +20 -0
  50. package/dist/errors/rate-limit-error.js.map +1 -0
  51. package/dist/errors/validation-error.d.ts +14 -0
  52. package/dist/errors/validation-error.d.ts.map +1 -0
  53. package/dist/errors/validation-error.js +17 -0
  54. package/dist/errors/validation-error.js.map +1 -0
  55. package/dist/errors/webhook-error.d.ts +14 -0
  56. package/dist/errors/webhook-error.d.ts.map +1 -0
  57. package/dist/errors/webhook-error.js +17 -0
  58. package/dist/errors/webhook-error.js.map +1 -0
  59. package/dist/index.d.ts +15 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +15 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/isv/accounts.d.ts +38 -0
  64. package/dist/isv/accounts.d.ts.map +1 -0
  65. package/dist/isv/accounts.js +60 -0
  66. package/dist/isv/accounts.js.map +1 -0
  67. package/dist/isv/client.d.ts +187 -0
  68. package/dist/isv/client.d.ts.map +1 -0
  69. package/dist/isv/client.js +465 -0
  70. package/dist/isv/client.js.map +1 -0
  71. package/dist/isv/index.d.ts +52 -0
  72. package/dist/isv/index.d.ts.map +1 -0
  73. package/dist/isv/index.js +53 -0
  74. package/dist/isv/index.js.map +1 -0
  75. package/dist/isv/legacy-basic-client.d.ts +122 -0
  76. package/dist/isv/legacy-basic-client.d.ts.map +1 -0
  77. package/dist/isv/legacy-basic-client.js +281 -0
  78. package/dist/isv/legacy-basic-client.js.map +1 -0
  79. package/dist/isv/payments.d.ts +199 -0
  80. package/dist/isv/payments.d.ts.map +1 -0
  81. package/dist/isv/payments.js +385 -0
  82. package/dist/isv/payments.js.map +1 -0
  83. package/dist/isv/sources.d.ts +80 -0
  84. package/dist/isv/sources.d.ts.map +1 -0
  85. package/dist/isv/sources.js +112 -0
  86. package/dist/isv/sources.js.map +1 -0
  87. package/dist/isv/webhooks-api.d.ts +48 -0
  88. package/dist/isv/webhooks-api.d.ts.map +1 -0
  89. package/dist/isv/webhooks-api.js +66 -0
  90. package/dist/isv/webhooks-api.js.map +1 -0
  91. package/dist/legacy/client.d.ts +199 -0
  92. package/dist/legacy/client.d.ts.map +1 -0
  93. package/dist/legacy/client.js +351 -0
  94. package/dist/legacy/client.js.map +1 -0
  95. package/dist/legacy/index.d.ts +15 -0
  96. package/dist/legacy/index.d.ts.map +1 -0
  97. package/dist/legacy/index.js +14 -0
  98. package/dist/legacy/index.js.map +1 -0
  99. package/dist/observability/context.d.ts +30 -0
  100. package/dist/observability/context.d.ts.map +1 -0
  101. package/dist/observability/context.js +40 -0
  102. package/dist/observability/context.js.map +1 -0
  103. package/dist/observability/index.d.ts +15 -0
  104. package/dist/observability/index.d.ts.map +1 -0
  105. package/dist/observability/index.js +11 -0
  106. package/dist/observability/index.js.map +1 -0
  107. package/dist/observability/logger.d.ts +81 -0
  108. package/dist/observability/logger.d.ts.map +1 -0
  109. package/dist/observability/logger.js +127 -0
  110. package/dist/observability/logger.js.map +1 -0
  111. package/dist/observability/metrics.d.ts +37 -0
  112. package/dist/observability/metrics.d.ts.map +1 -0
  113. package/dist/observability/metrics.js +40 -0
  114. package/dist/observability/metrics.js.map +1 -0
  115. package/dist/observability/redact.d.ts +21 -0
  116. package/dist/observability/redact.d.ts.map +1 -0
  117. package/dist/observability/redact.js +72 -0
  118. package/dist/observability/redact.js.map +1 -0
  119. package/dist/observability/tracer.d.ts +25 -0
  120. package/dist/observability/tracer.d.ts.map +1 -0
  121. package/dist/observability/tracer.js +18 -0
  122. package/dist/observability/tracer.js.map +1 -0
  123. package/dist/payments/client.d.ts +247 -0
  124. package/dist/payments/client.d.ts.map +1 -0
  125. package/dist/payments/client.js +488 -0
  126. package/dist/payments/client.js.map +1 -0
  127. package/dist/payments/index.d.ts +14 -0
  128. package/dist/payments/index.d.ts.map +1 -0
  129. package/dist/payments/index.js +13 -0
  130. package/dist/payments/index.js.map +1 -0
  131. package/dist/refunds/fast-refund-client.d.ts +128 -0
  132. package/dist/refunds/fast-refund-client.d.ts.map +1 -0
  133. package/dist/refunds/fast-refund-client.js +138 -0
  134. package/dist/refunds/fast-refund-client.js.map +1 -0
  135. package/dist/refunds/index.d.ts +19 -0
  136. package/dist/refunds/index.d.ts.map +1 -0
  137. package/dist/refunds/index.js +17 -0
  138. package/dist/refunds/index.js.map +1 -0
  139. package/dist/refunds/strategy.d.ts +78 -0
  140. package/dist/refunds/strategy.d.ts.map +1 -0
  141. package/dist/refunds/strategy.js +75 -0
  142. package/dist/refunds/strategy.js.map +1 -0
  143. package/dist/types/auth.d.ts +80 -0
  144. package/dist/types/auth.d.ts.map +1 -0
  145. package/dist/types/auth.js +12 -0
  146. package/dist/types/auth.js.map +1 -0
  147. package/dist/types/card-types.d.ts +48 -0
  148. package/dist/types/card-types.d.ts.map +1 -0
  149. package/dist/types/card-types.js +62 -0
  150. package/dist/types/card-types.js.map +1 -0
  151. package/dist/types/common.d.ts +160 -0
  152. package/dist/types/common.d.ts.map +1 -0
  153. package/dist/types/common.js +70 -0
  154. package/dist/types/common.js.map +1 -0
  155. package/dist/types/index.d.ts +21 -0
  156. package/dist/types/index.d.ts.map +1 -0
  157. package/dist/types/index.js +21 -0
  158. package/dist/types/index.js.map +1 -0
  159. package/dist/types/isv-accounts.d.ts +109 -0
  160. package/dist/types/isv-accounts.d.ts.map +1 -0
  161. package/dist/types/isv-accounts.js +22 -0
  162. package/dist/types/isv-accounts.js.map +1 -0
  163. package/dist/types/isv-payments.d.ts +262 -0
  164. package/dist/types/isv-payments.d.ts.map +1 -0
  165. package/dist/types/isv-payments.js +19 -0
  166. package/dist/types/isv-payments.js.map +1 -0
  167. package/dist/types/status.d.ts +125 -0
  168. package/dist/types/status.d.ts.map +1 -0
  169. package/dist/types/status.js +19 -0
  170. package/dist/types/status.js.map +1 -0
  171. package/dist/types/webhook-events.d.ts +447 -0
  172. package/dist/types/webhook-events.d.ts.map +1 -0
  173. package/dist/types/webhook-events.js +76 -0
  174. package/dist/types/webhook-events.js.map +1 -0
  175. package/dist/webhooks/challenge-response.d.ts +28 -0
  176. package/dist/webhooks/challenge-response.d.ts.map +1 -0
  177. package/dist/webhooks/challenge-response.js +35 -0
  178. package/dist/webhooks/challenge-response.js.map +1 -0
  179. package/dist/webhooks/event-types.d.ts +44 -0
  180. package/dist/webhooks/event-types.d.ts.map +1 -0
  181. package/dist/webhooks/event-types.js +50 -0
  182. package/dist/webhooks/event-types.js.map +1 -0
  183. package/dist/webhooks/extract-client-ip.d.ts +40 -0
  184. package/dist/webhooks/extract-client-ip.d.ts.map +1 -0
  185. package/dist/webhooks/extract-client-ip.js +72 -0
  186. package/dist/webhooks/extract-client-ip.js.map +1 -0
  187. package/dist/webhooks/hmac-verify.d.ts +38 -0
  188. package/dist/webhooks/hmac-verify.d.ts.map +1 -0
  189. package/dist/webhooks/hmac-verify.js +92 -0
  190. package/dist/webhooks/hmac-verify.js.map +1 -0
  191. package/dist/webhooks/index.d.ts +19 -0
  192. package/dist/webhooks/index.d.ts.map +1 -0
  193. package/dist/webhooks/index.js +19 -0
  194. package/dist/webhooks/index.js.map +1 -0
  195. package/dist/webhooks/ip-allowlist.d.ts +59 -0
  196. package/dist/webhooks/ip-allowlist.d.ts.map +1 -0
  197. package/dist/webhooks/ip-allowlist.js +147 -0
  198. package/dist/webhooks/ip-allowlist.js.map +1 -0
  199. package/dist/webhooks/status-lattice.d.ts +72 -0
  200. package/dist/webhooks/status-lattice.d.ts.map +1 -0
  201. package/dist/webhooks/status-lattice.js +208 -0
  202. package/dist/webhooks/status-lattice.js.map +1 -0
  203. package/package.json +85 -0
@@ -0,0 +1,122 @@
1
+ /**
2
+ * LegacyBasicClient — HTTP client for Viva legacy API endpoints (Basic auth).
3
+ *
4
+ * Viva exposes a subset of endpoints ONLY on their legacy host
5
+ * (`demo.vivapayments.com` / `www.vivapayments.com`) behind Basic auth
6
+ * (MerchantId:ApiKey). These endpoints are NOT available on the v2/OAuth2
7
+ * surface (`demo-api.vivapayments.com`).
8
+ *
9
+ * Verified against Viva sandbox 2026-04-25:
10
+ * - Refund: `POST /api/transactions/{transactionId}` on legacy host with Basic auth.
11
+ * - Body: `application/x-www-form-urlencoded` — `Amount={minor}&SourceCode={code}`.
12
+ * - Response: PascalCase JSON `{StatusId, Amount, TransactionId, ...}`.
13
+ * - `POST /checkout/v2/transactions/{id}` (v2) returns 405 → NOT valid.
14
+ *
15
+ * Design:
16
+ * - NOT a fallback. This is the PRIMARY (and only) client for legacy endpoints.
17
+ * - Same retry / error-shape conventions as IsvHttpClient.
18
+ * - Form-encoded body builder via URLSearchParams (built-in, zero deps).
19
+ * - Tracing headers (`x-viva-correlationid`, `x-viva-eventid`) extracted and
20
+ * attached to errors and successful responses.
21
+ *
22
+ * @see references/viva-docs/md/tut-create-recurring-payment.txt:288
23
+ * @see references/viva-docs/md/merchant-id-and-api-key.txt:1
24
+ */
25
+ import type { Dispatcher } from 'undici';
26
+ import type { VivaEnvironment } from '../types/index.js';
27
+ import type { MetricsHook } from '../observability/index.js';
28
+ export interface LegacyBasicClientConfig {
29
+ /**
30
+ * Viva environment selector. Determines legacy host.
31
+ * demo → https://demo.vivapayments.com
32
+ * production → https://www.vivapayments.com
33
+ */
34
+ environment: VivaEnvironment;
35
+ /**
36
+ * Merchant ID (UUID). Used as the Basic-auth username.
37
+ * @see references/viva-docs/md/merchant-id-and-api-key.txt:1
38
+ */
39
+ merchantId: string;
40
+ /**
41
+ * API Key. Used as the Basic-auth password.
42
+ * @see references/viva-docs/md/merchant-id-and-api-key.txt:1
43
+ */
44
+ apiKey: string;
45
+ /** Override undici dispatcher for tests (e.g. MockAgent). */
46
+ dispatcher?: Dispatcher;
47
+ /** Override global fetch. Defaults to undici fetch when dispatcher is provided. */
48
+ fetchImpl?: typeof fetch;
49
+ /** Clock override for tests. Defaults to Date.now. */
50
+ now?: () => number;
51
+ /**
52
+ * Exponential backoff schedule in milliseconds.
53
+ * Default: [500, 1500, 4500].
54
+ */
55
+ retryBackoffsMs?: number[];
56
+ /** Jitter ratio applied to each backoff (±ratio * backoff). Default: 0.2. */
57
+ jitterRatio?: number;
58
+ /** Default per-request timeout in milliseconds. Default: 30_000. */
59
+ defaultTimeoutMs?: number;
60
+ /** Optional metrics hook. */
61
+ metrics?: MetricsHook;
62
+ }
63
+ export interface LegacyRequestOptions {
64
+ method: 'GET' | 'POST';
65
+ path: string;
66
+ /** Form-encoded key-value pairs (values coerced to string). */
67
+ formBody?: Record<string, string | number | bigint | undefined>;
68
+ /**
69
+ * When true: retries on 429 and 5xx per the backoff schedule.
70
+ * When false: only retries on connection-level errors.
71
+ */
72
+ idempotent: boolean;
73
+ timeoutMs?: number;
74
+ /** Endpoint label for metrics. */
75
+ endpoint?: string;
76
+ }
77
+ /**
78
+ * Wrapper returned from successful legacy API calls.
79
+ * Includes Viva tracing headers for observability.
80
+ */
81
+ export interface LegacyApiResult<T> {
82
+ data: T;
83
+ /** `x-viva-correlationid` header value (e.g. "26-115-EDAA55BC"). */
84
+ vivaCorrelationId: string | undefined;
85
+ /** `x-viva-eventid` header value (e.g. "0"). */
86
+ vivaEventId: string | undefined;
87
+ }
88
+ /**
89
+ * HTTP client for Viva legacy API endpoints (Basic auth + legacy host).
90
+ *
91
+ * Instantiate once per (merchantId + apiKey + environment) tuple.
92
+ * Thread-safe; no shared mutable state.
93
+ */
94
+ export declare class LegacyBasicClient {
95
+ private readonly legacyBaseUrl;
96
+ private readonly authorizationHeader;
97
+ private readonly dispatcher;
98
+ private readonly fetchImpl;
99
+ private readonly now;
100
+ private readonly backoffsMs;
101
+ private readonly jitterRatio;
102
+ private readonly defaultTimeoutMs;
103
+ private readonly metrics;
104
+ constructor(config: LegacyBasicClientConfig);
105
+ /**
106
+ * Execute a request against the Viva legacy API.
107
+ *
108
+ * Returns a `LegacyApiResult<T>` wrapping both the parsed response and the
109
+ * Viva tracing headers (`x-viva-correlationid`, `x-viva-eventid`).
110
+ *
111
+ * @see references/viva-docs/md/tut-create-recurring-payment.txt:288
112
+ */
113
+ request<T>(opts: LegacyRequestOptions): Promise<LegacyApiResult<T>>;
114
+ private _executeWithRetry;
115
+ private _attempt;
116
+ private _backoffMs;
117
+ private _retryAfterMs;
118
+ private _parseErrorBody;
119
+ private _isConnectionError;
120
+ private _sleep;
121
+ }
122
+ //# sourceMappingURL=legacy-basic-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-basic-client.d.ts","sourceRoot":"","sources":["../../src/isv/legacy-basic-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAGzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAO7D,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,WAAW,EAAE,eAAe,CAAC;IAE7B;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf,6DAA6D;IAC7D,UAAU,CAAC,EAAE,UAAU,CAAC;IAExB,mFAAmF;IACnF,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAEzB,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAEnB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAE3B,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,6BAA6B;IAC7B,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;IAChE;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,IAAI,EAAE,CAAC,CAAC;IACR,oEAAoE;IACpE,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,gDAAgD;IAChD,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AA0BD;;;;;GAKG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;gBAE1B,MAAM,EAAE,uBAAuB;IAuB3C;;;;;;;OAOG;IACG,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;YAa3D,iBAAiB;YA0EjB,QAAQ;IA+DtB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,eAAe;IA2BvB,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,MAAM;CAGf"}
@@ -0,0 +1,281 @@
1
+ /**
2
+ * LegacyBasicClient — HTTP client for Viva legacy API endpoints (Basic auth).
3
+ *
4
+ * Viva exposes a subset of endpoints ONLY on their legacy host
5
+ * (`demo.vivapayments.com` / `www.vivapayments.com`) behind Basic auth
6
+ * (MerchantId:ApiKey). These endpoints are NOT available on the v2/OAuth2
7
+ * surface (`demo-api.vivapayments.com`).
8
+ *
9
+ * Verified against Viva sandbox 2026-04-25:
10
+ * - Refund: `POST /api/transactions/{transactionId}` on legacy host with Basic auth.
11
+ * - Body: `application/x-www-form-urlencoded` — `Amount={minor}&SourceCode={code}`.
12
+ * - Response: PascalCase JSON `{StatusId, Amount, TransactionId, ...}`.
13
+ * - `POST /checkout/v2/transactions/{id}` (v2) returns 405 → NOT valid.
14
+ *
15
+ * Design:
16
+ * - NOT a fallback. This is the PRIMARY (and only) client for legacy endpoints.
17
+ * - Same retry / error-shape conventions as IsvHttpClient.
18
+ * - Form-encoded body builder via URLSearchParams (built-in, zero deps).
19
+ * - Tracing headers (`x-viva-correlationid`, `x-viva-eventid`) extracted and
20
+ * attached to errors and successful responses.
21
+ *
22
+ * @see references/viva-docs/md/tut-create-recurring-payment.txt:288
23
+ * @see references/viva-docs/md/merchant-id-and-api-key.txt:1
24
+ */
25
+ import { fetch as undiciFetch } from 'undici';
26
+ import { LEGACY_HOST } from '../types/index.js';
27
+ import { VivaApiError, VivaAuthError, VivaRateLimitError } from '../errors/index.js';
28
+ import { NoopMetricsHook } from '../observability/index.js';
29
+ // ---------------------------------------------------------------------------
30
+ // Constants
31
+ // ---------------------------------------------------------------------------
32
+ const DEFAULT_BACKOFFS_MS = [500, 1500, 4500];
33
+ const DEFAULT_JITTER_RATIO = 0.2;
34
+ const DEFAULT_TIMEOUT_MS = 30_000;
35
+ const MAX_RETRIES = 3;
36
+ const CONNECTION_ERROR_CODES = new Set([
37
+ 'ECONNRESET',
38
+ 'ENOTFOUND',
39
+ 'ETIMEDOUT',
40
+ 'ECONNREFUSED',
41
+ 'ECONNABORTED',
42
+ 'UND_ERR_CONNECT_TIMEOUT',
43
+ 'UND_ERR_HEADERS_TIMEOUT',
44
+ 'UND_ERR_BODY_TIMEOUT',
45
+ ]);
46
+ // ---------------------------------------------------------------------------
47
+ // LegacyBasicClient
48
+ // ---------------------------------------------------------------------------
49
+ /**
50
+ * HTTP client for Viva legacy API endpoints (Basic auth + legacy host).
51
+ *
52
+ * Instantiate once per (merchantId + apiKey + environment) tuple.
53
+ * Thread-safe; no shared mutable state.
54
+ */
55
+ export class LegacyBasicClient {
56
+ legacyBaseUrl;
57
+ authorizationHeader;
58
+ dispatcher;
59
+ fetchImpl;
60
+ now;
61
+ backoffsMs;
62
+ jitterRatio;
63
+ defaultTimeoutMs;
64
+ metrics;
65
+ constructor(config) {
66
+ this.legacyBaseUrl = LEGACY_HOST[config.environment];
67
+ // Basic auth: Base64(merchantId:apiKey)
68
+ // @see references/viva-docs/md/merchant-id-and-api-key.txt:1
69
+ const encoded = Buffer.from(`${config.merchantId}:${config.apiKey}`).toString('base64');
70
+ this.authorizationHeader = `Basic ${encoded}`;
71
+ this.now = config.now ?? (() => Date.now());
72
+ this.backoffsMs = config.retryBackoffsMs ?? DEFAULT_BACKOFFS_MS;
73
+ this.jitterRatio = config.jitterRatio ?? DEFAULT_JITTER_RATIO;
74
+ this.defaultTimeoutMs = config.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
75
+ this.metrics = config.metrics ?? new NoopMetricsHook();
76
+ if (config.dispatcher) {
77
+ this.dispatcher = config.dispatcher;
78
+ this.fetchImpl = config.fetchImpl ?? undiciFetch;
79
+ }
80
+ else {
81
+ this.dispatcher = undefined;
82
+ this.fetchImpl = config.fetchImpl ?? undiciFetch;
83
+ }
84
+ }
85
+ /**
86
+ * Execute a request against the Viva legacy API.
87
+ *
88
+ * Returns a `LegacyApiResult<T>` wrapping both the parsed response and the
89
+ * Viva tracing headers (`x-viva-correlationid`, `x-viva-eventid`).
90
+ *
91
+ * @see references/viva-docs/md/tut-create-recurring-payment.txt:288
92
+ */
93
+ async request(opts) {
94
+ const endpoint = opts.endpoint ?? `${opts.method} ${opts.path}`;
95
+ return this.metrics.timeAsync('viva_legacy_api_request_duration_seconds', () => this._executeWithRetry(opts), { endpoint });
96
+ }
97
+ // --------------------------------------------------------------------------
98
+ // Private helpers
99
+ // --------------------------------------------------------------------------
100
+ async _executeWithRetry(opts) {
101
+ let attempt = 0;
102
+ // eslint-disable-next-line no-constant-condition
103
+ while (true) {
104
+ const { response, text } = await this._attempt(opts);
105
+ const vivaCorrelationId = response.headers.get('x-viva-correlationid') ?? undefined;
106
+ const vivaEventId = response.headers.get('x-viva-eventid') ?? undefined;
107
+ // --- happy path ---
108
+ if (response.status >= 200 && response.status < 300) {
109
+ if (!text || response.status === 204) {
110
+ return { data: undefined, vivaCorrelationId, vivaEventId };
111
+ }
112
+ const data = JSON.parse(text);
113
+ return { data, vivaCorrelationId, vivaEventId };
114
+ }
115
+ // --- 401 ---
116
+ if (response.status === 401) {
117
+ throw new VivaAuthError({
118
+ message: 'Viva legacy API authentication failed (HTTP 401) — check merchantId and apiKey',
119
+ httpStatus: 401,
120
+ requestId: vivaCorrelationId,
121
+ });
122
+ }
123
+ // --- 429 ---
124
+ if (response.status === 429) {
125
+ if (opts.idempotent && attempt < MAX_RETRIES) {
126
+ const retryAfterMs = this._retryAfterMs(response) ?? this._backoffMs(attempt);
127
+ await this._sleep(retryAfterMs);
128
+ attempt++;
129
+ continue;
130
+ }
131
+ const retryAfterMs = this._retryAfterMs(response);
132
+ throw new VivaRateLimitError({
133
+ message: 'Viva legacy API rate limited (HTTP 429)',
134
+ httpStatus: 429,
135
+ requestId: vivaCorrelationId,
136
+ ...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
137
+ });
138
+ }
139
+ // --- 5xx ---
140
+ if (response.status >= 500) {
141
+ if (opts.idempotent && attempt < MAX_RETRIES) {
142
+ const backoffMs = this._backoffMs(attempt);
143
+ await this._sleep(backoffMs);
144
+ attempt++;
145
+ continue;
146
+ }
147
+ const { vivaCode, vivaMessage } = this._parseErrorBody(text);
148
+ throw new VivaApiError({
149
+ message: vivaMessage ?? `Viva legacy API error (HTTP ${response.status})`,
150
+ httpStatus: response.status,
151
+ vivaCode,
152
+ requestId: vivaCorrelationId,
153
+ });
154
+ }
155
+ // --- other 4xx ---
156
+ {
157
+ const { vivaCode, vivaMessage } = this._parseErrorBody(text);
158
+ throw new VivaApiError({
159
+ message: vivaMessage ?? `Viva legacy API error (HTTP ${response.status})`,
160
+ httpStatus: response.status,
161
+ vivaCode,
162
+ requestId: vivaCorrelationId,
163
+ });
164
+ }
165
+ }
166
+ }
167
+ async _attempt(opts) {
168
+ const url = `${this.legacyBaseUrl}${opts.path}`;
169
+ const timeoutMs = opts.timeoutMs ?? this.defaultTimeoutMs;
170
+ const controller = new AbortController();
171
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
172
+ const headers = {
173
+ Authorization: this.authorizationHeader,
174
+ Accept: 'application/json',
175
+ };
176
+ const fetchOptions = {
177
+ method: opts.method,
178
+ headers,
179
+ signal: controller.signal,
180
+ };
181
+ // Form-encode the body (Viva legacy API uses application/x-www-form-urlencoded)
182
+ if (opts.formBody && Object.keys(opts.formBody).length > 0) {
183
+ const params = new URLSearchParams();
184
+ for (const [k, v] of Object.entries(opts.formBody)) {
185
+ if (v !== undefined) {
186
+ params.set(k, String(v));
187
+ }
188
+ }
189
+ fetchOptions.body = params.toString();
190
+ headers['Content-Type'] = 'application/x-www-form-urlencoded';
191
+ }
192
+ if (this.dispatcher) {
193
+ fetchOptions['dispatcher'] = this.dispatcher;
194
+ }
195
+ let maxConnectionRetries = opts.idempotent ? 0 : 1;
196
+ let connAttempt = 0;
197
+ // eslint-disable-next-line no-constant-condition
198
+ while (true) {
199
+ try {
200
+ const response = await this.fetchImpl(url, fetchOptions);
201
+ clearTimeout(timeoutId);
202
+ const text = await response.text();
203
+ return { response, text };
204
+ }
205
+ catch (err) {
206
+ clearTimeout(timeoutId);
207
+ const isConnectionError = this._isConnectionError(err);
208
+ const isAbort = err instanceof Error && err.name === 'AbortError';
209
+ if ((isConnectionError || isAbort) && !opts.idempotent && connAttempt < maxConnectionRetries) {
210
+ connAttempt++;
211
+ continue;
212
+ }
213
+ throw new VivaApiError({
214
+ message: isAbort
215
+ ? `Viva legacy API request timed out after ${timeoutMs}ms`
216
+ : `Viva legacy API network error: ${err instanceof Error ? err.message : String(err)}`,
217
+ cause: err,
218
+ });
219
+ }
220
+ }
221
+ }
222
+ _backoffMs(attempt) {
223
+ const base = this.backoffsMs[attempt] ?? this.backoffsMs[this.backoffsMs.length - 1] ?? 500;
224
+ const jitter = base * this.jitterRatio * (Math.random() * 2 - 1);
225
+ return Math.max(0, Math.round(base + jitter));
226
+ }
227
+ _retryAfterMs(response) {
228
+ const header = response.headers.get('Retry-After');
229
+ if (!header)
230
+ return undefined;
231
+ const seconds = parseFloat(header);
232
+ if (!isNaN(seconds))
233
+ return Math.max(0, Math.round(seconds * 1000));
234
+ const date = new Date(header).getTime();
235
+ if (!isNaN(date))
236
+ return Math.max(0, date - this.now());
237
+ return undefined;
238
+ }
239
+ _parseErrorBody(text) {
240
+ if (!text)
241
+ return {};
242
+ try {
243
+ const parsed = JSON.parse(text);
244
+ const vivaCode = typeof parsed['ErrorCode'] === 'number'
245
+ ? String(parsed['ErrorCode'])
246
+ : typeof parsed['errorCode'] === 'number'
247
+ ? String(parsed['errorCode'])
248
+ : typeof parsed['error'] === 'string'
249
+ ? parsed['error']
250
+ : undefined;
251
+ const vivaMessage = typeof parsed['Message'] === 'string'
252
+ ? parsed['Message']
253
+ : typeof parsed['message'] === 'string'
254
+ ? parsed['message']
255
+ : undefined;
256
+ const result = {};
257
+ if (vivaCode !== undefined)
258
+ result.vivaCode = vivaCode;
259
+ if (vivaMessage !== undefined)
260
+ result.vivaMessage = vivaMessage;
261
+ return result;
262
+ }
263
+ catch {
264
+ return {};
265
+ }
266
+ }
267
+ _isConnectionError(err) {
268
+ if (!(err instanceof Error))
269
+ return false;
270
+ const code = err.code;
271
+ if (code && CONNECTION_ERROR_CODES.has(code))
272
+ return true;
273
+ if (err.message.includes('ECONNRESET') || err.message.includes('ENOTFOUND'))
274
+ return true;
275
+ return false;
276
+ }
277
+ _sleep(ms) {
278
+ return new Promise((resolve) => setTimeout(resolve, ms));
279
+ }
280
+ }
281
+ //# sourceMappingURL=legacy-basic-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-basic-client.js","sourceRoot":"","sources":["../../src/isv/legacy-basic-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,199 @@
1
+ /**
2
+ * IsvPayments — ISV Payment API methods.
3
+ *
4
+ * Covers:
5
+ * - createOrder → POST /checkout/v2/orders?merchantId={merchantId} (OAuth2)
6
+ * - retrieveTransaction → GET /checkout/v2/transactions/{transactionId} (OAuth2)
7
+ * - refundPayment → POST /api/transactions/{transactionId} (Legacy/Basic)
8
+ * - cancelOrder → DELETE /checkout/v2/orders/{orderCode} (OAuth2)
9
+ *
10
+ * All methods validate inputs locally before making HTTP calls.
11
+ * Amounts are in integer minor units (bigint) per plan P15.
12
+ *
13
+ * Idempotency: createOrder and refundPayment are non-idempotent (idempotent: false).
14
+ * retrieveTransaction and cancelOrder are idempotent.
15
+ *
16
+ * --- Refund path (F1 — probe-verified 2026-04-25) ---
17
+ * Viva returns 405 on `POST /checkout/v2/transactions/{id}` (v2/OAuth2 path).
18
+ * The ONLY working refund path is `POST /api/transactions/{transactionId}` on the
19
+ * LEGACY HOST (`demo.vivapayments.com` / `www.vivapayments.com`) with Basic auth
20
+ * (MerchantId + ApiKey). The 401-fallback design is NOT applicable here — Viva
21
+ * returns 405 (not 401) on the v2 path, so no fallback would ever trigger.
22
+ * refundPayment now calls `legacyClient` DIRECTLY without any v2 attempt.
23
+ *
24
+ * @see references/viva-docs/md/tut-create-recurring-payment.txt:288 (legacy refund endpoint)
25
+ *
26
+ * --- retrieveTransaction / cancelOrder fallback (D15 — kept defensive) ---
27
+ * The optional `secondaryClient` is a fallback for retrieveTransaction and
28
+ * cancelOrder. When the primary OAuth2 client returns 401 (after force-refresh),
29
+ * the call retries once with the secondary client. A 401 from secondary surfaces
30
+ * as VivaAuthError — no further retry.
31
+ * Note: cancelOrder is unverified against live sandbox as of 2026-04-25 probe;
32
+ * kept on OAuth2 + 401-fallback path defensively.
33
+ *
34
+ * --- Idempotency-Key header (F2 — probe-verified 2026-04-25) ---
35
+ * The `Idempotency-Key` header is sent on createOrder but Viva does NOT appear
36
+ * to deduplicate server-side (same key returned two different orderCodes in probe).
37
+ * Local dedup via `viva_transaction` row is the authoritative dedup mechanism.
38
+ * Header is retained for forward-compat (zero cost, may be honoured in future).
39
+ *
40
+ * @see references/viva-docs/md/payment-isv-api.txt:1
41
+ * @see references/viva-docs/md/webhooks-for-payments.txt:248 (retrieve before update)
42
+ * @see references/viva-docs/md/isv-partner-program.txt:104 (ISV overview)
43
+ * @see references/viva-docs/md/isv-credentials.txt:107 (reseller credentials)
44
+ * @see docs/plans/vendure-plugin-v0.md §D15 (reseller fallback scope)
45
+ */
46
+ import type { IsvHttpClient } from './client.js';
47
+ import type { LegacyBasicClient } from './legacy-basic-client.js';
48
+ import type { CreateOrderRequest, CreateOrderResponse, RetrieveTransactionResponse, RefundResponse, CancelOrderResponse, MerchantId, TransactionId, OrderCode, MinorUnits } from '../types/index.js';
49
+ /**
50
+ * ISV Payment API client.
51
+ *
52
+ * Constructed with an IsvHttpClient instance shared across all ISV modules.
53
+ * Each method scopes its request to a specific merchant via the merchantId
54
+ * query parameter per the ISV integration model.
55
+ *
56
+ * - `client`: primary OAuth2 client for createOrder, retrieveTransaction, cancelOrder.
57
+ * - `secondaryClient`: optional 401-fallback for retrieveTransaction and cancelOrder.
58
+ * When undefined, 401 errors propagate normally.
59
+ * - `legacyClient`: REQUIRED for refundPayment. Calls `POST /api/transactions/{id}`
60
+ * on the legacy host with Basic auth (MerchantId + ApiKey). If absent, refundPayment
61
+ * throws `VIVA_REFUND_REJECTED` with a config-missing message.
62
+ *
63
+ * Probe-verified 2026-04-25: refund MUST go through legacy client — v2/OAuth2 path
64
+ * returns 405. cancelOrder is unverified but kept on v2/OAuth2 + 401-fallback defensively.
65
+ *
66
+ * @see references/viva-docs/md/tut-create-recurring-payment.txt:288 (legacy refund path)
67
+ * @see references/viva-docs/md/payment-isv-api.txt:1
68
+ * @see references/viva-docs/md/isv-partner-program.txt:61 (P1: merchant scoping)
69
+ * @see references/viva-docs/md/isv-credentials.txt:107 (reseller credentials)
70
+ * @see docs/plans/vendure-plugin-v0.md §D15 (reseller fallback)
71
+ */
72
+ export declare class IsvPayments {
73
+ private readonly client;
74
+ private readonly secondaryClient?;
75
+ private readonly legacyClient?;
76
+ constructor(client: IsvHttpClient, secondaryClient?: IsvHttpClient | undefined, legacyClient?: LegacyBasicClient | undefined);
77
+ /**
78
+ * Create a payment order for a specific ISV merchant.
79
+ *
80
+ * Sends a POST /checkout/v2/orders?merchantId={merchantId} request.
81
+ * The `Idempotency-Key` header is sent on every call but Viva does NOT appear
82
+ * to deduplicate server-side as of 2026-04-25 (probe: same key → two different
83
+ * orderCodes). Local dedup via `viva_transaction` row is the authoritative
84
+ * dedup mechanism. Header retained for forward-compat only.
85
+ *
86
+ * Input validation (throws VivaValidationError before HTTP call):
87
+ * - amountMinor must be > 0 (per plan P15)
88
+ * - currencyCode must be a valid 3-digit numeric string (per plan P15)
89
+ *
90
+ * Non-idempotent: does not retry on 4xx/5xx (only connection-level errors).
91
+ *
92
+ * @see references/viva-docs/md/payment-isv-api.txt:1
93
+ * @see references/viva-docs/md/isv-partner-program.txt:61 (P14 idempotency)
94
+ * @see references/viva-docs/md/isv-partner-program.txt:83 (P15 amounts)
95
+ */
96
+ createOrder(req: CreateOrderRequest, opts: {
97
+ merchantId: MerchantId;
98
+ idempotencyKey: string;
99
+ }): Promise<CreateOrderResponse>;
100
+ /**
101
+ * Retrieve transaction details for a specific ISV merchant transaction.
102
+ *
103
+ * Per Viva docs, this SHOULD be called before updating any local transaction
104
+ * status on receipt of a webhook. Validates orderCode and statusId from Viva.
105
+ *
106
+ * Idempotent: safe to retry on 429 and 5xx.
107
+ *
108
+ * Path + auth verified 2026-04-25 (F3):
109
+ * `GET /checkout/v2/transactions/{transactionId}` with OAuth2 Bearer → correct.
110
+ * 404 error envelope shape: `{"status": 404, "message": null, "eventId": "0"}`.
111
+ * Note: `message` can be null — handle defensively.
112
+ *
113
+ * 401 → reseller fallback (D15) — kept DEFENSIVE:
114
+ * If the primary OAuth2 client receives a 401 and secondaryClient is configured,
115
+ * the request is retried once with the secondary (Reseller basic-auth) client.
116
+ * A 401 from the secondary surfaces immediately as VivaAuthError — no further retry.
117
+ * The primary path is verified working; fallback is defensive for edge-case tenants.
118
+ *
119
+ * @see references/viva-docs/md/webhooks-for-payments.txt:248 (retrieve before update)
120
+ * @see references/viva-docs/md/payment-isv-api.txt:1
121
+ * @see references/viva-docs/md/isv-credentials.txt:107 (reseller basic-auth)
122
+ * @see docs/plans/vendure-plugin-v0.md §D15 (reseller fallback scope)
123
+ */
124
+ retrieveTransaction(transactionId: TransactionId, opts?: {
125
+ merchantId?: MerchantId;
126
+ }): Promise<RetrieveTransactionResponse>;
127
+ /**
128
+ * Issue a full or partial refund for a captured transaction.
129
+ *
130
+ * IMPORTANT — probe-verified 2026-04-25 (F1):
131
+ * Viva returns 405 Method Not Allowed on `POST /checkout/v2/transactions/{id}`
132
+ * (the v2/OAuth2 path). The ONLY working refund path is:
133
+ * `POST /api/transactions/{transactionId}` on the LEGACY HOST
134
+ * (`demo.vivapayments.com` / `www.vivapayments.com`) with Basic auth
135
+ * (MerchantId:ApiKey) and form-urlencoded body.
136
+ *
137
+ * This method calls `legacyClient` DIRECTLY. The 401-fallback (D15) does NOT
138
+ * apply here — Viva returns 405 (not 401) on the v2 path, so no fallback
139
+ * could ever trigger.
140
+ *
141
+ * If `legacyClient` is not configured (absent from constructor), this method
142
+ * throws VivaValidationError with code VIVA_REFUND_REJECTED indicating that
143
+ * the basic-auth credentials are required and missing.
144
+ *
145
+ * Per plan P18:
146
+ * - Full refund: omit amountMinor (no Amount in form body per Viva convention).
147
+ * - Partial refund: provide amountMinor > 0 (Amount sent in form body).
148
+ * - ISV fee reverses automatically on refund (per isv-partner-program.txt:296).
149
+ * - Failed refund (4xx) surfaces as VivaApiError; payment NOT marked refunded.
150
+ *
151
+ * Non-idempotent: does not retry on 4xx/5xx.
152
+ *
153
+ * Viva response fields (PascalCase, mapped to camelCase):
154
+ * StatusId → statusId, Amount → amount, TransactionId → transactionId.
155
+ *
156
+ * @see references/viva-docs/md/tut-create-recurring-payment.txt:288 (legacy path + basic auth)
157
+ * @see references/viva-docs/md/isv-partner-program.txt:296 (ISV fee reversal on refund)
158
+ * @see references/viva-docs/md/merchant-id-and-api-key.txt:1 (basic auth credentials)
159
+ */
160
+ refundPayment(transactionId: TransactionId, opts: {
161
+ merchantId: MerchantId;
162
+ amountMinor?: MinorUnits;
163
+ sourceCode?: string;
164
+ idempotencyKey: string;
165
+ }): Promise<RefundResponse>;
166
+ /**
167
+ * Cancel an order that has not yet been paid.
168
+ *
169
+ * Idempotent per Viva docs: calling cancel on an already-cancelled or
170
+ * captured order returns the existing Viva response without error.
171
+ *
172
+ * Per plan P18: cancellation triggers webhook 4865 (Order Updated).
173
+ *
174
+ * Path: `DELETE /checkout/v2/orders/{orderCode}?merchantId={merchantId}` (OAuth2).
175
+ *
176
+ * UNVERIFIED against live sandbox as of 2026-04-25 probe. Only
177
+ * cancel-transaction (refund/reverse) was tested; cancel-order was not.
178
+ * Kept on v2/OAuth2 + 401-fallback path defensively.
179
+ *
180
+ * 401 → reseller fallback (D15) — kept DEFENSIVE:
181
+ * If the primary OAuth2 client receives a 401 and secondaryClient is configured,
182
+ * the request is retried once with the secondary (Reseller basic-auth) client.
183
+ * A 401 from the secondary surfaces immediately as VivaAuthError — no further retry.
184
+ *
185
+ * Note: if cancelOrder already succeeded on Viva's side before any 401 is
186
+ * observed, a 4xx from Viva on a subsequent attempt signals the order is
187
+ * already cancelled (idempotent). The fallback does NOT apply to non-401
188
+ * errors — those propagate as VivaApiError unchanged.
189
+ *
190
+ * @see references/viva-docs/md/payment-isv-api.txt:1
191
+ * @see references/viva-docs/md/webhooks-for-payments.txt:205 (4865 Order Updated)
192
+ * @see references/viva-docs/md/isv-credentials.txt:107 (reseller basic-auth)
193
+ * @see docs/plans/vendure-plugin-v0.md §D15 (reseller fallback scope)
194
+ */
195
+ cancelOrder(orderCode: OrderCode, opts: {
196
+ merchantId: MerchantId;
197
+ }): Promise<CancelOrderResponse>;
198
+ }
199
+ //# sourceMappingURL=payments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payments.d.ts","sourceRoot":"","sources":["../../src/isv/payments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACnB,2BAA2B,EAC3B,cAAc,EACd,mBAAmB,EACnB,UAAU,EACV,aAAa,EACb,SAAS,EACT,UAAU,EACX,MAAM,mBAAmB,CAAC;AAuB3B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,WAAW;IAEpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;gBAFb,MAAM,EAAE,aAAa,EACrB,eAAe,CAAC,EAAE,aAAa,YAAA,EAC/B,YAAY,CAAC,EAAE,iBAAiB,YAAA;IAGnD;;;;;;;;;;;;;;;;;;OAkBG;IACG,WAAW,CACf,GAAG,EAAE,kBAAkB,EACvB,IAAI,EAAE;QAAE,UAAU,EAAE,UAAU,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,GACvD,OAAO,CAAC,mBAAmB,CAAC;IA4C/B;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACG,mBAAmB,CACvB,aAAa,EAAE,aAAa,EAC5B,IAAI,GAAE;QAAE,UAAU,CAAC,EAAE,UAAU,CAAA;KAAO,GACrC,OAAO,CAAC,2BAA2B,CAAC;IA+DvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACG,aAAa,CACjB,aAAa,EAAE,aAAa,EAC5B,IAAI,EAAE;QACJ,UAAU,EAAE,UAAU,CAAC;QACvB,WAAW,CAAC,EAAE,UAAU,CAAC;QACzB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,MAAM,CAAC;KACxB,GACA,OAAO,CAAC,cAAc,CAAC;IAuD1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACG,WAAW,CACf,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE;QAAE,UAAU,EAAE,UAAU,CAAA;KAAE,GAC/B,OAAO,CAAC,mBAAmB,CAAC;CAgChC"}