@secureprocessing/node 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +248 -2
- package/dist/client.d.ts +94 -0
- package/dist/client.js +196 -0
- package/dist/errors.d.ts +99 -0
- package/dist/errors.js +125 -0
- package/dist/index.d.ts +603 -0
- package/dist/index.js +520 -0
- package/dist/pagination.d.ts +32 -0
- package/dist/pagination.js +52 -0
- package/dist/types.d.ts +892 -0
- package/dist/types.js +3 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.js +9 -0
- package/dist/webhooks.d.ts +22 -0
- package/dist/webhooks.js +79 -0
- package/package.json +32 -4
package/dist/errors.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// @ts-self-types="./errors.d.ts"
|
|
2
|
+
/**
|
|
3
|
+
* Something the API refused.
|
|
4
|
+
*
|
|
5
|
+
* Branch on `type` for the category and `code` for the specific reason. For a declined card,
|
|
6
|
+
* `declineCode` says why in terms you can show a customer. `requestId` is what to quote to support.
|
|
7
|
+
* `param` names the field at fault — including a `404 resource_missing` for an id in the body that
|
|
8
|
+
* is not yours (`param: "customer"`). A `403` names the missing permission in `requiredCapability`.
|
|
9
|
+
*/
|
|
10
|
+
export class SecureProcessingError extends Error {
|
|
11
|
+
type;
|
|
12
|
+
code;
|
|
13
|
+
param;
|
|
14
|
+
declineCode;
|
|
15
|
+
requestId;
|
|
16
|
+
status;
|
|
17
|
+
/**
|
|
18
|
+
* The `Idempotency-Key` the failed request was sent with — yours, or the one the client generated
|
|
19
|
+
* for a POST you did not give a key. `undefined` for a GET, which has none.
|
|
20
|
+
*
|
|
21
|
+
* 🔴 To retry a failed POST, pass THIS back: `{ idempotencyKey: err.idempotencyKey }`. Calling the
|
|
22
|
+
* method again without it sends a new key, and a new key is a new request to the API: if the
|
|
23
|
+
* first one did reach the bank, that is a second charge or refund.
|
|
24
|
+
*/
|
|
25
|
+
idempotencyKey;
|
|
26
|
+
/**
|
|
27
|
+
* On `403 permission_error` / `insufficient_permissions`: the permission this request needed and
|
|
28
|
+
* your API key — or, from a dashboard session, your role — does not have, such as
|
|
29
|
+
* `payments:refund`. Ask the account's owner for it; retrying will not help (`isRetryable` is
|
|
30
|
+
* false). `undefined` on every other error.
|
|
31
|
+
*/
|
|
32
|
+
requiredCapability;
|
|
33
|
+
/**
|
|
34
|
+
* `requestId` falls back to the `X-Request-Id` response header: a response that never reached
|
|
35
|
+
* the API's error handler (a proxy's 502, say) has no envelope to carry it, and the header is
|
|
36
|
+
* the one thing support can still look up.
|
|
37
|
+
*/
|
|
38
|
+
constructor(status, body, requestIdHeader, idempotencyKey) {
|
|
39
|
+
super(body.error.code === "outcome_unknown"
|
|
40
|
+
? `${body.error.message} ${outcomeUnknownNote(idempotencyKey)}`
|
|
41
|
+
: body.error.message);
|
|
42
|
+
this.name = "SecureProcessingError";
|
|
43
|
+
this.idempotencyKey = idempotencyKey;
|
|
44
|
+
this.requiredCapability = body.error.required_capability;
|
|
45
|
+
this.status = status;
|
|
46
|
+
this.type = body.error.type;
|
|
47
|
+
this.code = body.error.code;
|
|
48
|
+
this.param = body.error.param;
|
|
49
|
+
this.declineCode = body.error.decline_code;
|
|
50
|
+
this.requestId = body.error.request_id ?? requestIdHeader ?? undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* True when retrying the identical request — with the SAME idempotency key — could reasonably
|
|
54
|
+
* succeed soon. For a POST that means `{ idempotencyKey: err.idempotencyKey }`, never a new key.
|
|
55
|
+
*
|
|
56
|
+
* `idempotency_in_progress` is included: the first request with that key is still running, and
|
|
57
|
+
* the API answers a retry either with another 409 or, once it has finished, with its stored
|
|
58
|
+
* answer, so a retry under that key cannot run the operation twice. The client already retries
|
|
59
|
+
* it for you, up to `maxRetries` times and never past 30 seconds from the first attempt.
|
|
60
|
+
* `idempotency_key_in_use` (a key reused with a different body) is not: that is a bug in the
|
|
61
|
+
* caller and will fail identically for ever.
|
|
62
|
+
*
|
|
63
|
+
* `payment_in_progress` is deliberately NOT retryable. See `isPaymentInProgress`. Nor is
|
|
64
|
+
* `outcome_unknown`, although it is a 500. See `isOutcomeUnknown`.
|
|
65
|
+
*/
|
|
66
|
+
get isRetryable() {
|
|
67
|
+
if (this.isOutcomeUnknown)
|
|
68
|
+
return false;
|
|
69
|
+
return (this.status >= 500 ||
|
|
70
|
+
this.status === 429 ||
|
|
71
|
+
this.code === "gateway_unavailable" ||
|
|
72
|
+
this.code === "idempotency_in_progress");
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* `409 payment_in_progress`: the customer may be on the bank's page, or in 3-D Secure, for this
|
|
76
|
+
* payment right now, so it cannot be cancelled yet. Nothing was changed. The message says when to
|
|
77
|
+
* try again — minutes, not a backoff — and an unpaid payment is cancelled automatically anyway.
|
|
78
|
+
* The client never retries it: a loop here would only poll the bank on the customer's behalf.
|
|
79
|
+
*/
|
|
80
|
+
get isPaymentInProgress() {
|
|
81
|
+
return this.code === "payment_in_progress";
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 🔴 `500 outcome_unknown`: the request asked the bank to move money — a charge, a capture, a
|
|
85
|
+
* refund — and the API could not confirm what happened. The money MAY HAVE MOVED.
|
|
86
|
+
*
|
|
87
|
+
* - Do not send it again under a NEW idempotency key: that is a second charge or refund.
|
|
88
|
+
* - Retrying under the SAME key (`err.idempotencyKey`) is always safe — it never reaches the bank
|
|
89
|
+
* again. Usually the API has stored this answer and replays it. If the message says an earlier
|
|
90
|
+
* request with the key "has not recorded its result yet", the original is still in flight, and
|
|
91
|
+
* a LATER retry under the same key returns its result once it finishes. Either way an immediate
|
|
92
|
+
* retry gains nothing, so the client does not retry it, and `isRetryable` is false.
|
|
93
|
+
* - Find out by reading the object: retrieve the payment intent (or list its refunds, or the
|
|
94
|
+
* invoice's payments) and look at its `status`. It settles on its own within minutes and its
|
|
95
|
+
* webhook follows.
|
|
96
|
+
*/
|
|
97
|
+
get isOutcomeUnknown() {
|
|
98
|
+
return this.code === "outcome_unknown";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Appended to an `outcome_unknown` message so the instruction travels into whatever logs it. */
|
|
102
|
+
function outcomeUnknownNote(idempotencyKey) {
|
|
103
|
+
const key = idempotencyKey ? ` (it was sent with Idempotency-Key ${idempotencyKey}, err.idempotencyKey)` : "";
|
|
104
|
+
return (`(Do not retry this under a new Idempotency-Key${key} — the money may already have moved. ` +
|
|
105
|
+
"Retrieve the payment, refund or invoice and check its status; it settles on its own and a " +
|
|
106
|
+
"webhook follows.)");
|
|
107
|
+
}
|
|
108
|
+
/** The request never got an answer: a timeout, a DNS failure, a dropped connection. */
|
|
109
|
+
export class SecureProcessingConnectionError extends Error {
|
|
110
|
+
// `override` because Error already declares `cause`; keeping the original is what lets a caller
|
|
111
|
+
// see the underlying network failure rather than only our wrapper.
|
|
112
|
+
cause;
|
|
113
|
+
/**
|
|
114
|
+
* 🔴 The `Idempotency-Key` the request was sent with, generated or yours. A connection failure
|
|
115
|
+
* does not say whether the request reached us: retry with `{ idempotencyKey: err.idempotencyKey }`
|
|
116
|
+
* and the API replays its answer if it did; a new key would run it again.
|
|
117
|
+
*/
|
|
118
|
+
idempotencyKey;
|
|
119
|
+
constructor(message, cause, idempotencyKey) {
|
|
120
|
+
super(message);
|
|
121
|
+
this.name = "SecureProcessingConnectionError";
|
|
122
|
+
this.cause = cause;
|
|
123
|
+
this.idempotencyKey = idempotencyKey;
|
|
124
|
+
}
|
|
125
|
+
}
|