@shware/purchase 5.2.2 → 6.0.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.
@@ -26,3 +26,4 @@ exports.mapTime = require_stripe_mapper.mapTime;
26
26
  exports.minorUnits = require_stripe_mapper.minorUnits;
27
27
  exports.price = require_stripe_mapper.price;
28
28
  exports.removePendingCheckout = require_stripe_pending_checkout.removePendingCheckout;
29
+ exports.returnUrlSchema = require_stripe_schema.returnUrlSchema;
@@ -2,8 +2,8 @@ import { CancellationDetails } from "../subscription/index.cjs";
2
2
  import { PriceId, StripeConfig, StripeMode } from "./config.cjs";
3
3
  import { StripeEventHandler } from "./handler.cjs";
4
4
  import { BeginCheckoutProperties, CheckoutSession, LineItem, PaymentStatus, Price, ProductPrice, PurchaseProperties, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price } from "./mapper.cjs";
5
- import { CreateCheckoutSessionDTO, cancellationDetailsSchema, createCheckoutSessionSchema } from "./schema.cjs";
5
+ import { AllowedOrigins, CreateCheckoutSessionDTO, CreateCheckoutSessionSchemaOptions, cancellationDetailsSchema, createCheckoutSessionSchema, returnUrlSchema } from "./schema.cjs";
6
6
  import { METADATA_KEYS } from "./metadata.cjs";
7
7
  import { addPendingCheckout, getPendingCheckouts, removePendingCheckout } from "./pending-checkout.cjs";
8
8
  import { Stripe } from "stripe";
9
- export { type BeginCheckoutProperties, type CancellationDetails, type CheckoutSession, type CreateCheckoutSessionDTO, type LineItem, METADATA_KEYS, type PaymentStatus, type Price, type PriceId, type ProductPrice, type PurchaseProperties, type Stripe, StripeConfig, StripeEventHandler, type StripeMode, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price, removePendingCheckout };
9
+ export { type AllowedOrigins, type BeginCheckoutProperties, type CancellationDetails, type CheckoutSession, type CreateCheckoutSessionDTO, type CreateCheckoutSessionSchemaOptions, type LineItem, METADATA_KEYS, type PaymentStatus, type Price, type PriceId, type ProductPrice, type PurchaseProperties, type Stripe, StripeConfig, StripeEventHandler, type StripeMode, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price, removePendingCheckout, returnUrlSchema };
@@ -2,8 +2,8 @@ import { CancellationDetails } from "../subscription/index.mjs";
2
2
  import { PriceId, StripeConfig, StripeMode } from "./config.mjs";
3
3
  import { StripeEventHandler } from "./handler.mjs";
4
4
  import { BeginCheckoutProperties, CheckoutSession, LineItem, PaymentStatus, Price, ProductPrice, PurchaseProperties, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price } from "./mapper.mjs";
5
- import { CreateCheckoutSessionDTO, cancellationDetailsSchema, createCheckoutSessionSchema } from "./schema.mjs";
5
+ import { AllowedOrigins, CreateCheckoutSessionDTO, CreateCheckoutSessionSchemaOptions, cancellationDetailsSchema, createCheckoutSessionSchema, returnUrlSchema } from "./schema.mjs";
6
6
  import { METADATA_KEYS } from "./metadata.mjs";
7
7
  import { addPendingCheckout, getPendingCheckouts, removePendingCheckout } from "./pending-checkout.mjs";
8
8
  import { Stripe } from "stripe";
9
- export { type BeginCheckoutProperties, type CancellationDetails, type CheckoutSession, type CreateCheckoutSessionDTO, type LineItem, METADATA_KEYS, type PaymentStatus, type Price, type PriceId, type ProductPrice, type PurchaseProperties, type Stripe, StripeConfig, StripeEventHandler, type StripeMode, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price, removePendingCheckout };
9
+ export { type AllowedOrigins, type BeginCheckoutProperties, type CancellationDetails, type CheckoutSession, type CreateCheckoutSessionDTO, type CreateCheckoutSessionSchemaOptions, type LineItem, METADATA_KEYS, type PaymentStatus, type Price, type PriceId, type ProductPrice, type PurchaseProperties, type Stripe, StripeConfig, StripeEventHandler, type StripeMode, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price, removePendingCheckout, returnUrlSchema };
@@ -1,7 +1,7 @@
1
1
  import { StripeConfig } from "./config.mjs";
2
2
  import { StripeEventHandler } from "./handler.mjs";
3
3
  import { getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price } from "./mapper.mjs";
