@fire-spark/payments-clisitef-pinpad-rn 0.0.0-stage → 0.7.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.
@@ -0,0 +1,217 @@
1
+ import { DeviceField, PaymentAdapter, PaymentAdapterCapabilities, PaymentErrorEntry, PaymentMetadata, PaymentStorage, PaymentUI, StandardPaymentError } from "@fire-spark/payments";
2
+ import { CLISITEF_MODALITY, ClisitefClient, ClisitefConfigureParams, ClisitefFinishParams, ClisitefStartParams } from "@fire-spark/payments-clisitef-client";
3
+ import { TenderDetails } from "@fire-spark/types";
4
+ //#region src/adapter.d.ts
5
+ /** Confirmed in the act (`finishTransaction(1)`), never captured later. */
6
+ declare const clisitefPinPadCapabilities: PaymentAdapterCapabilities;
7
+ type ClisitefPinPadOptions = {
8
+ /** Defaults to the shared client bound to the native module. */
9
+ client?: ClisitefClient;
10
+ /**
11
+ * Device-wide storage for the pending-transaction record. Defaults to
12
+ * `ctx.storage`, which is scoped per payment: a transaction left open by
13
+ * the DEBIT payment is then not seen by the CREDIT one.
14
+ */
15
+ storage?: PaymentStorage;
16
+ /** Store time zone for the fiscal date; defaults to `America/Sao_Paulo`. */
17
+ timeZone?: string;
18
+ now?: () => Date;
19
+ confirmationTimeoutMs?: number;
20
+ /**
21
+ * Payment deadline (`policy.timeout_ms`); defaults to 180 s. A
22
+ * `runtime.timeout_ms` in the payment or method config overrides it.
23
+ */
24
+ timeoutMs?: number;
25
+ };
26
+ /**
27
+ * CliSiTef (Software Express) on the kiosk pinpad, Brazil. One adapter per
28
+ * modality payment (DEBIT / CREDIT / VOUCHER in `payments.config`). The
29
+ * pinpad waits on the customer, so the deadline is `timeoutMs` instead of
30
+ * the 30 s library default.
31
+ */
32
+ declare const clisitefPinPadPayments: (options?: ClisitefPinPadOptions) => PaymentAdapter;
33
+ //#endregion
34
+ //#region src/constants.d.ts
35
+ /**
36
+ * `onData` commands handled by the adapter (Fiserv "Tabela de códigos de
37
+ * comando"). Not handled, so they abort the transaction: 29 (silent field),
38
+ * 31 (check), 35 (barcode), 42 (identified menu, only with
39
+ * `ItemMenuIdentificado`) and 50/51 (QR, PIX only).
40
+ */
41
+ declare const CLISITEF_COMMAND: {
42
+ readonly STORE_FIELD: 0;
43
+ readonly MESSAGE_OPERATOR: 1;
44
+ readonly MESSAGE_CUSTOMER: 2;
45
+ readonly MESSAGE_BOTH: 3;
46
+ readonly MENU_TITLE: 4;
47
+ readonly CLEAR_OPERATOR: 11;
48
+ readonly CLEAR_CUSTOMER: 12;
49
+ readonly CLEAR_BOTH: 13;
50
+ readonly CLEAR_MENU_TITLE: 14;
51
+ readonly HEADER: 15;
52
+ readonly CLEAR_HEADER: 16;
53
+ readonly CONFIRM: 20;
54
+ readonly MENU: 21;
55
+ readonly PRESS_KEY: 22;
56
+ readonly CHECK_ABORT: 23;
57
+ readonly READ_FIELD: 30;
58
+ readonly READ_AMOUNT: 34;
59
+ readonly READ_MASKED: 41;
60
+ readonly FOOTER: 52;
61
+ };
62
+ /** `continueTransaction` answers to `CONFIRM` (20). */
63
+ declare const CLISITEF_YES = "0";
64
+ declare const CLISITEF_NO = "1";
65
+ /** `TipoCampo` ids stored with `STORE_FIELD` (Fiserv "Tabela TipoCampo"). */
66
+ declare const CLISITEF_FIELD: {
67
+ readonly TRANSACTION_DATE: 105;
68
+ readonly CUSTOMER_RECEIPT: 121;
69
+ readonly MERCHANT_RECEIPT: 122;
70
+ readonly INSTITUTION_CODE: 131;
71
+ readonly CARD_TYPE_CODE: 132;
72
+ readonly SITEF_NSU: 133;
73
+ readonly HOST_NSU: 134;
74
+ readonly AUTHORIZATION_CODE: 135;
75
+ readonly CARD_BIN: 136;
76
+ readonly INSTITUTION_NAME: 156;
77
+ readonly LAST4: 1190;
78
+ readonly ENTRY_MODE: 2090;
79
+ };
80
+ /** Fields that prove the card was read: an error after them is ambiguous. */
81
+ declare const CLISITEF_CAPTURE_FIELDS: ReadonlySet<number>;
82
+ /** Operator, pinpad or automation cancelled (ficha §11). */
83
+ declare const CLISITEF_CANCEL_CODES: ReadonlySet<number>;
84
+ /** Inconclusive when the card was already read (ficha §4). */
85
+ declare const CLISITEF_INCONCLUSIVE_CODES: ReadonlySet<number>;
86
+ /** Approved (`stage 1`) without `stage 2`: undo (legacy `CONFIRMATION_TIMEOUT_MS`). */
87
+ declare const CLISITEF_CONFIRMATION_TIMEOUT_MS = 60000;
88
+ /**
89
+ * Payment deadline: covers the pinpad table load (`OBTENDO REGISTRO`), which
90
+ * can take minutes, plus card and PIN entry.
91
+ */
92
+ declare const CLISITEF_PAYMENT_TIMEOUT_MS = 180000;
93
+ /** Legacy operator when the merchant config has none. */
94
+ declare const CLISITEF_DEFAULT_OPERATOR = "KIOSKO_BR";
95
+ /** Device-wide record of the last transaction, to undo it after a crash. */
96
+ declare const CLISITEF_STORAGE_KEY = "clisitef:transaction:v1";
97
+ //#endregion
98
+ //#region src/errors.d.ts
99
+ /**
100
+ * Ficha §11 (Fiserv "Tabela de códigos de retorno"); other positive codes
101
+ * (authorizer) fall back to `CARD_DECLINED`, other negatives to `UNKNOWN`.
102
+ */
103
+ declare const clisitefPinPadErrors: Readonly<Record<string, PaymentErrorEntry>> & {
104
+ "-1": PaymentErrorEntry;
105
+ "-2": PaymentErrorEntry;
106
+ "-3": PaymentErrorEntry;
107
+ "-4": PaymentErrorEntry;
108
+ "-5": PaymentErrorEntry;
109
+ "-6": PaymentErrorEntry;
110
+ "-8": PaymentErrorEntry;
111
+ "-9": PaymentErrorEntry;
112
+ "-10": PaymentErrorEntry;
113
+ "-12": PaymentErrorEntry;
114
+ "-13": PaymentErrorEntry;
115
+ "-15": PaymentErrorEntry;
116
+ "-20": PaymentErrorEntry;
117
+ "-21": PaymentErrorEntry;
118
+ "-24": PaymentErrorEntry;
119
+ "-30": PaymentErrorEntry;
120
+ "-40": PaymentErrorEntry;
121
+ "-41": PaymentErrorEntry;
122
+ "-43": PaymentErrorEntry;
123
+ "-50": PaymentErrorEntry;
124
+ "-100": PaymentErrorEntry;
125
+ CLISITEF_TIMEOUT: PaymentErrorEntry;
126
+ };
127
+ //#endregion
128
+ //#region src/settings.d.ts
129
+ declare const CLISITEF_PINPAD_TYPES: readonly ["ANDROID_USB", "ANDROID_BT", "ANDROID_AUTO"];
130
+ /** Ficha §6: filled per kiosk in the device settings. */
131
+ declare const clisitefPinPadDeviceFields: DeviceField[];
132
+ type ClisitefModality = keyof typeof CLISITEF_MODALITY;
133
+ type ClisitefPinPadSettings = {
134
+ configure: ClisitefConfigureParams;
135
+ modality: Exclude<ClisitefModality, "PIX">;
136
+ operator: string;
137
+ restrictions: string;
138
+ terminalNumber: string;
139
+ };
140
+ /** `payments.config` (merchant) + device settings; throws when incomplete. */
141
+ declare const readClisitefPinPadSettings: (config: Record<string, unknown>, device: Record<string, unknown>) => ClisitefPinPadSettings;
142
+ //#endregion
143
+ //#region src/metadata.d.ts
144
+ type ClisitefFields = ReadonlyMap<number, string>;
145
+ /** Last 4 digits of the masked PAN printed on the receipt. */
146
+ declare const clisitefLast4: (receipt: string | null) => string | null;
147
+ type MetadataInput = {
148
+ attemptReference: string;
149
+ fields: ClisitefFields;
150
+ modality: ClisitefModality;
151
+ terminalNumber: string;
152
+ fiscalCoupon: string;
153
+ fiscalDate: string;
154
+ };
155
+ declare const clisitefMetadata: (input: MetadataInput) => PaymentMetadata;
156
+ /** PIN_PAD tender details; `null` without brand and last 4 (schema minimum). */
157
+ declare const clisitefDetails: (metadata: PaymentMetadata) => TenderDetails | null;
158
+ //#endregion
159
+ //#region src/store.d.ts
160
+ /**
161
+ * `started`: `startTransaction` called. `approved`: `finishTransaction(1)`
162
+ * called, no `stage 2` yet. `confirmed` / `undone`: closed either way.
163
+ * `review`: approved, then lost, and no longer pending in CliSiTef: it may
164
+ * have been charged.
165
+ */
166
+ type ClisitefTransactionState = "started" | "approved" | "confirmed" | "undone" | "review";
167
+ type ClisitefTransactionRecord = {
168
+ attemptReference: string;
169
+ fiscalCoupon: string;
170
+ fiscalDate: string;
171
+ time: string;
172
+ startedAt: number;
173
+ state: ClisitefTransactionState;
174
+ amount: number;
175
+ metadata?: PaymentMetadata;
176
+ details?: TenderDetails | null;
177
+ error?: StandardPaymentError;
178
+ };
179
+ declare const readTransaction: (storage: PaymentStorage) => Promise<ClisitefTransactionRecord | null>;
180
+ declare const writeTransaction: (storage: PaymentStorage, record: ClisitefTransactionRecord) => Promise<void>;
181
+ declare const isOpen: (record: ClisitefTransactionRecord) => boolean;
182
+ //#endregion
183
+ //#region src/transaction.d.ts
184
+ /**
185
+ * `confirmed`: `stage 2` closed with 0. `declined`: failed before the card
186
+ * was charged. `undone`: `finishTransaction(0)` sent after a capture or an
187
+ * approval. `unresolved`: the undo itself failed; needs review.
188
+ */
189
+ type ClisitefOutcomeStatus = "confirmed" | "declined" | "undone" | "unresolved";
190
+ type ClisitefOutcome = {
191
+ status: ClisitefOutcomeStatus;
192
+ /** Provider code: `resultCode`, a client error code, or `null`. */
193
+ code: number | string | null;
194
+ message: string | null;
195
+ fields: ClisitefFields;
196
+ aborted: boolean;
197
+ /** Error thrown by `ctx.ui` (e.g. the customer did not answer). */
198
+ error?: unknown;
199
+ };
200
+ type ClisitefTransactionInput = {
201
+ client: ClisitefClient;
202
+ ui: PaymentUI;
203
+ signal: AbortSignal;
204
+ start: ClisitefStartParams;
205
+ finish: Omit<ClisitefFinishParams, "confirm">;
206
+ confirmationTimeoutMs: number;
207
+ /** Runs before `finishTransaction(1)`, to persist the approval. */
208
+ onApproved: (fields: ClisitefFields) => Promise<void>;
209
+ };
210
+ /**
211
+ * One CliSiTef transaction: `startTransaction`, the `onData` loop answered
212
+ * through `ui`, `finishTransaction(1)` on approval (`stage 1` = 0) and
213
+ * success only when `stage 2` closes with 0. Never rejects.
214
+ */
215
+ declare const runClisitefTransaction: (input: ClisitefTransactionInput) => Promise<ClisitefOutcome>;
216
+ //#endregion
217
+ export { CLISITEF_CANCEL_CODES, CLISITEF_CAPTURE_FIELDS, CLISITEF_COMMAND, CLISITEF_CONFIRMATION_TIMEOUT_MS, CLISITEF_DEFAULT_OPERATOR, CLISITEF_FIELD, CLISITEF_INCONCLUSIVE_CODES, CLISITEF_NO, CLISITEF_PAYMENT_TIMEOUT_MS, CLISITEF_PINPAD_TYPES, CLISITEF_STORAGE_KEY, CLISITEF_YES, ClisitefFields, ClisitefModality, ClisitefOutcome, ClisitefOutcomeStatus, ClisitefPinPadOptions, ClisitefPinPadSettings, ClisitefTransactionInput, ClisitefTransactionRecord, ClisitefTransactionState, clisitefDetails, clisitefLast4, clisitefMetadata, clisitefPinPadCapabilities, clisitefPinPadDeviceFields, clisitefPinPadErrors, clisitefPinPadPayments, isOpen, readClisitefPinPadSettings, readTransaction, runClisitefTransaction, writeTransaction };
package/dist/index.js ADDED
@@ -0,0 +1,1101 @@
1
+ import { PaymentError, createPaymentError, definePaymentErrors, toPaymentError } from "@fire-spark/payments";
2
+ import { CLISITEF_ABORT_FLAG, CLISITEF_CONFIRM, CLISITEF_MODALITY, CLISITEF_RESULT, CLISITEF_TIMEOUT_CODE, clisitef, clisitefAdditionalParams, clisitefDebug, clisitefDebugCall, clisitefFiscalDateTime, formatClisitefAmount, parseClisitefMenu } from "@fire-spark/payments-clisitef-client";
3
+ //#region src/constants.ts
4
+ /**
5
+ * `onData` commands handled by the adapter (Fiserv "Tabela de códigos de
6
+ * comando"). Not handled, so they abort the transaction: 29 (silent field),
7
+ * 31 (check), 35 (barcode), 42 (identified menu, only with
8
+ * `ItemMenuIdentificado`) and 50/51 (QR, PIX only).
9
+ */
10
+ const CLISITEF_COMMAND = {
11
+ STORE_FIELD: 0,
12
+ MESSAGE_OPERATOR: 1,
13
+ MESSAGE_CUSTOMER: 2,
14
+ MESSAGE_BOTH: 3,
15
+ MENU_TITLE: 4,
16
+ CLEAR_OPERATOR: 11,
17
+ CLEAR_CUSTOMER: 12,
18
+ CLEAR_BOTH: 13,
19
+ CLEAR_MENU_TITLE: 14,
20
+ HEADER: 15,
21
+ CLEAR_HEADER: 16,
22
+ CONFIRM: 20,
23
+ MENU: 21,
24
+ PRESS_KEY: 22,
25
+ CHECK_ABORT: 23,
26
+ READ_FIELD: 30,
27
+ READ_AMOUNT: 34,
28
+ READ_MASKED: 41,
29
+ FOOTER: 52
30
+ };
31
+ /** `continueTransaction` answers to `CONFIRM` (20). */
32
+ const CLISITEF_YES = "0";
33
+ const CLISITEF_NO = "1";
34
+ /** `TipoCampo` ids stored with `STORE_FIELD` (Fiserv "Tabela TipoCampo"). */
35
+ const CLISITEF_FIELD = {
36
+ TRANSACTION_DATE: 105,
37
+ CUSTOMER_RECEIPT: 121,
38
+ MERCHANT_RECEIPT: 122,
39
+ INSTITUTION_CODE: 131,
40
+ CARD_TYPE_CODE: 132,
41
+ SITEF_NSU: 133,
42
+ HOST_NSU: 134,
43
+ AUTHORIZATION_CODE: 135,
44
+ CARD_BIN: 136,
45
+ INSTITUTION_NAME: 156,
46
+ LAST4: 1190,
47
+ ENTRY_MODE: 2090
48
+ };
49
+ /** Fields that prove the card was read: an error after them is ambiguous. */
50
+ const CLISITEF_CAPTURE_FIELDS = /* @__PURE__ */ new Set([
51
+ CLISITEF_FIELD.CUSTOMER_RECEIPT,
52
+ CLISITEF_FIELD.MERCHANT_RECEIPT,
53
+ CLISITEF_FIELD.CARD_TYPE_CODE,
54
+ CLISITEF_FIELD.HOST_NSU,
55
+ CLISITEF_FIELD.AUTHORIZATION_CODE,
56
+ CLISITEF_FIELD.CARD_BIN,
57
+ CLISITEF_FIELD.LAST4,
58
+ CLISITEF_FIELD.ENTRY_MODE
59
+ ]);
60
+ /** Operator, pinpad or automation cancelled (ficha §11). */
61
+ const CLISITEF_CANCEL_CODES = /* @__PURE__ */ new Set([
62
+ -2,
63
+ -6,
64
+ -15
65
+ ]);
66
+ /** Inconclusive when the card was already read (ficha §4). */
67
+ const CLISITEF_INCONCLUSIVE_CODES = /* @__PURE__ */ new Set([
68
+ -5,
69
+ -43,
70
+ -100
71
+ ]);
72
+ /** Approved (`stage 1`) without `stage 2`: undo (legacy `CONFIRMATION_TIMEOUT_MS`). */
73
+ const CLISITEF_CONFIRMATION_TIMEOUT_MS = 6e4;
74
+ /**
75
+ * Payment deadline: covers the pinpad table load (`OBTENDO REGISTRO`), which
76
+ * can take minutes, plus card and PIN entry.
77
+ */
78
+ const CLISITEF_PAYMENT_TIMEOUT_MS = 18e4;
79
+ /** Legacy operator when the merchant config has none. */
80
+ const CLISITEF_DEFAULT_OPERATOR = "KIOSKO_BR";
81
+ /** Device-wide record of the last transaction, to undo it after a crash. */
82
+ const CLISITEF_STORAGE_KEY = "clisitef:transaction:v1";
83
+ //#endregion
84
+ //#region src/errors.ts
85
+ const source = "docs/payments/clisitef/pinpad.md#11-errores";
86
+ const entry = (code, extra = {}) => {
87
+ return {
88
+ code,
89
+ source,
90
+ ...extra
91
+ };
92
+ };
93
+ const invalid = (message) => entry("INVALID_REQUEST", { message });
94
+ /**
95
+ * Ficha §11 (Fiserv "Tabela de códigos de retorno"); other positive codes
96
+ * (authorizer) fall back to `CARD_DECLINED`, other negatives to `UNKNOWN`.
97
+ */
98
+ const clisitefPinPadErrors = definePaymentErrors({
99
+ "-1": entry("TERMINAL_NEEDS_INIT", { retryable: true }),
100
+ "-2": entry("CANCELLED_BY_USER"),
101
+ "-3": invalid("Invalid CliSiTef function or modality"),
102
+ "-4": entry("TERMINAL_UNAVAILABLE", { message: "Out of memory" }),
103
+ "-5": entry("PROVIDER_UNAVAILABLE"),
104
+ "-6": entry("CANCELLED_BY_USER"),
105
+ "-8": entry("TERMINAL_NEEDS_INIT", { message: "CliSiTef outdated" }),
106
+ "-9": invalid("CliSiTef iterative flow not started"),
107
+ "-10": invalid("Missing mandatory CliSiTef parameter"),
108
+ "-12": entry("TERMINAL_BUSY"),
109
+ "-13": entry("UNKNOWN", { message: "Fiscal document not found" }),
110
+ "-15": entry("CANCELLED_BY_USER"),
111
+ "-20": invalid("Invalid CliSiTef parameter"),
112
+ "-21": invalid("Prohibited word in pinpad collection"),
113
+ "-24": invalid("Digital wallet not enabled"),
114
+ "-30": entry("TERMINAL_UNAVAILABLE", { message: "File access error" }),
115
+ "-40": entry("CARD_DECLINED", { message: "Declined by the SiTef server" }),
116
+ "-41": invalid("Invalid data"),
117
+ "-43": entry("TERMINAL_UNAVAILABLE", { message: "Pinpad routine failed" }),
118
+ "-50": entry("FRAUD_SUSPECTED", { message: "Unsecured transaction" }),
119
+ "-100": entry("UNKNOWN", { message: "CliSiTef internal error" }),
120
+ [CLISITEF_TIMEOUT_CODE]: entry("TIMEOUT")
121
+ });
122
+ //#endregion
123
+ //#region src/metadata.ts
124
+ const MASKED_PAN = /\d{0,6}[*Xx]{4,}\s*(\d{4})(?!\d)/;
125
+ const field = (fields, id) => {
126
+ const value = fields.get(id)?.trim();
127
+ return value ? value : null;
128
+ };
129
+ /** Last 4 digits of the masked PAN printed on the receipt. */
130
+ const clisitefLast4 = (receipt) => {
131
+ if (!receipt) return null;
132
+ return MASKED_PAN.exec(receipt)?.[1] ?? null;
133
+ };
134
+ /**
135
+ * Field 132. Fiserv documents it as "a table to be provided"; these codes
136
+ * come from the legacy production catalog (ficha §8).
137
+ */
138
+ const BRANDS = {
139
+ "00001": "VISA",
140
+ "00002": "MASTERCARD",
141
+ "00004": "AMEX",
142
+ "00011": "JCB",
143
+ "10014": "DISCOVER",
144
+ "20001": "MAESTRO",
145
+ "20002": "VISA_ELECTRON",
146
+ "20137": "TELENET"
147
+ };
148
+ /** Field 2090 (Fiserv "Tabela TipoCampo"). */
149
+ const ENTRY_MODES = {
150
+ "00": "SWIPE",
151
+ "03": "CHIP",
152
+ "05": "CONTACTLESS",
153
+ "06": "CONTACTLESS",
154
+ "99": "MANUAL"
155
+ };
156
+ const clisitefBrand = (fields) => {
157
+ const code = field(fields, CLISITEF_FIELD.CARD_TYPE_CODE);
158
+ if (!code || code === "00000") return null;
159
+ return BRANDS[code] ?? code;
160
+ };
161
+ const CARD_TYPES = {
162
+ DEBIT: "DEBIT",
163
+ CREDIT: "CREDIT",
164
+ VOUCHER: "PREPAID",
165
+ PIX: null
166
+ };
167
+ const clisitefMetadata = (input) => {
168
+ const { fields } = input;
169
+ const customer = field(fields, CLISITEF_FIELD.CUSTOMER_RECEIPT);
170
+ const merchant = field(fields, CLISITEF_FIELD.MERCHANT_RECEIPT);
171
+ const brand = clisitefBrand(fields);
172
+ const last4 = field(fields, CLISITEF_FIELD.LAST4)?.slice(-4) ?? null;
173
+ const entry = field(fields, CLISITEF_FIELD.ENTRY_MODE);
174
+ const institution = field(fields, CLISITEF_FIELD.INSTITUTION_NAME);
175
+ const p1 = {
176
+ provider_transaction_id: field(fields, CLISITEF_FIELD.SITEF_NSU),
177
+ authorization_code: field(fields, CLISITEF_FIELD.AUTHORIZATION_CODE),
178
+ reference: field(fields, CLISITEF_FIELD.HOST_NSU)
179
+ };
180
+ const p2 = {
181
+ card_type: CARD_TYPES[input.modality] ?? null,
182
+ brand
183
+ };
184
+ const p3 = {
185
+ last4: last4 ?? clisitefLast4(customer ?? merchant),
186
+ entry_mode: entry ? ENTRY_MODES[entry] ?? null : null
187
+ };
188
+ const p4 = { acquirer: institution ?? field(fields, CLISITEF_FIELD.INSTITUTION_CODE) };
189
+ const p5 = { terminal_id: input.terminalNumber };
190
+ const p6 = { ticket_number: input.fiscalCoupon };
191
+ const p7 = { transaction_date: input.fiscalDate };
192
+ const payment = {
193
+ ...p1,
194
+ ...p2,
195
+ ...p3,
196
+ ...p4,
197
+ ...p5,
198
+ ...p6,
199
+ ...p7
200
+ };
201
+ const receipt = customer !== null || merchant !== null ? {
202
+ customer,
203
+ merchant,
204
+ format: "text"
205
+ } : void 0;
206
+ return {
207
+ attempt_reference: input.attemptReference,
208
+ payment,
209
+ receipt
210
+ };
211
+ };
212
+ /** PIN_PAD tender details; `null` without brand and last 4 (schema minimum). */
213
+ const clisitefDetails = (metadata) => {
214
+ const payment = metadata.payment;
215
+ const brand = payment?.brand?.slice(0, 32);
216
+ const last4 = payment?.last4;
217
+ const terminal = payment?.terminal_id;
218
+ if (!brand || !last4 || !terminal) return null;
219
+ return {
220
+ method: "PIN_PAD",
221
+ brand,
222
+ last4,
223
+ auth_code: payment.authorization_code ?? void 0,
224
+ terminal_id: terminal
225
+ };
226
+ };
227
+ //#endregion
228
+ //#region src/settings.ts
229
+ const CLISITEF_PINPAD_TYPES = [
230
+ "ANDROID_USB",
231
+ "ANDROID_BT",
232
+ "ANDROID_AUTO"
233
+ ];
234
+ /** Ficha §6: filled per kiosk in the device settings. */
235
+ const clisitefPinPadDeviceFields = [
236
+ {
237
+ key: "sitef_ip",
238
+ label: "SiTef server IP",
239
+ type: "text",
240
+ required: true
241
+ },
242
+ {
243
+ key: "store_code",
244
+ label: "SiTef store code",
245
+ type: "text",
246
+ required: true
247
+ },
248
+ {
249
+ key: "terminal_number",
250
+ label: "SiTef terminal number",
251
+ type: "text",
252
+ required: true
253
+ },
254
+ {
255
+ key: "cnpj_store",
256
+ label: "Store CNPJ",
257
+ type: "text",
258
+ required: true
259
+ },
260
+ {
261
+ key: "pinpad_type",
262
+ label: "Pinpad connection",
263
+ type: "select",
264
+ required: true,
265
+ options: [...CLISITEF_PINPAD_TYPES]
266
+ }
267
+ ];
268
+ const text = (value) => {
269
+ return typeof value === "string" ? value.trim() : "";
270
+ };
271
+ const notConfigured = (missing) => {
272
+ return new PaymentError(createPaymentError("TERMINAL_NOT_CONFIGURED", { message: `CliSiTef is missing ${missing.join(", ")}` }));
273
+ };
274
+ const isModality = (value) => {
275
+ return value === "DEBIT" || value === "CREDIT" || value === "VOUCHER";
276
+ };
277
+ /** `payments.config` (merchant) + device settings; throws when incomplete. */
278
+ const readClisitefPinPadSettings = (config, device) => {
279
+ const modality = text(config.modality);
280
+ const cnpjEnterprise = text(config.cnpj_enterprise);
281
+ const serverIp = text(device.sitef_ip);
282
+ const storeCode = text(device.store_code);
283
+ const terminalNumber = text(device.terminal_number);
284
+ const cnpjStore = text(device.cnpj_store).replace(/\D/g, "");
285
+ const pinpadType = text(device.pinpad_type) || void 0;
286
+ const required = {
287
+ modality,
288
+ cnpj_enterprise: cnpjEnterprise
289
+ };
290
+ const fromDevice = {
291
+ sitef_ip: serverIp,
292
+ store_code: storeCode
293
+ };
294
+ const more = {
295
+ terminal_number: terminalNumber,
296
+ cnpj_store: cnpjStore
297
+ };
298
+ const values = {
299
+ ...required,
300
+ ...fromDevice
301
+ };
302
+ Object.assign(values, more);
303
+ const missing = Object.keys(values).filter((key) => !values[key]);
304
+ clisitefDebug("pinpad.settings", "read", {
305
+ config,
306
+ device,
307
+ missing
308
+ });
309
+ if (missing.length) throw notConfigured(missing);
310
+ if (!isModality(modality)) throw notConfigured(["a valid modality"]);
311
+ return {
312
+ configure: {
313
+ serverIp,
314
+ storeCode,
315
+ terminalNumber,
316
+ additionalParams: clisitefAdditionalParams({
317
+ cnpjStore,
318
+ cnpjEnterprise,
319
+ pinpadType
320
+ })
321
+ },
322
+ modality,
323
+ operator: text(config.operator) || "KIOSKO_BR",
324
+ restrictions: text(config.restrictions),
325
+ terminalNumber
326
+ };
327
+ };
328
+ //#endregion
329
+ //#region src/store.ts
330
+ const isRecord = (value) => {
331
+ if (!value || typeof value !== "object") return false;
332
+ const record = value;
333
+ return typeof record.fiscalCoupon === "string" && typeof record.attemptReference === "string";
334
+ };
335
+ const readTransaction = async (storage) => {
336
+ const raw = await storage.get(CLISITEF_STORAGE_KEY);
337
+ clisitefDebug("pinpad.store", "read", { raw });
338
+ if (!raw) return null;
339
+ try {
340
+ const parsed = JSON.parse(raw);
341
+ return isRecord(parsed) ? parsed : null;
342
+ } catch {
343
+ return null;
344
+ }
345
+ };
346
+ const writeTransaction = async (storage, record) => {
347
+ clisitefDebug("pinpad.store", `write ${record.state}`, record);
348
+ await storage.set(CLISITEF_STORAGE_KEY, JSON.stringify(record));
349
+ };
350
+ const isOpen = (record) => {
351
+ return record.state === "started" || record.state === "approved";
352
+ };
353
+ //#endregion
354
+ //#region src/transaction.ts
355
+ const errorCode = (error) => {
356
+ if (!error || typeof error !== "object") return null;
357
+ const code = error.code;
358
+ return typeof code === "string" || typeof code === "number" ? code : null;
359
+ };
360
+ const errorMessage = (error) => {
361
+ return error instanceof Error ? error.message : null;
362
+ };
363
+ const inputType = (command) => {
364
+ if (command === CLISITEF_COMMAND.READ_MASKED) return "password";
365
+ if (command === CLISITEF_COMMAND.READ_AMOUNT) return "number";
366
+ return "text";
367
+ };
368
+ const MESSAGES = /* @__PURE__ */ new Set([
369
+ CLISITEF_COMMAND.MESSAGE_OPERATOR,
370
+ CLISITEF_COMMAND.MESSAGE_CUSTOMER,
371
+ CLISITEF_COMMAND.MESSAGE_BOTH,
372
+ CLISITEF_COMMAND.PRESS_KEY,
373
+ CLISITEF_COMMAND.FOOTER
374
+ ]);
375
+ const SILENT = /* @__PURE__ */ new Set([
376
+ CLISITEF_COMMAND.CLEAR_OPERATOR,
377
+ CLISITEF_COMMAND.CLEAR_CUSTOMER,
378
+ CLISITEF_COMMAND.CLEAR_BOTH,
379
+ CLISITEF_COMMAND.HEADER,
380
+ CLISITEF_COMMAND.CLEAR_HEADER,
381
+ CLISITEF_COMMAND.CHECK_ABORT
382
+ ]);
383
+ const READS = /* @__PURE__ */ new Set([
384
+ CLISITEF_COMMAND.READ_FIELD,
385
+ CLISITEF_COMMAND.READ_AMOUNT,
386
+ CLISITEF_COMMAND.READ_MASKED
387
+ ]);
388
+ /**
389
+ * One CliSiTef transaction: `startTransaction`, the `onData` loop answered
390
+ * through `ui`, `finishTransaction(1)` on approval (`stage 1` = 0) and
391
+ * success only when `stage 2` closes with 0. Never rejects.
392
+ */
393
+ const runClisitefTransaction = (input) => {
394
+ const { client, ui, signal } = input;
395
+ const fields = /* @__PURE__ */ new Map();
396
+ const subscriptions = [];
397
+ let title = "";
398
+ let captured = false;
399
+ let approved = false;
400
+ let closing = false;
401
+ let aborted = false;
402
+ let watchdog;
403
+ let queue = Promise.resolve();
404
+ let resolve = () => void 0;
405
+ const done = new Promise((r) => resolve = r);
406
+ const log = (event, data) => {
407
+ const flags = {
408
+ captured,
409
+ approved,
410
+ closing,
411
+ aborted
412
+ };
413
+ clisitefDebug("pinpad.transaction", event, {
414
+ ...data && typeof data === "object" ? data : { value: data },
415
+ flags
416
+ });
417
+ };
418
+ const settle = (status, reason) => {
419
+ log(`settle ${status}`, {
420
+ ...reason,
421
+ fields: Object.fromEntries(fields)
422
+ });
423
+ clearTimeout(watchdog);
424
+ for (const subscription of subscriptions) subscription.remove();
425
+ signal.removeEventListener("abort", onAbort);
426
+ resolve({
427
+ status,
428
+ ...reason,
429
+ fields,
430
+ aborted
431
+ });
432
+ };
433
+ const undo = async (reason) => {
434
+ log("undo (finishTransaction 0)", reason);
435
+ try {
436
+ const p1 = {
437
+ confirm: CLISITEF_CONFIRM.UNDO,
438
+ ...input.finish
439
+ };
440
+ await client.finishTransaction(p1);
441
+ settle("undone", reason);
442
+ } catch (error) {
443
+ settle("unresolved", {
444
+ ...reason,
445
+ message: errorMessage(error)
446
+ });
447
+ }
448
+ };
449
+ const close = async (reason) => {
450
+ log("close", reason);
451
+ if (closing) return;
452
+ closing = true;
453
+ if (!approved) await client.abortTransaction(CLISITEF_ABORT_FLAG).catch(() => 0);
454
+ if (captured || approved) return undo(reason);
455
+ settle("declined", reason);
456
+ };
457
+ function onAbort() {
458
+ log("signal aborted (payment deadline)");
459
+ aborted = true;
460
+ close({
461
+ code: null,
462
+ message: "Aborted by the payment deadline"
463
+ });
464
+ }
465
+ const reply = async (data) => {
466
+ log("reply", {
467
+ data,
468
+ skipped: closing
469
+ });
470
+ if (closing) return;
471
+ await client.continueTransaction(data);
472
+ };
473
+ const onData = async (event) => {
474
+ log("onData", {
475
+ ...event,
476
+ title
477
+ });
478
+ const { command, input: text } = event;
479
+ if (command === CLISITEF_COMMAND.STORE_FIELD) {
480
+ fields.set(event.fieldId, text);
481
+ if (CLISITEF_CAPTURE_FIELDS.has(event.fieldId)) captured = true;
482
+ return reply("");
483
+ }
484
+ if (MESSAGES.has(command)) {
485
+ if (text.trim()) ui.message({
486
+ text: text.trim(),
487
+ kind: "waiting"
488
+ });
489
+ return reply("");
490
+ }
491
+ if (command === CLISITEF_COMMAND.MENU_TITLE) {
492
+ title = text.trim();
493
+ return reply("");
494
+ }
495
+ if (command === CLISITEF_COMMAND.CLEAR_MENU_TITLE) {
496
+ title = "";
497
+ return reply("");
498
+ }
499
+ if (SILENT.has(command)) return reply("");
500
+ if (command === CLISITEF_COMMAND.CONFIRM) {
501
+ log("ui.confirm → in", { text: text.trim() || title });
502
+ const yes = await ui.confirm({ text: text.trim() || title });
503
+ log("ui.confirm ← out", { yes });
504
+ return reply(yes ? "0" : "1");
505
+ }
506
+ if (command === CLISITEF_COMMAND.MENU) {
507
+ const options = parseClisitefMenu(text);
508
+ const label = title || "Selecione uma opção";
509
+ log("ui.select → in", {
510
+ title: label,
511
+ options
512
+ });
513
+ const picked = await ui.select({
514
+ title: label,
515
+ options
516
+ });
517
+ log("ui.select ← out", { picked });
518
+ return reply(picked);
519
+ }
520
+ if (READS.has(command)) {
521
+ const label = text.trim() || title || "Informe o dado";
522
+ const max = event.maxLength > 0 ? event.maxLength : void 0;
523
+ const p1 = {
524
+ label,
525
+ type: inputType(command),
526
+ maxLength: max
527
+ };
528
+ log("ui.input → in", p1);
529
+ const value = await ui.input(p1);
530
+ log("ui.input ← out", { value });
531
+ return reply(value);
532
+ }
533
+ const message = `Unsupported CliSiTef command ${command}`;
534
+ return close({
535
+ code: `UNSUPPORTED_COMMAND_${command}`,
536
+ message
537
+ });
538
+ };
539
+ const confirm = () => {
540
+ const message = "CliSiTef did not close the confirmation";
541
+ const expire = () => {
542
+ log("confirmation watchdog expired", { skipped: closing });
543
+ if (closing) return;
544
+ closing = true;
545
+ undo({
546
+ code: null,
547
+ message
548
+ });
549
+ };
550
+ watchdog = setTimeout(expire, input.confirmationTimeoutMs);
551
+ const p1 = {
552
+ confirm: CLISITEF_CONFIRM.CONFIRM,
553
+ ...input.finish
554
+ };
555
+ log("confirm (finishTransaction 1)", {
556
+ ...p1,
557
+ watchdogMs: input.confirmationTimeoutMs
558
+ });
559
+ client.finishTransaction(p1).catch((error) => {
560
+ if (closing) return;
561
+ closing = true;
562
+ clearTimeout(watchdog);
563
+ undo({
564
+ code: errorCode(error),
565
+ message: errorMessage(error)
566
+ });
567
+ });
568
+ };
569
+ const onResult = async (event) => {
570
+ const { stage, resultCode: code } = event;
571
+ log("onTransactionResult", {
572
+ stage,
573
+ code,
574
+ skipped: closing
575
+ });
576
+ if (closing) return;
577
+ if (stage === 1 && code === CLISITEF_RESULT.OK) {
578
+ approved = true;
579
+ await input.onApproved(fields);
580
+ if (!closing) confirm();
581
+ return;
582
+ }
583
+ if (stage === 1) {
584
+ closing = true;
585
+ if (!(captured && CLISITEF_INCONCLUSIVE_CODES.has(code))) return settle("declined", {
586
+ code,
587
+ message: null
588
+ });
589
+ await client.abortTransaction(CLISITEF_ABORT_FLAG).catch(() => 0);
590
+ return undo({
591
+ code,
592
+ message: null
593
+ });
594
+ }
595
+ if (!approved) return;
596
+ closing = true;
597
+ if (code === CLISITEF_RESULT.OK) return settle("confirmed", {
598
+ code,
599
+ message: null
600
+ });
601
+ return undo({
602
+ code,
603
+ message: null
604
+ });
605
+ };
606
+ const enqueue = (handle) => {
607
+ const run = async () => {
608
+ await handle();
609
+ };
610
+ queue = queue.then(run).catch(async (error) => {
611
+ const reason = {
612
+ code: errorCode(error),
613
+ message: errorMessage(error)
614
+ };
615
+ log("handler threw", {
616
+ ...reason,
617
+ error
618
+ });
619
+ if (closing) return;
620
+ await close({
621
+ ...reason,
622
+ error
623
+ });
624
+ });
625
+ };
626
+ const begin = async () => {
627
+ log("begin", {
628
+ start: input.start,
629
+ finish: input.finish
630
+ });
631
+ if (signal.aborted) {
632
+ aborted = true;
633
+ return settle("declined", {
634
+ code: null,
635
+ message: "Aborted"
636
+ });
637
+ }
638
+ signal.addEventListener("abort", onAbort);
639
+ const handleData = (e) => enqueue(() => onData(e));
640
+ const handleResult = (e) => {
641
+ enqueue(() => onResult(e));
642
+ };
643
+ subscriptions.push(client.onData(handleData));
644
+ subscriptions.push(client.onTransactionResult(handleResult));
645
+ try {
646
+ const code = await client.startTransaction(input.start);
647
+ if (code === CLISITEF_RESULT.CONTINUE || closing) return;
648
+ closing = true;
649
+ settle("declined", {
650
+ code,
651
+ message: null
652
+ });
653
+ } catch (error) {
654
+ if (closing) return;
655
+ closing = true;
656
+ const reason = {
657
+ code: errorCode(error),
658
+ message: errorMessage(error)
659
+ };
660
+ settle("declined", reason);
661
+ }
662
+ };
663
+ begin();
664
+ return done;
665
+ };
666
+ //#endregion
667
+ //#region src/adapter.ts
668
+ /** Confirmed in the act (`finishTransaction(1)`), never captured later. */
669
+ const clisitefPinPadCapabilities = {
670
+ deferredCapture: false,
671
+ inPerson: true
672
+ };
673
+ const createLock = () => {
674
+ let tail = Promise.resolve();
675
+ return (task) => {
676
+ const run = tail.then(task, task);
677
+ tail = run.catch(() => void 0);
678
+ return run;
679
+ };
680
+ };
681
+ const failed = (record, reference, error, needsReview = false) => {
682
+ const base = record.metadata ?? { attempt_reference: reference };
683
+ const review = needsReview ? { needs_review: true } : {};
684
+ const metadata = {
685
+ ...base,
686
+ attempt_reference: reference,
687
+ error,
688
+ ...review
689
+ };
690
+ return {
691
+ status: "FAILED",
692
+ amount: record.amount,
693
+ metadata,
694
+ error
695
+ };
696
+ };
697
+ const paid = (record) => {
698
+ const reference = record.attemptReference;
699
+ const metadata = record.metadata ?? { attempt_reference: reference };
700
+ const details = record.details ?? null;
701
+ return {
702
+ status: "PAID",
703
+ amount: record.amount,
704
+ metadata,
705
+ details
706
+ };
707
+ };
708
+ const outcomeError = (outcome, attempt) => {
709
+ const provider = {
710
+ provider_message: outcome.message,
711
+ attempt
712
+ };
713
+ const code = outcome.code == null ? null : String(outcome.code);
714
+ const extra = {
715
+ ...provider,
716
+ provider_code: code
717
+ };
718
+ if (outcome.aborted) return createPaymentError("TIMEOUT", extra);
719
+ if (outcome.status === "unresolved") return createPaymentError("AMBIGUOUS", extra);
720
+ if (outcome.code === null) {
721
+ const message = outcome.message ?? void 0;
722
+ return createPaymentError("PROVIDER_UNAVAILABLE", {
723
+ ...extra,
724
+ retryable: false,
725
+ message
726
+ });
727
+ }
728
+ const fallback = typeof outcome.code === "number" && outcome.code > 0 ? "CARD_DECLINED" : "UNKNOWN";
729
+ return toPaymentError(clisitefPinPadErrors, {
730
+ code: outcome.code,
731
+ message: outcome.message,
732
+ attempt,
733
+ fallback
734
+ });
735
+ };
736
+ const debugContext = (ctx) => {
737
+ const { attemptReference, attempt, amount, config, deviceSettings } = ctx;
738
+ return {
739
+ attemptReference,
740
+ attempt,
741
+ amount,
742
+ config,
743
+ deviceSettings
744
+ };
745
+ };
746
+ const terminalError = (error) => {
747
+ const message = error instanceof Error ? error.message : String(error);
748
+ return {
749
+ ok: false,
750
+ message,
751
+ error: createPaymentError("TERMINAL_UNAVAILABLE", { message })
752
+ };
753
+ };
754
+ /**
755
+ * CliSiTef (Software Express) on the kiosk pinpad, Brazil. One adapter per
756
+ * modality payment (DEBIT / CREDIT / VOUCHER in `payments.config`). The
757
+ * pinpad waits on the customer, so the deadline is `timeoutMs` instead of
758
+ * the 30 s library default.
759
+ */
760
+ const clisitefPinPadPayments = (options = {}) => {
761
+ const client = options.client ?? clisitef;
762
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
763
+ const confirmationMs = options.confirmationTimeoutMs ?? 6e4;
764
+ const timeoutMs = options.timeoutMs ?? 18e4;
765
+ const lock = createLock();
766
+ const storageOf = (ctx) => {
767
+ return options.storage ?? ctx.storage;
768
+ };
769
+ const configure = async (settings) => {
770
+ const code = await client.configure(settings.configure);
771
+ clisitefDebug("pinpad.adapter.configure", "result", {
772
+ code,
773
+ settings
774
+ });
775
+ if (code === 0) return null;
776
+ client.invalidate();
777
+ return toPaymentError(clisitefPinPadErrors, {
778
+ code,
779
+ fallback: "TERMINAL_NOT_CONFIGURED"
780
+ });
781
+ };
782
+ /** Undoes a transaction a crash or a lost result left open. */
783
+ const recover = async (storage, record) => {
784
+ const { fiscalCoupon, fiscalDate, time } = record;
785
+ const count = client.getQttPendingTransactions;
786
+ const pending = await count(fiscalDate, fiscalCoupon).catch(() => null);
787
+ clisitefDebug("pinpad.adapter.recover", "pending", {
788
+ pending,
789
+ record
790
+ });
791
+ if (pending === null) return record;
792
+ let next;
793
+ if (pending > 0) {
794
+ const p1 = {
795
+ confirm: CLISITEF_CONFIRM.UNDO,
796
+ fiscalCoupon,
797
+ fiscalDate
798
+ };
799
+ if (await client.finishTransaction({
800
+ ...p1,
801
+ time
802
+ }).catch(() => -1) !== 0) return record;
803
+ const error = createPaymentError("PROVIDER_UNAVAILABLE", {
804
+ retryable: false,
805
+ message: "Pending CliSiTef transaction undone"
806
+ });
807
+ next = {
808
+ ...record,
809
+ state: "undone",
810
+ error
811
+ };
812
+ } else if (record.state === "approved") {
813
+ const error = createPaymentError("AMBIGUOUS");
814
+ next = {
815
+ ...record,
816
+ state: "review",
817
+ error
818
+ };
819
+ } else {
820
+ const error = createPaymentError("PROVIDER_UNAVAILABLE", {
821
+ retryable: false,
822
+ message: "CliSiTef transaction never approved"
823
+ });
824
+ next = {
825
+ ...record,
826
+ state: "undone",
827
+ error
828
+ };
829
+ }
830
+ clisitefDebug("pinpad.adapter.recover", `→ ${next.state}`, next);
831
+ await writeTransaction(storage, next);
832
+ return next;
833
+ };
834
+ /** Configures and resolves the open record of the device, if any. */
835
+ const prepare = async (ctx) => {
836
+ const settings = readClisitefPinPadSettings(ctx.config, ctx.deviceSettings);
837
+ const error = await configure(settings);
838
+ const storage = storageOf(ctx);
839
+ let record = await readTransaction(storage);
840
+ if (!error && record && isOpen(record)) record = await recover(storage, record);
841
+ clisitefDebug("pinpad.adapter.prepare", "done", {
842
+ error,
843
+ record
844
+ });
845
+ return {
846
+ settings,
847
+ error,
848
+ storage,
849
+ record
850
+ };
851
+ };
852
+ const fromRecord = (record, ctx) => {
853
+ const reference = ctx.attemptReference;
854
+ if (record.state === "confirmed") return paid(record);
855
+ if (record.state === "review") {
856
+ const error = record.error ?? createPaymentError("AMBIGUOUS");
857
+ return failed(record, reference, error, true);
858
+ }
859
+ if (record.state === "undone") {
860
+ const fallback = createPaymentError("UNKNOWN", { retryable: false });
861
+ return failed(record, reference, record.error ?? fallback);
862
+ }
863
+ const error = createPaymentError("TIMEOUT", { attempt: ctx.attempt });
864
+ return failed(record, reference, error);
865
+ };
866
+ const charge = async (ctx) => {
867
+ const amount = ctx.amount.value;
868
+ const reference = ctx.attemptReference;
869
+ const empty = { amount };
870
+ if (!client.available) {
871
+ const error = createPaymentError("TERMINAL_UNAVAILABLE");
872
+ return failed(empty, reference, error);
873
+ }
874
+ const prepared = await prepare(ctx);
875
+ const { settings, storage, record: previous } = prepared;
876
+ if (prepared.error) return failed(empty, reference, prepared.error);
877
+ if (previous?.attemptReference === reference && previous?.state === "confirmed") return paid(previous);
878
+ if (previous && isOpen(previous)) {
879
+ const error = createPaymentError("TERMINAL_BUSY", { attempt: ctx.attempt });
880
+ return failed(empty, reference, error);
881
+ }
882
+ const date = now();
883
+ const fiscal = clisitefFiscalDateTime(date, options.timeZone);
884
+ const fiscalCoupon = String(date.getTime());
885
+ const finish = {
886
+ fiscalCoupon,
887
+ ...fiscal
888
+ };
889
+ const record = {
890
+ attemptReference: reference,
891
+ ...finish,
892
+ amount,
893
+ startedAt: date.getTime(),
894
+ state: "started"
895
+ };
896
+ await writeTransaction(storage, record);
897
+ const s1 = {
898
+ modality: CLISITEF_MODALITY[settings.modality],
899
+ value: formatClisitefAmount(amount),
900
+ ...finish
901
+ };
902
+ const s2 = { operator: settings.operator };
903
+ const start = {
904
+ ...s1,
905
+ ...s2,
906
+ restrictions: settings.restrictions
907
+ };
908
+ const meta = {
909
+ modality: settings.modality,
910
+ fiscalCoupon
911
+ };
912
+ const m1 = {
913
+ terminalNumber: settings.terminalNumber,
914
+ ...meta
915
+ };
916
+ const metadataOf = (fields) => {
917
+ const m2 = {
918
+ attemptReference: reference,
919
+ fields
920
+ };
921
+ return clisitefMetadata({
922
+ ...m1,
923
+ ...m2,
924
+ fiscalDate: fiscal.fiscalDate
925
+ });
926
+ };
927
+ const onApproved = async (fields) => {
928
+ const metadata = metadataOf(fields);
929
+ await writeTransaction(storage, {
930
+ ...record,
931
+ state: "approved",
932
+ metadata
933
+ });
934
+ };
935
+ const t1 = {
936
+ client,
937
+ ui: ctx.ui,
938
+ signal: ctx.signal,
939
+ start,
940
+ finish
941
+ };
942
+ const t2 = {
943
+ confirmationTimeoutMs: confirmationMs,
944
+ onApproved
945
+ };
946
+ const outcome = await runClisitefTransaction({
947
+ ...t1,
948
+ ...t2
949
+ });
950
+ const fields = Object.fromEntries(outcome.fields);
951
+ clisitefDebug("pinpad.adapter.charge", "outcome", {
952
+ ...outcome,
953
+ fields
954
+ });
955
+ const metadata = metadataOf(outcome.fields);
956
+ if (outcome.status === "confirmed") {
957
+ const details = clisitefDetails(metadata);
958
+ const confirmed = {
959
+ ...record,
960
+ metadata,
961
+ details,
962
+ state: "confirmed"
963
+ };
964
+ await writeTransaction(storage, confirmed);
965
+ return paid(confirmed);
966
+ }
967
+ if (outcome.code === -1) client.invalidate();
968
+ const error = outcomeError(outcome, ctx.attempt);
969
+ const unresolved = outcome.status === "unresolved";
970
+ const state = unresolved ? "approved" : "undone";
971
+ const closed = {
972
+ ...record,
973
+ state,
974
+ metadata,
975
+ error
976
+ };
977
+ await writeTransaction(storage, closed);
978
+ if (outcome.error) throw outcome.error;
979
+ return failed(closed, reference, error, unresolved);
980
+ };
981
+ const query = async (ctx, result) => {
982
+ const reference = result.metadata.attempt_reference;
983
+ const scoped = {
984
+ ...ctx,
985
+ attemptReference: reference
986
+ };
987
+ const { record, error } = await prepare(scoped);
988
+ if (record?.attemptReference !== reference) {
989
+ const missing = createPaymentError("UNKNOWN", {
990
+ retryable: false,
991
+ message: "No CliSiTef transaction for this attempt"
992
+ });
993
+ return failed(result, reference, missing);
994
+ }
995
+ if (error && isOpen(record)) return failed(record, reference, error);
996
+ return fromRecord(record, scoped);
997
+ };
998
+ const cancel = async (ctx, result) => {
999
+ const next = await query(ctx, result);
1000
+ if (next.status === "PAID") {
1001
+ const error = createPaymentError("REVERSAL_NOT_ALLOWED");
1002
+ return {
1003
+ ...next,
1004
+ status: "FAILED",
1005
+ error
1006
+ };
1007
+ }
1008
+ const code = next.error?.code;
1009
+ if (!(code !== "AMBIGUOUS" && code !== "TIMEOUT")) return next;
1010
+ return {
1011
+ ...next,
1012
+ status: "CANCELLED",
1013
+ error: void 0
1014
+ };
1015
+ };
1016
+ const operation = (run, name = "terminal") => {
1017
+ return (ctx) => {
1018
+ const scope = `pinpad.terminal.${name}`;
1019
+ const input = {
1020
+ config: ctx.config,
1021
+ deviceSettings: ctx.deviceSettings
1022
+ };
1023
+ const traced = () => clisitefDebugCall(scope, input, task);
1024
+ const task = async () => {
1025
+ try {
1026
+ const data = await run(ctx);
1027
+ return {
1028
+ ok: true,
1029
+ message: typeof data === "string" ? data : null,
1030
+ data: data && typeof data === "object" ? data : void 0
1031
+ };
1032
+ } catch (error) {
1033
+ return terminalError(error);
1034
+ }
1035
+ };
1036
+ return lock(traced);
1037
+ };
1038
+ };
1039
+ const terminal = {
1040
+ init: operation(async (ctx) => {
1041
+ client.invalidate();
1042
+ const s1 = readClisitefPinPadSettings(ctx.config, ctx.deviceSettings);
1043
+ const error = await configure(s1);
1044
+ if (error) throw new Error(error.message);
1045
+ return "CliSiTef configured";
1046
+ }, "init"),
1047
+ test: operation(async () => {
1048
+ return `CliSiTef ${await client.getVersion()}`;
1049
+ }, "test"),
1050
+ status: operation(async () => client.getHealth() ?? {}, "status"),
1051
+ extra: [{
1052
+ key: "submit_pending_messages",
1053
+ label: "Send pending messages",
1054
+ run: operation(async () => {
1055
+ return `Pending messages sent (${await client.submitPendingMessages()})`;
1056
+ }, "submit_pending_messages")
1057
+ }, {
1058
+ key: "release_pinpad",
1059
+ label: "Release pinpad",
1060
+ run: operation(async () => {
1061
+ await client.closePinPad();
1062
+ await client.disconnectSitef();
1063
+ client.invalidate();
1064
+ return "Pinpad released";
1065
+ }, "release_pinpad")
1066
+ }]
1067
+ };
1068
+ return {
1069
+ provider: "clisitef",
1070
+ method: "PIN_PAD",
1071
+ deviceFields: clisitefPinPadDeviceFields,
1072
+ errors: clisitefPinPadErrors,
1073
+ capabilities: clisitefPinPadCapabilities,
1074
+ policy: { timeout_ms: timeoutMs },
1075
+ start: (ctx) => {
1076
+ const run = () => charge(ctx);
1077
+ const traced = () => clisitefDebugCall("pinpad.start", debugContext(ctx), run);
1078
+ return lock(traced);
1079
+ },
1080
+ query: (ctx, result) => {
1081
+ const input = {
1082
+ ctx: debugContext(ctx),
1083
+ result
1084
+ };
1085
+ const run = () => query(ctx, result);
1086
+ return lock(() => clisitefDebugCall("pinpad.query", input, run));
1087
+ },
1088
+ cancel: (ctx, result) => {
1089
+ const input = {
1090
+ ctx: debugContext(ctx),
1091
+ result
1092
+ };
1093
+ const run = () => cancel(ctx, result);
1094
+ return lock(() => clisitefDebugCall("pinpad.cancel", input, run));
1095
+ },
1096
+ reversalFor: (result) => result.status === "PAID" ? "manual" : "cancel",
1097
+ terminal
1098
+ };
1099
+ };
1100
+ //#endregion
1101
+ export { CLISITEF_CANCEL_CODES, CLISITEF_CAPTURE_FIELDS, CLISITEF_COMMAND, CLISITEF_CONFIRMATION_TIMEOUT_MS, CLISITEF_DEFAULT_OPERATOR, CLISITEF_FIELD, CLISITEF_INCONCLUSIVE_CODES, CLISITEF_NO, CLISITEF_PAYMENT_TIMEOUT_MS, CLISITEF_PINPAD_TYPES, CLISITEF_STORAGE_KEY, CLISITEF_YES, clisitefDetails, clisitefLast4, clisitefMetadata, clisitefPinPadCapabilities, clisitefPinPadDeviceFields, clisitefPinPadErrors, clisitefPinPadPayments, isOpen, readClisitefPinPadSettings, readTransaction, runClisitefTransaction, writeTransaction };
package/package.json CHANGED
@@ -1,6 +1,61 @@
1
1
  {
2
2
  "name": "@fire-spark/payments-clisitef-pinpad-rn",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.7.1",
4
+ "description": "Fire Spark CliSiTef (Software Express) pinpad adapter for Brazil: debit, credit and voucher on Android kiosks",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "dependencies": {
20
+ "@fire-spark/payments": "^0.7.1",
21
+ "@fire-spark/payments-clisitef-client": "^0.7.1",
22
+ "@fire-spark/types": "^0.7.1"
23
+ },
24
+ "devDependencies": {
25
+ "tsdown": "^0.22.14",
26
+ "typescript": "^7.0.2",
27
+ "vitest": "^2.1.8"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "tsdown": {
33
+ "entry": [
34
+ "src/index.ts"
35
+ ],
36
+ "format": [
37
+ "esm"
38
+ ],
39
+ "platform": "neutral",
40
+ "dts": {
41
+ "tsconfig": "tsconfig.build.json"
42
+ },
43
+ "clean": true,
44
+ "deps": {
45
+ "neverBundle": [
46
+ "@fire-spark/payments",
47
+ "@fire-spark/payments-clisitef-client",
48
+ "@fire-spark/types"
49
+ ]
50
+ }
51
+ },
52
+ "scripts": {
53
+ "build": "tsdown",
54
+ "dev": "tsdown --watch",
55
+ "check-types": "tsc --noEmit",
56
+ "test:utils": "vitest run",
57
+ "lint": "oxlint ./src --max-warnings=0",
58
+ "format": "oxfmt ./src",
59
+ "check-format": "oxfmt --check ./src"
60
+ }
6
61
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.