@distilled.cloud/stripe 1.0.0-rc.15 → 1.0.0-rc.17
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/lib/.lazy.json +1 -0
- package/lib/_bundle/lazy.js +4 -0
- package/lib/_bundle/sdk.js +2 -0
- package/lib/_bundle/sdk.js.map +1 -0
- package/lib/credentials.d.ts +1 -1
- package/lib/credentials.d.ts.map +1 -1
- package/lib/credentials.js +1 -31
- package/lib/errors.d.ts +83 -83
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +1 -130
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -14
- package/lib/index.lazy.js +8 -0
- package/lib/pagination.d.ts +3 -3
- package/lib/pagination.js +1 -28
- package/lib/protocol.d.ts +12 -12
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +1 -399
- package/lib/retry.d.ts +1 -1
- package/lib/retry.d.ts.map +1 -1
- package/lib/retry.js +1 -32
- package/lib/services/index.js +0 -2
- package/lib/services/stripe.d.ts +507 -14141
- package/lib/services/stripe.d.ts.map +1 -1
- package/lib/services/stripe.js +918 -53147
- package/lib/services/stripe.js.map +1 -1
- package/lib/services.lazy/index.js +3 -0
- package/lib/services.lazy/stripe.js +4 -0
- package/lib/traits.d.ts +2 -2
- package/lib/traits.js +1 -16
- package/lib/webhooks.js +1 -160
- package/lib/webhooks.js.map +1 -1
- package/package.json +9 -7
- package/src/credentials.ts +12 -11
- package/src/errors.ts +89 -88
- package/src/index.ts +1 -1
- package/src/pagination.ts +3 -3
- package/src/protocol.ts +278 -162
- package/src/retry.ts +6 -4
- package/src/services/stripe.ts +37296 -85780
- package/src/traits.ts +2 -2
- package/src/webhooks.ts +2 -2
- package/lib/credentials.js.map +0 -1
- package/lib/errors.js.map +0 -1
- package/lib/pagination.js.map +0 -1
- package/lib/protocol.js.map +0 -1
- package/lib/protocol.test.d.ts +0 -2
- package/lib/protocol.test.d.ts.map +0 -1
- package/lib/protocol.test.js +0 -353
- package/lib/protocol.test.js.map +0 -1
- package/lib/retry.js.map +0 -1
- package/lib/services/index.js.map +0 -1
- package/lib/traits.js.map +0 -1
- package/src/protocol.test.ts +0 -519
- /package/lib/{index.js.map → index.lazy.js.map} +0 -0
package/lib/pagination.js
CHANGED
|
@@ -1,28 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Stripe pagination — hand-written.
|
|
3
|
-
*
|
|
4
|
-
* Only the GET …/search operations paginate (v0 parity): the converter
|
|
5
|
-
* detects them as `mode: "page"` (inputToken `page`, outputToken
|
|
6
|
-
* `next_page`, items `data`), but Stripe's `page` parameter is an OPAQUE
|
|
7
|
-
* CURSOR — `next_page` is the next cursor string, not a page number — so
|
|
8
|
-
* "page" mode traverses by following the cursor, exactly core's
|
|
9
|
-
* `paginateCursor` semantics.
|
|
10
|
-
*
|
|
11
|
-
* Stripe's regular list endpoints (starting_after / ending_before +
|
|
12
|
-
* has_more) are plain operations without the paginated surface, mirroring
|
|
13
|
-
* distilled v0.
|
|
14
|
-
*/
|
|
15
|
-
import * as Pagination from "@distilled.cloud/core/pagination";
|
|
16
|
-
/** Dispatch on the operation's pagination mode, Stripe-style. */
|
|
17
|
-
export const stripeSearchPaginate = (operation, input, pagination) => {
|
|
18
|
-
switch (pagination.mode) {
|
|
19
|
-
case "page":
|
|
20
|
-
// Stripe search: `page` is a cursor, `next_page` the next cursor.
|
|
21
|
-
return Pagination.paginateCursor(operation, input, pagination);
|
|
22
|
-
case "single":
|
|
23
|
-
return Pagination.paginateSingle(operation, input, pagination);
|
|
24
|
-
default:
|
|
25
|
-
return Pagination.paginateWithDefaults(operation, input, pagination);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
//# sourceMappingURL=pagination.js.map
|
|
1
|
+
export { pagination$stripeSearchPaginate as stripeSearchPaginate } from "./_bundle/sdk.js";
|
package/lib/protocol.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import * as API from "@distilled.cloud/core/api";
|
|
2
2
|
import { ConfigError, type DefaultErrors as CoreDefaultErrors, BadRequest, Conflict, Forbidden, Locked, NotFound, UnprocessableEntity } from "@distilled.cloud/core/errors";
|
|
3
3
|
/**
|
|
4
|
-
* StripeProtocol
|
|
4
|
+
* StripeProtocol -- hand-written.
|
|
5
5
|
*
|
|
6
6
|
* Speaks Stripe's REST protocol. The generic trait helpers live in
|
|
7
7
|
* `@distilled.cloud/core/protocol-http`; this module supplies what is
|
|
8
8
|
* Stripe's own (ported from the distilled v0 stripe client):
|
|
9
9
|
*
|
|
10
|
-
* request: credentials
|
|
10
|
+
* request: credentials -> `Authorization: Bearer <apiKey>` + API base URL;
|
|
11
11
|
* per-call {@link StripeRequestOptions} (resolved from the
|
|
12
|
-
* calling fiber via {@link RequestOptions})
|
|
12
|
+
* calling fiber via {@link RequestOptions}) -> Idempotency-Key /
|
|
13
13
|
* Stripe-Account / Stripe-Context / Stripe-Version headers;
|
|
14
14
|
* GET inputs flatten body-bound members into the query string
|
|
15
15
|
* with Stripe bracket notation (`created[gte]`, `expand[]`);
|
|
@@ -20,20 +20,20 @@ import { ConfigError, type DefaultErrors as CoreDefaultErrors, BadRequest, Confl
|
|
|
20
20
|
* as JSON otherwise (the /v2 ops), and as multipart form-data
|
|
21
21
|
* for `contentType: "multipart"` (POST /v1/files)
|
|
22
22
|
*
|
|
23
|
-
* response: 2xx JSON is the payload (no envelope)
|
|
23
|
+
* response: 2xx JSON is the payload (no envelope) -> wire->TS key mapping ->
|
|
24
24
|
* Redacted wrapping of sensitive members; failures parse the
|
|
25
|
-
* `{ error: { type, code, message,
|
|
25
|
+
* `{ error: { type, code, message, ... } }` envelope and dispatch
|
|
26
26
|
* by per-op matcher classes first (`matchTypedError`, e.g.
|
|
27
|
-
* ProductHasPrices), then `error.type` (card_error
|
|
28
|
-
* idempotency_error
|
|
29
|
-
* InvalidRequestError, api_error
|
|
30
|
-
* Stripe-specific status map (402
|
|
27
|
+
* ProductHasPrices), then `error.type` (card_error -> CardError,
|
|
28
|
+
* idempotency_error -> IdempotencyError, invalid_request_error ->
|
|
29
|
+
* InvalidRequestError, api_error -> ApiError), then the
|
|
30
|
+
* Stripe-specific status map (402 -> PaymentError, 424 ->
|
|
31
31
|
* ExternalDependencyFailed), then core's HTTP status map (with
|
|
32
32
|
* retryAfter hints), then UnknownStripeError.
|
|
33
33
|
*/
|
|
34
34
|
import * as Context from "effect/Context";
|
|
35
35
|
import * as Effect from "effect/Effect";
|
|
36
|
-
import
|
|
36
|
+
import * as HttpClient from "effect/http/HttpClient";
|
|
37
37
|
import type * as HttpClientError from "effect/http/HttpClientError";
|
|
38
38
|
import * as Layer from "effect/Layer";
|
|
39
39
|
import { Credentials } from "./credentials.ts";
|
|
@@ -53,9 +53,9 @@ declare const RequestOptions_base: Context.ServiceClass<RequestOptions, "StripeR
|
|
|
53
53
|
/**
|
|
54
54
|
* Per-call request options service. distilled v0 passed these as a second
|
|
55
55
|
* call argument; v1 operations take only their input, so options travel on
|
|
56
|
-
* the calling fiber's context instead
|
|
56
|
+
* the calling fiber's context instead -- see {@link withRequestOptions}.
|
|
57
57
|
*/
|
|
58
|
-
export declare class RequestOptions extends RequestOptions_base {
|
|
58
|
+
export declare class RequestOptions extends /*@__PURE__*/ RequestOptions_base {
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
61
|
* Provide {@link StripeRequestOptions} to every Stripe API call below it.
|
package/lib/protocol.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,2BAA2B,CAAC;AAEjD,OAAO,EACL,WAAW,EAGX,KAAK,aAAa,IAAI,iBAAiB,EACvC,UAAU,EACV,QAAQ,EACR,SAAS,EACT,MAAM,EACN,QAAQ,EACR,mBAAmB,EACpB,MAAM,8BAA8B,CAAC;AAoBtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,KAAK,UAAU,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,KAAK,eAAe,MAAM,6BAA6B,CAAC;AAGpE,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAGtC,OAAO,EAAE,WAAW,EAAe,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EACL,QAAQ,EACR,SAAS,EACT,wBAAwB,EACxB,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EAEZ,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAcrB,MAAM,MAAM,2BAA2B,GACnC;IACE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC;CAChC,GACD;IACE,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC;IAC/B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7C,CAAC;AAEN,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C,GAAG,2BAA2B,CAAC;;AAEhC;;;;GAIG;AACH,qBAAa,cACX,SAAQ,aAAa,CAAC,mBAErB;CAAG;AAEN;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,YAAa,oBAAoB,+FACR,CAAC;AAyBzD;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,gBAAgB,GAChB,mBAAmB,GACnB,QAAQ,GACR,YAAY,GACZ,wBAAwB,GACxB,kBAAkB,GAClB,gBAAgB,GAChB,UAAU,GACV,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,mBAAmB,GACnB,MAAM,GACN,iBAAiB,GACjB,WAAW,GACX,eAAe,CAAC,eAAe,CAAC;AAEpC,yEAAyE;AACzE,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,UAAU,CAAC,UAAU,CAAC;AA4dlE,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAWpD,CAAC"}
|
package/lib/protocol.js
CHANGED
|
@@ -1,399 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { ConfigError, HTTP_STATUS_MAP, InternalServerError, BadRequest, Conflict, Forbidden, Locked, NotFound, UnprocessableEntity, } from "@distilled.cloud/core/errors";
|
|
3
|
-
import { buildRequest, getAnn, getProps, hasPropAnn, mapKeys, matchTypedError, nameOf, } from "@distilled.cloud/core/protocol-http";
|
|
4
|
-
import { unwrapRedactedDeep, wrapSensitive } from "@distilled.cloud/core/protocol-rest";
|
|
5
|
-
import { validateResponse } from "@distilled.cloud/core/response-validation";
|
|
6
|
-
import { parseRetryAfterForStatus } from "@distilled.cloud/core/retry-after";
|
|
7
|
-
/**
|
|
8
|
-
* StripeProtocol — hand-written.
|
|
9
|
-
*
|
|
10
|
-
* Speaks Stripe's REST protocol. The generic trait helpers live in
|
|
11
|
-
* `@distilled.cloud/core/protocol-http`; this module supplies what is
|
|
12
|
-
* Stripe's own (ported from the distilled v0 stripe client):
|
|
13
|
-
*
|
|
14
|
-
* request: credentials → `Authorization: Bearer <apiKey>` + API base URL;
|
|
15
|
-
* per-call {@link StripeRequestOptions} (resolved from the
|
|
16
|
-
* calling fiber via {@link RequestOptions}) → Idempotency-Key /
|
|
17
|
-
* Stripe-Account / Stripe-Context / Stripe-Version headers;
|
|
18
|
-
* GET inputs flatten body-bound members into the query string
|
|
19
|
-
* with Stripe bracket notation (`created[gte]`, `expand[]`);
|
|
20
|
-
* non-GET bodies encode as `application/x-www-form-urlencoded`
|
|
21
|
-
* with Stripe deepObject bracket expansion (`shipping[address]
|
|
22
|
-
* [city]=SF`, `expand[0]=data`) when the operation's `T.Http`
|
|
23
|
-
* trait says `contentType: "form-urlencoded"` (every /v1 op),
|
|
24
|
-
* as JSON otherwise (the /v2 ops), and as multipart form-data
|
|
25
|
-
* for `contentType: "multipart"` (POST /v1/files)
|
|
26
|
-
*
|
|
27
|
-
* response: 2xx JSON is the payload (no envelope) → wire→TS key mapping →
|
|
28
|
-
* Redacted wrapping of sensitive members; failures parse the
|
|
29
|
-
* `{ error: { type, code, message, … } }` envelope and dispatch
|
|
30
|
-
* by per-op matcher classes first (`matchTypedError`, e.g.
|
|
31
|
-
* ProductHasPrices), then `error.type` (card_error → CardError,
|
|
32
|
-
* idempotency_error → IdempotencyError, invalid_request_error →
|
|
33
|
-
* InvalidRequestError, api_error → ApiError), then the
|
|
34
|
-
* Stripe-specific status map (402 → PaymentError, 424 →
|
|
35
|
-
* ExternalDependencyFailed), then core's HTTP status map (with
|
|
36
|
-
* retryAfter hints), then UnknownStripeError.
|
|
37
|
-
*/
|
|
38
|
-
import * as Context from "effect/Context";
|
|
39
|
-
import * as Effect from "effect/Effect";
|
|
40
|
-
import * as HttpBody from "effect/http/HttpBody";
|
|
41
|
-
import * as HttpClientRequest from "effect/http/HttpClientRequest";
|
|
42
|
-
import * as Layer from "effect/Layer";
|
|
43
|
-
import * as Option from "effect/Option";
|
|
44
|
-
import * as Redacted from "effect/Redacted";
|
|
45
|
-
import { Credentials } from "./credentials.js";
|
|
46
|
-
import { ApiError, CardError, ExternalDependencyFailed, IdempotencyError, InvalidRequestError, PaymentError, STRIPE_HTTP_STATUS_MAP, StripeParseError, UnknownStripeError, } from "./errors.js";
|
|
47
|
-
import { bodySymbol, headerSymbol, httpSymbol, labelSymbol, querySymbol, } from "./traits.js";
|
|
48
|
-
/**
|
|
49
|
-
* Per-call request options service. distilled v0 passed these as a second
|
|
50
|
-
* call argument; v1 operations take only their input, so options travel on
|
|
51
|
-
* the calling fiber's context instead — see {@link withRequestOptions}.
|
|
52
|
-
*/
|
|
53
|
-
export class RequestOptions extends Context.Service()("StripeRequestOptions") {
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Provide {@link StripeRequestOptions} to every Stripe API call below it.
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* ```ts
|
|
60
|
-
* yield* PostCharges({ amount: 2000, currency: "usd" }).pipe(
|
|
61
|
-
* Stripe.withRequestOptions({ idempotencyKey: "ch-create-1" }),
|
|
62
|
-
* );
|
|
63
|
-
* ```
|
|
64
|
-
*/
|
|
65
|
-
export const withRequestOptions = (options) => Effect.provide(Layer.succeed(RequestOptions, options));
|
|
66
|
-
const stripeRequestHeaders = (options) => {
|
|
67
|
-
const headers = {};
|
|
68
|
-
if (options?.idempotencyKey !== undefined) {
|
|
69
|
-
headers["Idempotency-Key"] = options.idempotencyKey;
|
|
70
|
-
}
|
|
71
|
-
if (options?.stripeAccount !== undefined) {
|
|
72
|
-
headers["Stripe-Account"] = options.stripeAccount;
|
|
73
|
-
}
|
|
74
|
-
if (options?.stripeContext !== undefined) {
|
|
75
|
-
headers["Stripe-Context"] = options.stripeContext;
|
|
76
|
-
}
|
|
77
|
-
if (options?.apiVersion !== undefined) {
|
|
78
|
-
headers["Stripe-Version"] = options.apiVersion;
|
|
79
|
-
}
|
|
80
|
-
return headers;
|
|
81
|
-
};
|
|
82
|
-
// Bridge: Protocol.decode is typed as Effect<unknown> (no error channel), but
|
|
83
|
-
// Stripe failures are real typed errors that an operation re-surfaces via its
|
|
84
|
-
// `errors: [...]` list / StripeOpError annotation. Fail with the instance and
|
|
85
|
-
// erase the error type here; the generated annotations reintroduce it.
|
|
86
|
-
const fail = (e) => Effect.fail(e);
|
|
87
|
-
// =============================================================================
|
|
88
|
-
// Stripe query / form encoding (ported from distilled v0)
|
|
89
|
-
// =============================================================================
|
|
90
|
-
const BODYLESS = new Set(["GET", "HEAD"]);
|
|
91
|
-
const appendQueryValue = (query, key, value) => {
|
|
92
|
-
if (value === undefined || value === null)
|
|
93
|
-
return;
|
|
94
|
-
query.append(key, typeof value === "boolean" ? (value ? "true" : "false") : String(value));
|
|
95
|
-
};
|
|
96
|
-
/**
|
|
97
|
-
* Stripe GET-query bracket expansion (v0's `appendStripeQuery`): arrays as
|
|
98
|
-
* repeated `key[]` entries, nested objects as `key[nested]`, recursively.
|
|
99
|
-
*/
|
|
100
|
-
const appendStripeQuery = (query, key, value) => {
|
|
101
|
-
if (value === undefined || value === null)
|
|
102
|
-
return;
|
|
103
|
-
if (Array.isArray(value)) {
|
|
104
|
-
for (const item of value)
|
|
105
|
-
appendQueryValue(query, `${key}[]`, item);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
if (typeof value === "object" && !(value instanceof Date)) {
|
|
109
|
-
for (const [nestedKey, nestedValue] of Object.entries(value)) {
|
|
110
|
-
appendStripeQuery(query, `${key}[${nestedKey}]`, nestedValue);
|
|
111
|
-
}
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
appendQueryValue(query, key, value);
|
|
115
|
-
};
|
|
116
|
-
/**
|
|
117
|
-
* Recursively flatten a nested object into Stripe-style bracket notation
|
|
118
|
-
* for application/x-www-form-urlencoded encoding (v0's `flattenToFormPairs`).
|
|
119
|
-
*
|
|
120
|
-
* Examples:
|
|
121
|
-
* { amount: 2000 } -> "amount=2000"
|
|
122
|
-
* { shipping: { address: { city: "SF" } } } -> "shipping[address][city]=SF"
|
|
123
|
-
* { expand: ["data"] } -> "expand[0]=data"
|
|
124
|
-
* { metadata: { key: "val" } } -> "metadata[key]=val"
|
|
125
|
-
*/
|
|
126
|
-
const flattenToFormPairs = (obj, prefix = "") => {
|
|
127
|
-
const pairs = [];
|
|
128
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
129
|
-
if (value === undefined || value === null)
|
|
130
|
-
continue;
|
|
131
|
-
const fullKey = prefix ? `${prefix}[${key}]` : key;
|
|
132
|
-
if (Array.isArray(value)) {
|
|
133
|
-
for (let i = 0; i < value.length; i++) {
|
|
134
|
-
const item = value[i];
|
|
135
|
-
if (item !== null &&
|
|
136
|
-
item !== undefined &&
|
|
137
|
-
typeof item === "object" &&
|
|
138
|
-
!Array.isArray(item)) {
|
|
139
|
-
pairs.push(...flattenToFormPairs(item, `${fullKey}[${i}]`));
|
|
140
|
-
}
|
|
141
|
-
else if (item !== undefined && item !== null) {
|
|
142
|
-
pairs.push([`${fullKey}[${i}]`, String(item)]);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
else if (typeof value === "object") {
|
|
147
|
-
pairs.push(...flattenToFormPairs(value, fullKey));
|
|
148
|
-
}
|
|
149
|
-
else if (typeof value === "boolean") {
|
|
150
|
-
pairs.push([fullKey, value ? "true" : "false"]);
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
pairs.push([fullKey, String(value)]);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return pairs;
|
|
157
|
-
};
|
|
158
|
-
/** Build the urlencoded body from a nested object (Stripe deepObject style). */
|
|
159
|
-
const buildFormUrlEncoded = (body) => {
|
|
160
|
-
const params = new URLSearchParams();
|
|
161
|
-
for (const [key, value] of flattenToFormPairs(body)) {
|
|
162
|
-
params.append(key, value);
|
|
163
|
-
}
|
|
164
|
-
return params.toString();
|
|
165
|
-
};
|
|
166
|
-
// =============================================================================
|
|
167
|
-
// Request building
|
|
168
|
-
// =============================================================================
|
|
169
|
-
// The protocol layer is memoized per process by `API.make`, so the build
|
|
170
|
-
// must not capture credentials — `encode` resolves Credentials (and the
|
|
171
|
-
// per-call RequestOptions) from the calling fiber's context on every
|
|
172
|
-
// request. The requirement is erased at this boundary and reintroduced for
|
|
173
|
-
// callers by the generated `StripeOpContext` annotations.
|
|
174
|
-
const encode = ({ input, inputAst }) => Effect.gen(function* () {
|
|
175
|
-
const resolveCredentials = yield* Credentials;
|
|
176
|
-
const creds = yield* resolveCredentials;
|
|
177
|
-
const options = Option.getOrUndefined(yield* Effect.serviceOption(RequestOptions));
|
|
178
|
-
const http = getAnn(inputAst, httpSymbol);
|
|
179
|
-
if (!http) {
|
|
180
|
-
throw new Error("operation input is missing the Http() trait");
|
|
181
|
-
}
|
|
182
|
-
const baseHeaders = {
|
|
183
|
-
Authorization: `Bearer ${Redacted.value(creds.apiKey)}`,
|
|
184
|
-
...stripeRequestHeaders(options),
|
|
185
|
-
Accept: "application/json",
|
|
186
|
-
};
|
|
187
|
-
// Multipart (POST /v1/files): core's buildRequest speaks multipart
|
|
188
|
-
// form-data (file parts + JSON-encoded object parts) — same behavior as
|
|
189
|
-
// v0's buildFormData.
|
|
190
|
-
if (http.contentType === "multipart") {
|
|
191
|
-
return buildRequest({
|
|
192
|
-
input: unwrapRedactedDeep(input),
|
|
193
|
-
inputAst,
|
|
194
|
-
baseUrl: creds.apiBaseUrl,
|
|
195
|
-
headers: baseHeaders,
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
const inputObj = (unwrapRedactedDeep(input) ?? {});
|
|
199
|
-
const headers = { ...baseHeaders };
|
|
200
|
-
const body = {};
|
|
201
|
-
const query = new URLSearchParams();
|
|
202
|
-
let uri = http.uri;
|
|
203
|
-
const consumed = new Set();
|
|
204
|
-
for (const prop of getProps(inputAst)) {
|
|
205
|
-
const key = String(prop.name);
|
|
206
|
-
consumed.add(key);
|
|
207
|
-
const value = inputObj[key];
|
|
208
|
-
if (value === undefined)
|
|
209
|
-
continue;
|
|
210
|
-
if (hasPropAnn(prop, labelSymbol)) {
|
|
211
|
-
const token = nameOf(prop, labelSymbol);
|
|
212
|
-
uri = uri.replace(`{${token}}`, encodeURIComponent(String(value)));
|
|
213
|
-
}
|
|
214
|
-
else if (hasPropAnn(prop, headerSymbol)) {
|
|
215
|
-
headers[nameOf(prop, headerSymbol).toLowerCase()] = String(value);
|
|
216
|
-
}
|
|
217
|
-
else if (hasPropAnn(prop, querySymbol)) {
|
|
218
|
-
// Stripe bracket expansion, not dotted params (`created[gte]=1`).
|
|
219
|
-
appendStripeQuery(query, nameOf(prop, querySymbol), value);
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
body[nameOf(prop, bodySymbol)] = mapKeys(prop.type, value, "encode");
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
// Input keys the schema doesn't know pass through as body fields — the
|
|
226
|
-
// generated schemas can lag the real API, and silently dropping them
|
|
227
|
-
// would break working callers.
|
|
228
|
-
for (const [key, value] of Object.entries(inputObj)) {
|
|
229
|
-
if (consumed.has(key) || value === undefined)
|
|
230
|
-
continue;
|
|
231
|
-
body[key] = value;
|
|
232
|
-
}
|
|
233
|
-
// v0's normalizeStripeGetQuery: GET inputs carry no body — flatten the
|
|
234
|
-
// body-bound members into the query string with bracket notation.
|
|
235
|
-
if (BODYLESS.has(http.method)) {
|
|
236
|
-
for (const [key, value] of Object.entries(body)) {
|
|
237
|
-
appendStripeQuery(query, key, value);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
const qs = query.toString();
|
|
241
|
-
const url = `${creds.apiBaseUrl}${uri}${qs ? `?${qs}` : ""}`;
|
|
242
|
-
if (process.env.DISTILLED_DEBUG_HTTP) {
|
|
243
|
-
console.error(`[distilled] ${http.method} ${url}` +
|
|
244
|
-
(Object.keys(body).length && !BODYLESS.has(http.method)
|
|
245
|
-
? ` body=${JSON.stringify(body).slice(0, 400)}`
|
|
246
|
-
: ""));
|
|
247
|
-
}
|
|
248
|
-
let request = HttpClientRequest.make(http.method)(url).pipe(HttpClientRequest.setHeaders(headers));
|
|
249
|
-
if (!BODYLESS.has(http.method) && Object.keys(body).length > 0) {
|
|
250
|
-
request =
|
|
251
|
-
http.contentType === "form-urlencoded"
|
|
252
|
-
? request.pipe(HttpClientRequest.setBody(HttpBody.text(buildFormUrlEncoded(body), "application/x-www-form-urlencoded")))
|
|
253
|
-
: // The /v2/* operations are plain JSON.
|
|
254
|
-
request.pipe(HttpClientRequest.bodyJsonUnsafe(body));
|
|
255
|
-
}
|
|
256
|
-
return request;
|
|
257
|
-
});
|
|
258
|
-
// =============================================================================
|
|
259
|
-
// Response decoding / error matching (ported from v0's matchError)
|
|
260
|
-
// =============================================================================
|
|
261
|
-
const str = (v) => (typeof v === "string" ? v : undefined);
|
|
262
|
-
// Structural views of the status maps for numeric lookup (the const objects'
|
|
263
|
-
// per-status class types don't unify without widening the constructor).
|
|
264
|
-
const stripeStatusMap = STRIPE_HTTP_STATUS_MAP;
|
|
265
|
-
const coreStatusMap = HTTP_STATUS_MAP;
|
|
266
|
-
/** Stripe's `{ error: { type, message, … } }` object, when present. */
|
|
267
|
-
const stripeErrorEnvelope = (errorBody) => {
|
|
268
|
-
const envelope = errorBody !== null && typeof errorBody === "object"
|
|
269
|
-
? errorBody.error
|
|
270
|
-
: undefined;
|
|
271
|
-
return envelope !== null && typeof envelope === "object"
|
|
272
|
-
? envelope
|
|
273
|
-
: undefined;
|
|
274
|
-
};
|
|
275
|
-
const stripeErrorMessage = (errorBody) => {
|
|
276
|
-
const message = stripeErrorEnvelope(errorBody)?.message;
|
|
277
|
-
return typeof message === "string" ? message : undefined;
|
|
278
|
-
};
|
|
279
|
-
/**
|
|
280
|
-
* Match a Stripe API error response to the appropriate error class.
|
|
281
|
-
*
|
|
282
|
-
* Stripe dispatches errors by:
|
|
283
|
-
* 1. Per-operation matcher classes (`matchTypedError`) — see decode
|
|
284
|
-
* 2. The `error.type` field (card_error, idempotency_error,
|
|
285
|
-
* invalid_request_error, api_error)
|
|
286
|
-
* 3. HTTP status code (402/424 Stripe-specific, then the core status map)
|
|
287
|
-
*/
|
|
288
|
-
const matchStripeError = (status, errorBody, headers) => {
|
|
289
|
-
const err = stripeErrorEnvelope(errorBody);
|
|
290
|
-
// v0 parity: an unparseable envelope (no `error.type` string) is an
|
|
291
|
-
// UnknownStripeError carrying the raw body.
|
|
292
|
-
if (!err || typeof err.type !== "string") {
|
|
293
|
-
return new UnknownStripeError({ body: errorBody });
|
|
294
|
-
}
|
|
295
|
-
const common = {
|
|
296
|
-
message: str(err.message),
|
|
297
|
-
code: str(err.code),
|
|
298
|
-
doc_url: str(err.doc_url),
|
|
299
|
-
request_log_url: str(err.request_log_url),
|
|
300
|
-
};
|
|
301
|
-
// Match by Stripe error type first for richer error info.
|
|
302
|
-
switch (err.type) {
|
|
303
|
-
case "card_error":
|
|
304
|
-
return new CardError({
|
|
305
|
-
...common,
|
|
306
|
-
decline_code: str(err.decline_code),
|
|
307
|
-
charge: str(err.charge),
|
|
308
|
-
param: str(err.param),
|
|
309
|
-
advice_code: str(err.advice_code),
|
|
310
|
-
network_advice_code: str(err.network_advice_code),
|
|
311
|
-
network_decline_code: str(err.network_decline_code),
|
|
312
|
-
payment_method_type: str(err.payment_method_type),
|
|
313
|
-
});
|
|
314
|
-
case "idempotency_error":
|
|
315
|
-
return new IdempotencyError(common);
|
|
316
|
-
case "invalid_request_error":
|
|
317
|
-
return new InvalidRequestError({ ...common, param: str(err.param) });
|
|
318
|
-
case "api_error":
|
|
319
|
-
return new ApiError({ ...common, param: str(err.param) });
|
|
320
|
-
}
|
|
321
|
-
// Fall back to Stripe-specific HTTP status codes (402, 424).
|
|
322
|
-
const StripeErrorClass = stripeStatusMap[status];
|
|
323
|
-
if (StripeErrorClass) {
|
|
324
|
-
return new StripeErrorClass({
|
|
325
|
-
message: common.message,
|
|
326
|
-
code: common.code,
|
|
327
|
-
...(str(err.decline_code) !== undefined ? { decline_code: str(err.decline_code) } : {}),
|
|
328
|
-
...(str(err.charge) !== undefined ? { charge: str(err.charge) } : {}),
|
|
329
|
-
...(str(err.param) !== undefined ? { param: str(err.param) } : {}),
|
|
330
|
-
...(common.doc_url !== undefined ? { doc_url: common.doc_url } : {}),
|
|
331
|
-
...(common.request_log_url !== undefined ? { request_log_url: common.request_log_url } : {}),
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
// Fall back to standard HTTP status errors (400, 401, 403, 404, …).
|
|
335
|
-
const CoreErrorClass = coreStatusMap[status];
|
|
336
|
-
if (CoreErrorClass) {
|
|
337
|
-
return new CoreErrorClass({
|
|
338
|
-
message: common.message ?? "",
|
|
339
|
-
retryAfter: parseRetryAfterForStatus(status, headers),
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
// Unmapped 5xx (e.g. proxy statuses) → retryable server error (v1
|
|
343
|
-
// convention; v0 fell through to UnknownStripeError here).
|
|
344
|
-
if (status >= 500) {
|
|
345
|
-
return new InternalServerError({
|
|
346
|
-
message: common.message ?? `HTTP ${status}`,
|
|
347
|
-
retryAfter: parseRetryAfterForStatus(status, headers),
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
return new UnknownStripeError({
|
|
351
|
-
type: str(err.type),
|
|
352
|
-
code: common.code,
|
|
353
|
-
message: common.message,
|
|
354
|
-
param: str(err.param),
|
|
355
|
-
body: errorBody,
|
|
356
|
-
});
|
|
357
|
-
};
|
|
358
|
-
const decode = ({ response, outputAst, errors: errorClasses, }) => Effect.gen(function* () {
|
|
359
|
-
// Read as text and parse tolerantly — error pages can be non-JSON.
|
|
360
|
-
const text = (yield* response.text.pipe(Effect.orDie)) ?? "";
|
|
361
|
-
if (process.env.DISTILLED_DEBUG_HTTP) {
|
|
362
|
-
console.error(`[distilled] <- ${response.status} ${text.slice(0, 400)}`);
|
|
363
|
-
}
|
|
364
|
-
let json;
|
|
365
|
-
let nonJson = false;
|
|
366
|
-
if (text.trim().length > 0) {
|
|
367
|
-
try {
|
|
368
|
-
json = JSON.parse(text);
|
|
369
|
-
}
|
|
370
|
-
catch {
|
|
371
|
-
nonJson = true;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
const status = response.status;
|
|
375
|
-
const headers = response.headers;
|
|
376
|
-
if (status >= 400) {
|
|
377
|
-
const errorBody = nonJson ? text : json;
|
|
378
|
-
const message = stripeErrorMessage(errorBody) ?? (nonJson && text.trim() ? text.trim() : `HTTP ${status}`);
|
|
379
|
-
const typed = matchTypedError(errorClasses, status, [{ message }], {
|
|
380
|
-
body: nonJson ? text : json,
|
|
381
|
-
headers,
|
|
382
|
-
});
|
|
383
|
-
if (typed !== undefined)
|
|
384
|
-
return yield* fail(typed);
|
|
385
|
-
return yield* fail(matchStripeError(status, errorBody, headers));
|
|
386
|
-
}
|
|
387
|
-
// 2xx: the response body IS the payload (no envelope). Wire→TS key
|
|
388
|
-
// mapping is schema-driven; sensitive members come back Redacted.
|
|
389
|
-
// Strict mode checks the mapped body against the output schema first.
|
|
390
|
-
const body = nonJson ? text : (json ?? {});
|
|
391
|
-
const mapped = yield* validateResponse(outputAst, mapKeys(outputAst, body, "decode"), (cause) => new StripeParseError({ body: nonJson ? text : json, cause })).pipe(Effect.catch(fail));
|
|
392
|
-
return wrapSensitive(outputAst, mapped);
|
|
393
|
-
});
|
|
394
|
-
export const StripeProtocol = Layer.succeed(API.Protocol, API.Protocol.of({
|
|
395
|
-
// Erase encode's Credentials requirement (see comment above).
|
|
396
|
-
encode: (args) => encode(args),
|
|
397
|
-
decode,
|
|
398
|
-
}));
|
|
399
|
-
//# sourceMappingURL=protocol.js.map
|
|
1
|
+
export { protocol$RequestOptions as RequestOptions, protocol$StripeProtocol as StripeProtocol, protocol$withRequestOptions as withRequestOptions } from "./_bundle/sdk.js";
|
package/lib/retry.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ import * as Effect from "effect/Effect";
|
|
|
19
19
|
export { type Options, type Factory, type Policy, makeDefault, jittered, capped, throttlingOptions, transientOptions, throttlingFactory, transientFactory, } from "@distilled.cloud/core/retry";
|
|
20
20
|
declare const Retry_base: Context.ServiceClass<Retry, "StripeRetry", Policy>;
|
|
21
21
|
/** Context tag for configuring retry behavior of Stripe API calls. */
|
|
22
|
-
export declare class Retry extends Retry_base {
|
|
22
|
+
export declare class Retry extends /*@__PURE__*/ Retry_base {
|
|
23
23
|
}
|
|
24
24
|
/** Provides a custom retry policy to every Stripe API call below it. */
|
|
25
25
|
export declare const policy: (optionsOrFactory: Policy) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Retry>>;
|
package/lib/retry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAuC,MAAM,6BAA6B,CAAC;AAC/F;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,OAAO,EACZ,KAAK,MAAM,EACX,WAAW,EACX,QAAQ,EACR,MAAM,EACN,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;;AAErC,sEAAsE;AACtE,qBAAa,KAAM,SAAQ,UAA+C;CAAG;
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAuC,MAAM,6BAA6B,CAAC;AAC/F;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,OAAO,EACZ,KAAK,MAAM,EACX,WAAW,EACX,QAAQ,EACR,MAAM,EACN,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;;AAErC,sEAAsE;AACtE,qBAAa,KAAM,SAAQ,aAAa,CAAC,UAA+C;CAAG;AAE3F,wEAAwE;AACxE,eAAO,MAAM,MAAM,qBAAsB,MAAM,sFACS,CAAC;AAEzD,sCAAsC;AACtC,eAAO,MAAM,IAAI,mFAEhB,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,UAAU,mFAA0C,CAAC;AAElE,oFAAoF;AACpF,eAAO,MAAM,SAAS,mFAAyC,CAAC"}
|
package/lib/retry.js
CHANGED
|
@@ -1,32 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Stripe retry configuration.
|
|
4
|
-
*
|
|
5
|
-
* Defines the per-SDK `Retry` Context.Service tag that generated operations
|
|
6
|
-
* wire into `API.make`. Callers can install a blanket retry policy at the
|
|
7
|
-
* layer level and have every Stripe API call below it pick it up:
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* import * as Stripe from "@distilled.cloud/stripe";
|
|
12
|
-
*
|
|
13
|
-
* myEffect.pipe(Stripe.Retry.transient);
|
|
14
|
-
* Effect.provide(myEffect, Layer.succeed(Stripe.Retry.Retry, customPolicy));
|
|
15
|
-
* ```
|
|
16
|
-
*/
|
|
17
|
-
import * as Context from "effect/Context";
|
|
18
|
-
import * as Effect from "effect/Effect";
|
|
19
|
-
import * as Layer from "effect/Layer";
|
|
20
|
-
export { makeDefault, jittered, capped, throttlingOptions, transientOptions, throttlingFactory, transientFactory, } from "@distilled.cloud/core/retry";
|
|
21
|
-
/** Context tag for configuring retry behavior of Stripe API calls. */
|
|
22
|
-
export class Retry extends Context.Service()("StripeRetry") {
|
|
23
|
-
}
|
|
24
|
-
/** Provides a custom retry policy to every Stripe API call below it. */
|
|
25
|
-
export const policy = (optionsOrFactory) => Effect.provide(Layer.succeed(Retry, optionsOrFactory));
|
|
26
|
-
/** Disables all automatic retries. */
|
|
27
|
-
export const none = Effect.provide(Layer.succeed(Retry, { while: () => false }));
|
|
28
|
-
/** Apply the throttling retry policy (retries throttling errors indefinitely). */
|
|
29
|
-
export const throttling = policy(throttlingFactory);
|
|
30
|
-
/** Apply the transient retry policy (retries all transient errors indefinitely). */
|
|
31
|
-
export const transient = policy(transientFactory);
|
|
32
|
-
//# sourceMappingURL=retry.js.map
|
|
1
|
+
export { retry$Retry as Retry, retry$capped as capped, retry$jittered as jittered, retry$makeDefault as makeDefault, retry$none as none, retry$policy as policy, retry$throttling as throttling, retry$throttlingFactory as throttlingFactory, retry$throttlingOptions as throttlingOptions, retry$transient as transient, retry$transientFactory as transientFactory, retry$transientOptions as transientOptions } from "./_bundle/sdk.js";
|
package/lib/services/index.js
CHANGED