4
- import { cancellationDetailsSchema, createCheckoutSessionSchema } from "./schema.mjs";
4
+ import { cancellationDetailsSchema, createCheckoutSessionSchema, returnUrlSchema } from "./schema.mjs";
5
5
  import { METADATA_KEYS } from "./metadata.mjs";
6
6
  import { addPendingCheckout, getPendingCheckouts, removePendingCheckout } from "./pending-checkout.mjs";
7
- export { METADATA_KEYS, StripeConfig, StripeEventHandler, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price, removePendingCheckout };
7
+ export { METADATA_KEYS, StripeConfig, StripeEventHandler, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price, removePendingCheckout, returnUrlSchema };
@@ -8,16 +8,56 @@ const cancellationDetailsSchema = (0, zod_mini.object)({
8
8
  feedback: (0, zod_mini.optional)((0, zod_mini.nullable)((0, zod_mini.enum)(require_subscription_index.CANCELLATION_FEEDBACKS))),
9
9
  resubscribeIntent: (0, zod_mini.optional)((0, zod_mini.nullable)((0, zod_mini.enum)(require_subscription_index.RESUBSCRIBE_INTENTS)))
10
10
  });
11
- function createCheckoutSessionSchema(productIds) {
11
+ /**
12
+ * Normalizes each allowed origin with `new URL()`, so `https://example.com/` and
13
+ * `https://example.com` are the same entry. Throws on anything that is not an http(s) origin: a
14
+ * misconfigured list must fail at startup, not let every return URL through or refuse them all.
15
+ */
16
+ function normalizeOrigins(origins) {
17
+ return origins.map((origin) => {
18
+ const parsed = new URL(origin);
19
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") throw new Error(`returnUrl: ${origin} is not an http(s) origin`);
20
+ return parsed.origin;
21
+ });
22
+ }
23
+ /**
24
+ * Whether `value` may be handed to Stripe as a URL its hosted pages (Checkout, the billing portal)
25
+ * send the browser back to. Origins are compared as parsed by `new URL()`, never by string prefix,
26
+ * so a lookalike host (`https://example.com.evil.com`), userinfo (`https://example.com@evil.com`),
27
+ * protocol-relative (`//evil.com`) and `javascript:` values are all refused. The scheme is part of
28
+ * the origin, so an https-only list refuses `http:` too.
29
+ */
30
+ function isAllowedReturnUrl(value, allowedOrigins) {
31
+ let parsed;
32
+ try {
33
+ parsed = new URL(value);
34
+ } catch {
35
+ return false;
36
+ }
37
+ if (parsed.username !== "" || parsed.password !== "") return false;
38
+ return allowedOrigins.includes(parsed.origin);
39
+ }
40
+ /**
41
+ * A client-supplied return / success / cancel URL. It goes straight to Stripe, so without an
42
+ * origin check anyone could create a session with their own account and hand out a
43
+ * `checkout.stripe.com` / `billing.stripe.com` link that ends on a page of their choosing: an open
44
+ * redirect behind a trusted domain. `allowedOrigins` is usually just the web app's origin.
45
+ */
46
+ function returnUrlSchema(allowedOrigins) {
47
+ const origins = normalizeOrigins(allowedOrigins);
48
+ return (0, zod_mini.url)().check((0, zod_mini.refine)((value) => isAllowedReturnUrl(value, origins), { message: `URL must be on ${origins.join(", ")}` }));
49
+ }
50
+ function createCheckoutSessionSchema(productIds, { allowedOrigins }) {
12
51
  return (0, zod_mini.object)({
13
52
  quantity: (0, zod_mini._default)((0, zod_mini.int)().check((0, zod_mini.gte)(1)), 1),
14
53
  productId: (0, zod_mini.enum)(productIds),
15
- cancelUrl: (0, zod_mini.optional)((0, zod_mini.url)()),
16
- successUrl: (0, zod_mini.optional)((0, zod_mini.url)())
54
+ cancelUrl: (0, zod_mini.optional)(returnUrlSchema(allowedOrigins)),
55
+ successUrl: (0, zod_mini.optional)(returnUrlSchema(allowedOrigins))
17
56
  });
18
57
  }
19
58
  //#endregion
20
59
  exports.cancellationDetailsSchema = cancellationDetailsSchema;
21
60
  exports.createCheckoutSessionSchema = createCheckoutSessionSchema;
61
+ exports.returnUrlSchema = returnUrlSchema;
22
62
 
23
63
  //# sourceMappingURL=schema.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.cjs","names":["object","optional","nullable","string","maxLength","_enum","CANCELLATION_FEEDBACKS","RESUBSCRIBE_INTENTS","_default","int","gte","url"],"sources":["../../src/stripe/schema.ts"],"sourcesContent":["import {\n _default,\n enum as _enum,\n gte,\n int,\n maxLength,\n nullable,\n object,\n optional,\n string,\n url,\n} from 'zod/mini';\nimport { CANCELLATION_FEEDBACKS, RESUBSCRIBE_INTENTS } from '../subscription/index';\n\n/** What our own cancel dialog may send along with a cancellation (see `CancellationDetails`). */\nexport const cancellationDetailsSchema = object({\n comment: optional(nullable(string().check(maxLength(1024)))),\n feedback: optional(nullable(_enum(CANCELLATION_FEEDBACKS))),\n resubscribeIntent: optional(nullable(_enum(RESUBSCRIBE_INTENTS))),\n});\n\nexport function createCheckoutSessionSchema(productIds: string[]) {\n return object({\n quantity: _default(int().check(gte(1)), 1),\n productId: _enum(productIds),\n cancelUrl: optional(url()),\n successUrl: optional(url()),\n });\n}\n\n// Kept under the stripe entry for callers that imported it from here; the type is platform\n// neutral and lives with the subscription statuses.\nexport type { CancellationDetails } from '../subscription/index';\n\nexport interface CreateCheckoutSessionDTO {\n productId: string;\n cancelUrl?: string;\n successUrl?: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n}\n"],"mappings":";;;;;AAeA,MAAa,6BAAA,GAA4BA,SAAAA,OAAAA,CAAO;CAC9C,UAAA,GAASC,SAAAA,SAAAA,EAAAA,GAASC,SAAAA,SAAAA,EAAAA,GAASC,SAAAA,OAAAA,CAAO,CAAC,CAAC,OAAA,GAAMC,SAAAA,UAAAA,CAAU,IAAI,CAAC,CAAC,CAAC;CAC3D,WAAA,GAAUH,SAAAA,SAAAA,EAAAA,GAASC,SAAAA,SAAAA,EAAAA,GAASG,SAAAA,KAAAA,CAAMC,2BAAAA,sBAAsB,CAAC,CAAC;CAC1D,oBAAA,GAAmBL,SAAAA,SAAAA,EAAAA,GAASC,SAAAA,SAAAA,EAAAA,GAASG,SAAAA,KAAAA,CAAME,2BAAAA,mBAAmB,CAAC,CAAC;AAClE,CAAC;AAED,SAAgB,4BAA4B,YAAsB;CAChE,QAAA,GAAOP,SAAAA,OAAAA,CAAO;EACZ,WAAA,GAAUQ,SAAAA,SAAAA,EAAAA,GAASC,SAAAA,IAAAA,CAAI,CAAC,CAAC,OAAA,GAAMC,SAAAA,IAAAA,CAAI,CAAC,CAAC,GAAG,CAAC;EACzC,YAAA,GAAWL,SAAAA,KAAAA,CAAM,UAAU;EAC3B,YAAA,GAAWJ,SAAAA,SAAAA,EAAAA,GAASU,SAAAA,IAAAA,CAAI,CAAC;EACzB,aAAA,GAAYV,SAAAA,SAAAA,EAAAA,GAASU,SAAAA,IAAAA,CAAI,CAAC;CAC5B,CAAC;AACH"}
1
+ {"version":3,"file":"schema.cjs","names":["object","optional","nullable","string","maxLength","_enum","CANCELLATION_FEEDBACKS","RESUBSCRIBE_INTENTS","url","refine","_default","int","gte"],"sources":["../../src/stripe/schema.ts"],"sourcesContent":["import {\n _default,\n enum as _enum,\n gte,\n int,\n maxLength,\n nullable,\n object,\n optional,\n refine,\n string,\n url,\n} from 'zod/mini';\nimport { CANCELLATION_FEEDBACKS, RESUBSCRIBE_INTENTS } from '../subscription/index';\n\n/** What our own cancel dialog may send along with a cancellation (see `CancellationDetails`). */\nexport const cancellationDetailsSchema = object({\n comment: optional(nullable(string().check(maxLength(1024)))),\n feedback: optional(nullable(_enum(CANCELLATION_FEEDBACKS))),\n resubscribeIntent: optional(nullable(_enum(RESUBSCRIBE_INTENTS))),\n});\n\n/** At least one origin: an empty list would refuse every return URL. */\nexport type AllowedOrigins = readonly [string, ...string[]];\n\n/**\n * Normalizes each allowed origin with `new URL()`, so `https://example.com/` and\n * `https://example.com` are the same entry. Throws on anything that is not an http(s) origin: a\n * misconfigured list must fail at startup, not let every return URL through or refuse them all.\n */\nfunction normalizeOrigins(origins: AllowedOrigins): string[] {\n return origins.map((origin) => {\n const parsed = new URL(origin);\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new Error(`returnUrl: ${origin} is not an http(s) origin`);\n }\n return parsed.origin;\n });\n}\n\n/**\n * Whether `value` may be handed to Stripe as a URL its hosted pages (Checkout, the billing portal)\n * send the browser back to. Origins are compared as parsed by `new URL()`, never by string prefix,\n * so a lookalike host (`https://example.com.evil.com`), userinfo (`https://example.com@evil.com`),\n * protocol-relative (`//evil.com`) and `javascript:` values are all refused. The scheme is part of\n * the origin, so an https-only list refuses `http:` too.\n */\nfunction isAllowedReturnUrl(value: string, allowedOrigins: readonly string[]): boolean {\n let parsed: URL;\n try {\n parsed = new URL(value);\n } catch {\n return false;\n }\n if (parsed.username !== '' || parsed.password !== '') return false;\n return allowedOrigins.includes(parsed.origin);\n}\n\n/**\n * A client-supplied return / success / cancel URL. It goes straight to Stripe, so without an\n * origin check anyone could create a session with their own account and hand out a\n * `checkout.stripe.com` / `billing.stripe.com` link that ends on a page of their choosing: an open\n * redirect behind a trusted domain. `allowedOrigins` is usually just the web app's origin.\n */\nexport function returnUrlSchema(allowedOrigins: AllowedOrigins) {\n const origins = normalizeOrigins(allowedOrigins);\n return url().check(\n refine((value) => isAllowedReturnUrl(value, origins), {\n message: `URL must be on ${origins.join(', ')}`,\n })\n );\n}\n\nexport interface CreateCheckoutSessionSchemaOptions {\n /** Origins (`https://example.com`) `cancelUrl` and `successUrl` may point at; see `returnUrlSchema`. */\n allowedOrigins: AllowedOrigins;\n}\n\nexport function createCheckoutSessionSchema(\n productIds: string[],\n { allowedOrigins }: CreateCheckoutSessionSchemaOptions\n) {\n return object({\n quantity: _default(int().check(gte(1)), 1),\n productId: _enum(productIds),\n cancelUrl: optional(returnUrlSchema(allowedOrigins)),\n successUrl: optional(returnUrlSchema(allowedOrigins)),\n });\n}\n\n// Kept under the stripe entry for callers that imported it from here; the type is platform\n// neutral and lives with the subscription statuses.\nexport type { CancellationDetails } from '../subscription/index';\n\nexport interface CreateCheckoutSessionDTO {\n productId: string;\n cancelUrl?: string;\n successUrl?: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n}\n"],"mappings":";;;;;AAgBA,MAAa,6BAAA,GAA4BA,SAAAA,OAAAA,CAAO;CAC9C,UAAA,GAASC,SAAAA,SAAAA,EAAAA,GAASC,SAAAA,SAAAA,EAAAA,GAASC,SAAAA,OAAAA,CAAO,CAAC,CAAC,OAAA,GAAMC,SAAAA,UAAAA,CAAU,IAAI,CAAC,CAAC,CAAC;CAC3D,WAAA,GAAUH,SAAAA,SAAAA,EAAAA,GAASC,SAAAA,SAAAA,EAAAA,GAASG,SAAAA,KAAAA,CAAMC,2BAAAA,sBAAsB,CAAC,CAAC;CAC1D,oBAAA,GAAmBL,SAAAA,SAAAA,EAAAA,GAASC,SAAAA,SAAAA,EAAAA,GAASG,SAAAA,KAAAA,CAAME,2BAAAA,mBAAmB,CAAC,CAAC;AAClE,CAAC;;;;;;AAUD,SAAS,iBAAiB,SAAmC;CAC3D,OAAO,QAAQ,KAAK,WAAW;EAC7B,MAAM,SAAS,IAAI,IAAI,MAAM;EAC7B,IAAI,OAAO,aAAa,YAAY,OAAO,aAAa,SACtD,MAAM,IAAI,MAAM,cAAc,OAAO,0BAA0B;EAEjE,OAAO,OAAO;CAChB,CAAC;AACH;;;;;;;;AASA,SAAS,mBAAmB,OAAe,gBAA4C;CACrF,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,IAAI,KAAK;CACxB,QAAQ;EACN,OAAO;CACT;CACA,IAAI,OAAO,aAAa,MAAM,OAAO,aAAa,IAAI,OAAO;CAC7D,OAAO,eAAe,SAAS,OAAO,MAAM;AAC9C;;;;;;;AAQA,SAAgB,gBAAgB,gBAAgC;CAC9D,MAAM,UAAU,iBAAiB,cAAc;CAC/C,QAAA,GAAOC,SAAAA,IAAAA,CAAI,CAAC,CAAC,OAAA,GACXC,SAAAA,OAAAA,EAAQ,UAAU,mBAAmB,OAAO,OAAO,GAAG,EACpD,SAAS,kBAAkB,QAAQ,KAAK,IAAI,IAC9C,CAAC,CACH;AACF;AAOA,SAAgB,4BACd,YACA,EAAE,kBACF;CACA,QAAA,GAAOT,SAAAA,OAAAA,CAAO;EACZ,WAAA,GAAUU,SAAAA,SAAAA,EAAAA,GAASC,SAAAA,IAAAA,CAAI,CAAC,CAAC,OAAA,GAAMC,SAAAA,IAAAA,CAAI,CAAC,CAAC,GAAG,CAAC;EACzC,YAAA,GAAWP,SAAAA,KAAAA,CAAM,UAAU;EAC3B,YAAA,GAAWJ,SAAAA,SAAAA,CAAS,gBAAgB,cAAc,CAAC;EACnD,aAAA,GAAYA,SAAAA,SAAAA,CAAS,gBAAgB,cAAc,CAAC;CACtD,CAAC;AACH"}
@@ -19,7 +19,20 @@ export declare const cancellationDetailsSchema: import("zod/mini").ZodMiniObject
19
19
  yes: "yes";
20
20
  }>>>;
