awesome-node-checkout 2.0.0 → 2.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/CHANGELOG.md +35 -0
- package/README.md +280 -2
- package/dist/adapters/express/index.d.ts +69 -1
- package/dist/adapters/express/index.d.ts.map +1 -1
- package/dist/adapters/express/index.js +147 -2
- package/dist/adapters/express/index.js.map +1 -1
- package/dist/checkout-configurator.d.ts +106 -0
- package/dist/checkout-configurator.d.ts.map +1 -1
- package/dist/checkout-configurator.js +515 -0
- package/dist/checkout-configurator.js.map +1 -1
- package/dist/events/checkout-event-bus.d.ts +11 -1
- package/dist/events/checkout-event-bus.d.ts.map +1 -1
- package/dist/events/checkout-event-bus.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/mandate-provider.interface.d.ts +40 -0
- package/dist/interfaces/mandate-provider.interface.d.ts.map +1 -0
- package/dist/interfaces/mandate-provider.interface.js +18 -0
- package/dist/interfaces/mandate-provider.interface.js.map +1 -0
- package/dist/models/checkout-config.model.d.ts +16 -0
- package/dist/models/checkout-config.model.d.ts.map +1 -1
- package/dist/models/errors.d.ts +1 -1
- package/dist/models/errors.d.ts.map +1 -1
- package/dist/models/errors.js.map +1 -1
- package/dist/models/mandate.model.d.ts +166 -0
- package/dist/models/mandate.model.d.ts.map +1 -0
- package/dist/models/mandate.model.js +19 -0
- package/dist/models/mandate.model.js.map +1 -0
- package/dist/providers/nexi/nexi.provider.d.ts +125 -1
- package/dist/providers/nexi/nexi.provider.d.ts.map +1 -1
- package/dist/providers/nexi/nexi.provider.js +652 -10
- package/dist/providers/nexi/nexi.provider.js.map +1 -1
- package/dist/providers/paypal/paypal.provider.d.ts +44 -1
- package/dist/providers/paypal/paypal.provider.d.ts.map +1 -1
- package/dist/providers/paypal/paypal.provider.js +519 -0
- package/dist/providers/paypal/paypal.provider.js.map +1 -1
- package/dist/providers/satispay/satispay.provider.d.ts +86 -1
- package/dist/providers/satispay/satispay.provider.d.ts.map +1 -1
- package/dist/providers/satispay/satispay.provider.js +417 -0
- package/dist/providers/satispay/satispay.provider.js.map +1 -1
- package/dist/utils/redact.d.ts +15 -0
- package/dist/utils/redact.d.ts.map +1 -0
- package/dist/utils/redact.js +60 -0
- package/dist/utils/redact.js.map +1 -0
- package/dist/utils/url.d.ts +12 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/url.js +19 -0
- package/dist/utils/url.js.map +1 -0
- package/package.json +2 -2
|
@@ -39,11 +39,44 @@ const base_payment_provider_abstract_1 = require("../../abstract/base-payment-pr
|
|
|
39
39
|
const ids_1 = require("../../utils/ids");
|
|
40
40
|
const money_1 = require("../../utils/money");
|
|
41
41
|
const parsing_util_1 = require("../../utils/parsing.util");
|
|
42
|
+
const url_1 = require("../../utils/url");
|
|
43
|
+
const mandate_model_1 = require("../../models/mandate.model");
|
|
44
|
+
/** Nexi XPay paid statuses (`situazioneOrdine` report `stato`, lower-case). */
|
|
45
|
+
const NEXI_PAID_STATUSES = new Set(['autorizzato', 'contabilizzato', 'catturato']);
|
|
46
|
+
/**
|
|
47
|
+
* Replay guard (`situazioneOrdine` report `stato`, trimmed and lower-case; manual v20.4 p. 622).
|
|
48
|
+
* Money was taken for the code: paid, partly captured, or charged and then (partly) refunded.
|
|
49
|
+
*/
|
|
50
|
+
const NEXI_CHARGED_STATUSES = new Set([
|
|
51
|
+
...NEXI_PAID_STATUSES,
|
|
52
|
+
'contabilizzato parz.',
|
|
53
|
+
'rimborsato',
|
|
54
|
+
'rimborsato parz.',
|
|
55
|
+
]);
|
|
56
|
+
/** Replay guard: the clear refusals, the only states that make an existing code `failed`. */
|
|
57
|
+
const NEXI_REFUSED_STATUSES = new Set(['negato', 'annullato', 'non creato']);
|
|
58
|
+
/** Nexi XPay numeric code of EUR, the only currency `pagamentoRicorrente` accepts. */
|
|
59
|
+
const NEXI_EUR_NUMERIC = '978';
|
|
60
|
+
const EUR_ONLY = 'Nexi mandates support EUR only';
|
|
61
|
+
/** `pagamentoRicorrente` `importo`: cents, at most 8 digits (pp. 188-193). */
|
|
62
|
+
const NEXI_RECURRING_MAX_IMPORTO_DIGITS = 8;
|
|
63
|
+
/** Hosted-page `esito` values that are a final refusal of the setup. */
|
|
64
|
+
const NEXI_SETUP_REFUSALS = new Set(['KO', 'ANNULLO', 'ERRORE']);
|
|
65
|
+
/** Configuration, credentials or technical errors: never the customer's fault. */
|
|
66
|
+
const NEXI_MERCHANT_ERRORS = new Set([1, 3, 4, 5, 7, 12, 50, 97, 98, 99, 100]);
|
|
67
|
+
const NEXI_NOT_FOUND_ERRORS = new Set([2, 13]);
|
|
68
|
+
const NEXI_MANDATE_UNUSABLE_ERRORS = new Set([8, 14, 22]);
|
|
69
|
+
const NEXI_DUPLICATE_ERROR = 9;
|
|
42
70
|
/**
|
|
43
71
|
* Nexi eCommerce provider.
|
|
44
72
|
* Flow: `redirect` — builds a signed checkout URL, redirects the user to Nexi,
|
|
45
73
|
* then handles the POST-back via `handleRedirect`.
|
|
46
74
|
*
|
|
75
|
+
* Recurring payments (`IMandateProvider`): the setup is a hosted-page first payment that
|
|
76
|
+
* registers a contract (`num_contratto` = `mandateRef`, `tipo_servizio=paga_multi`,
|
|
77
|
+
* `tipo_richiesta=PP`); later charges use `pagamentoRicorrente`; `cancellaContratto` revokes.
|
|
78
|
+
* EUR only. See "Specifiche tecniche di integrazione con il gateway di pagamento XPay" v20.4.
|
|
79
|
+
*
|
|
47
80
|
* Note: Nexi XPay mandates SHA-1 for MAC computation. This is a requirement of
|
|
48
81
|
* the Nexi API and cannot be changed on the client side.
|
|
49
82
|
*/
|
|
@@ -57,6 +90,10 @@ class NexiProvider extends base_payment_provider_abstract_1.BasePaymentProvider
|
|
|
57
90
|
config.environment === 'production'
|
|
58
91
|
? 'https://ecommerce.nexi.it'
|
|
59
92
|
: 'https://int-ecommerce.nexi.it';
|
|
93
|
+
this.timeoutMs =
|
|
94
|
+
typeof config.timeoutMs === 'number' && Number.isFinite(config.timeoutMs) && config.timeoutMs > 0
|
|
95
|
+
? config.timeoutMs
|
|
96
|
+
: 30000;
|
|
60
97
|
}
|
|
61
98
|
/**
|
|
62
99
|
* Computes the SHA-1 MAC required by Nexi to authenticate the payment request.
|
|
@@ -123,6 +160,208 @@ class NexiProvider extends base_payment_provider_abstract_1.BasePaymentProvider
|
|
|
123
160
|
return false;
|
|
124
161
|
}
|
|
125
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Extracts the fields of a hosted-page outcome (redirect query or `urlpost` form). Missing
|
|
165
|
+
* fields are `''`; `codTrans` falls back to `fallbackCodTrans` when given. `mac` is returned as
|
|
166
|
+
* is, to be checked with `isValidHexMac`.
|
|
167
|
+
*/
|
|
168
|
+
extractOutcomeFields(data, fallbackCodTrans) {
|
|
169
|
+
const f = (k) => {
|
|
170
|
+
const v = data?.[k];
|
|
171
|
+
return v === undefined || v === null ? '' : String(v);
|
|
172
|
+
};
|
|
173
|
+
const rawCodTrans = data?.['codTrans'];
|
|
174
|
+
return {
|
|
175
|
+
codTrans: rawCodTrans === undefined || rawCodTrans === null ? (fallbackCodTrans ?? '') : String(rawCodTrans),
|
|
176
|
+
esito: f('esito'),
|
|
177
|
+
importo: f('importo'),
|
|
178
|
+
divisa: f('divisa'),
|
|
179
|
+
dataStr: f('data'),
|
|
180
|
+
orario: f('orario'),
|
|
181
|
+
codAut: f('codAut'),
|
|
182
|
+
mac: data?.['mac'],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** True when the 7-field outcome MAC of `fields` is present and valid (constant time). */
|
|
186
|
+
outcomeMacValid(fields) {
|
|
187
|
+
const { codTrans, esito, importo, divisa, dataStr, orario, codAut, mac } = fields;
|
|
188
|
+
return (this.isValidHexMac(mac) &&
|
|
189
|
+
this.safeVerifyResponseMac(codTrans, esito, importo, divisa, dataStr, orario, codAut, mac));
|
|
190
|
+
}
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Recurring payments (mandates): MACs and API helpers
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
/** Constant-time comparison of a hex MAC against the expected SHA-1 hex digest. */
|
|
195
|
+
hexMacEquals(expected, mac) {
|
|
196
|
+
if (!this.isValidHexMac(mac))
|
|
197
|
+
return false;
|
|
198
|
+
try {
|
|
199
|
+
const expBuf = Buffer.from(expected, 'hex');
|
|
200
|
+
const macBuf = Buffer.from(mac, 'hex');
|
|
201
|
+
return expBuf.length === macBuf.length && expBuf.length > 0 && crypto.timingSafeEqual(expBuf, macBuf);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Verifies the standard XPay REST response MAC:
|
|
209
|
+
* `esito={esito}idOperazione={idOperazione}timeStamp={timeStamp}{macKey}`.
|
|
210
|
+
*/
|
|
211
|
+
responseMacValid(json) {
|
|
212
|
+
const str = (k) => {
|
|
213
|
+
const v = json[k];
|
|
214
|
+
return v === undefined || v === null ? '' : String(v);
|
|
215
|
+
};
|
|
216
|
+
const expected = this.sha1(`esito=${str('esito')}idOperazione=${str('idOperazione')}timeStamp=${str('timeStamp')}${this.config.macKey}`);
|
|
217
|
+
return this.hexMacEquals(expected, json['mac']);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* MAC policy of the recurring and contract APIs: a present `mac` must be valid whatever the
|
|
221
|
+
* `esito`; a missing one is tolerated only on `KO` (an error answer may carry none).
|
|
222
|
+
*/
|
|
223
|
+
responseAuthentic(json) {
|
|
224
|
+
const mac = json['mac'];
|
|
225
|
+
if (mac === undefined || mac === null || mac === '')
|
|
226
|
+
return json['esito'] === 'KO';
|
|
227
|
+
return this.responseMacValid(json);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Classifies a "Tabella Codici errore API Restful" code (pp. 883-885). A missing or
|
|
231
|
+
* non-numeric code is `merchant`: without a code nothing says the customer is at fault.
|
|
232
|
+
*/
|
|
233
|
+
classifyApiError(codice) {
|
|
234
|
+
const code = typeof codice === 'number' || (typeof codice === 'string' && /^\d+$/.test(codice.trim()))
|
|
235
|
+
? Number(codice)
|
|
236
|
+
: NaN;
|
|
237
|
+
if (!Number.isInteger(code))
|
|
238
|
+
return 'merchant';
|
|
239
|
+
if (NEXI_MERCHANT_ERRORS.has(code))
|
|
240
|
+
return 'merchant';
|
|
241
|
+
if (code === NEXI_DUPLICATE_ERROR)
|
|
242
|
+
return 'duplicate';
|
|
243
|
+
if (NEXI_NOT_FOUND_ERRORS.has(code))
|
|
244
|
+
return 'notFound';
|
|
245
|
+
if (NEXI_MANDATE_UNUSABLE_ERRORS.has(code))
|
|
246
|
+
return 'mandateUnusable';
|
|
247
|
+
return 'declined';
|
|
248
|
+
}
|
|
249
|
+
/** `errore.codice` / `errore.messaggio` of a response, when present. */
|
|
250
|
+
apiError(json) {
|
|
251
|
+
const errore = json['errore'];
|
|
252
|
+
if (errore === null || typeof errore !== 'object')
|
|
253
|
+
return { codice: undefined };
|
|
254
|
+
const e = errore;
|
|
255
|
+
return {
|
|
256
|
+
codice: e['codice'],
|
|
257
|
+
messaggio: typeof e['messaggio'] === 'string' ? e['messaggio'] : undefined,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Computes the MAC of `pagamentoRicorrente` (p. 191). `scadenza` is not sent, so it is in the
|
|
262
|
+
* string with an empty value, as the manual requires:
|
|
263
|
+
* `apiKey=…numeroContratto=…codiceTransazione=…importo=…divisa=978scadenza=timeStamp=…{macKey}`.
|
|
264
|
+
*/
|
|
265
|
+
generateRecurringMac(numeroContratto, codiceTransazione, importo, timeStamp) {
|
|
266
|
+
return this.sha1(`apiKey=${this.config.merchantId}numeroContratto=${numeroContratto}` +
|
|
267
|
+
`codiceTransazione=${codiceTransazione}importo=${importo}divisa=${NEXI_EUR_NUMERIC}` +
|
|
268
|
+
`scadenza=timeStamp=${timeStamp}${this.config.macKey}`);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* POSTs a JSON body to an XPay REST endpoint with the configured timeout. Never throws; the
|
|
272
|
+
* error never contains the response body.
|
|
273
|
+
*/
|
|
274
|
+
async postApi(path, body) {
|
|
275
|
+
let response;
|
|
276
|
+
try {
|
|
277
|
+
response = await fetch(`${this.apiUrl}${path}`, {
|
|
278
|
+
method: 'POST',
|
|
279
|
+
headers: { 'Content-Type': 'application/json' },
|
|
280
|
+
body: JSON.stringify(body),
|
|
281
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
const name = err instanceof Error ? err.name : '';
|
|
286
|
+
return {
|
|
287
|
+
ok: false,
|
|
288
|
+
error: name === 'TimeoutError' || name === 'AbortError' ? 'Nexi API timeout' : 'Nexi API network error',
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
if (!response || !response.ok) {
|
|
292
|
+
return { ok: false, error: `Nexi API error: HTTP ${response?.status}` };
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
const json = (await response.json());
|
|
296
|
+
if (json === null || typeof json !== 'object' || Array.isArray(json)) {
|
|
297
|
+
return { ok: false, error: 'Invalid provider response' };
|
|
298
|
+
}
|
|
299
|
+
return { ok: true, json: json };
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
return { ok: false, error: 'Invalid provider response' };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Reads `situazioneOrdine` for `codTrans` (same request as `getPaymentDetails`) and classifies
|
|
307
|
+
* the raw response, over every report entry for `codTrans`:
|
|
308
|
+
* - `paid`: some entry shows the money was taken (`NEXI_CHARGED_STATUSES`: paid, partly captured,
|
|
309
|
+
* refunded or partly refunded);
|
|
310
|
+
* - `pending`: otherwise, some entry is in progress (`In Corso`), has an unrecognised `stato`, or
|
|
311
|
+
* has none. Fail safe: such a code may still be charged, so it is never reported as refused;
|
|
312
|
+
* - `refused`: every entry is a clear refusal (`Negato`, `Annullato`, `Non Creato`);
|
|
313
|
+
* - `absent`: OK with no matching report entry, or KO "not found" 2/13;
|
|
314
|
+
* - `error`: anything else (HTTP, network, timeout, non-JSON, bad MAC, a missing MAC on OK, no
|
|
315
|
+
* `report` array, any other KO).
|
|
316
|
+
*/
|
|
317
|
+
async lookupTransaction(codTrans) {
|
|
318
|
+
const timeStamp = String(Date.now());
|
|
319
|
+
const mac = this.generateStatusMac(this.config.merchantId, codTrans, timeStamp);
|
|
320
|
+
const res = await this.postApi('/ecomm/api/bo/situazioneOrdine', {
|
|
321
|
+
apiKey: this.config.merchantId,
|
|
322
|
+
codiceTransazione: codTrans,
|
|
323
|
+
timeStamp,
|
|
324
|
+
mac,
|
|
325
|
+
});
|
|
326
|
+
if (!res.ok)
|
|
327
|
+
return { kind: 'error' };
|
|
328
|
+
const json = res.json;
|
|
329
|
+
if (!this.responseAuthentic(json))
|
|
330
|
+
return { kind: 'error' };
|
|
331
|
+
if (json['esito'] === 'KO') {
|
|
332
|
+
return this.classifyApiError(this.apiError(json).codice) === 'notFound'
|
|
333
|
+
? { kind: 'absent' }
|
|
334
|
+
: { kind: 'error' };
|
|
335
|
+
}
|
|
336
|
+
if (json['esito'] !== 'OK' || !Array.isArray(json['report']))
|
|
337
|
+
return { kind: 'error' };
|
|
338
|
+
const entries = json['report'].filter((r) => r !== null && typeof r === 'object' && r['codiceTransazione'] === codTrans);
|
|
339
|
+
if (entries.length === 0)
|
|
340
|
+
return { kind: 'absent' };
|
|
341
|
+
// Nexi may list a code more than once (it accepts up to 2 retries of a code after a KO): one
|
|
342
|
+
// charged entry means the customer was charged, whatever the order of the entries; one entry
|
|
343
|
+
// that is not final (or not understood) means the code may still be charged
|
|
344
|
+
const statoOf = (e) => typeof e['stato'] === 'string' ? e['stato'] : undefined;
|
|
345
|
+
const classOf = (e) => {
|
|
346
|
+
const stato = statoOf(e)?.trim().toLowerCase();
|
|
347
|
+
if (stato === undefined)
|
|
348
|
+
return 'pending';
|
|
349
|
+
if (NEXI_CHARGED_STATUSES.has(stato))
|
|
350
|
+
return 'paid';
|
|
351
|
+
return NEXI_REFUSED_STATUSES.has(stato) ? 'refused' : 'pending';
|
|
352
|
+
};
|
|
353
|
+
const kind = ['paid', 'pending'].find((k) => entries.some((e) => classOf(e) === k)) ?? 'refused';
|
|
354
|
+
const entry = entries.find((e) => classOf(e) === kind) ?? entries[0];
|
|
355
|
+
const stato = statoOf(entry);
|
|
356
|
+
const amount = (0, parsing_util_1.parseAmountFromCents)(entry['importo']);
|
|
357
|
+
const currency = (0, parsing_util_1.parseNexiCurrency)(entry['divisa']);
|
|
358
|
+
return {
|
|
359
|
+
kind,
|
|
360
|
+
...(stato !== undefined ? { stato } : {}),
|
|
361
|
+
...(amount !== undefined ? { amount } : {}),
|
|
362
|
+
...(currency !== undefined ? { currency } : {}),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
126
365
|
async createPayment(request) {
|
|
127
366
|
try {
|
|
128
367
|
// Outcome amounts (redirect, webhook, details) and refunds are read and sent in hundredths:
|
|
@@ -179,15 +418,9 @@ class NexiProvider extends base_payment_provider_abstract_1.BasePaymentProvider
|
|
|
179
418
|
*/
|
|
180
419
|
async executePayment(paymentId, data) {
|
|
181
420
|
try {
|
|
182
|
-
const esito = data?.esito ?? '';
|
|
183
421
|
const requestedId = typeof paymentId === 'string' && paymentId !== '' ? paymentId : undefined;
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
const divisa = data?.divisa ?? '';
|
|
187
|
-
const dataStr = data?.data ?? '';
|
|
188
|
-
const orario = data?.orario ?? '';
|
|
189
|
-
const codAut = data?.codAut ?? '';
|
|
190
|
-
const mac = data?.mac;
|
|
422
|
+
const fields = this.extractOutcomeFields(data, requestedId);
|
|
423
|
+
const { codTrans, esito, importo, divisa, mac } = fields;
|
|
191
424
|
if (!mac) {
|
|
192
425
|
return {
|
|
193
426
|
success: false,
|
|
@@ -197,7 +430,7 @@ class NexiProvider extends base_payment_provider_abstract_1.BasePaymentProvider
|
|
|
197
430
|
error: 'MAC missing',
|
|
198
431
|
};
|
|
199
432
|
}
|
|
200
|
-
if (!this.
|
|
433
|
+
if (!this.outcomeMacValid(fields)) {
|
|
201
434
|
return {
|
|
202
435
|
success: false,
|
|
203
436
|
verified: false,
|
|
@@ -367,7 +600,6 @@ class NexiProvider extends base_payment_provider_abstract_1.BasePaymentProvider
|
|
|
367
600
|
const stato = item.stato;
|
|
368
601
|
const amount = (0, parsing_util_1.parseAmountFromCents)(item.importo);
|
|
369
602
|
const currency = (0, parsing_util_1.parseNexiCurrency)(item.divisa);
|
|
370
|
-
const NEXI_PAID_STATUSES = new Set(['autorizzato', 'contabilizzato', 'catturato']);
|
|
371
603
|
const statoStr = typeof stato === 'string' ? stato.toLowerCase() : '';
|
|
372
604
|
const isPaidStatus = NEXI_PAID_STATUSES.has(statoStr);
|
|
373
605
|
const isSuccess = isPaidStatus && amount !== undefined && amount > 0 && !!currency && currency.trim() !== '';
|
|
@@ -445,6 +677,416 @@ class NexiProvider extends base_payment_provider_abstract_1.BasePaymentProvider
|
|
|
445
677
|
async handleRedirect(query) {
|
|
446
678
|
return this.executePayment(query['codTrans'] ?? '', query);
|
|
447
679
|
}
|
|
680
|
+
// ---------------------------------------------------------------------------
|
|
681
|
+
// Recurring payments (mandates) — IMandateProvider
|
|
682
|
+
// ---------------------------------------------------------------------------
|
|
683
|
+
/**
|
|
684
|
+
* Starts a contract registration: a hosted-page first payment with `num_contratto` =
|
|
685
|
+
* `mandateRef`, `tipo_servizio=paga_multi`, `tipo_richiesta=PP` (pp. 59-65), charging the first
|
|
686
|
+
* period. `codTrans` is `orderId`. EUR only. `mandate_ref` is appended to `url`, `url_back` and
|
|
687
|
+
* `urlpost`.
|
|
688
|
+
*/
|
|
689
|
+
async createMandate(request) {
|
|
690
|
+
try {
|
|
691
|
+
if (request.currency !== 'EUR') {
|
|
692
|
+
return { success: false, error: EUR_ONLY };
|
|
693
|
+
}
|
|
694
|
+
if (!mandate_model_1.MANDATE_REF_PATTERN.test(request.mandateRef) || !mandate_model_1.CHARGE_REF_PATTERN.test(request.orderId)) {
|
|
695
|
+
return { success: false, error: 'Invalid mandateRef or orderId for Nexi' };
|
|
696
|
+
}
|
|
697
|
+
const importo = (0, money_1.toMinorUnits)(request.amount, 'EUR');
|
|
698
|
+
const codTrans = request.orderId;
|
|
699
|
+
const contractType = this.config.contractType;
|
|
700
|
+
const mac = contractType === undefined
|
|
701
|
+
? this.generateMac(codTrans, 'EUR', importo)
|
|
702
|
+
: this.sha1(`codTrans=${codTrans}divisa=EURimporto=${importo}tipo_contratto=${contractType}${this.config.macKey}`);
|
|
703
|
+
const params = {
|
|
704
|
+
alias: this.config.merchantId,
|
|
705
|
+
importo: String(importo),
|
|
706
|
+
divisa: 'EUR',
|
|
707
|
+
codTrans,
|
|
708
|
+
url: (0, url_1.appendMandateRef)(request.returnUrl, request.mandateRef),
|
|
709
|
+
url_back: (0, url_1.appendMandateRef)(request.cancelUrl, request.mandateRef),
|
|
710
|
+
mac,
|
|
711
|
+
num_contratto: request.mandateRef,
|
|
712
|
+
tipo_servizio: 'paga_multi',
|
|
713
|
+
tipo_richiesta: 'PP',
|
|
714
|
+
};
|
|
715
|
+
if (contractType !== undefined)
|
|
716
|
+
params.tipo_contratto = contractType;
|
|
717
|
+
if (request.notifyUrl)
|
|
718
|
+
params.urlpost = (0, url_1.appendMandateRef)(request.notifyUrl, request.mandateRef);
|
|
719
|
+
if (this.config.group)
|
|
720
|
+
params.gruppo = this.config.group;
|
|
721
|
+
return {
|
|
722
|
+
success: true,
|
|
723
|
+
approvalUrl: `${this.apiUrl}/ecomm/ecomm/DispatcherServlet?${new URLSearchParams(params)}`,
|
|
724
|
+
pending: {
|
|
725
|
+
provider: this.name,
|
|
726
|
+
mandateRef: request.mandateRef,
|
|
727
|
+
orderId: codTrans,
|
|
728
|
+
amount: request.amount,
|
|
729
|
+
currency: 'EUR',
|
|
730
|
+
providerRef: codTrans,
|
|
731
|
+
createdAt: new Date().toISOString(),
|
|
732
|
+
},
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
catch (error) {
|
|
736
|
+
if ((0, money_1.isInvalidAmountError)(error))
|
|
737
|
+
return { success: false, error: error.message };
|
|
738
|
+
if (error instanceof TypeError)
|
|
739
|
+
return { success: false, error: 'Invalid returnUrl, cancelUrl or notifyUrl' };
|
|
740
|
+
return { success: false, error: 'Failed to create Nexi mandate' };
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Confirms a setup from the hosted-page outcome: the redirect on `url` (GET query) or the
|
|
745
|
+
* `urlpost` notification (form POST). Pure function of its inputs, hence idempotent.
|
|
746
|
+
*
|
|
747
|
+
* - The 7-field outcome MAC must be valid, and the MAC-covered `codTrans`, `importo` and
|
|
748
|
+
* `divisa` must match `pending`; otherwise the outcome is unverified and non-terminal (a forged
|
|
749
|
+
* callback never consumes the pending state). The unsigned `num_contratto`, when present, must
|
|
750
|
+
* be `pending.mandateRef` (and `tipo_servizio` / `tipo_richiesta`, when present, `paga_multi` /
|
|
751
|
+
* `PP`); the contract number used is always `pending.mandateRef`.
|
|
752
|
+
* - `esito=OK` with `num_contratto`: active mandate, first charge `succeeded` (terminal). An OK
|
|
753
|
+
* without `num_contratto` is unverified and non-terminal (no contract may exist).
|
|
754
|
+
* - `esito` `KO`, `ANNULLO` or `ERRORE`: no mandate, first charge `failed` (terminal). Start a
|
|
755
|
+
* new setup with a fresh `mandateRef` and `orderId`: a `codTrans` is never reused. This
|
|
756
|
+
* requires a terminal that allows a single payment attempt per `codTrans`: on a terminal
|
|
757
|
+
* configured for up to 3 attempts (manual, pp. 32-33), a KO then an OK arrive for the same
|
|
758
|
+
* `codTrans`, and the OK finds the pending state already consumed.
|
|
759
|
+
* - Any other `esito` (e.g. a pending one) is unverified and non-terminal: it may still turn OK.
|
|
760
|
+
*
|
|
761
|
+
* An empty input (`{}`) has no MAC, so a re-confirm is unverified and non-terminal: Nexi never
|
|
762
|
+
* reports a terminal outcome with an `unknown` first charge.
|
|
763
|
+
*/
|
|
764
|
+
async confirmMandate(pending, input) {
|
|
765
|
+
const unverified = (error) => ({
|
|
766
|
+
success: false,
|
|
767
|
+
verified: false,
|
|
768
|
+
terminal: false,
|
|
769
|
+
firstCharge: { status: 'unknown', verified: false, chargeRef: pending.orderId, error },
|
|
770
|
+
error,
|
|
771
|
+
});
|
|
772
|
+
try {
|
|
773
|
+
const params = {};
|
|
774
|
+
for (const source of [input?.query, input?.body]) {
|
|
775
|
+
if (source === null || typeof source !== 'object')
|
|
776
|
+
continue;
|
|
777
|
+
for (const [k, v] of Object.entries(source)) {
|
|
778
|
+
if (typeof v === 'string')
|
|
779
|
+
params[k] = v;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
const fields = this.extractOutcomeFields(params);
|
|
783
|
+
if (!this.outcomeMacValid(fields))
|
|
784
|
+
return unverified('Invalid outcome MAC');
|
|
785
|
+
if (fields.codTrans !== pending.orderId) {
|
|
786
|
+
return unverified('Outcome does not match the pending setup');
|
|
787
|
+
}
|
|
788
|
+
if (fields.importo !== String((0, money_1.toMinorUnits)(pending.amount, 'EUR')) || fields.divisa !== 'EUR') {
|
|
789
|
+
return unverified('Outcome amount does not match');
|
|
790
|
+
}
|
|
791
|
+
// Not MAC-covered: only tampering indicators, never the source of the contract number
|
|
792
|
+
if (params['num_contratto'] !== undefined && params['num_contratto'] !== pending.mandateRef) {
|
|
793
|
+
return unverified('Outcome does not match the pending setup');
|
|
794
|
+
}
|
|
795
|
+
if ((params['tipo_servizio'] !== undefined && params['tipo_servizio'] !== 'paga_multi') ||
|
|
796
|
+
(params['tipo_richiesta'] !== undefined && params['tipo_richiesta'] !== 'PP')) {
|
|
797
|
+
return unverified('Outcome does not match the pending setup');
|
|
798
|
+
}
|
|
799
|
+
if (fields.esito === 'OK') {
|
|
800
|
+
// Nexi echoes num_contratto on a contract-registering outcome. Without it, the customer may
|
|
801
|
+
// have stripped the contract parameters from the (unsigned) approvalUrl, so a plain payment
|
|
802
|
+
// was made and no contract may exist: never report that as an active mandate.
|
|
803
|
+
if (params['num_contratto'] === undefined) {
|
|
804
|
+
return unverified('Outcome does not match the pending setup');
|
|
805
|
+
}
|
|
806
|
+
const unverifiedHints = {};
|
|
807
|
+
const scadenzaPan = params['scadenza_pan'];
|
|
808
|
+
if (scadenzaPan !== undefined && /^\d{6}$/.test(scadenzaPan))
|
|
809
|
+
unverifiedHints.cardExpiry = scadenzaPan;
|
|
810
|
+
const pan = params['pan'];
|
|
811
|
+
if (pan !== undefined && /^[0-9*]{8,19}$/.test(pan))
|
|
812
|
+
unverifiedHints.maskedPan = pan;
|
|
813
|
+
const mandate = {
|
|
814
|
+
provider: this.name,
|
|
815
|
+
mandateRef: pending.mandateRef,
|
|
816
|
+
providerMandateId: pending.mandateRef,
|
|
817
|
+
status: 'active',
|
|
818
|
+
createdAt: new Date().toISOString(),
|
|
819
|
+
...(Object.keys(unverifiedHints).length > 0 ? { unverified: unverifiedHints } : {}),
|
|
820
|
+
};
|
|
821
|
+
return {
|
|
822
|
+
success: true,
|
|
823
|
+
verified: true,
|
|
824
|
+
terminal: true,
|
|
825
|
+
mandate,
|
|
826
|
+
firstCharge: {
|
|
827
|
+
status: 'succeeded',
|
|
828
|
+
verified: true,
|
|
829
|
+
chargeRef: pending.orderId,
|
|
830
|
+
providerPaymentId: pending.orderId,
|
|
831
|
+
amount: pending.amount,
|
|
832
|
+
currency: 'EUR',
|
|
833
|
+
},
|
|
834
|
+
raw: params,
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
if (!NEXI_SETUP_REFUSALS.has(fields.esito)) {
|
|
838
|
+
// e.g. a pending outcome: the setup may still turn OK, so the pending state must survive
|
|
839
|
+
return unverified(`Mandate setup outcome ${fields.esito} is not final`);
|
|
840
|
+
}
|
|
841
|
+
const messaggio = params['messaggio'];
|
|
842
|
+
const codiceEsito = params['codiceEsito'];
|
|
843
|
+
return {
|
|
844
|
+
success: false,
|
|
845
|
+
verified: true,
|
|
846
|
+
terminal: true,
|
|
847
|
+
firstCharge: {
|
|
848
|
+
status: 'failed',
|
|
849
|
+
verified: true,
|
|
850
|
+
chargeRef: pending.orderId,
|
|
851
|
+
error: messaggio !== undefined && messaggio !== '' ? messaggio : fields.esito,
|
|
852
|
+
...(codiceEsito !== undefined ? { providerCode: codiceEsito } : {}),
|
|
853
|
+
},
|
|
854
|
+
error: `Mandate setup failed with outcome ${fields.esito}`,
|
|
855
|
+
raw: params,
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
catch {
|
|
859
|
+
return unverified('Mandate outcome could not be verified');
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Merchant-initiated charge with `pagamentoRicorrente` (pp. 188-193), EUR only.
|
|
864
|
+
* `codiceTransazione` is `charge.chargeRef`, so `refundPayment(chargeRef, amount)` refunds it.
|
|
865
|
+
*
|
|
866
|
+
* Replay guard: `situazioneOrdine` is read first. A transaction that took the money (paid,
|
|
867
|
+
* partly captured, refunded or partly refunded) is returned as `succeeded` without charging
|
|
868
|
+
* again, with its `stato` in `raw`; one that only Nexi refused (`Negato`, `Annullato`,
|
|
869
|
+
* `Non Creato`) is `failed` (a dunning retry needs a new `chargeRef`: Nexi's own retries of a
|
|
870
|
+
* code after a KO are not used); one still in progress (`In Corso`) or with an unrecognised
|
|
871
|
+
* `stato` is `unknown`, and so is a lookup error: nothing is charged. A Nexi "transaction
|
|
872
|
+
* already present" (9) is re-read.
|
|
873
|
+
* Merchant-side errors (credentials, MAC, technical) are `unknown`, never `failed`.
|
|
874
|
+
*/
|
|
875
|
+
async chargeMandate(mandate, charge) {
|
|
876
|
+
const chargeRef = charge.chargeRef;
|
|
877
|
+
const unknown = (error, providerCode) => ({
|
|
878
|
+
status: 'unknown',
|
|
879
|
+
verified: false,
|
|
880
|
+
chargeRef,
|
|
881
|
+
error,
|
|
882
|
+
...(providerCode !== undefined ? { providerCode } : {}),
|
|
883
|
+
});
|
|
884
|
+
const fromLookup = (lookup) => ({
|
|
885
|
+
status: 'succeeded',
|
|
886
|
+
verified: true,
|
|
887
|
+
chargeRef,
|
|
888
|
+
providerPaymentId: chargeRef,
|
|
889
|
+
...(lookup.amount !== undefined ? { amount: lookup.amount } : {}),
|
|
890
|
+
...(lookup.currency !== undefined ? { currency: lookup.currency } : {}),
|
|
891
|
+
...(lookup.stato !== undefined ? { raw: { stato: lookup.stato } } : {}),
|
|
892
|
+
});
|
|
893
|
+
try {
|
|
894
|
+
if (charge.currency !== 'EUR') {
|
|
895
|
+
return { status: 'failed', verified: false, chargeRef, error: EUR_ONLY };
|
|
896
|
+
}
|
|
897
|
+
if (typeof chargeRef !== 'string' || chargeRef.includes('_')) {
|
|
898
|
+
return { status: 'failed', verified: false, chargeRef, error: 'Nexi codiceTransazione cannot contain "_"' };
|
|
899
|
+
}
|
|
900
|
+
const importo = String((0, money_1.toMinorUnits)(charge.amount, 'EUR'));
|
|
901
|
+
if (importo.length > NEXI_RECURRING_MAX_IMPORTO_DIGITS) {
|
|
902
|
+
// pagamentoRicorrente takes at most 8 digits of cents (pp. 188-193): Nexi would answer KO 1
|
|
903
|
+
// (a merchant-side `unknown`), and a retry with the same chargeRef could never succeed
|
|
904
|
+
return {
|
|
905
|
+
status: 'failed',
|
|
906
|
+
verified: false,
|
|
907
|
+
chargeRef,
|
|
908
|
+
error: 'Amount exceeds the Nexi recurring limit (8 digits of cents)',
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
// Replay guard: never charge twice for the same chargeRef
|
|
912
|
+
const lookup = await this.lookupTransaction(chargeRef);
|
|
913
|
+
if (lookup.kind === 'paid')
|
|
914
|
+
return fromLookup(lookup);
|
|
915
|
+
if (lookup.kind === 'refused') {
|
|
916
|
+
return {
|
|
917
|
+
status: 'failed',
|
|
918
|
+
verified: true,
|
|
919
|
+
chargeRef,
|
|
920
|
+
error: `A transaction with this chargeRef already exists: ${lookup.stato ?? 'unknown'}`,
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
if (lookup.kind === 'pending') {
|
|
924
|
+
// `In Corso` may still end Autorizzato: a `failed` here would send the app to dunning with a
|
|
925
|
+
// new chargeRef, charging the customer twice
|
|
926
|
+
return unknown(`A transaction with this chargeRef is not final at Nexi: ${lookup.stato ?? 'unknown'}`);
|
|
927
|
+
}
|
|
928
|
+
if (lookup.kind !== 'absent') {
|
|
929
|
+
return unknown('Could not check whether this chargeRef was already charged');
|
|
930
|
+
}
|
|
931
|
+
const numeroContratto = mandate.providerMandateId;
|
|
932
|
+
const timeStamp = String(Date.now());
|
|
933
|
+
const body = {
|
|
934
|
+
apiKey: this.config.merchantId,
|
|
935
|
+
numeroContratto,
|
|
936
|
+
codiceTransazione: chargeRef,
|
|
937
|
+
importo,
|
|
938
|
+
divisa: NEXI_EUR_NUMERIC,
|
|
939
|
+
timeStamp,
|
|
940
|
+
mac: this.generateRecurringMac(numeroContratto, chargeRef, importo, timeStamp),
|
|
941
|
+
};
|
|
942
|
+
if (this.config.group)
|
|
943
|
+
body.codiceGruppo = this.config.group;
|
|
944
|
+
const res = await this.postApi('/ecomm/api/recurring/pagamentoRicorrente', body);
|
|
945
|
+
if (!res.ok)
|
|
946
|
+
return unknown(res.error);
|
|
947
|
+
const json = res.json;
|
|
948
|
+
if (!this.responseAuthentic(json))
|
|
949
|
+
return unknown('Response MAC mismatch');
|
|
950
|
+
if (json['esito'] === 'OK') {
|
|
951
|
+
const raw = {};
|
|
952
|
+
if (json['codiceAutorizzazione'] !== undefined)
|
|
953
|
+
raw.codiceAutorizzazione = json['codiceAutorizzazione'];
|
|
954
|
+
const dettagli = json['dettagliCarta'];
|
|
955
|
+
const expiry = dettagli !== null && typeof dettagli === 'object'
|
|
956
|
+
? dettagli['expiry']
|
|
957
|
+
: undefined;
|
|
958
|
+
if (typeof expiry === 'string' && /^\d{6}$/.test(expiry))
|
|
959
|
+
raw.cardExpiry = expiry;
|
|
960
|
+
// The response carries no amount: amount and currency are deliberately not set
|
|
961
|
+
return { status: 'succeeded', verified: true, chargeRef, providerPaymentId: chargeRef, raw };
|
|
962
|
+
}
|
|
963
|
+
if (json['esito'] !== 'KO')
|
|
964
|
+
return unknown('Unexpected Nexi outcome');
|
|
965
|
+
const { codice, messaggio } = this.apiError(json);
|
|
966
|
+
const providerCode = codice === undefined || codice === null ? undefined : String(codice);
|
|
967
|
+
const raw = { esito: 'KO', ...(providerCode !== undefined ? { codice: providerCode } : {}) };
|
|
968
|
+
switch (this.classifyApiError(codice)) {
|
|
969
|
+
case 'duplicate': {
|
|
970
|
+
const again = await this.lookupTransaction(chargeRef);
|
|
971
|
+
if (again.kind === 'paid')
|
|
972
|
+
return fromLookup(again);
|
|
973
|
+
return unknown('Transaction already present', providerCode);
|
|
974
|
+
}
|
|
975
|
+
case 'merchant':
|
|
976
|
+
return { ...unknown('Nexi refused the request (merchant-side error)', providerCode), raw };
|
|
977
|
+
case 'mandateUnusable':
|
|
978
|
+
return {
|
|
979
|
+
status: 'failed',
|
|
980
|
+
verified: true,
|
|
981
|
+
chargeRef,
|
|
982
|
+
mandateUnusable: true,
|
|
983
|
+
error: messaggio ?? 'Mandate is no longer usable',
|
|
984
|
+
...(providerCode !== undefined ? { providerCode } : {}),
|
|
985
|
+
raw,
|
|
986
|
+
};
|
|
987
|
+
default:
|
|
988
|
+
return {
|
|
989
|
+
status: 'failed',
|
|
990
|
+
verified: true,
|
|
991
|
+
chargeRef,
|
|
992
|
+
error: messaggio ?? 'Payment refused',
|
|
993
|
+
...(providerCode !== undefined ? { providerCode } : {}),
|
|
994
|
+
raw,
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
catch (error) {
|
|
999
|
+
if ((0, money_1.isInvalidAmountError)(error)) {
|
|
1000
|
+
return { status: 'failed', verified: false, chargeRef, error: error.message };
|
|
1001
|
+
}
|
|
1002
|
+
return unknown('Charge outcome unknown');
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Re-reads the contract with `dettagliContratto` (pp. 851-853). `active` when it is listed and
|
|
1007
|
+
* its first payment is not KO; `revoked` when its first payment is KO or Nexi answers "not
|
|
1008
|
+
* found" or "invalid contract"; otherwise `unknown` (unverified).
|
|
1009
|
+
*/
|
|
1010
|
+
async getMandate(mandate) {
|
|
1011
|
+
try {
|
|
1012
|
+
const numeroContratto = mandate.providerMandateId;
|
|
1013
|
+
const timeStamp = String(Date.now());
|
|
1014
|
+
const mac = this.sha1(`apiKey=${this.config.merchantId}numeroContratto=${numeroContratto}codiceFiscale=` +
|
|
1015
|
+
`dataRegistrazioneDa=dataRegistrazioneA=timeStamp=${timeStamp}${this.config.macKey}`);
|
|
1016
|
+
const res = await this.postApi('/ecomm/api/contratti/dettagliContratto', {
|
|
1017
|
+
apiKey: this.config.merchantId,
|
|
1018
|
+
numeroContratto,
|
|
1019
|
+
codiceFiscale: '',
|
|
1020
|
+
dataRegistrazioneDa: '',
|
|
1021
|
+
dataRegistrazioneA: '',
|
|
1022
|
+
timeStamp,
|
|
1023
|
+
mac,
|
|
1024
|
+
});
|
|
1025
|
+
if (!res.ok)
|
|
1026
|
+
return { status: 'unknown', verified: false };
|
|
1027
|
+
const json = res.json;
|
|
1028
|
+
if (!this.responseAuthentic(json))
|
|
1029
|
+
return { status: 'unknown', verified: false, raw: json };
|
|
1030
|
+
if (json['esito'] === 'OK' && Array.isArray(json['contratti'])) {
|
|
1031
|
+
const entry = json['contratti'].find((c) => c !== null && typeof c === 'object' && c['numeroContratto'] === numeroContratto);
|
|
1032
|
+
if (entry) {
|
|
1033
|
+
const stato = entry['statoPrimoPag'];
|
|
1034
|
+
const ko = typeof stato === 'string' && stato.toUpperCase() === 'KO';
|
|
1035
|
+
return { status: ko ? 'revoked' : 'active', verified: true, raw: json };
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
if (json['esito'] === 'KO') {
|
|
1039
|
+
const kind = this.classifyApiError(this.apiError(json).codice);
|
|
1040
|
+
if (kind === 'notFound' || kind === 'mandateUnusable') {
|
|
1041
|
+
return { status: 'revoked', verified: true, raw: json };
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
return { status: 'unknown', verified: false, raw: json };
|
|
1045
|
+
}
|
|
1046
|
+
catch {
|
|
1047
|
+
return { status: 'unknown', verified: false };
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Cancels the contract with `cancellaContratto` (pp. 838-840). "Not found" and "invalid
|
|
1052
|
+
* contract" count as revoked: the contract is already gone.
|
|
1053
|
+
*/
|
|
1054
|
+
async revokeMandate(mandate) {
|
|
1055
|
+
try {
|
|
1056
|
+
const numeroContratto = mandate.providerMandateId;
|
|
1057
|
+
const timeStamp = String(Date.now());
|
|
1058
|
+
const mac = this.sha1(`apiKey=${this.config.merchantId}numeroContratto=${numeroContratto}timeStamp=${timeStamp}${this.config.macKey}`);
|
|
1059
|
+
const res = await this.postApi('/ecomm/api/contratti/cancellaContratto', {
|
|
1060
|
+
apiKey: this.config.merchantId,
|
|
1061
|
+
numeroContratto,
|
|
1062
|
+
timeStamp,
|
|
1063
|
+
mac,
|
|
1064
|
+
});
|
|
1065
|
+
if (!res.ok)
|
|
1066
|
+
return { revokedAtProvider: false, error: res.error };
|
|
1067
|
+
const json = res.json;
|
|
1068
|
+
if (!this.responseAuthentic(json)) {
|
|
1069
|
+
return { revokedAtProvider: false, error: 'Response MAC mismatch', raw: json };
|
|
1070
|
+
}
|
|
1071
|
+
if (json['esito'] === 'OK')
|
|
1072
|
+
return { revokedAtProvider: true, raw: json };
|
|
1073
|
+
if (json['esito'] === 'KO') {
|
|
1074
|
+
const { codice } = this.apiError(json);
|
|
1075
|
+
const kind = this.classifyApiError(codice);
|
|
1076
|
+
if (kind === 'notFound' || kind === 'mandateUnusable')
|
|
1077
|
+
return { revokedAtProvider: true, raw: json };
|
|
1078
|
+
return {
|
|
1079
|
+
revokedAtProvider: false,
|
|
1080
|
+
error: `Nexi refused the cancellation (code ${codice === undefined ? 'missing' : String(codice)})`,
|
|
1081
|
+
raw: json,
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
return { revokedAtProvider: false, error: 'Unexpected Nexi outcome', raw: json };
|
|
1085
|
+
}
|
|
1086
|
+
catch {
|
|
1087
|
+
return { revokedAtProvider: false, error: 'Failed to revoke Nexi mandate' };
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
448
1090
|
}
|
|
449
1091
|
exports.NexiProvider = NexiProvider;
|
|
450
1092
|
//# sourceMappingURL=nexi.provider.js.map
|