@fire-spark/payments 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/dist/index.d.ts +397 -0
- package/dist/index.js +386 -0
- package/package.json +54 -3
- package/README.md +0 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { OrderTotals, PaymentMethod, PaymentProvider } from "@fire-spark/types";
|
|
2
|
+
//#region src/errors.d.ts
|
|
3
|
+
type PaymentErrorReason = "timeout" | "network" | "declined" | "provider" | "ambiguous" | "user";
|
|
4
|
+
type ErrorDefaults = {
|
|
5
|
+
reason: PaymentErrorReason;
|
|
6
|
+
retryable: boolean;
|
|
7
|
+
message: string;
|
|
8
|
+
};
|
|
9
|
+
/** Closed list of Spark payment error codes with their defaults. */
|
|
10
|
+
declare const PAYMENT_ERROR_DEFAULTS: {
|
|
11
|
+
readonly CARD_DECLINED: ErrorDefaults;
|
|
12
|
+
readonly INSUFFICIENT_FUNDS: ErrorDefaults;
|
|
13
|
+
readonly EXPIRED_CARD: ErrorDefaults;
|
|
14
|
+
readonly INVALID_CARD: ErrorDefaults;
|
|
15
|
+
readonly INVALID_PIN: ErrorDefaults;
|
|
16
|
+
readonly LIMIT_EXCEEDED: ErrorDefaults;
|
|
17
|
+
readonly FRAUD_SUSPECTED: ErrorDefaults;
|
|
18
|
+
readonly WALLET_REJECTED: ErrorDefaults;
|
|
19
|
+
readonly WALLET_EXPIRED: ErrorDefaults;
|
|
20
|
+
readonly INSUFFICIENT_BALANCE: ErrorDefaults;
|
|
21
|
+
readonly TRANSFER_NOT_FOUND: ErrorDefaults;
|
|
22
|
+
readonly TRANSFER_ALREADY_USED: ErrorDefaults;
|
|
23
|
+
readonly TRANSFER_AMOUNT_MISMATCH: ErrorDefaults;
|
|
24
|
+
readonly FINANCING_REJECTED: ErrorDefaults;
|
|
25
|
+
readonly FINANCING_EXPIRED: ErrorDefaults;
|
|
26
|
+
readonly CANCELLED_BY_USER: {
|
|
27
|
+
readonly reason: "user";
|
|
28
|
+
readonly retryable: false;
|
|
29
|
+
readonly message: "Payment cancelled";
|
|
30
|
+
};
|
|
31
|
+
readonly USER_TIMEOUT: {
|
|
32
|
+
readonly reason: "user";
|
|
33
|
+
readonly retryable: false;
|
|
34
|
+
readonly message: "No response from the customer";
|
|
35
|
+
};
|
|
36
|
+
readonly TERMINAL_UNAVAILABLE: ErrorDefaults;
|
|
37
|
+
readonly TERMINAL_BUSY: ErrorDefaults;
|
|
38
|
+
readonly TERMINAL_NOT_CONFIGURED: ErrorDefaults;
|
|
39
|
+
readonly TERMINAL_NEEDS_INIT: ErrorDefaults;
|
|
40
|
+
readonly BATCH_CLOSE_REQUIRED: ErrorDefaults;
|
|
41
|
+
readonly INVALID_CREDENTIALS: ErrorDefaults;
|
|
42
|
+
readonly INVALID_REQUEST: ErrorDefaults;
|
|
43
|
+
readonly PROVIDER_UNAVAILABLE: ErrorDefaults;
|
|
44
|
+
readonly NETWORK_ERROR: {
|
|
45
|
+
readonly reason: "network";
|
|
46
|
+
readonly retryable: true;
|
|
47
|
+
readonly message: "Network error";
|
|
48
|
+
};
|
|
49
|
+
readonly TIMEOUT: {
|
|
50
|
+
readonly reason: "timeout";
|
|
51
|
+
readonly retryable: true;
|
|
52
|
+
readonly message: "Payment timed out";
|
|
53
|
+
};
|
|
54
|
+
readonly REVERSAL_NOT_ALLOWED: ErrorDefaults;
|
|
55
|
+
readonly MANUAL_REVERSAL_REQUIRED: ErrorDefaults;
|
|
56
|
+
readonly AMBIGUOUS: {
|
|
57
|
+
readonly reason: "ambiguous";
|
|
58
|
+
readonly retryable: false;
|
|
59
|
+
readonly message: "Payment result unknown";
|
|
60
|
+
};
|
|
61
|
+
readonly UNKNOWN: ErrorDefaults;
|
|
62
|
+
};
|
|
63
|
+
type PaymentErrorCode = keyof typeof PAYMENT_ERROR_DEFAULTS;
|
|
64
|
+
declare const PAYMENT_ERROR_CODES: PaymentErrorCode[];
|
|
65
|
+
type StandardPaymentError = {
|
|
66
|
+
code: PaymentErrorCode;
|
|
67
|
+
reason: PaymentErrorReason;
|
|
68
|
+
retryable: boolean;
|
|
69
|
+
message: string;
|
|
70
|
+
provider_code: string | null;
|
|
71
|
+
provider_message: string | null;
|
|
72
|
+
attempt: number;
|
|
73
|
+
elapsed_ms: number;
|
|
74
|
+
};
|
|
75
|
+
type PaymentErrorEntry = {
|
|
76
|
+
code: PaymentErrorCode;
|
|
77
|
+
message?: string;
|
|
78
|
+
retryable?: boolean;
|
|
79
|
+
reason?: PaymentErrorReason;
|
|
80
|
+
/** Ficha anchor, e.g. `docs/payments/transbank/pinpad.md#errores`. */
|
|
81
|
+
source: string;
|
|
82
|
+
/** Provider version since the code applies. */
|
|
83
|
+
since?: string;
|
|
84
|
+
};
|
|
85
|
+
/** Keyed by the provider code exactly as returned, as a string. */
|
|
86
|
+
type PaymentErrorDictionary = Readonly<Record<string, PaymentErrorEntry>>;
|
|
87
|
+
declare const definePaymentErrors: <T extends Record<string, PaymentErrorEntry>>(dictionary: T) => PaymentErrorDictionary & T;
|
|
88
|
+
type RawPaymentError = {
|
|
89
|
+
code?: string | number | null;
|
|
90
|
+
message?: string | null;
|
|
91
|
+
/** Spark code to use when the provider code is not in the dictionary. */
|
|
92
|
+
fallback?: PaymentErrorCode;
|
|
93
|
+
attempt?: number;
|
|
94
|
+
elapsed_ms?: number;
|
|
95
|
+
};
|
|
96
|
+
declare const createPaymentError: (code: PaymentErrorCode, extra?: Partial<Omit<StandardPaymentError, "code">>) => StandardPaymentError;
|
|
97
|
+
/** Translates a provider error with its dictionary; unknown codes are `UNKNOWN`. */
|
|
98
|
+
declare const toPaymentError: (dictionary: PaymentErrorDictionary, raw: RawPaymentError) => StandardPaymentError;
|
|
99
|
+
/** Thrown by adapters and by `PaymentUI` (e.g. `CANCELLED_BY_USER`). */
|
|
100
|
+
declare class PaymentError extends Error {
|
|
101
|
+
readonly error: StandardPaymentError;
|
|
102
|
+
constructor(error: StandardPaymentError);
|
|
103
|
+
}
|
|
104
|
+
declare const isPaymentError: (value: unknown) => value is PaymentError;
|
|
105
|
+
/** "Tarjeta rechazada (Transbank -1)" style label. */
|
|
106
|
+
declare const formatPaymentError: (error: StandardPaymentError, providerName?: string) => string;
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/types.d.ts
|
|
109
|
+
type PaymentCollectAt = "DEVICE" | "KIOSK" | "COUNTER" | "DOOR" | "OTHER";
|
|
110
|
+
type PaymentIntentStatus = "IDLE" | "PENDING" | "PAID" | "FAILED" | "CANCELLED" | "REFUNDED";
|
|
111
|
+
type PaymentEnvironment = "sandbox" | "production";
|
|
112
|
+
/** Amount of one intent, already in the charge currency. */
|
|
113
|
+
type PaymentAmount = {
|
|
114
|
+
value: number;
|
|
115
|
+
minor: number;
|
|
116
|
+
currency: string;
|
|
117
|
+
decimals: number;
|
|
118
|
+
};
|
|
119
|
+
type TaxBreakdown = {
|
|
120
|
+
rate: number;
|
|
121
|
+
base: number;
|
|
122
|
+
tax: number;
|
|
123
|
+
};
|
|
124
|
+
/** One field the device must fill in `settings_overrides.payment_settings`. */
|
|
125
|
+
type DeviceField = {
|
|
126
|
+
key: string;
|
|
127
|
+
label: string;
|
|
128
|
+
type: "text" | "number" | "password" | "select" | "boolean";
|
|
129
|
+
required?: boolean;
|
|
130
|
+
secret?: boolean;
|
|
131
|
+
options?: string[];
|
|
132
|
+
visible_if?: Record<string, unknown>;
|
|
133
|
+
};
|
|
134
|
+
type PaymentAction = {
|
|
135
|
+
type: "qr";
|
|
136
|
+
payload: string;
|
|
137
|
+
format: "text" | "image";
|
|
138
|
+
expiresAt: string | null;
|
|
139
|
+
} | {
|
|
140
|
+
type: "redirect";
|
|
141
|
+
url: string;
|
|
142
|
+
expiresAt: string;
|
|
143
|
+
} | {
|
|
144
|
+
type: "deeplink";
|
|
145
|
+
url: string;
|
|
146
|
+
expiresAt: string;
|
|
147
|
+
} | {
|
|
148
|
+
type: "instructions";
|
|
149
|
+
title: string;
|
|
150
|
+
fields: {
|
|
151
|
+
label: string;
|
|
152
|
+
value: string;
|
|
153
|
+
}[];
|
|
154
|
+
amount: PaymentAmount;
|
|
155
|
+
} | {
|
|
156
|
+
type: "terminal";
|
|
157
|
+
text: string;
|
|
158
|
+
};
|
|
159
|
+
/** Implemented once per template (modal, sheet or dialog). */
|
|
160
|
+
type PaymentUI = {
|
|
161
|
+
message(input: {
|
|
162
|
+
text: string;
|
|
163
|
+
kind: "info" | "waiting" | "error";
|
|
164
|
+
}): void;
|
|
165
|
+
select<T>(input: {
|
|
166
|
+
title: string;
|
|
167
|
+
options: {
|
|
168
|
+
label: string;
|
|
169
|
+
value: T;
|
|
170
|
+
}[];
|
|
171
|
+
}): Promise<T>;
|
|
172
|
+
input(input: {
|
|
173
|
+
label: string;
|
|
174
|
+
type: "text" | "number" | "password";
|
|
175
|
+
mask?: string;
|
|
176
|
+
maxLength?: number;
|
|
177
|
+
}): Promise<string>;
|
|
178
|
+
confirm(input: {
|
|
179
|
+
text: string;
|
|
180
|
+
}): Promise<boolean>;
|
|
181
|
+
action(input: PaymentAction): void;
|
|
182
|
+
};
|
|
183
|
+
/** Persistent key-value store per device. */
|
|
184
|
+
type PaymentStorage = {
|
|
185
|
+
get(key: string): Promise<string | null>;
|
|
186
|
+
set(key: string, value: string): Promise<void>;
|
|
187
|
+
remove(key: string): Promise<void>;
|
|
188
|
+
};
|
|
189
|
+
/** Calls `POST /storefront/payments/:payment_id/operations/:operation`. */
|
|
190
|
+
type PaymentServer = {
|
|
191
|
+
call<T>(operation: string, input: Record<string, unknown>): Promise<T>;
|
|
192
|
+
};
|
|
193
|
+
type PaymentPolicy = {
|
|
194
|
+
timeout_ms: number;
|
|
195
|
+
interaction_timeout_ms: number;
|
|
196
|
+
max_attempts: number;
|
|
197
|
+
poll_interval_ms: number;
|
|
198
|
+
qr_ttl_ms: number;
|
|
199
|
+
batch_close_at: string | null;
|
|
200
|
+
};
|
|
201
|
+
type PaymentCustomer = {
|
|
202
|
+
document: string | null;
|
|
203
|
+
documentType: string | null;
|
|
204
|
+
phone: string | null;
|
|
205
|
+
};
|
|
206
|
+
type PaymentContext = {
|
|
207
|
+
paymentId: string;
|
|
208
|
+
order: {
|
|
209
|
+
id: string;
|
|
210
|
+
currency: string;
|
|
211
|
+
totals: OrderTotals;
|
|
212
|
+
};
|
|
213
|
+
customer: PaymentCustomer;
|
|
214
|
+
amount: PaymentAmount;
|
|
215
|
+
taxes: TaxBreakdown[];
|
|
216
|
+
collectAt: PaymentCollectAt;
|
|
217
|
+
attemptReference: string;
|
|
218
|
+
attempt: number;
|
|
219
|
+
environment: PaymentEnvironment;
|
|
220
|
+
config: Record<string, unknown>;
|
|
221
|
+
deviceSettings: Record<string, unknown>;
|
|
222
|
+
policy: PaymentPolicy;
|
|
223
|
+
ui: PaymentUI;
|
|
224
|
+
storage: PaymentStorage;
|
|
225
|
+
server: PaymentServer;
|
|
226
|
+
signal: AbortSignal;
|
|
227
|
+
};
|
|
228
|
+
type TerminalContext = Omit<PaymentContext, "order" | "amount" | "taxes" | "customer" | "collectAt">;
|
|
229
|
+
type TerminalResult = {
|
|
230
|
+
ok: boolean;
|
|
231
|
+
message: string | null;
|
|
232
|
+
error?: StandardPaymentError;
|
|
233
|
+
data?: Record<string, unknown>;
|
|
234
|
+
};
|
|
235
|
+
type TerminalOperation = (ctx: TerminalContext) => Promise<TerminalResult>;
|
|
236
|
+
type TerminalOperations = {
|
|
237
|
+
init?: TerminalOperation;
|
|
238
|
+
test?: TerminalOperation;
|
|
239
|
+
closeBatch?: TerminalOperation;
|
|
240
|
+
status?: TerminalOperation;
|
|
241
|
+
extra?: {
|
|
242
|
+
key: string;
|
|
243
|
+
label: string;
|
|
244
|
+
run: TerminalOperation;
|
|
245
|
+
}[];
|
|
246
|
+
};
|
|
247
|
+
type PaymentCardType = "DEBIT" | "CREDIT" | "PREPAID";
|
|
248
|
+
type PaymentEntryMode = "CHIP" | "CONTACTLESS" | "SWIPE" | "MANUAL" | "QR";
|
|
249
|
+
type PaymentMetadataPayment = {
|
|
250
|
+
provider_transaction_id: string | null;
|
|
251
|
+
authorization_code: string | null;
|
|
252
|
+
reference: string | null;
|
|
253
|
+
card_type: PaymentCardType | null;
|
|
254
|
+
brand: string | null;
|
|
255
|
+
last4: string | null;
|
|
256
|
+
entry_mode: PaymentEntryMode | null;
|
|
257
|
+
installments: {
|
|
258
|
+
count: number;
|
|
259
|
+
type: string | null;
|
|
260
|
+
} | null;
|
|
261
|
+
acquirer: string | null;
|
|
262
|
+
acquirer_tax_id: string | null;
|
|
263
|
+
terminal_id: string | null;
|
|
264
|
+
merchant_id: string | null;
|
|
265
|
+
batch: string | null;
|
|
266
|
+
ticket_number: string | null;
|
|
267
|
+
transaction_date: string | null;
|
|
268
|
+
account_type: string | null;
|
|
269
|
+
charged_amount: number | null;
|
|
270
|
+
charged_currency: string | null;
|
|
271
|
+
exchange_rate: number | null;
|
|
272
|
+
financed_amount: number | null;
|
|
273
|
+
};
|
|
274
|
+
/** Standard sections of `payment_intent.metadata`. */
|
|
275
|
+
type PaymentMetadata = {
|
|
276
|
+
attempt_reference: string;
|
|
277
|
+
payment?: Partial<PaymentMetadataPayment>;
|
|
278
|
+
receipt?: {
|
|
279
|
+
customer: string | null;
|
|
280
|
+
merchant: string | null;
|
|
281
|
+
format: "text" | "escpos";
|
|
282
|
+
};
|
|
283
|
+
action?: PaymentAction;
|
|
284
|
+
confirm_after_inject?: boolean;
|
|
285
|
+
needs_review?: boolean;
|
|
286
|
+
error?: StandardPaymentError;
|
|
287
|
+
provider?: Record<string, unknown>;
|
|
288
|
+
};
|
|
289
|
+
/** What an adapter returns from every operation. */
|
|
290
|
+
type PaymentResult = {
|
|
291
|
+
status: PaymentIntentStatus;
|
|
292
|
+
/** Amount this result covers, in the order currency. */
|
|
293
|
+
amount: number;
|
|
294
|
+
metadata: PaymentMetadata;
|
|
295
|
+
error?: StandardPaymentError;
|
|
296
|
+
};
|
|
297
|
+
type PaymentReversal = "cancel" | "refund" | "manual";
|
|
298
|
+
type PaymentOperation = (ctx: PaymentContext, result: PaymentResult) => Promise<PaymentResult>;
|
|
299
|
+
type PaymentAdapter = {
|
|
300
|
+
provider: PaymentProvider;
|
|
301
|
+
method: PaymentMethod;
|
|
302
|
+
deviceFields?: DeviceField[];
|
|
303
|
+
start(ctx: PaymentContext): Promise<PaymentResult>;
|
|
304
|
+
query?: PaymentOperation;
|
|
305
|
+
confirm?: PaymentOperation;
|
|
306
|
+
cancel?: PaymentOperation;
|
|
307
|
+
refund?: PaymentOperation;
|
|
308
|
+
reversalFor?(result: PaymentResult, now: Date): PaymentReversal;
|
|
309
|
+
terminal?: TerminalOperations;
|
|
310
|
+
errors: PaymentErrorDictionary;
|
|
311
|
+
};
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region src/amount.d.ts
|
|
314
|
+
/** ISO 4217 minor units; anything not listed uses 2. */
|
|
315
|
+
declare const CURRENCY_DECIMALS: Readonly<Record<string, number>>;
|
|
316
|
+
declare const currencyDecimals: (currency: string) => number;
|
|
317
|
+
declare const roundTo: (value: number, decimals: number) => number;
|
|
318
|
+
declare const toPaymentAmount: (value: number, currency: string) => PaymentAmount;
|
|
319
|
+
type ChargeConversion = {
|
|
320
|
+
amount: PaymentAmount;
|
|
321
|
+
charged_amount: number;
|
|
322
|
+
charged_currency: string;
|
|
323
|
+
exchange_rate: number;
|
|
324
|
+
};
|
|
325
|
+
/** Converts an order-currency value to the charge currency at a fixed rate. */
|
|
326
|
+
declare const toChargeAmount: (input: {
|
|
327
|
+
value: number;
|
|
328
|
+
orderCurrency: string;
|
|
329
|
+
chargeCurrency?: string | null;
|
|
330
|
+
rate?: number | null;
|
|
331
|
+
}) => ChargeConversion;
|
|
332
|
+
/** Tolerance used when comparing amounts in a currency. */
|
|
333
|
+
declare const amountTolerance: (currency: string) => number;
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/device.d.ts
|
|
336
|
+
/** Required, visible device fields that are still empty on this device. */
|
|
337
|
+
declare const missingDeviceFields: (fields: DeviceField[] | null | undefined, settings: Record<string, unknown> | null | undefined) => DeviceField[];
|
|
338
|
+
/** Default when the adapter has no `reversalFor`. */
|
|
339
|
+
declare const defaultReversalFor: (result: PaymentResult) => PaymentReversal;
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region src/policy.d.ts
|
|
342
|
+
declare const DEFAULT_PAYMENT_POLICY: PaymentPolicy;
|
|
343
|
+
/**
|
|
344
|
+
* Library default, then `payment_methods.config` (its `runtime` or root
|
|
345
|
+
* `qr_ttl_ms`), then `payments.config.runtime`.
|
|
346
|
+
*/
|
|
347
|
+
declare const resolvePaymentPolicy: (input: {
|
|
348
|
+
methodConfig?: Record<string, unknown> | null;
|
|
349
|
+
paymentConfig?: Record<string, unknown> | null;
|
|
350
|
+
}) => PaymentPolicy;
|
|
351
|
+
/** Production rows use the provider's production unless the config overrides it. */
|
|
352
|
+
declare const resolveProviderEnvironment: (input: {
|
|
353
|
+
isProduction: boolean;
|
|
354
|
+
paymentConfig?: Record<string, unknown> | null;
|
|
355
|
+
}) => PaymentEnvironment;
|
|
356
|
+
/** True the first time the device is free after `batch_close_at` each local day. */
|
|
357
|
+
declare const isBatchCloseDue: (input: {
|
|
358
|
+
batchCloseAt: string | null;
|
|
359
|
+
lastClosedDay: string | null;
|
|
360
|
+
now: Date;
|
|
361
|
+
}) => boolean;
|
|
362
|
+
declare const batchCloseDay: (now: Date) => string;
|
|
363
|
+
//#endregion
|
|
364
|
+
//#region src/reference.d.ts
|
|
365
|
+
/** ULID per attempt; sent to the provider as merchant/idempotency reference. */
|
|
366
|
+
declare const createAttemptReference: (now?: number) => string;
|
|
367
|
+
//#endregion
|
|
368
|
+
//#region src/taxes.d.ts
|
|
369
|
+
type TaxLine = {
|
|
370
|
+
type?: string;
|
|
371
|
+
tax_rate: number | null;
|
|
372
|
+
subtotal: number;
|
|
373
|
+
discount_total: number;
|
|
374
|
+
tax_total: number;
|
|
375
|
+
};
|
|
376
|
+
/**
|
|
377
|
+
* Tax breakdown by rate from the order lines (each line counted once, without
|
|
378
|
+
* its children). `share` prorates it when the intent covers part of the order.
|
|
379
|
+
*/
|
|
380
|
+
declare const taxBreakdown: (lines: TaxLine[], options?: {
|
|
381
|
+
share?: number;
|
|
382
|
+
decimals?: number;
|
|
383
|
+
}) => TaxBreakdown[];
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region src/terminal.d.ts
|
|
386
|
+
/**
|
|
387
|
+
* Serializes everything that touches one terminal (charge, query, batch close)
|
|
388
|
+
* by `payment_id`.
|
|
389
|
+
*/
|
|
390
|
+
declare const withTerminalLock: <T>(paymentId: string, task: () => Promise<T>) => Promise<T>;
|
|
391
|
+
declare const isTerminalLocked: (paymentId: string) => boolean;
|
|
392
|
+
/** Keys are prefixed with the `payment_id`. */
|
|
393
|
+
declare const scopedPaymentStorage: (storage: PaymentStorage, paymentId: string) => PaymentStorage;
|
|
394
|
+
/** For tests and web previews; apps provide AsyncStorage or localStorage. */
|
|
395
|
+
declare const memoryPaymentStorage: () => PaymentStorage;
|
|
396
|
+
//#endregion
|
|
397
|
+
export { CURRENCY_DECIMALS, ChargeConversion, DEFAULT_PAYMENT_POLICY, type DeviceField, PAYMENT_ERROR_CODES, PAYMENT_ERROR_DEFAULTS, type PaymentAction, type PaymentAdapter, type PaymentAmount, type PaymentCardType, type PaymentCollectAt, type PaymentContext, type PaymentCustomer, type PaymentEntryMode, type PaymentEnvironment, PaymentError, PaymentErrorCode, PaymentErrorDictionary, PaymentErrorEntry, PaymentErrorReason, type PaymentIntentStatus, type PaymentMetadata, type PaymentMetadataPayment, type PaymentOperation, type PaymentPolicy, type PaymentResult, type PaymentReversal, type PaymentServer, type PaymentStorage, type PaymentUI, RawPaymentError, StandardPaymentError, type TaxBreakdown, TaxLine, type TerminalContext, type TerminalOperation, type TerminalOperations, type TerminalResult, amountTolerance, batchCloseDay, createAttemptReference, createPaymentError, currencyDecimals, defaultReversalFor, definePaymentErrors, formatPaymentError, isBatchCloseDue, isPaymentError, isTerminalLocked, memoryPaymentStorage, missingDeviceFields, resolvePaymentPolicy, resolveProviderEnvironment, roundTo, scopedPaymentStorage, taxBreakdown, toChargeAmount, toPaymentAmount, toPaymentError, withTerminalLock };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
//#region src/amount.ts
|
|
2
|
+
/** ISO 4217 minor units; anything not listed uses 2. */
|
|
3
|
+
const CURRENCY_DECIMALS = {
|
|
4
|
+
CLP: 0,
|
|
5
|
+
PYG: 0,
|
|
6
|
+
JPY: 0,
|
|
7
|
+
COP: 2,
|
|
8
|
+
USD: 2,
|
|
9
|
+
BRL: 2,
|
|
10
|
+
PEN: 2,
|
|
11
|
+
ARS: 2,
|
|
12
|
+
VES: 2,
|
|
13
|
+
MXN: 2,
|
|
14
|
+
EUR: 2
|
|
15
|
+
};
|
|
16
|
+
const currencyDecimals = (currency) => {
|
|
17
|
+
return CURRENCY_DECIMALS[currency.toUpperCase()] ?? 2;
|
|
18
|
+
};
|
|
19
|
+
const roundTo = (value, decimals) => {
|
|
20
|
+
const factor = 10 ** decimals;
|
|
21
|
+
return Math.round((value + Number.EPSILON) * factor) / factor;
|
|
22
|
+
};
|
|
23
|
+
const toPaymentAmount = (value, currency) => {
|
|
24
|
+
const code = currency.toUpperCase();
|
|
25
|
+
const decimals = currencyDecimals(code);
|
|
26
|
+
const rounded = roundTo(value, decimals);
|
|
27
|
+
return {
|
|
28
|
+
value: rounded,
|
|
29
|
+
minor: Math.round(rounded * 10 ** decimals),
|
|
30
|
+
currency: code,
|
|
31
|
+
decimals
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/** Converts an order-currency value to the charge currency at a fixed rate. */
|
|
35
|
+
const toChargeAmount = (input) => {
|
|
36
|
+
const orderCurrency = input.orderCurrency.toUpperCase();
|
|
37
|
+
const chargeCurrency = (input.chargeCurrency ?? orderCurrency).toUpperCase();
|
|
38
|
+
const same = chargeCurrency === orderCurrency;
|
|
39
|
+
if (!same && !(input.rate && input.rate > 0)) {
|
|
40
|
+
const message = `Missing exchange rate ${orderCurrency}->${chargeCurrency}`;
|
|
41
|
+
throw new Error(message);
|
|
42
|
+
}
|
|
43
|
+
const rate = same ? 1 : input.rate;
|
|
44
|
+
const amount = toPaymentAmount(input.value * rate, chargeCurrency);
|
|
45
|
+
return {
|
|
46
|
+
amount,
|
|
47
|
+
charged_amount: amount.value,
|
|
48
|
+
charged_currency: chargeCurrency,
|
|
49
|
+
exchange_rate: rate
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
/** Tolerance used when comparing amounts in a currency. */
|
|
53
|
+
const amountTolerance = (currency) => {
|
|
54
|
+
return 10 ** -currencyDecimals(currency) / 2;
|
|
55
|
+
};
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/device.ts
|
|
58
|
+
const isEmpty = (value) => {
|
|
59
|
+
return value == null || typeof value === "string" && value.trim() === "";
|
|
60
|
+
};
|
|
61
|
+
const isVisible = (field, settings) => {
|
|
62
|
+
if (!field.visible_if) return true;
|
|
63
|
+
return Object.entries(field.visible_if).every(([key, expected]) => {
|
|
64
|
+
return settings[key] === expected;
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
/** Required, visible device fields that are still empty on this device. */
|
|
68
|
+
const missingDeviceFields = (fields, settings) => {
|
|
69
|
+
const values = settings ?? {};
|
|
70
|
+
return (fields ?? []).filter((field) => {
|
|
71
|
+
if (!field.required) return false;
|
|
72
|
+
if (!isVisible(field, values)) return false;
|
|
73
|
+
return isEmpty(values[field.key]);
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
/** Default when the adapter has no `reversalFor`. */
|
|
77
|
+
const defaultReversalFor = (result) => {
|
|
78
|
+
return result.status === "PAID" ? "refund" : "cancel";
|
|
79
|
+
};
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/errors.ts
|
|
82
|
+
const declined = (message) => {
|
|
83
|
+
return {
|
|
84
|
+
reason: "declined",
|
|
85
|
+
retryable: false,
|
|
86
|
+
message
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const provider = (message, retryable = false) => {
|
|
90
|
+
return {
|
|
91
|
+
reason: "provider",
|
|
92
|
+
retryable,
|
|
93
|
+
message
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
/** Closed list of Spark payment error codes with their defaults. */
|
|
97
|
+
const PAYMENT_ERROR_DEFAULTS = {
|
|
98
|
+
CARD_DECLINED: declined("Card declined"),
|
|
99
|
+
INSUFFICIENT_FUNDS: declined("Insufficient funds"),
|
|
100
|
+
EXPIRED_CARD: declined("Expired card"),
|
|
101
|
+
INVALID_CARD: declined("Invalid card"),
|
|
102
|
+
INVALID_PIN: declined("Invalid PIN"),
|
|
103
|
+
LIMIT_EXCEEDED: declined("Limit exceeded"),
|
|
104
|
+
FRAUD_SUSPECTED: declined("Payment rejected for security reasons"),
|
|
105
|
+
WALLET_REJECTED: declined("Wallet payment rejected"),
|
|
106
|
+
WALLET_EXPIRED: declined("Payment request expired"),
|
|
107
|
+
INSUFFICIENT_BALANCE: declined("Insufficient balance"),
|
|
108
|
+
TRANSFER_NOT_FOUND: declined("Transfer not found"),
|
|
109
|
+
TRANSFER_ALREADY_USED: declined("Transfer already used"),
|
|
110
|
+
TRANSFER_AMOUNT_MISMATCH: declined("Transfer amount does not match"),
|
|
111
|
+
FINANCING_REJECTED: declined("Financing rejected"),
|
|
112
|
+
FINANCING_EXPIRED: declined("Financing request expired"),
|
|
113
|
+
CANCELLED_BY_USER: {
|
|
114
|
+
reason: "user",
|
|
115
|
+
retryable: false,
|
|
116
|
+
message: "Payment cancelled"
|
|
117
|
+
},
|
|
118
|
+
USER_TIMEOUT: {
|
|
119
|
+
reason: "user",
|
|
120
|
+
retryable: false,
|
|
121
|
+
message: "No response from the customer"
|
|
122
|
+
},
|
|
123
|
+
TERMINAL_UNAVAILABLE: provider("Terminal unavailable", true),
|
|
124
|
+
TERMINAL_BUSY: provider("Terminal busy", true),
|
|
125
|
+
TERMINAL_NOT_CONFIGURED: provider("Terminal not configured"),
|
|
126
|
+
TERMINAL_NEEDS_INIT: provider("Terminal needs initialization"),
|
|
127
|
+
BATCH_CLOSE_REQUIRED: provider("Batch close required"),
|
|
128
|
+
INVALID_CREDENTIALS: provider("Invalid payment credentials"),
|
|
129
|
+
INVALID_REQUEST: provider("Invalid payment request"),
|
|
130
|
+
PROVIDER_UNAVAILABLE: provider("Payment provider unavailable", true),
|
|
131
|
+
NETWORK_ERROR: {
|
|
132
|
+
reason: "network",
|
|
133
|
+
retryable: true,
|
|
134
|
+
message: "Network error"
|
|
135
|
+
},
|
|
136
|
+
TIMEOUT: {
|
|
137
|
+
reason: "timeout",
|
|
138
|
+
retryable: true,
|
|
139
|
+
message: "Payment timed out"
|
|
140
|
+
},
|
|
141
|
+
REVERSAL_NOT_ALLOWED: provider("Reversal not allowed"),
|
|
142
|
+
MANUAL_REVERSAL_REQUIRED: provider("Manual reversal required"),
|
|
143
|
+
AMBIGUOUS: {
|
|
144
|
+
reason: "ambiguous",
|
|
145
|
+
retryable: false,
|
|
146
|
+
message: "Payment result unknown"
|
|
147
|
+
},
|
|
148
|
+
UNKNOWN: provider("Payment failed")
|
|
149
|
+
};
|
|
150
|
+
const PAYMENT_ERROR_CODES = Object.keys(PAYMENT_ERROR_DEFAULTS);
|
|
151
|
+
const definePaymentErrors = (dictionary) => {
|
|
152
|
+
return Object.freeze({ ...dictionary });
|
|
153
|
+
};
|
|
154
|
+
const createPaymentError = (code, extra = {}) => {
|
|
155
|
+
const defaults = PAYMENT_ERROR_DEFAULTS[code];
|
|
156
|
+
const b1 = {
|
|
157
|
+
code,
|
|
158
|
+
reason: defaults.reason,
|
|
159
|
+
retryable: defaults.retryable
|
|
160
|
+
};
|
|
161
|
+
const b2 = {
|
|
162
|
+
message: defaults.message,
|
|
163
|
+
provider_code: null
|
|
164
|
+
};
|
|
165
|
+
const b3 = {
|
|
166
|
+
provider_message: null,
|
|
167
|
+
attempt: 1,
|
|
168
|
+
elapsed_ms: 0
|
|
169
|
+
};
|
|
170
|
+
return {
|
|
171
|
+
...b1,
|
|
172
|
+
...b2,
|
|
173
|
+
...b3,
|
|
174
|
+
...extra
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
/** Translates a provider error with its dictionary; unknown codes are `UNKNOWN`. */
|
|
178
|
+
const toPaymentError = (dictionary, raw) => {
|
|
179
|
+
const providerCode = raw.code == null ? null : String(raw.code);
|
|
180
|
+
const entry = providerCode == null ? void 0 : dictionary[providerCode];
|
|
181
|
+
const code = entry?.code ?? raw.fallback ?? "UNKNOWN";
|
|
182
|
+
const defaults = PAYMENT_ERROR_DEFAULTS[code];
|
|
183
|
+
const e1 = { provider_code: providerCode };
|
|
184
|
+
const e2 = { provider_message: raw.message ?? null };
|
|
185
|
+
const e3 = {
|
|
186
|
+
attempt: raw.attempt ?? 1,
|
|
187
|
+
elapsed_ms: raw.elapsed_ms ?? 0
|
|
188
|
+
};
|
|
189
|
+
const e4 = { message: entry?.message ?? defaults.message };
|
|
190
|
+
const e5 = { retryable: entry?.retryable ?? defaults.retryable };
|
|
191
|
+
const e6 = { reason: entry?.reason ?? defaults.reason };
|
|
192
|
+
return createPaymentError(code, {
|
|
193
|
+
...e1,
|
|
194
|
+
...e2,
|
|
195
|
+
...e3,
|
|
196
|
+
...e4,
|
|
197
|
+
...e5,
|
|
198
|
+
...e6
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
/** Thrown by adapters and by `PaymentUI` (e.g. `CANCELLED_BY_USER`). */
|
|
202
|
+
var PaymentError = class extends Error {
|
|
203
|
+
error;
|
|
204
|
+
constructor(error) {
|
|
205
|
+
super(error.message);
|
|
206
|
+
this.name = "PaymentError";
|
|
207
|
+
this.error = error;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const isPaymentError = (value) => {
|
|
211
|
+
return value instanceof PaymentError;
|
|
212
|
+
};
|
|
213
|
+
/** "Tarjeta rechazada (Transbank -1)" style label. */
|
|
214
|
+
const formatPaymentError = (error, providerName) => {
|
|
215
|
+
if (!error.provider_code) return error.message;
|
|
216
|
+
const prefix = providerName ? `${providerName} ` : "";
|
|
217
|
+
return `${error.message} (${prefix}${error.provider_code})`;
|
|
218
|
+
};
|
|
219
|
+
//#endregion
|
|
220
|
+
//#region src/policy.ts
|
|
221
|
+
const DEFAULT_PAYMENT_POLICY = {
|
|
222
|
+
timeout_ms: 3e4,
|
|
223
|
+
interaction_timeout_ms: 6e4,
|
|
224
|
+
max_attempts: 2,
|
|
225
|
+
poll_interval_ms: 3e3,
|
|
226
|
+
qr_ttl_ms: 3e5,
|
|
227
|
+
batch_close_at: null
|
|
228
|
+
};
|
|
229
|
+
const pickPolicy = (value) => {
|
|
230
|
+
if (!value || typeof value !== "object") return {};
|
|
231
|
+
const source = value;
|
|
232
|
+
const out = {};
|
|
233
|
+
for (const key of [
|
|
234
|
+
"timeout_ms",
|
|
235
|
+
"interaction_timeout_ms",
|
|
236
|
+
"max_attempts",
|
|
237
|
+
"poll_interval_ms",
|
|
238
|
+
"qr_ttl_ms"
|
|
239
|
+
]) {
|
|
240
|
+
const item = source[key];
|
|
241
|
+
if (typeof item === "number" && Number.isFinite(item) && item > 0) out[key] = item;
|
|
242
|
+
}
|
|
243
|
+
const batch = source.batch_close_at;
|
|
244
|
+
if (typeof batch === "string") out.batch_close_at = batch;
|
|
245
|
+
return out;
|
|
246
|
+
};
|
|
247
|
+
/**
|
|
248
|
+
* Library default, then `payment_methods.config` (its `runtime` or root
|
|
249
|
+
* `qr_ttl_ms`), then `payments.config.runtime`.
|
|
250
|
+
*/
|
|
251
|
+
const resolvePaymentPolicy = (input) => {
|
|
252
|
+
const method = input.methodConfig ?? {};
|
|
253
|
+
const fromMethod = {
|
|
254
|
+
...pickPolicy(method),
|
|
255
|
+
...pickPolicy(method.runtime)
|
|
256
|
+
};
|
|
257
|
+
const fromPayment = pickPolicy(input.paymentConfig?.runtime);
|
|
258
|
+
return {
|
|
259
|
+
...DEFAULT_PAYMENT_POLICY,
|
|
260
|
+
...fromMethod,
|
|
261
|
+
...fromPayment
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
/** Production rows use the provider's production unless the config overrides it. */
|
|
265
|
+
const resolveProviderEnvironment = (input) => {
|
|
266
|
+
const override = input.paymentConfig?.provider_environment;
|
|
267
|
+
if (override === "sandbox" || override === "production") return override;
|
|
268
|
+
return input.isProduction ? "production" : "sandbox";
|
|
269
|
+
};
|
|
270
|
+
const minutesOf = (value) => {
|
|
271
|
+
const match = /^([01]\d|2[0-3]):([0-5]\d)$/.exec(value);
|
|
272
|
+
if (!match) return null;
|
|
273
|
+
return Number(match[1]) * 60 + Number(match[2]);
|
|
274
|
+
};
|
|
275
|
+
const localDay = (date) => {
|
|
276
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
277
|
+
};
|
|
278
|
+
/** True the first time the device is free after `batch_close_at` each local day. */
|
|
279
|
+
const isBatchCloseDue = (input) => {
|
|
280
|
+
if (!input.batchCloseAt) return false;
|
|
281
|
+
const target = minutesOf(input.batchCloseAt);
|
|
282
|
+
if (target == null) return false;
|
|
283
|
+
const now = input.now;
|
|
284
|
+
if (now.getHours() * 60 + now.getMinutes() < target) return false;
|
|
285
|
+
return input.lastClosedDay !== localDay(now);
|
|
286
|
+
};
|
|
287
|
+
const batchCloseDay = (now) => localDay(now);
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/reference.ts
|
|
290
|
+
const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
291
|
+
const randomBytes = (size) => {
|
|
292
|
+
const bytes = new Uint8Array(size);
|
|
293
|
+
const crypto = globalThis.crypto;
|
|
294
|
+
if (crypto?.getRandomValues) return crypto.getRandomValues(bytes);
|
|
295
|
+
for (let i = 0; i < size; i += 1) bytes[i] = Math.floor(Math.random() * 256);
|
|
296
|
+
return bytes;
|
|
297
|
+
};
|
|
298
|
+
const encodeTime = (time) => {
|
|
299
|
+
let out = "";
|
|
300
|
+
let rest = time;
|
|
301
|
+
for (let i = 0; i < 10; i += 1) {
|
|
302
|
+
out = ENCODING[rest % 32] + out;
|
|
303
|
+
rest = Math.floor(rest / 32);
|
|
304
|
+
}
|
|
305
|
+
return out;
|
|
306
|
+
};
|
|
307
|
+
const encodeRandom = () => {
|
|
308
|
+
const bytes = randomBytes(16);
|
|
309
|
+
let out = "";
|
|
310
|
+
for (const byte of bytes) out += ENCODING[byte % 32];
|
|
311
|
+
return out;
|
|
312
|
+
};
|
|
313
|
+
/** ULID per attempt; sent to the provider as merchant/idempotency reference. */
|
|
314
|
+
const createAttemptReference = (now = Date.now()) => {
|
|
315
|
+
return encodeTime(now) + encodeRandom();
|
|
316
|
+
};
|
|
317
|
+
//#endregion
|
|
318
|
+
//#region src/taxes.ts
|
|
319
|
+
/**
|
|
320
|
+
* Tax breakdown by rate from the order lines (each line counted once, without
|
|
321
|
+
* its children). `share` prorates it when the intent covers part of the order.
|
|
322
|
+
*/
|
|
323
|
+
const taxBreakdown = (lines, options = {}) => {
|
|
324
|
+
const share = options.share ?? 1;
|
|
325
|
+
const decimals = options.decimals ?? 2;
|
|
326
|
+
const byRate = /* @__PURE__ */ new Map();
|
|
327
|
+
for (const line of lines) {
|
|
328
|
+
const rate = line.tax_rate ?? 0;
|
|
329
|
+
const current = byRate.get(rate) ?? {
|
|
330
|
+
base: 0,
|
|
331
|
+
tax: 0
|
|
332
|
+
};
|
|
333
|
+
current.base += line.subtotal - line.discount_total;
|
|
334
|
+
current.tax += line.tax_total;
|
|
335
|
+
byRate.set(rate, current);
|
|
336
|
+
}
|
|
337
|
+
return [...byRate.entries()].toSorted(([a], [b]) => a - b).map(([rate, sums]) => {
|
|
338
|
+
return {
|
|
339
|
+
rate,
|
|
340
|
+
base: roundTo(sums.base * share, decimals),
|
|
341
|
+
tax: roundTo(sums.tax * share, decimals)
|
|
342
|
+
};
|
|
343
|
+
});
|
|
344
|
+
};
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region src/terminal.ts
|
|
347
|
+
const tails = /* @__PURE__ */ new Map();
|
|
348
|
+
/**
|
|
349
|
+
* Serializes everything that touches one terminal (charge, query, batch close)
|
|
350
|
+
* by `payment_id`.
|
|
351
|
+
*/
|
|
352
|
+
const withTerminalLock = async (paymentId, task) => {
|
|
353
|
+
const run = (tails.get(paymentId) ?? Promise.resolve()).catch(() => void 0).then(task);
|
|
354
|
+
const tail = run.catch(() => void 0);
|
|
355
|
+
tails.set(paymentId, tail);
|
|
356
|
+
try {
|
|
357
|
+
return await run;
|
|
358
|
+
} finally {
|
|
359
|
+
if (tails.get(paymentId) === tail) tails.delete(paymentId);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
const isTerminalLocked = (paymentId) => tails.has(paymentId);
|
|
363
|
+
/** Keys are prefixed with the `payment_id`. */
|
|
364
|
+
const scopedPaymentStorage = (storage, paymentId) => {
|
|
365
|
+
const key = (name) => `${paymentId}:${name}`;
|
|
366
|
+
return {
|
|
367
|
+
get: (name) => storage.get(key(name)),
|
|
368
|
+
set: (name, value) => storage.set(key(name), value),
|
|
369
|
+
remove: (name) => storage.remove(key(name))
|
|
370
|
+
};
|
|
371
|
+
};
|
|
372
|
+
/** For tests and web previews; apps provide AsyncStorage or localStorage. */
|
|
373
|
+
const memoryPaymentStorage = () => {
|
|
374
|
+
const values = /* @__PURE__ */ new Map();
|
|
375
|
+
return {
|
|
376
|
+
get: async (key) => values.get(key) ?? null,
|
|
377
|
+
set: async (key, value) => {
|
|
378
|
+
values.set(key, value);
|
|
379
|
+
},
|
|
380
|
+
remove: async (key) => {
|
|
381
|
+
values.delete(key);
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
};
|
|
385
|
+
//#endregion
|
|
386
|
+
export { CURRENCY_DECIMALS, DEFAULT_PAYMENT_POLICY, PAYMENT_ERROR_CODES, PAYMENT_ERROR_DEFAULTS, PaymentError, amountTolerance, batchCloseDay, createAttemptReference, createPaymentError, currencyDecimals, defaultReversalFor, definePaymentErrors, formatPaymentError, isBatchCloseDue, isPaymentError, isTerminalLocked, memoryPaymentStorage, missingDeviceFields, resolvePaymentPolicy, resolveProviderEnvironment, roundTo, scopedPaymentStorage, taxBreakdown, toChargeAmount, toPaymentAmount, toPaymentError, withTerminalLock };
|
package/package.json
CHANGED
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fire-spark/payments",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Fire Spark payments contract: adapters, errors, amounts and policy",
|
|
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/types": "0.0.4"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"tsdown": "^0.22.14",
|
|
24
|
+
"typescript": "^7.0.2",
|
|
25
|
+
"vitest": "^2.1.8"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"tsdown": {
|
|
31
|
+
"entry": [
|
|
32
|
+
"src/index.ts"
|
|
33
|
+
],
|
|
34
|
+
"format": [
|
|
35
|
+
"esm"
|
|
36
|
+
],
|
|
37
|
+
"platform": "neutral",
|
|
38
|
+
"dts": {
|
|
39
|
+
"tsconfig": "tsconfig.build.json"
|
|
40
|
+
},
|
|
41
|
+
"clean": true,
|
|
42
|
+
"deps": {
|
|
43
|
+
"neverBundle": [
|
|
44
|
+
"@fire-spark/types"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsdown",
|
|
50
|
+
"dev": "tsdown --watch",
|
|
51
|
+
"check-types": "tsc --noEmit",
|
|
52
|
+
"test:utils": "vitest run",
|
|
53
|
+
"lint": "oxlint ./src --max-warnings=0",
|
|
54
|
+
"format": "oxfmt ./src",
|
|
55
|
+
"check-format": "oxfmt --check ./src"
|
|
56
|
+
}
|
|
6
57
|
}
|
package/README.md
DELETED