@obhco/pay 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +203 -2
- package/dist/errors.d.ts +9 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +15 -0
- package/dist/errors.js.map +1 -0
- package/dist/gateways/digipay.d.ts +36 -0
- package/dist/gateways/digipay.d.ts.map +1 -0
- package/dist/gateways/digipay.js +489 -0
- package/dist/gateways/digipay.js.map +1 -0
- package/dist/gateways/index.d.ts +5 -0
- package/dist/gateways/index.d.ts.map +1 -0
- package/dist/gateways/index.js +5 -0
- package/dist/gateways/index.js.map +1 -0
- package/dist/gateways/mellat-constants.d.ts +3 -0
- package/dist/gateways/mellat-constants.d.ts.map +1 -0
- package/dist/gateways/mellat-constants.js +3 -0
- package/dist/gateways/mellat-constants.js.map +1 -0
- package/dist/gateways/mellat.d.ts +30 -0
- package/dist/gateways/mellat.d.ts.map +1 -0
- package/dist/gateways/mellat.js +397 -0
- package/dist/gateways/mellat.js.map +1 -0
- package/dist/gateways/sadad.d.ts +43 -0
- package/dist/gateways/sadad.d.ts.map +1 -0
- package/dist/gateways/sadad.js +541 -0
- package/dist/gateways/sadad.js.map +1 -0
- package/dist/gateways/zarinpal.d.ts +21 -0
- package/dist/gateways/zarinpal.d.ts.map +1 -0
- package/dist/gateways/zarinpal.js +312 -0
- package/dist/gateways/zarinpal.js.map +1 -0
- package/dist/http.d.ts +31 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +101 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/pay.d.ts +114 -0
- package/dist/pay.d.ts.map +1 -0
- package/dist/pay.js +427 -0
- package/dist/pay.js.map +1 -0
- package/dist/report.d.ts +71 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +106 -0
- package/dist/report.js.map +1 -0
- package/dist/store.d.ts +420 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +68 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/package.json +34 -4
package/README.md
CHANGED
|
@@ -1,3 +1,204 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @obhco/pay
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Take payments inside your own service with Iranian gateways — **Sadad**,
|
|
4
|
+
**Zarinpal**, **Mellat (Behpardakht)** and **Digipay** — and report every
|
|
5
|
+
payment to OBH for accounting, settlement tracking and reports.
|
|
6
|
+
|
|
7
|
+
Each service uses **its own gateway terminal and credentials**. Money settles
|
|
8
|
+
straight to the service's account; OBH never touches a bank, it only receives
|
|
9
|
+
the finance reports.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
customer ──► your service ──► bank gateway
|
|
13
|
+
│ ▲ │
|
|
14
|
+
│ └── callback ──┘ (verified server-to-server)
|
|
15
|
+
▼
|
|
16
|
+
OBH ◄── signed finance report (payment.snapshot)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires Node.js 20+, server-side only. Never ship gateway credentials or the
|
|
20
|
+
OBH service key to a browser.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm add @obhco/pay
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 1. Storage
|
|
29
|
+
|
|
30
|
+
The package keeps no state of its own. You provide a `PaymentStore`; it needs
|
|
31
|
+
a table and a handful of lookups. `MemoryPaymentStore` is included for tests
|
|
32
|
+
and local development only.
|
|
33
|
+
|
|
34
|
+
Minimum table (adapt to your database):
|
|
35
|
+
|
|
36
|
+
| Column | Type | Notes |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `reference` | text, unique | `PAY-…` id of the attempt |
|
|
39
|
+
| `order_id` | text, indexed | your own order id |
|
|
40
|
+
| `attempt` | int | 1, 2, 3… per order |
|
|
41
|
+
| `gateway` | text | `sadad`, `zarinpal`, … |
|
|
42
|
+
| `gateway_order_id` | text | digits only, unique per gateway |
|
|
43
|
+
| `amount` | text/bigint | whole Rials |
|
|
44
|
+
| `status` | text | `pending` `completed` `failed` `cancelled` `expired` |
|
|
45
|
+
| `environment` | text | `live` or `test` |
|
|
46
|
+
| `provider_session_id` | text, indexed | token / authority / RefId |
|
|
47
|
+
| … | | the remaining fields of `PaymentRecord` |
|
|
48
|
+
|
|
49
|
+
Implement `PaymentStore` (see `src/store.ts`). The only subtle method is
|
|
50
|
+
`transition(reference, from, patch)`: it must update **only if the stored
|
|
51
|
+
status still equals `from`** and return `null` otherwise (for SQL:
|
|
52
|
+
`UPDATE … WHERE reference = ? AND status = ?` and check the row count). That
|
|
53
|
+
is what stops a double callback from completing a payment twice.
|
|
54
|
+
|
|
55
|
+
`nextGatewayOrderId(gateway)` must return a digits-only number that is unique
|
|
56
|
+
per gateway; a database sequence is ideal.
|
|
57
|
+
|
|
58
|
+
## 2. Configure
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// lib/pay.ts
|
|
62
|
+
import { createPay } from "@obhco/pay"
|
|
63
|
+
import { store } from "./payment-store"
|
|
64
|
+
|
|
65
|
+
export const pay = createPay({
|
|
66
|
+
service: "afarin", // short id, same as your OBH service slug
|
|
67
|
+
baseUrl: "https://afarinai.ir", // your public origin (for bank callbacks)
|
|
68
|
+
store,
|
|
69
|
+
// gateways: ["zarinpal", "sadad"], // default: every gateway with credentials
|
|
70
|
+
// defaultGateway: "zarinpal",
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Gateways are enabled by environment variables in **your** service:
|
|
75
|
+
|
|
76
|
+
| Gateway | Variables |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| Sadad | `SADAD_MERCHANT_ID`, `SADAD_TERMINAL_ID`, `SADAD_TERMINAL_KEY` (+ `SADAD_BASE_URL`, `SADAD_RELAY_URL`, `SADAD_RELAY_SECRET` if you call it through an Iranian relay) |
|
|
79
|
+
| Zarinpal | `ZARINPAL_MERCHANT_ID` (`ZARINPAL_SANDBOX=true` for the sandbox) |
|
|
80
|
+
| Mellat | `BEHPARDAKHT_TERMINAL_ID`, `BEHPARDAKHT_USERNAME`, `BEHPARDAKHT_PASSWORD` (+ `BEHPARDAKHT_RELAY_URL`, `BEHPARDAKHT_RELAY_SECRET`, `BEHPARDAKHT_CALLBACK_URL`) |
|
|
81
|
+
| Digipay | `DIGIPAY_USERNAME`, `DIGIPAY_PASSWORD`, and `DIGIPAY_AUTHORIZATION` or `DIGIPAY_CLIENT_ID` + `DIGIPAY_CLIENT_SECRET` (+ `DIGIPAY_BASE_URL`) |
|
|
82
|
+
|
|
83
|
+
Finance reporting to OBH:
|
|
84
|
+
|
|
85
|
+
| Variable | Value |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| `OBH_BASE_URL` | OBH origin, e.g. `https://obhco.ir` |
|
|
88
|
+
| `OBH_SERVICE_KEY` | Service credential (`keyId.secret`) created in OBH → Services |
|
|
89
|
+
| `PAY_ENVIRONMENT` | `test` while using sandbox terminals; default `live` |
|
|
90
|
+
|
|
91
|
+
If `OBH_BASE_URL` / `OBH_SERVICE_KEY` are missing, payments still work and
|
|
92
|
+
nothing is reported. Check the connection with `await pay.pingObh()`.
|
|
93
|
+
|
|
94
|
+
## 3. Start a payment
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// app/api/checkout/route.ts (Next.js)
|
|
98
|
+
import { redirectResponse } from "@obhco/pay"
|
|
99
|
+
import { pay } from "@/lib/pay"
|
|
100
|
+
|
|
101
|
+
export async function POST(request: Request) {
|
|
102
|
+
const { orderId, amount, gateway, mobile } = await request.json()
|
|
103
|
+
|
|
104
|
+
const { redirect } = await pay.start({
|
|
105
|
+
orderId, // your order id; one order can have several attempts
|
|
106
|
+
amount, // whole Rials
|
|
107
|
+
description: `Order ${orderId}`,
|
|
108
|
+
gateway, // optional; see pay.listGateways()
|
|
109
|
+
customerMobile: mobile, // required by Digipay
|
|
110
|
+
returnUrl: `https://afarinai.ir/orders/${orderId}`,
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
return redirectResponse(redirect) // 303, or an auto-POST form for Mellat
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`pay.listGateways()` returns what you can offer at checkout (Persian
|
|
118
|
+
`displayName`, `kind: "card" | "installment"`, `requiresCustomerMobile`).
|
|
119
|
+
|
|
120
|
+
Starting the same order twice returns the open session instead of creating a
|
|
121
|
+
second one, and an order that is already paid throws `ORDER_ALREADY_PAID`.
|
|
122
|
+
|
|
123
|
+
## 4. Receive the bank callback
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
// app/api/pay/[gateway]/callback/route.ts
|
|
127
|
+
import { callbackRoute } from "@obhco/pay"
|
|
128
|
+
import { pay } from "@/lib/pay"
|
|
129
|
+
|
|
130
|
+
async function handle(request: Request, ctx: { params: Promise<{ gateway: string }> }) {
|
|
131
|
+
const { gateway } = await ctx.params
|
|
132
|
+
return callbackRoute(pay, gateway, {
|
|
133
|
+
fallbackUrl: "https://afarinai.ir/checkout",
|
|
134
|
+
})(request)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export { handle as GET, handle as POST }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The path must match `callbackPath` (default `/api/pay/{gateway}/callback`).
|
|
141
|
+
The handler finds the payment, **verifies it with the bank server-to-server**,
|
|
142
|
+
records the result and sends the customer back to `returnUrl` with
|
|
143
|
+
`?reference=…&status=completed|failed|pending`.
|
|
144
|
+
|
|
145
|
+
> Never trust that query string. On the page the customer lands on, load the
|
|
146
|
+
> payment with `pay.get(reference)` and fulfil the order only if
|
|
147
|
+
> `status === "completed"`.
|
|
148
|
+
|
|
149
|
+
## 5. Keep it healthy
|
|
150
|
+
|
|
151
|
+
Run `pay.reconcile()` every few minutes (cron, queue worker or a protected
|
|
152
|
+
route). It expires abandoned sessions, retries verifications that hit a
|
|
153
|
+
network error, and re-sends any report OBH has not acknowledged:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const { expired, reverified, reported } = await pay.reconcile()
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Without it, a payment whose callback was lost stays pending, and OBH shows the
|
|
160
|
+
service as **stale** on its reports page.
|
|
161
|
+
|
|
162
|
+
When the customer comes back and the payment is still pending (the bank's verify
|
|
163
|
+
gave no answer the first time), call `await pay.recheck(reference)` to verify it
|
|
164
|
+
again right away.
|
|
165
|
+
|
|
166
|
+
## What OBH receives
|
|
167
|
+
|
|
168
|
+
For every state change the package sends one `payment.snapshot` (the full
|
|
169
|
+
state of one attempt) to `POST {OBH_BASE_URL}/api/v1/finance/events`,
|
|
170
|
+
authenticated with your service key. OBH upserts it by
|
|
171
|
+
`(service, reference)`, so retries and reordering are harmless; a failed
|
|
172
|
+
report never blocks the payment and is re-sent by `reconcile()`.
|
|
173
|
+
|
|
174
|
+
Sent: reference, order id, attempt, gateway, amount, status, timestamps, trace
|
|
175
|
+
and reference numbers, masked card, installment type (Digipay). **Not sent:**
|
|
176
|
+
customer mobile, return URL, or your `metadata` (opt in with
|
|
177
|
+
`report: { …, includeMetadata: true }`).
|
|
178
|
+
|
|
179
|
+
OBH then does the rest: accounting and revenue, per-service settlement
|
|
180
|
+
accounts per gateway, installment schedules for Digipay, reconciliation,
|
|
181
|
+
success rates by gateway and service, and stale-service alerts.
|
|
182
|
+
|
|
183
|
+
## Testing
|
|
184
|
+
|
|
185
|
+
* Use the gateways' own sandboxes (`ZARINPAL_SANDBOX=true`, bank test
|
|
186
|
+
terminals) with `PAY_ENVIRONMENT=test`; test payments are kept out of
|
|
187
|
+
OBH's financial totals.
|
|
188
|
+
* Unit tests can pass a fake adapter in `gateways: [...]` and a
|
|
189
|
+
`MemoryPaymentStore`; see `tests/pay.test.ts` for a complete example.
|
|
190
|
+
|
|
191
|
+
## Errors
|
|
192
|
+
|
|
193
|
+
`PayError.code` is one of `INVALID_REQUEST`, `GATEWAY_NOT_AVAILABLE`,
|
|
194
|
+
`GATEWAY_ERROR`, `ORDER_ALREADY_PAID`, `PAYMENT_NOT_FOUND`, `ORDER_MISMATCH`,
|
|
195
|
+
`INVALID_CALLBACK`, `REPORT_FAILED`. Use `isPayError(error)` to branch on it.
|
|
196
|
+
|
|
197
|
+
## Security notes
|
|
198
|
+
|
|
199
|
+
* A callback is only a hint; the result always comes from the bank's verify
|
|
200
|
+
call, and the verified amount must equal the stored amount (otherwise the
|
|
201
|
+
payment is failed and flagged, never completed).
|
|
202
|
+
* Payments move `pending → final` exactly once (compare-and-set).
|
|
203
|
+
* Keep gateway credentials and `OBH_SERVICE_KEY` in server environment
|
|
204
|
+
variables only.
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type PayErrorCode = "INVALID_REQUEST" | "GATEWAY_NOT_AVAILABLE" | "GATEWAY_ERROR" | "ORDER_ALREADY_PAID" | "PAYMENT_NOT_FOUND" | "ORDER_MISMATCH" | "INVALID_CALLBACK" | "REPORT_FAILED";
|
|
2
|
+
/** Error raised by the package. `code` is stable; `message` is for logs. */
|
|
3
|
+
export declare class PayError extends Error {
|
|
4
|
+
readonly code: PayErrorCode;
|
|
5
|
+
readonly details?: Record<string, unknown>;
|
|
6
|
+
constructor(code: PayErrorCode, message: string, details?: Record<string, unknown>);
|
|
7
|
+
}
|
|
8
|
+
export declare function isPayError(error: unknown): error is PayError;
|
|
9
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GACpB,iBAAiB,GACjB,uBAAuB,GACvB,eAAe,GACf,oBAAoB,GACpB,mBAAmB,GACnB,gBAAgB,GAChB,kBAAkB,GAClB,eAAe,CAAA;AAEnB,4EAA4E;AAC5E,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;gBAGxC,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAOpC;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Error raised by the package. `code` is stable; `message` is for logs. */
|
|
2
|
+
export class PayError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
details;
|
|
5
|
+
constructor(code, message, details) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "PayError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.details = details;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function isPayError(error) {
|
|
13
|
+
return error instanceof PayError;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAUA,4EAA4E;AAC5E,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,IAAI,CAAc;IAClB,OAAO,CAA0B;IAE1C,YACE,IAAkB,EAClB,OAAe,EACf,OAAiC;QAEjC,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,UAAU,CAAA;QACtB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;CACF;AAED,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,KAAK,YAAY,QAAQ,CAAA;AAClC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type PaymentGatewayAdapter } from "../types.js";
|
|
2
|
+
export declare const DIGIPAY_PROVIDER_CODE = "digipay";
|
|
3
|
+
export declare const DIGIPAY_PROVIDER_NAME = "Digipay (Installments / BNPL)";
|
|
4
|
+
/**
|
|
5
|
+
* Purchase `type` values Digipay posts back in the payment result and expects
|
|
6
|
+
* again on `purchases/verify?type=`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DIGIPAY_PURCHASE_TYPES: Record<number, string>;
|
|
9
|
+
/** Business error codes from the Digipay UPG guide (HTTP 422 bodies). */
|
|
10
|
+
export declare const DIGIPAY_RESULT_CODES: Record<number, {
|
|
11
|
+
message: string;
|
|
12
|
+
retryable: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function describeDigipayCode(code: number | null | undefined, fallback?: string | null): string;
|
|
15
|
+
export type DigipayConfigStatus = {
|
|
16
|
+
providerCode: typeof DIGIPAY_PROVIDER_CODE;
|
|
17
|
+
providerName: typeof DIGIPAY_PROVIDER_NAME;
|
|
18
|
+
baseUrl: string;
|
|
19
|
+
liveConfigured: boolean;
|
|
20
|
+
usernameMasked: string | null;
|
|
21
|
+
clientIdMasked: string | null;
|
|
22
|
+
};
|
|
23
|
+
export declare function getDigipayConfigStatus(): DigipayConfigStatus;
|
|
24
|
+
/** Test hook: forget the cached OAuth access token. */
|
|
25
|
+
export declare function resetDigipayTokenCache(): void;
|
|
26
|
+
export declare function normalizeIranianMobile(value: string | null | undefined): string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Digipay keys tickets by `providerId` across the whole merchant account,
|
|
29
|
+
* which other services share, so OBH's order number alone can clash with an
|
|
30
|
+
* existing ticket. OBH sends it with an `obh-` prefix and accepts both forms
|
|
31
|
+
* back.
|
|
32
|
+
*/
|
|
33
|
+
export declare function toDigipayProviderId(gatewayOrderId: string): string;
|
|
34
|
+
export declare function fromDigipayProviderId(value: string | null): string | null;
|
|
35
|
+
export declare const digipayPaymentAdapter: PaymentGatewayAdapter;
|
|
36
|
+
//# sourceMappingURL=digipay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"digipay.d.ts","sourceRoot":"","sources":["../../src/gateways/digipay.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,qBAAqB,EAG3B,MAAM,aAAa,CAAA;AAEpB,eAAO,MAAM,qBAAqB,YAAY,CAAA;AAC9C,eAAO,MAAM,qBAAqB,kCAAkC,CAAA;AAQpE;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAMzD,CAAA;AAED,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,EAAE,MAAM,CACvC,MAAM,EACN;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAuCxC,CAAA;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,UAazB;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,YAAY,EAAE,OAAO,qBAAqB,CAAA;IAC1C,YAAY,EAAE,OAAO,qBAAqB,CAAA;IAC1C,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,EAAE,OAAO,CAAA;IACvB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B,CAAA;AAmBD,wBAAgB,sBAAsB,IAAI,mBAAmB,CAa5D;AAoDD,uDAAuD;AACvD,wBAAgB,sBAAsB,SAErC;AAwHD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,iBAmBtE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,MAAM,UAEzD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,iBAEzD;AAQD,eAAO,MAAM,qBAAqB,EAAE,qBAkVnC,CAAA"}
|