21
21
  }, import("zod/v4/core").$strip>;
22
- export declare function createCheckoutSessionSchema(productIds: string[]): import("zod/mini").ZodMiniObject<{
22
+ /** At least one origin: an empty list would refuse every return URL. */
23
+ export type AllowedOrigins = readonly [string, ...string[]];
24
+ /**
25
+ * A client-supplied return / success / cancel URL. It goes straight to Stripe, so without an
26
+ * origin check anyone could create a session with their own account and hand out a
27
+ * `checkout.stripe.com` / `billing.stripe.com` link that ends on a page of their choosing: an open
28
+ * redirect behind a trusted domain. `allowedOrigins` is usually just the web app's origin.
29
+ */
30
+ export declare function returnUrlSchema(allowedOrigins: AllowedOrigins): import("zod/mini").ZodMiniURL;
31
+ export interface CreateCheckoutSessionSchemaOptions {
32
+ /** Origins (`https://example.com`) `cancelUrl` and `successUrl` may point at; see `returnUrlSchema`. */
33
+ allowedOrigins: AllowedOrigins;
34
+ }
35
+ export declare function createCheckoutSessionSchema(productIds: string[], { allowedOrigins }: CreateCheckoutSessionSchemaOptions): import("zod/mini").ZodMiniObject<{
23
36
  quantity: import("zod/mini").ZodMiniDefault<import("zod/mini").ZodMiniInt>;
24
37
  productId: import("zod/mini").ZodMiniEnum<{
25
38
  [x: string]: string;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.cts","names":[],"sources":["../../src/stripe/schema.ts"],"mappings":";;;qBAea,8CAAyB;;;;;;;;;;;;;;;;;yBAIpC;wBAEc,4BAA4B,0CAAoB;;;;;;;yBAO/D;iBAMgB;EACf;EACA;EACA"}
1
+ {"version":3,"file":"schema.d.cts","names":[],"sources":["../../src/stripe/schema.ts"],"mappings":";;;qBAgBa,8CAAyB;;;;;;;;;;;;;;;;;yBAIpC;;YAGU;;;;;;;wBAyCI,gBAAgB,gBAAgB,oCAAc;iBAS7C;;EAEf,gBAAgB;;wBAGF,4BACd,wBACE,kBAAkB,wDAAkC;;;;;;;yBAQvD;iBAMgB;EACf;EACA;EACA"}
@@ -19,7 +19,20 @@ export declare const cancellationDetailsSchema: import("zod/mini").ZodMiniObject
19
19
  yes: "yes";
20
20
  }>>>;
21
21
  }, import("zod/v4/core").$strip>;
22
- export declare function createCheckoutSessionSchema(productIds: string[]): import("zod/mini").ZodMiniObject<{
22
+ /** At least one origin: an empty list would refuse every return URL. */
23
+ export type AllowedOrigins = readonly [string, ...string[]];
24
+ /**
25
+ * A client-supplied return / success / cancel URL. It goes straight to Stripe, so without an
26
+ * origin check anyone could create a session with their own account and hand out a
27
+ * `checkout.stripe.com` / `billing.stripe.com` link that ends on a page of their choosing: an open
28
+ * redirect behind a trusted domain. `allowedOrigins` is usually just the web app's origin.
29
+ */
30
+ export declare function returnUrlSchema(allowedOrigins: AllowedOrigins): import("zod/mini").ZodMiniURL;
31
+ export interface CreateCheckoutSessionSchemaOptions {
32
+ /** Origins (`https://example.com`) `cancelUrl` and `successUrl` may point at; see `returnUrlSchema`. */
33
+ allowedOrigins: AllowedOrigins;
34
+ }
35
+ export declare function createCheckoutSessionSchema(productIds: string[], { allowedOrigins }: CreateCheckoutSessionSchemaOptions): import("zod/mini").ZodMiniObject<{
23
36
  quantity: import("zod/mini").ZodMiniDefault<import("zod/mini").ZodMiniInt>;
24
37
  productId: import("zod/mini").ZodMiniEnum<{
25
38
  [x: string]: string;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.mts","names":[],"sources":["../../src/stripe/schema.ts"],"mappings":";;;qBAea,8CAAyB;;;;;;;;;;;;;;;;;yBAIpC;wBAEc,4BAA4B,0CAAoB;;;;;;;yBAO/D;iBAMgB;EACf;EACA;EACA"}
1
+ {"version":3,"file":"schema.d.mts","names":[],"sources":["../../src/stripe/schema.ts"],"mappings":";;;qBAgBa,8CAAyB;;;;;;;;;;;;;;;;;yBAIpC;;YAGU;;;;;;;wBAyCI,gBAAgB,gBAAgB,oCAAc;iBAS7C;;EAEf,gBAAgB;;wBAGF,4BACd,wBACE,kBAAkB,wDAAkC;;;;;;;yBAQvD;iBAMgB;EACf;EACA;EACA"}
@@ -1,5 +1,5 @@
1
1
  import { CANCELLATION_FEEDBACKS, RESUBSCRIBE_INTENTS } from "../subscription/index.mjs";
2
- import { _default, enum as enum$1, gte, int, maxLength, nullable, object, optional, string, url } from "zod/mini";
2
+ import { _default, enum as enum$1, gte, int, maxLength, nullable, object, optional, refine, string, url } from "zod/mini";
3
3
  //#region src/stripe/schema.ts
4
4
  /** What our own cancel dialog may send along with a cancellation (see `CancellationDetails`). */
5
5
  const cancellationDetailsSchema = object({
@@ -7,15 +7,54 @@ const cancellationDetailsSchema = object({
7
7
  feedback: optional(nullable(enum$1(CANCELLATION_FEEDBACKS))),
8
8
  resubscribeIntent: optional(nullable(enum$1(RESUBSCRIBE_INTENTS)))
9
9
  });
10
- function createCheckoutSessionSchema(productIds) {
10
+ /**
11
+ * Normalizes each allowed origin with `new URL()`, so `https://example.com/` and
12
+ * `https://example.com` are the same entry. Throws on anything that is not an http(s) origin: a
13
+ * misconfigured list must fail at startup, not let every return URL through or refuse them all.
14
+ */
15
+ function normalizeOrigins(origins) {
16
+ return origins.map((origin) => {
17
+ const parsed = new URL(origin);
18
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") throw new Error(`returnUrl: ${origin} is not an http(s) origin`);
19
+ return parsed.origin;
20
+ });
21
+ }
22
+ /**
23
+ * Whether `value` may be handed to Stripe as a URL its hosted pages (Checkout, the billing portal)
24
+ * send the browser back to. Origins are compared as parsed by `new URL()`, never by string prefix,
25
+ * so a lookalike host (`https://example.com.evil.com`), userinfo (`https://example.com@evil.com`),
26
+ * protocol-relative (`//evil.com`) and `javascript:` values are all refused. The scheme is part of
27
+ * the origin, so an https-only list refuses `http:` too.
28
+ */
29
+ function isAllowedReturnUrl(value, allowedOrigins) {
30
+ let parsed;
31
+ try {
32
+ parsed = new URL(value);
33
+ } catch {
34
+ return false;
35
+ }
36
+ if (parsed.username !== "" || parsed.password !== "") return false;
37
+ return allowedOrigins.includes(parsed.origin);
38
+ }
39
+ /**
40
+ * A client-supplied return / success / cancel URL. It goes straight to Stripe, so without an
41
+ * origin check anyone could create a session with their own account and hand out a
42
+ * `checkout.stripe.com` / `billing.stripe.com` link that ends on a page of their choosing: an open
43
+ * redirect behind a trusted domain. `allowedOrigins` is usually just the web app's origin.
44
+ */
45
+ function returnUrlSchema(allowedOrigins) {
46
+ const origins = normalizeOrigins(allowedOrigins);
47
+ return url().check(refine((value) => isAllowedReturnUrl(value, origins), { message: `URL must be on ${origins.join(", ")}` }));
48
+ }
49
+ function createCheckoutSessionSchema(productIds, { allowedOrigins }) {
11
50
  return object({
12
51
  quantity: _default(int().check(gte(1)), 1),
13
52
  productId: enum$1(productIds),
14
- cancelUrl: optional(url()),
15
- successUrl: optional(url())
53
+ cancelUrl: optional(returnUrlSchema(allowedOrigins)),
54
+ successUrl: optional(returnUrlSchema(allowedOrigins))
16
55
  });
17
56
  }
18
57
  //#endregion
19
- export { cancellationDetailsSchema, createCheckoutSessionSchema };
58
+ export { cancellationDetailsSchema, createCheckoutSessionSchema, returnUrlSchema };
20
59
 
21
60
  //# sourceMappingURL=schema.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.mjs","names":["_enum"],"sources":["../../src/stripe/schema.ts"],"sourcesContent":["import {\n _default,\n enum as _enum,\n gte,\n int,\n maxLength,\n nullable,\n object,\n optional,\n string,\n url,\n} from 'zod/mini';\nimport { CANCELLATION_FEEDBACKS, RESUBSCRIBE_INTENTS } from '../subscription/index';\n\n/** What our own cancel dialog may send along with a cancellation (see `CancellationDetails`). */\nexport const cancellationDetailsSchema = object({\n comment: optional(nullable(string().check(maxLength(1024)))),\n feedback: optional(nullable(_enum(CANCELLATION_FEEDBACKS))),\n resubscribeIntent: optional(nullable(_enum(RESUBSCRIBE_INTENTS))),\n});\n\nexport function createCheckoutSessionSchema(productIds: string[]) {\n return object({\n quantity: _default(int().check(gte(1)), 1),\n productId: _enum(productIds),\n cancelUrl: optional(url()),\n successUrl: optional(url()),\n });\n}\n\n// Kept under the stripe entry for callers that imported it from here; the type is platform\n// neutral and lives with the subscription statuses.\nexport type { CancellationDetails } from '../subscription/index';\n\nexport interface CreateCheckoutSessionDTO {\n productId: string;\n cancelUrl?: string;\n successUrl?: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n}\n"],"mappings":";;;;AAeA,MAAa,4BAA4B,OAAO;CAC9C,SAAS,SAAS,SAAS,OAAO,CAAC,CAAC,MAAM,UAAU,IAAI,CAAC,CAAC,CAAC;CAC3D,UAAU,SAAS,SAASA,OAAM,sBAAsB,CAAC,CAAC;CAC1D,mBAAmB,SAAS,SAASA,OAAM,mBAAmB,CAAC,CAAC;AAClE,CAAC;AAED,SAAgB,4BAA4B,YAAsB;CAChE,OAAO,OAAO;EACZ,UAAU,SAAS,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC;EACzC,WAAWA,OAAM,UAAU;EAC3B,WAAW,SAAS,IAAI,CAAC;EACzB,YAAY,SAAS,IAAI,CAAC;CAC5B,CAAC;AACH"}
1
+ {"version":3,"file":"schema.mjs","names":["_enum"],"sources":["../../src/stripe/schema.ts"],"sourcesContent":["import {\n _default,\n enum as _enum,\n gte,\n int,\n maxLength,\n nullable,\n object,\n optional,\n refine,\n string,\n url,\n} from 'zod/mini';\nimport { CANCELLATION_FEEDBACKS, RESUBSCRIBE_INTENTS } from '../subscription/index';\n\n/** What our own cancel dialog may send along with a cancellation (see `CancellationDetails`). */\nexport const cancellationDetailsSchema = object({\n comment: optional(nullable(string().check(maxLength(1024)))),\n feedback: optional(nullable(_enum(CANCELLATION_FEEDBACKS))),\n resubscribeIntent: optional(nullable(_enum(RESUBSCRIBE_INTENTS))),\n});\n\n/** At least one origin: an empty list would refuse every return URL. */\nexport type AllowedOrigins = readonly [string, ...string[]];\n\n/**\n * Normalizes each allowed origin with `new URL()`, so `https://example.com/` and\n * `https://example.com` are the same entry. Throws on anything that is not an http(s) origin: a\n * misconfigured list must fail at startup, not let every return URL through or refuse them all.\n */\nfunction normalizeOrigins(origins: AllowedOrigins): string[] {\n return origins.map((origin) => {\n const parsed = new URL(origin);\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new Error(`returnUrl: ${origin} is not an http(s) origin`);\n }\n return parsed.origin;\n });\n}\n\n/**\n * Whether `value` may be handed to Stripe as a URL its hosted pages (Checkout, the billing portal)\n * send the browser back to. Origins are compared as parsed by `new URL()`, never by string prefix,\n * so a lookalike host (`https://example.com.evil.com`), userinfo (`https://example.com@evil.com`),\n * protocol-relative (`//evil.com`) and `javascript:` values are all refused. The scheme is part of\n * the origin, so an https-only list refuses `http:` too.\n */\nfunction isAllowedReturnUrl(value: string, allowedOrigins: readonly string[]): boolean {\n let parsed: URL;\n try {\n parsed = new URL(value);\n } catch {\n return false;\n }\n if (parsed.username !== '' || parsed.password !== '') return false;\n return allowedOrigins.includes(parsed.origin);\n}\n\n/**\n * A client-supplied return / success / cancel URL. It goes straight to Stripe, so without an\n * origin check anyone could create a session with their own account and hand out a\n * `checkout.stripe.com` / `billing.stripe.com` link that ends on a page of their choosing: an open\n * redirect behind a trusted domain. `allowedOrigins` is usually just the web app's origin.\n */\nexport function returnUrlSchema(allowedOrigins: AllowedOrigins) {\n const origins = normalizeOrigins(allowedOrigins);\n return url().check(\n refine((value) => isAllowedReturnUrl(value, origins), {\n message: `URL must be on ${origins.join(', ')}`,\n })\n );\n}\n\nexport interface CreateCheckoutSessionSchemaOptions {\n /** Origins (`https://example.com`) `cancelUrl` and `successUrl` may point at; see `returnUrlSchema`. */\n allowedOrigins: AllowedOrigins;\n}\n\nexport function createCheckoutSessionSchema(\n productIds: string[],\n { allowedOrigins }: CreateCheckoutSessionSchemaOptions\n) {\n return object({\n quantity: _default(int().check(gte(1)), 1),\n productId: _enum(productIds),\n cancelUrl: optional(returnUrlSchema(allowedOrigins)),\n successUrl: optional(returnUrlSchema(allowedOrigins)),\n });\n}\n\n// Kept under the stripe entry for callers that imported it from here; the type is platform\n// neutral and lives with the subscription statuses.\nexport type { CancellationDetails } from '../subscription/index';\n\nexport interface CreateCheckoutSessionDTO {\n productId: string;\n cancelUrl?: string;\n successUrl?: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n}\n"],"mappings":";;;;AAgBA,MAAa,4BAA4B,OAAO;CAC9C,SAAS,SAAS,SAAS,OAAO,CAAC,CAAC,MAAM,UAAU,IAAI,CAAC,CAAC,CAAC;CAC3D,UAAU,SAAS,SAASA,OAAM,sBAAsB,CAAC,CAAC;CAC1D,mBAAmB,SAAS,SAASA,OAAM,mBAAmB,CAAC,CAAC;AAClE,CAAC;;;;;;AAUD,SAAS,iBAAiB,SAAmC;CAC3D,OAAO,QAAQ,KAAK,WAAW;EAC7B,MAAM,SAAS,IAAI,IAAI,MAAM;EAC7B,IAAI,OAAO,aAAa,YAAY,OAAO,aAAa,SACtD,MAAM,IAAI,MAAM,cAAc,OAAO,0BAA0B;EAEjE,OAAO,OAAO;CAChB,CAAC;AACH;;;;;;;;AASA,SAAS,mBAAmB,OAAe,gBAA4C;CACrF,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,IAAI,KAAK;CACxB,QAAQ;EACN,OAAO;CACT;CACA,IAAI,OAAO,aAAa,MAAM,OAAO,aAAa,IAAI,OAAO;CAC7D,OAAO,eAAe,SAAS,OAAO,MAAM;AAC9C;;;;;;;AAQA,SAAgB,gBAAgB,gBAAgC;CAC9D,MAAM,UAAU,iBAAiB,cAAc;CAC/C,OAAO,IAAI,CAAC,CAAC,MACX,QAAQ,UAAU,mBAAmB,OAAO,OAAO,GAAG,EACpD,SAAS,kBAAkB,QAAQ,KAAK,IAAI,IAC9C,CAAC,CACH;AACF;AAOA,SAAgB,4BACd,YACA,EAAE,kBACF;CACA,OAAO,OAAO;EACZ,UAAU,SAAS,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC;EACzC,WAAWA,OAAM,UAAU;EAC3B,WAAW,SAAS,gBAAgB,cAAc,CAAC;EACnD,YAAY,SAAS,gBAAgB,cAAc,CAAC;CACtD,CAAC;AACH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/purchase",
3
- "version": "5.2.2",
3
+ "version": "6.0.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",