@spaire/remix 2.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.
- package/README.md +88 -0
- package/dist/index.cjs +174 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +148 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# @spaire/remix
|
|
2
|
+
|
|
3
|
+
Payments and Checkouts made dead simple with Remix and React Router.
|
|
4
|
+
|
|
5
|
+
`pnpm install @spaire/remix zod`
|
|
6
|
+
|
|
7
|
+
## Checkout
|
|
8
|
+
|
|
9
|
+
Create a Checkout handler which takes care of redirections.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { Checkout } from "@spaire/remix";
|
|
13
|
+
|
|
14
|
+
export const loader = Checkout({
|
|
15
|
+
accessToken: "xxx", // Or set an environment variable to SPAIRE_ACCESS_TOKEN
|
|
16
|
+
successUrl: process.env.SUCCESS_URL,
|
|
17
|
+
returnUrl: "https://myapp.com", // Optional Return URL, which renders a Back-button in the Checkout
|
|
18
|
+
server: "sandbox", // Use sandbox if you're testing Spaire - omit the parameter or pass 'production' otherwise
|
|
19
|
+
theme: "dark" // Enforces the theme - System-preferred theme will be set if left omitted
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Query Params
|
|
24
|
+
|
|
25
|
+
Pass query params to this route.
|
|
26
|
+
|
|
27
|
+
- products `?products=123`
|
|
28
|
+
- customerId (optional) `?products=123&customerId=xxx`
|
|
29
|
+
- customerExternalId (optional) `?products=123&customerExternalId=xxx`
|
|
30
|
+
- customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com`
|
|
31
|
+
- customerName (optional) `?products=123&customerName=Jane`
|
|
32
|
+
- seats (optional) `?products=123&seats=5` - Number of seats for seat-based products
|
|
33
|
+
- metadata (optional) `URL-Encoded JSON string`
|
|
34
|
+
|
|
35
|
+
## Customer Portal
|
|
36
|
+
|
|
37
|
+
Create a customer portal where your customer can view orders and subscriptions.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { CustomerPortal } from "@spaire/remix";
|
|
41
|
+
|
|
42
|
+
export const loader = CustomerPortal({
|
|
43
|
+
accessToken: "xxx", // Or set an environment variable to SPAIRE_ACCESS_TOKEN
|
|
44
|
+
getCustomerId: (event) => "", // Fuction to resolve a Spaire Customer ID
|
|
45
|
+
returnUrl: "https://myapp.com", // Optional Return URL, which renders a Back-button in the Customer Portal
|
|
46
|
+
server: "sandbox", // Use sandbox if you're testing Spaire - omit the parameter or pass 'production' otherwise
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Webhooks
|
|
51
|
+
|
|
52
|
+
A simple utility which resolves incoming webhook payloads by signing the webhook secret properly.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { Webhooks } from '@spaire/remix';
|
|
56
|
+
|
|
57
|
+
export const action = Webhooks({
|
|
58
|
+
webhookSecret: process.env.SPAIRE_WEBHOOK_SECRET!,
|
|
59
|
+
onPayload: async (payload) => /** Handle payload */,
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### Payload Handlers
|
|
64
|
+
|
|
65
|
+
The Webhook handler also supports granular handlers for easy integration.
|
|
66
|
+
|
|
67
|
+
- onCheckoutCreated: (payload) =>
|
|
68
|
+
- onCheckoutUpdated: (payload) =>
|
|
69
|
+
- onOrderCreated: (payload) =>
|
|
70
|
+
- onOrderUpdated: (payload) =>
|
|
71
|
+
- onOrderPaid: (payload) =>
|
|
72
|
+
- onSubscriptionCreated: (payload) =>
|
|
73
|
+
- onSubscriptionUpdated: (payload) =>
|
|
74
|
+
- onSubscriptionActive: (payload) =>
|
|
75
|
+
- onSubscriptionCanceled: (payload) =>
|
|
76
|
+
- onSubscriptionRevoked: (payload) =>
|
|
77
|
+
- onProductCreated: (payload) =>
|
|
78
|
+
- onProductUpdated: (payload) =>
|
|
79
|
+
- onOrganizationUpdated: (payload) =>
|
|
80
|
+
- onBenefitCreated: (payload) =>
|
|
81
|
+
- onBenefitUpdated: (payload) =>
|
|
82
|
+
- onBenefitGrantCreated: (payload) =>
|
|
83
|
+
- onBenefitGrantUpdated: (payload) =>
|
|
84
|
+
- onBenefitGrantRevoked: (payload) =>
|
|
85
|
+
- onCustomerCreated: (payload) =>
|
|
86
|
+
- onCustomerUpdated: (payload) =>
|
|
87
|
+
- onCustomerDeleted: (payload) =>
|
|
88
|
+
- onCustomerStateChanged: (payload) =>
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Checkout: () => Checkout,
|
|
24
|
+
CustomerPortal: () => CustomerPortal,
|
|
25
|
+
EntitlementStrategy: () => import_adapter_utils2.EntitlementStrategy,
|
|
26
|
+
Entitlements: () => import_adapter_utils2.Entitlements,
|
|
27
|
+
Webhooks: () => Webhooks
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
|
|
31
|
+
// src/checkout/checkout.ts
|
|
32
|
+
var import_sdk = require("@spaire/sdk");
|
|
33
|
+
var Checkout = ({
|
|
34
|
+
accessToken,
|
|
35
|
+
successUrl,
|
|
36
|
+
returnUrl,
|
|
37
|
+
server,
|
|
38
|
+
theme,
|
|
39
|
+
includeCheckoutId = true
|
|
40
|
+
}) => {
|
|
41
|
+
const spaire = new import_sdk.Spaire({
|
|
42
|
+
accessToken: accessToken ?? process.env["SPAIRE_ACCESS_TOKEN"],
|
|
43
|
+
server
|
|
44
|
+
});
|
|
45
|
+
return async ({ request }) => {
|
|
46
|
+
const url = new URL(request.url);
|
|
47
|
+
const products = url.searchParams.getAll("products");
|
|
48
|
+
if (products.length === 0) {
|
|
49
|
+
return Response.json(
|
|
50
|
+
{ error: "Missing products in query params" },
|
|
51
|
+
{ status: 400 }
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const success = successUrl ? new URL(successUrl) : void 0;
|
|
55
|
+
if (success && includeCheckoutId) {
|
|
56
|
+
success.searchParams.set("checkoutId", "{CHECKOUT_ID}");
|
|
57
|
+
}
|
|
58
|
+
const retUrl = returnUrl ? new URL(returnUrl) : void 0;
|
|
59
|
+
try {
|
|
60
|
+
const result = await spaire.checkouts.create({
|
|
61
|
+
products,
|
|
62
|
+
successUrl: success ? decodeURI(success.toString()) : void 0,
|
|
63
|
+
customerId: url.searchParams.get("customerId") ?? void 0,
|
|
64
|
+
externalCustomerId: url.searchParams.get("customerExternalId") ?? void 0,
|
|
65
|
+
customerEmail: url.searchParams.get("customerEmail") ?? void 0,
|
|
66
|
+
customerName: url.searchParams.get("customerName") ?? void 0,
|
|
67
|
+
customerBillingAddress: url.searchParams.has("customerBillingAddress") ? JSON.parse(url.searchParams.get("customerBillingAddress") ?? "{}") : void 0,
|
|
68
|
+
customerTaxId: url.searchParams.get("customerTaxId") ?? void 0,
|
|
69
|
+
customerIpAddress: url.searchParams.get("customerIpAddress") ?? void 0,
|
|
70
|
+
customerMetadata: url.searchParams.has("customerMetadata") ? JSON.parse(url.searchParams.get("customerMetadata") ?? "{}") : void 0,
|
|
71
|
+
allowDiscountCodes: url.searchParams.has("allowDiscountCodes") ? url.searchParams.get("allowDiscountCodes") === "true" : void 0,
|
|
72
|
+
discountId: url.searchParams.get("discountId") ?? void 0,
|
|
73
|
+
metadata: url.searchParams.has("metadata") ? JSON.parse(url.searchParams.get("metadata") ?? "{}") : void 0,
|
|
74
|
+
seats: url.searchParams.has("seats") ? Number.parseInt(url.searchParams.get("seats") ?? "1", 10) : void 0,
|
|
75
|
+
returnUrl: retUrl ? decodeURI(retUrl.toString()) : void 0
|
|
76
|
+
});
|
|
77
|
+
const redirectUrl = new URL(result.url);
|
|
78
|
+
if (theme) {
|
|
79
|
+
redirectUrl.searchParams.set("theme", theme);
|
|
80
|
+
}
|
|
81
|
+
return Response.redirect(redirectUrl.toString());
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error(error);
|
|
84
|
+
return Response.json({ error: "Internal server error" }, { status: 500 });
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// src/customerPortal/customerPortal.ts
|
|
90
|
+
var import_sdk2 = require("@spaire/sdk");
|
|
91
|
+
var CustomerPortal = ({
|
|
92
|
+
accessToken,
|
|
93
|
+
server,
|
|
94
|
+
getCustomerId,
|
|
95
|
+
returnUrl
|
|
96
|
+
}) => {
|
|
97
|
+
const spaire = new import_sdk2.Spaire({
|
|
98
|
+
accessToken: accessToken ?? process.env["SPAIRE_ACCESS_TOKEN"],
|
|
99
|
+
server
|
|
100
|
+
});
|
|
101
|
+
return async ({ request }) => {
|
|
102
|
+
const retUrl = returnUrl ? new URL(returnUrl) : void 0;
|
|
103
|
+
const customerId = await getCustomerId(request);
|
|
104
|
+
if (!customerId) {
|
|
105
|
+
return Response.json(
|
|
106
|
+
{ error: "customerId not defined" },
|
|
107
|
+
{ status: 400 }
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const result = await spaire.customerSessions.create({
|
|
112
|
+
customerId,
|
|
113
|
+
returnUrl: retUrl ? decodeURI(retUrl.toString()) : void 0
|
|
114
|
+
});
|
|
115
|
+
return Response.redirect(result.customerPortalUrl);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error(error);
|
|
118
|
+
return Response.json({ error: "Internal server error" }, { status: 500 });
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// src/webhooks/webhooks.ts
|
|
124
|
+
var import_adapter_utils = require("@spaire/adapter-utils");
|
|
125
|
+
var import_webhooks = require("@spaire/sdk/webhooks");
|
|
126
|
+
var import_adapter_utils2 = require("@spaire/adapter-utils");
|
|
127
|
+
var Webhooks = ({
|
|
128
|
+
webhookSecret,
|
|
129
|
+
onPayload,
|
|
130
|
+
entitlements,
|
|
131
|
+
...eventHandlers
|
|
132
|
+
}) => {
|
|
133
|
+
return async ({ request }) => {
|
|
134
|
+
if (request.method !== "POST") {
|
|
135
|
+
return Response.json({ message: "Method not allowed" }, { status: 405 });
|
|
136
|
+
}
|
|
137
|
+
const requestBody = await request.text();
|
|
138
|
+
const webhookHeaders = {
|
|
139
|
+
"webhook-id": request.headers.get("webhook-id") ?? "",
|
|
140
|
+
"webhook-timestamp": request.headers.get("webhook-timestamp") ?? "",
|
|
141
|
+
"webhook-signature": request.headers.get("webhook-signature") ?? ""
|
|
142
|
+
};
|
|
143
|
+
let webhookPayload;
|
|
144
|
+
try {
|
|
145
|
+
webhookPayload = (0, import_webhooks.validateEvent)(
|
|
146
|
+
requestBody,
|
|
147
|
+
webhookHeaders,
|
|
148
|
+
webhookSecret
|
|
149
|
+
);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.log(error);
|
|
152
|
+
if (error instanceof import_webhooks.WebhookVerificationError) {
|
|
153
|
+
return Response.json({ received: false }, { status: 403 });
|
|
154
|
+
}
|
|
155
|
+
return Response.json({ error: "Internal server error" }, { status: 500 });
|
|
156
|
+
}
|
|
157
|
+
await (0, import_adapter_utils.handleWebhookPayload)(webhookPayload, {
|
|
158
|
+
webhookSecret,
|
|
159
|
+
entitlements,
|
|
160
|
+
onPayload,
|
|
161
|
+
...eventHandlers
|
|
162
|
+
});
|
|
163
|
+
return Response.json({ received: true });
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
167
|
+
0 && (module.exports = {
|
|
168
|
+
Checkout,
|
|
169
|
+
CustomerPortal,
|
|
170
|
+
EntitlementStrategy,
|
|
171
|
+
Entitlements,
|
|
172
|
+
Webhooks
|
|
173
|
+
});
|
|
174
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/checkout/checkout.ts","../src/customerPortal/customerPortal.ts","../src/webhooks/webhooks.ts"],"sourcesContent":["export * from \"./checkout/checkout\";\nexport * from \"./customerPortal/customerPortal\";\nexport * from \"./webhooks/webhooks\";\n","import { Spaire } from \"@spaire/sdk\";\nimport type { LoaderFunction } from \"../types\";\n\nexport interface CheckoutConfig {\n\taccessToken?: string;\n\tsuccessUrl?: string;\n\treturnUrl?: string;\n\tincludeCheckoutId?: boolean;\n\tserver?: \"sandbox\" | \"production\";\n\ttheme?: \"light\" | \"dark\";\n}\n\nexport const Checkout = ({\n\taccessToken,\n\tsuccessUrl,\n\treturnUrl,\n\tserver,\n\ttheme,\n\tincludeCheckoutId = true,\n}: CheckoutConfig): LoaderFunction => {\n\tconst spaire = new Spaire({\n\t\taccessToken: accessToken ?? process.env[\"SPAIRE_ACCESS_TOKEN\"],\n\t\tserver,\n\t});\n\n\treturn async ({ request }) => {\n\t\tconst url = new URL(request.url);\n\t\tconst products = url.searchParams.getAll(\"products\");\n\n\t\tif (products.length === 0) {\n\t\t\treturn Response.json(\n\t\t\t\t{ error: \"Missing products in query params\" },\n\t\t\t\t{ status: 400 },\n\t\t\t);\n\t\t}\n\n\t\tconst success = successUrl ? new URL(successUrl) : undefined;\n\n\t\tif (success && includeCheckoutId) {\n\t\t\tsuccess.searchParams.set(\"checkoutId\", \"{CHECKOUT_ID}\");\n\t\t}\n\n\t\tconst retUrl = returnUrl ? new URL(returnUrl) : undefined;\n\n\t\ttry {\n\t\t\tconst result = await spaire.checkouts.create({\n\t\t\t\tproducts,\n\t\t\t\tsuccessUrl: success ? decodeURI(success.toString()) : undefined,\n\t\t\t\tcustomerId: url.searchParams.get(\"customerId\") ?? undefined,\n\t\t\t\texternalCustomerId:\n\t\t\t\t\turl.searchParams.get(\"customerExternalId\") ?? undefined,\n\t\t\t\tcustomerEmail: url.searchParams.get(\"customerEmail\") ?? undefined,\n\t\t\t\tcustomerName: url.searchParams.get(\"customerName\") ?? undefined,\n\t\t\t\tcustomerBillingAddress: url.searchParams.has(\"customerBillingAddress\")\n\t\t\t\t\t? JSON.parse(url.searchParams.get(\"customerBillingAddress\") ?? \"{}\")\n\t\t\t\t\t: undefined,\n\t\t\t\tcustomerTaxId: url.searchParams.get(\"customerTaxId\") ?? undefined,\n\t\t\t\tcustomerIpAddress:\n\t\t\t\t\turl.searchParams.get(\"customerIpAddress\") ?? undefined,\n\t\t\t\tcustomerMetadata: url.searchParams.has(\"customerMetadata\")\n\t\t\t\t\t? JSON.parse(url.searchParams.get(\"customerMetadata\") ?? \"{}\")\n\t\t\t\t\t: undefined,\n\t\t\t\tallowDiscountCodes: url.searchParams.has(\"allowDiscountCodes\")\n\t\t\t\t\t? url.searchParams.get(\"allowDiscountCodes\") === \"true\"\n\t\t\t\t\t: undefined,\n\t\t\t\tdiscountId: url.searchParams.get(\"discountId\") ?? undefined,\n\t\t\t\tmetadata: url.searchParams.has(\"metadata\")\n\t\t\t\t\t? JSON.parse(url.searchParams.get(\"metadata\") ?? \"{}\")\n\t\t\t\t\t: undefined,\n\t\t\t\tseats: url.searchParams.has(\"seats\")\n\t\t\t\t\t? Number.parseInt(url.searchParams.get(\"seats\") ?? \"1\", 10)\n\t\t\t\t\t: undefined,\n\t\t\t\treturnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined,\n\t\t\t});\n\n\t\t\tconst redirectUrl = new URL(result.url);\n\n\t\t\tif (theme) {\n\t\t\t\tredirectUrl.searchParams.set(\"theme\", theme);\n\t\t\t}\n\n\t\t\treturn Response.redirect(redirectUrl.toString());\n\t\t} catch (error) {\n\t\t\tconsole.error(error);\n\t\t\treturn Response.json({ error: \"Internal server error\" }, { status: 500 });\n\t\t}\n\t};\n};\n","import { Spaire } from \"@spaire/sdk\";\nimport type { LoaderFunction } from \"../types\";\n\nexport interface CustomerPortalConfig {\n\taccessToken?: string;\n\tgetCustomerId: (req: Request) => Promise<string>;\n\tserver?: \"sandbox\" | \"production\";\n\treturnUrl?: string;\n}\n\nexport const CustomerPortal = ({\n\taccessToken,\n\tserver,\n\tgetCustomerId,\n\treturnUrl,\n}: CustomerPortalConfig): LoaderFunction => {\n\tconst spaire = new Spaire({\n\t\taccessToken: accessToken ?? process.env[\"SPAIRE_ACCESS_TOKEN\"],\n\t\tserver,\n\t});\n\n\treturn async ({ request }) => {\n\t\tconst retUrl = returnUrl ? new URL(returnUrl) : undefined;\n\n\t\tconst customerId = await getCustomerId(request);\n\n\t\tif (!customerId) {\n\t\t\treturn Response.json(\n\t\t\t\t{ error: \"customerId not defined\" },\n\t\t\t\t{ status: 400 },\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await spaire.customerSessions.create({\n\t\t\t\tcustomerId,\n\t\t\t\treturnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined,\n\t\t\t});\n\n\t\t\treturn Response.redirect(result.customerPortalUrl);\n\t\t} catch (error) {\n\t\t\tconsole.error(error);\n\t\t\treturn Response.json({ error: \"Internal server error\" }, { status: 500 });\n\t\t}\n\t};\n};\n","import {\n\ttype WebhooksConfig,\n\thandleWebhookPayload,\n} from \"@spaire/adapter-utils\";\nimport { WebhookVerificationError, validateEvent } from \"@spaire/sdk/webhooks\";\nimport type { ActionFunction } from \"../types\";\n\nexport {\n\ttype EntitlementContext,\n\ttype EntitlementHandler,\n\ttype EntitlementProperties,\n\tEntitlementStrategy,\n\tEntitlements,\n} from \"@spaire/adapter-utils\";\n\nexport const Webhooks = ({\n\twebhookSecret,\n\tonPayload,\n\tentitlements,\n\t...eventHandlers\n}: WebhooksConfig): ActionFunction => {\n\treturn async ({ request }) => {\n\t\tif (request.method !== \"POST\") {\n\t\t\treturn Response.json({ message: \"Method not allowed\" }, { status: 405 });\n\t\t}\n\n\t\tconst requestBody = await request.text();\n\n\t\tconst webhookHeaders: Record<string, string> = {\n\t\t\t\"webhook-id\": request.headers.get(\"webhook-id\") ?? \"\",\n\t\t\t\"webhook-timestamp\": request.headers.get(\"webhook-timestamp\") ?? \"\",\n\t\t\t\"webhook-signature\": request.headers.get(\"webhook-signature\") ?? \"\",\n\t\t};\n\n\t\tlet webhookPayload: ReturnType<typeof validateEvent>;\n\t\ttry {\n\t\t\twebhookPayload = validateEvent(\n\t\t\t\trequestBody,\n\t\t\t\twebhookHeaders,\n\t\t\t\twebhookSecret,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconsole.log(error);\n\t\t\tif (error instanceof WebhookVerificationError) {\n\t\t\t\treturn Response.json({ received: false }, { status: 403 });\n\t\t\t}\n\n\t\t\treturn Response.json({ error: \"Internal server error\" }, { status: 500 });\n\t\t}\n\n\t\tawait handleWebhookPayload(webhookPayload, {\n\t\t\twebhookSecret,\n\t\t\tentitlements,\n\t\t\tonPayload,\n\t\t\t...eventHandlers,\n\t\t});\n\n\t\treturn Response.json({ received: true });\n\t};\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,iBAAuB;AAYhB,IAAM,WAAW,CAAC;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,oBAAoB;AACrB,MAAsC;AACrC,QAAM,SAAS,IAAI,kBAAO;AAAA,IACzB,aAAa,eAAe,QAAQ,IAAI,qBAAqB;AAAA,IAC7D;AAAA,EACD,CAAC;AAED,SAAO,OAAO,EAAE,QAAQ,MAAM;AAC7B,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,UAAM,WAAW,IAAI,aAAa,OAAO,UAAU;AAEnD,QAAI,SAAS,WAAW,GAAG;AAC1B,aAAO,SAAS;AAAA,QACf,EAAE,OAAO,mCAAmC;AAAA,QAC5C,EAAE,QAAQ,IAAI;AAAA,MACf;AAAA,IACD;AAEA,UAAM,UAAU,aAAa,IAAI,IAAI,UAAU,IAAI;AAEnD,QAAI,WAAW,mBAAmB;AACjC,cAAQ,aAAa,IAAI,cAAc,eAAe;AAAA,IACvD;AAEA,UAAM,SAAS,YAAY,IAAI,IAAI,SAAS,IAAI;AAEhD,QAAI;AACH,YAAM,SAAS,MAAM,OAAO,UAAU,OAAO;AAAA,QAC5C;AAAA,QACA,YAAY,UAAU,UAAU,QAAQ,SAAS,CAAC,IAAI;AAAA,QACtD,YAAY,IAAI,aAAa,IAAI,YAAY,KAAK;AAAA,QAClD,oBACC,IAAI,aAAa,IAAI,oBAAoB,KAAK;AAAA,QAC/C,eAAe,IAAI,aAAa,IAAI,eAAe,KAAK;AAAA,QACxD,cAAc,IAAI,aAAa,IAAI,cAAc,KAAK;AAAA,QACtD,wBAAwB,IAAI,aAAa,IAAI,wBAAwB,IAClE,KAAK,MAAM,IAAI,aAAa,IAAI,wBAAwB,KAAK,IAAI,IACjE;AAAA,QACH,eAAe,IAAI,aAAa,IAAI,eAAe,KAAK;AAAA,QACxD,mBACC,IAAI,aAAa,IAAI,mBAAmB,KAAK;AAAA,QAC9C,kBAAkB,IAAI,aAAa,IAAI,kBAAkB,IACtD,KAAK,MAAM,IAAI,aAAa,IAAI,kBAAkB,KAAK,IAAI,IAC3D;AAAA,QACH,oBAAoB,IAAI,aAAa,IAAI,oBAAoB,IAC1D,IAAI,aAAa,IAAI,oBAAoB,MAAM,SAC/C;AAAA,QACH,YAAY,IAAI,aAAa,IAAI,YAAY,KAAK;AAAA,QAClD,UAAU,IAAI,aAAa,IAAI,UAAU,IACtC,KAAK,MAAM,IAAI,aAAa,IAAI,UAAU,KAAK,IAAI,IACnD;AAAA,QACH,OAAO,IAAI,aAAa,IAAI,OAAO,IAChC,OAAO,SAAS,IAAI,aAAa,IAAI,OAAO,KAAK,KAAK,EAAE,IACxD;AAAA,QACH,WAAW,SAAS,UAAU,OAAO,SAAS,CAAC,IAAI;AAAA,MACpD,CAAC;AAED,YAAM,cAAc,IAAI,IAAI,OAAO,GAAG;AAEtC,UAAI,OAAO;AACV,oBAAY,aAAa,IAAI,SAAS,KAAK;AAAA,MAC5C;AAEA,aAAO,SAAS,SAAS,YAAY,SAAS,CAAC;AAAA,IAChD,SAAS,OAAO;AACf,cAAQ,MAAM,KAAK;AACnB,aAAO,SAAS,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACzE;AAAA,EACD;AACD;;;ACvFA,IAAAA,cAAuB;AAUhB,IAAM,iBAAiB,CAAC;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,MAA4C;AAC3C,QAAM,SAAS,IAAI,mBAAO;AAAA,IACzB,aAAa,eAAe,QAAQ,IAAI,qBAAqB;AAAA,IAC7D;AAAA,EACD,CAAC;AAED,SAAO,OAAO,EAAE,QAAQ,MAAM;AAC7B,UAAM,SAAS,YAAY,IAAI,IAAI,SAAS,IAAI;AAEhD,UAAM,aAAa,MAAM,cAAc,OAAO;AAE9C,QAAI,CAAC,YAAY;AAChB,aAAO,SAAS;AAAA,QACf,EAAE,OAAO,yBAAyB;AAAA,QAClC,EAAE,QAAQ,IAAI;AAAA,MACf;AAAA,IACD;AAEA,QAAI;AACH,YAAM,SAAS,MAAM,OAAO,iBAAiB,OAAO;AAAA,QACnD;AAAA,QACA,WAAW,SAAS,UAAU,OAAO,SAAS,CAAC,IAAI;AAAA,MACpD,CAAC;AAED,aAAO,SAAS,SAAS,OAAO,iBAAiB;AAAA,IAClD,SAAS,OAAO;AACf,cAAQ,MAAM,KAAK;AACnB,aAAO,SAAS,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACzE;AAAA,EACD;AACD;;;AC7CA,2BAGO;AACP,sBAAwD;AAGxD,IAAAC,wBAMO;AAEA,IAAM,WAAW,CAAC;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACJ,MAAsC;AACrC,SAAO,OAAO,EAAE,QAAQ,MAAM;AAC7B,QAAI,QAAQ,WAAW,QAAQ;AAC9B,aAAO,SAAS,KAAK,EAAE,SAAS,qBAAqB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACxE;AAEA,UAAM,cAAc,MAAM,QAAQ,KAAK;AAEvC,UAAM,iBAAyC;AAAA,MAC9C,cAAc,QAAQ,QAAQ,IAAI,YAAY,KAAK;AAAA,MACnD,qBAAqB,QAAQ,QAAQ,IAAI,mBAAmB,KAAK;AAAA,MACjE,qBAAqB,QAAQ,QAAQ,IAAI,mBAAmB,KAAK;AAAA,IAClE;AAEA,QAAI;AACJ,QAAI;AACH,2BAAiB;AAAA,QAChB;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,IACD,SAAS,OAAO;AACf,cAAQ,IAAI,KAAK;AACjB,UAAI,iBAAiB,0CAA0B;AAC9C,eAAO,SAAS,KAAK,EAAE,UAAU,MAAM,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC1D;AAEA,aAAO,SAAS,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACzE;AAEA,cAAM,2CAAqB,gBAAgB;AAAA,MAC1C;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAG;AAAA,IACJ,CAAC;AAED,WAAO,SAAS,KAAK,EAAE,UAAU,KAAK,CAAC;AAAA,EACxC;AACD;","names":["import_sdk","import_adapter_utils"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { WebhooksConfig } from '@spaire/adapter-utils';
|
|
2
|
+
export { EntitlementContext, EntitlementHandler, EntitlementProperties, EntitlementStrategy, Entitlements } from '@spaire/adapter-utils';
|
|
3
|
+
|
|
4
|
+
type Params<Key extends string = string> = {
|
|
5
|
+
readonly [key in Key]: string | undefined;
|
|
6
|
+
};
|
|
7
|
+
type DataFunctionArgs = {
|
|
8
|
+
request: Request;
|
|
9
|
+
context: unknown;
|
|
10
|
+
params: Params;
|
|
11
|
+
};
|
|
12
|
+
type LoaderFunction = (args: DataFunctionArgs) => Promise<Response>;
|
|
13
|
+
type ActionFunction = (args: DataFunctionArgs) => Promise<Response>;
|
|
14
|
+
|
|
15
|
+
interface CheckoutConfig {
|
|
16
|
+
accessToken?: string;
|
|
17
|
+
successUrl?: string;
|
|
18
|
+
returnUrl?: string;
|
|
19
|
+
includeCheckoutId?: boolean;
|
|
20
|
+
server?: "sandbox" | "production";
|
|
21
|
+
theme?: "light" | "dark";
|
|
22
|
+
}
|
|
23
|
+
declare const Checkout: ({ accessToken, successUrl, returnUrl, server, theme, includeCheckoutId, }: CheckoutConfig) => LoaderFunction;
|
|
24
|
+
|
|
25
|
+
interface CustomerPortalConfig {
|
|
26
|
+
accessToken?: string;
|
|
27
|
+
getCustomerId: (req: Request) => Promise<string>;
|
|
28
|
+
server?: "sandbox" | "production";
|
|
29
|
+
returnUrl?: string;
|
|
30
|
+
}
|
|
31
|
+
declare const CustomerPortal: ({ accessToken, server, getCustomerId, returnUrl, }: CustomerPortalConfig) => LoaderFunction;
|
|
32
|
+
|
|
33
|
+
declare const Webhooks: ({ webhookSecret, onPayload, entitlements, ...eventHandlers }: WebhooksConfig) => ActionFunction;
|
|
34
|
+
|
|
35
|
+
export { Checkout, type CheckoutConfig, CustomerPortal, type CustomerPortalConfig, Webhooks };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { WebhooksConfig } from '@spaire/adapter-utils';
|
|
2
|
+
export { EntitlementContext, EntitlementHandler, EntitlementProperties, EntitlementStrategy, Entitlements } from '@spaire/adapter-utils';
|
|
3
|
+
|
|
4
|
+
type Params<Key extends string = string> = {
|
|
5
|
+
readonly [key in Key]: string | undefined;
|
|
6
|
+
};
|
|
7
|
+
type DataFunctionArgs = {
|
|
8
|
+
request: Request;
|
|
9
|
+
context: unknown;
|
|
10
|
+
params: Params;
|
|
11
|
+
};
|
|
12
|
+
type LoaderFunction = (args: DataFunctionArgs) => Promise<Response>;
|
|
13
|
+
type ActionFunction = (args: DataFunctionArgs) => Promise<Response>;
|
|
14
|
+
|
|
15
|
+
interface CheckoutConfig {
|
|
16
|
+
accessToken?: string;
|
|
17
|
+
successUrl?: string;
|
|
18
|
+
returnUrl?: string;
|
|
19
|
+
includeCheckoutId?: boolean;
|
|
20
|
+
server?: "sandbox" | "production";
|
|
21
|
+
theme?: "light" | "dark";
|
|
22
|
+
}
|
|
23
|
+
declare const Checkout: ({ accessToken, successUrl, returnUrl, server, theme, includeCheckoutId, }: CheckoutConfig) => LoaderFunction;
|
|
24
|
+
|
|
25
|
+
interface CustomerPortalConfig {
|
|
26
|
+
accessToken?: string;
|
|
27
|
+
getCustomerId: (req: Request) => Promise<string>;
|
|
28
|
+
server?: "sandbox" | "production";
|
|
29
|
+
returnUrl?: string;
|
|
30
|
+
}
|
|
31
|
+
declare const CustomerPortal: ({ accessToken, server, getCustomerId, returnUrl, }: CustomerPortalConfig) => LoaderFunction;
|
|
32
|
+
|
|
33
|
+
declare const Webhooks: ({ webhookSecret, onPayload, entitlements, ...eventHandlers }: WebhooksConfig) => ActionFunction;
|
|
34
|
+
|
|
35
|
+
export { Checkout, type CheckoutConfig, CustomerPortal, type CustomerPortalConfig, Webhooks };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// src/checkout/checkout.ts
|
|
2
|
+
import { Spaire } from "@spaire/sdk";
|
|
3
|
+
var Checkout = ({
|
|
4
|
+
accessToken,
|
|
5
|
+
successUrl,
|
|
6
|
+
returnUrl,
|
|
7
|
+
server,
|
|
8
|
+
theme,
|
|
9
|
+
includeCheckoutId = true
|
|
10
|
+
}) => {
|
|
11
|
+
const spaire = new Spaire({
|
|
12
|
+
accessToken: accessToken ?? process.env["SPAIRE_ACCESS_TOKEN"],
|
|
13
|
+
server
|
|
14
|
+
});
|
|
15
|
+
return async ({ request }) => {
|
|
16
|
+
const url = new URL(request.url);
|
|
17
|
+
const products = url.searchParams.getAll("products");
|
|
18
|
+
if (products.length === 0) {
|
|
19
|
+
return Response.json(
|
|
20
|
+
{ error: "Missing products in query params" },
|
|
21
|
+
{ status: 400 }
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
const success = successUrl ? new URL(successUrl) : void 0;
|
|
25
|
+
if (success && includeCheckoutId) {
|
|
26
|
+
success.searchParams.set("checkoutId", "{CHECKOUT_ID}");
|
|
27
|
+
}
|
|
28
|
+
const retUrl = returnUrl ? new URL(returnUrl) : void 0;
|
|
29
|
+
try {
|
|
30
|
+
const result = await spaire.checkouts.create({
|
|
31
|
+
products,
|
|
32
|
+
successUrl: success ? decodeURI(success.toString()) : void 0,
|
|
33
|
+
customerId: url.searchParams.get("customerId") ?? void 0,
|
|
34
|
+
externalCustomerId: url.searchParams.get("customerExternalId") ?? void 0,
|
|
35
|
+
customerEmail: url.searchParams.get("customerEmail") ?? void 0,
|
|
36
|
+
customerName: url.searchParams.get("customerName") ?? void 0,
|
|
37
|
+
customerBillingAddress: url.searchParams.has("customerBillingAddress") ? JSON.parse(url.searchParams.get("customerBillingAddress") ?? "{}") : void 0,
|
|
38
|
+
customerTaxId: url.searchParams.get("customerTaxId") ?? void 0,
|
|
39
|
+
customerIpAddress: url.searchParams.get("customerIpAddress") ?? void 0,
|
|
40
|
+
customerMetadata: url.searchParams.has("customerMetadata") ? JSON.parse(url.searchParams.get("customerMetadata") ?? "{}") : void 0,
|
|
41
|
+
allowDiscountCodes: url.searchParams.has("allowDiscountCodes") ? url.searchParams.get("allowDiscountCodes") === "true" : void 0,
|
|
42
|
+
discountId: url.searchParams.get("discountId") ?? void 0,
|
|
43
|
+
metadata: url.searchParams.has("metadata") ? JSON.parse(url.searchParams.get("metadata") ?? "{}") : void 0,
|
|
44
|
+
seats: url.searchParams.has("seats") ? Number.parseInt(url.searchParams.get("seats") ?? "1", 10) : void 0,
|
|
45
|
+
returnUrl: retUrl ? decodeURI(retUrl.toString()) : void 0
|
|
46
|
+
});
|
|
47
|
+
const redirectUrl = new URL(result.url);
|
|
48
|
+
if (theme) {
|
|
49
|
+
redirectUrl.searchParams.set("theme", theme);
|
|
50
|
+
}
|
|
51
|
+
return Response.redirect(redirectUrl.toString());
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(error);
|
|
54
|
+
return Response.json({ error: "Internal server error" }, { status: 500 });
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// src/customerPortal/customerPortal.ts
|
|
60
|
+
import { Spaire as Spaire2 } from "@spaire/sdk";
|
|
61
|
+
var CustomerPortal = ({
|
|
62
|
+
accessToken,
|
|
63
|
+
server,
|
|
64
|
+
getCustomerId,
|
|
65
|
+
returnUrl
|
|
66
|
+
}) => {
|
|
67
|
+
const spaire = new Spaire2({
|
|
68
|
+
accessToken: accessToken ?? process.env["SPAIRE_ACCESS_TOKEN"],
|
|
69
|
+
server
|
|
70
|
+
});
|
|
71
|
+
return async ({ request }) => {
|
|
72
|
+
const retUrl = returnUrl ? new URL(returnUrl) : void 0;
|
|
73
|
+
const customerId = await getCustomerId(request);
|
|
74
|
+
if (!customerId) {
|
|
75
|
+
return Response.json(
|
|
76
|
+
{ error: "customerId not defined" },
|
|
77
|
+
{ status: 400 }
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const result = await spaire.customerSessions.create({
|
|
82
|
+
customerId,
|
|
83
|
+
returnUrl: retUrl ? decodeURI(retUrl.toString()) : void 0
|
|
84
|
+
});
|
|
85
|
+
return Response.redirect(result.customerPortalUrl);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error(error);
|
|
88
|
+
return Response.json({ error: "Internal server error" }, { status: 500 });
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/webhooks/webhooks.ts
|
|
94
|
+
import {
|
|
95
|
+
handleWebhookPayload
|
|
96
|
+
} from "@spaire/adapter-utils";
|
|
97
|
+
import { WebhookVerificationError, validateEvent } from "@spaire/sdk/webhooks";
|
|
98
|
+
import {
|
|
99
|
+
EntitlementStrategy,
|
|
100
|
+
Entitlements
|
|
101
|
+
} from "@spaire/adapter-utils";
|
|
102
|
+
var Webhooks = ({
|
|
103
|
+
webhookSecret,
|
|
104
|
+
onPayload,
|
|
105
|
+
entitlements,
|
|
106
|
+
...eventHandlers
|
|
107
|
+
}) => {
|
|
108
|
+
return async ({ request }) => {
|
|
109
|
+
if (request.method !== "POST") {
|
|
110
|
+
return Response.json({ message: "Method not allowed" }, { status: 405 });
|
|
111
|
+
}
|
|
112
|
+
const requestBody = await request.text();
|
|
113
|
+
const webhookHeaders = {
|
|
114
|
+
"webhook-id": request.headers.get("webhook-id") ?? "",
|
|
115
|
+
"webhook-timestamp": request.headers.get("webhook-timestamp") ?? "",
|
|
116
|
+
"webhook-signature": request.headers.get("webhook-signature") ?? ""
|
|
117
|
+
};
|
|
118
|
+
let webhookPayload;
|
|
119
|
+
try {
|
|
120
|
+
webhookPayload = validateEvent(
|
|
121
|
+
requestBody,
|
|
122
|
+
webhookHeaders,
|
|
123
|
+
webhookSecret
|
|
124
|
+
);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.log(error);
|
|
127
|
+
if (error instanceof WebhookVerificationError) {
|
|
128
|
+
return Response.json({ received: false }, { status: 403 });
|
|
129
|
+
}
|
|
130
|
+
return Response.json({ error: "Internal server error" }, { status: 500 });
|
|
131
|
+
}
|
|
132
|
+
await handleWebhookPayload(webhookPayload, {
|
|
133
|
+
webhookSecret,
|
|
134
|
+
entitlements,
|
|
135
|
+
onPayload,
|
|
136
|
+
...eventHandlers
|
|
137
|
+
});
|
|
138
|
+
return Response.json({ received: true });
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
export {
|
|
142
|
+
Checkout,
|
|
143
|
+
CustomerPortal,
|
|
144
|
+
EntitlementStrategy,
|
|
145
|
+
Entitlements,
|
|
146
|
+
Webhooks
|
|
147
|
+
};
|
|
148
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/checkout/checkout.ts","../src/customerPortal/customerPortal.ts","../src/webhooks/webhooks.ts"],"sourcesContent":["import { Spaire } from \"@spaire/sdk\";\nimport type { LoaderFunction } from \"../types\";\n\nexport interface CheckoutConfig {\n\taccessToken?: string;\n\tsuccessUrl?: string;\n\treturnUrl?: string;\n\tincludeCheckoutId?: boolean;\n\tserver?: \"sandbox\" | \"production\";\n\ttheme?: \"light\" | \"dark\";\n}\n\nexport const Checkout = ({\n\taccessToken,\n\tsuccessUrl,\n\treturnUrl,\n\tserver,\n\ttheme,\n\tincludeCheckoutId = true,\n}: CheckoutConfig): LoaderFunction => {\n\tconst spaire = new Spaire({\n\t\taccessToken: accessToken ?? process.env[\"SPAIRE_ACCESS_TOKEN\"],\n\t\tserver,\n\t});\n\n\treturn async ({ request }) => {\n\t\tconst url = new URL(request.url);\n\t\tconst products = url.searchParams.getAll(\"products\");\n\n\t\tif (products.length === 0) {\n\t\t\treturn Response.json(\n\t\t\t\t{ error: \"Missing products in query params\" },\n\t\t\t\t{ status: 400 },\n\t\t\t);\n\t\t}\n\n\t\tconst success = successUrl ? new URL(successUrl) : undefined;\n\n\t\tif (success && includeCheckoutId) {\n\t\t\tsuccess.searchParams.set(\"checkoutId\", \"{CHECKOUT_ID}\");\n\t\t}\n\n\t\tconst retUrl = returnUrl ? new URL(returnUrl) : undefined;\n\n\t\ttry {\n\t\t\tconst result = await spaire.checkouts.create({\n\t\t\t\tproducts,\n\t\t\t\tsuccessUrl: success ? decodeURI(success.toString()) : undefined,\n\t\t\t\tcustomerId: url.searchParams.get(\"customerId\") ?? undefined,\n\t\t\t\texternalCustomerId:\n\t\t\t\t\turl.searchParams.get(\"customerExternalId\") ?? undefined,\n\t\t\t\tcustomerEmail: url.searchParams.get(\"customerEmail\") ?? undefined,\n\t\t\t\tcustomerName: url.searchParams.get(\"customerName\") ?? undefined,\n\t\t\t\tcustomerBillingAddress: url.searchParams.has(\"customerBillingAddress\")\n\t\t\t\t\t? JSON.parse(url.searchParams.get(\"customerBillingAddress\") ?? \"{}\")\n\t\t\t\t\t: undefined,\n\t\t\t\tcustomerTaxId: url.searchParams.get(\"customerTaxId\") ?? undefined,\n\t\t\t\tcustomerIpAddress:\n\t\t\t\t\turl.searchParams.get(\"customerIpAddress\") ?? undefined,\n\t\t\t\tcustomerMetadata: url.searchParams.has(\"customerMetadata\")\n\t\t\t\t\t? JSON.parse(url.searchParams.get(\"customerMetadata\") ?? \"{}\")\n\t\t\t\t\t: undefined,\n\t\t\t\tallowDiscountCodes: url.searchParams.has(\"allowDiscountCodes\")\n\t\t\t\t\t? url.searchParams.get(\"allowDiscountCodes\") === \"true\"\n\t\t\t\t\t: undefined,\n\t\t\t\tdiscountId: url.searchParams.get(\"discountId\") ?? undefined,\n\t\t\t\tmetadata: url.searchParams.has(\"metadata\")\n\t\t\t\t\t? JSON.parse(url.searchParams.get(\"metadata\") ?? \"{}\")\n\t\t\t\t\t: undefined,\n\t\t\t\tseats: url.searchParams.has(\"seats\")\n\t\t\t\t\t? Number.parseInt(url.searchParams.get(\"seats\") ?? \"1\", 10)\n\t\t\t\t\t: undefined,\n\t\t\t\treturnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined,\n\t\t\t});\n\n\t\t\tconst redirectUrl = new URL(result.url);\n\n\t\t\tif (theme) {\n\t\t\t\tredirectUrl.searchParams.set(\"theme\", theme);\n\t\t\t}\n\n\t\t\treturn Response.redirect(redirectUrl.toString());\n\t\t} catch (error) {\n\t\t\tconsole.error(error);\n\t\t\treturn Response.json({ error: \"Internal server error\" }, { status: 500 });\n\t\t}\n\t};\n};\n","import { Spaire } from \"@spaire/sdk\";\nimport type { LoaderFunction } from \"../types\";\n\nexport interface CustomerPortalConfig {\n\taccessToken?: string;\n\tgetCustomerId: (req: Request) => Promise<string>;\n\tserver?: \"sandbox\" | \"production\";\n\treturnUrl?: string;\n}\n\nexport const CustomerPortal = ({\n\taccessToken,\n\tserver,\n\tgetCustomerId,\n\treturnUrl,\n}: CustomerPortalConfig): LoaderFunction => {\n\tconst spaire = new Spaire({\n\t\taccessToken: accessToken ?? process.env[\"SPAIRE_ACCESS_TOKEN\"],\n\t\tserver,\n\t});\n\n\treturn async ({ request }) => {\n\t\tconst retUrl = returnUrl ? new URL(returnUrl) : undefined;\n\n\t\tconst customerId = await getCustomerId(request);\n\n\t\tif (!customerId) {\n\t\t\treturn Response.json(\n\t\t\t\t{ error: \"customerId not defined\" },\n\t\t\t\t{ status: 400 },\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await spaire.customerSessions.create({\n\t\t\t\tcustomerId,\n\t\t\t\treturnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined,\n\t\t\t});\n\n\t\t\treturn Response.redirect(result.customerPortalUrl);\n\t\t} catch (error) {\n\t\t\tconsole.error(error);\n\t\t\treturn Response.json({ error: \"Internal server error\" }, { status: 500 });\n\t\t}\n\t};\n};\n","import {\n\ttype WebhooksConfig,\n\thandleWebhookPayload,\n} from \"@spaire/adapter-utils\";\nimport { WebhookVerificationError, validateEvent } from \"@spaire/sdk/webhooks\";\nimport type { ActionFunction } from \"../types\";\n\nexport {\n\ttype EntitlementContext,\n\ttype EntitlementHandler,\n\ttype EntitlementProperties,\n\tEntitlementStrategy,\n\tEntitlements,\n} from \"@spaire/adapter-utils\";\n\nexport const Webhooks = ({\n\twebhookSecret,\n\tonPayload,\n\tentitlements,\n\t...eventHandlers\n}: WebhooksConfig): ActionFunction => {\n\treturn async ({ request }) => {\n\t\tif (request.method !== \"POST\") {\n\t\t\treturn Response.json({ message: \"Method not allowed\" }, { status: 405 });\n\t\t}\n\n\t\tconst requestBody = await request.text();\n\n\t\tconst webhookHeaders: Record<string, string> = {\n\t\t\t\"webhook-id\": request.headers.get(\"webhook-id\") ?? \"\",\n\t\t\t\"webhook-timestamp\": request.headers.get(\"webhook-timestamp\") ?? \"\",\n\t\t\t\"webhook-signature\": request.headers.get(\"webhook-signature\") ?? \"\",\n\t\t};\n\n\t\tlet webhookPayload: ReturnType<typeof validateEvent>;\n\t\ttry {\n\t\t\twebhookPayload = validateEvent(\n\t\t\t\trequestBody,\n\t\t\t\twebhookHeaders,\n\t\t\t\twebhookSecret,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconsole.log(error);\n\t\t\tif (error instanceof WebhookVerificationError) {\n\t\t\t\treturn Response.json({ received: false }, { status: 403 });\n\t\t\t}\n\n\t\t\treturn Response.json({ error: \"Internal server error\" }, { status: 500 });\n\t\t}\n\n\t\tawait handleWebhookPayload(webhookPayload, {\n\t\t\twebhookSecret,\n\t\t\tentitlements,\n\t\t\tonPayload,\n\t\t\t...eventHandlers,\n\t\t});\n\n\t\treturn Response.json({ received: true });\n\t};\n};\n"],"mappings":"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","names":["Spaire"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@spaire/remix",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Spaire integration for Remix and React Router",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
"import": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"require": {
|
|
14
|
+
"types": "./dist/index.d.cts",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=16"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"test": "vitest",
|
|
24
|
+
"build": "tsup ./src/index.ts --format esm,cjs --dts --clean --sourcemap",
|
|
25
|
+
"dev": "tsc --watch",
|
|
26
|
+
"check": "biome check --write ./src"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"keywords": [
|
|
32
|
+
"spaire",
|
|
33
|
+
"remix",
|
|
34
|
+
"react-router",
|
|
35
|
+
"payments",
|
|
36
|
+
"subscriptions"
|
|
37
|
+
],
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@biomejs/biome": "1.9.4",
|
|
40
|
+
"@sindresorhus/tsconfig": "^7.0.0",
|
|
41
|
+
"@types/node": "^20.0.0",
|
|
42
|
+
"prettier": "^3.7.4",
|
|
43
|
+
"tsup": "^8.5.1",
|
|
44
|
+
"vitest": "^2.1.8"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@spaire/adapter-utils": "^2.0.0",
|
|
48
|
+
"@spaire/sdk": "^0.45.1"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
}
|
|
53
|
+
}
|