@polar-sh/sdk 1.0.0-alpha.15 → 1.0.0-alpha.16
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/.agents/skills/polar-typescript-sdk/SKILL.md +166 -0
- package/.agents/skills/polar-typescript-sdk/agents/openai.yaml +4 -0
- package/.agents/skills/polar-typescript-sdk/references/migration-from-v0.md +114 -0
- package/.agents/skills/polar-typescript-sdk/references/usage-events.md +59 -0
- package/.agents/skills/polar-typescript-sdk/references/webhooks.md +96 -0
- package/dist/2026-04/index.cjs.map +1 -1
- package/dist/2026-04/index.d.cts +1 -1
- package/dist/2026-04/index.d.cts.map +1 -1
- package/dist/2026-04/index.d.mts +1 -1
- package/dist/2026-04/index.d.mts.map +1 -1
- package/dist/2026-04/index.mjs.map +1 -1
- package/dist/2026-04/services/benefit_grants.d.cts +1 -1
- package/dist/2026-04/services/benefit_grants.d.cts.map +1 -1
- package/dist/2026-04/services/benefit_grants.d.mts +1 -1
- package/dist/2026-04/services/benefit_grants.d.mts.map +1 -1
- package/dist/2026-04/services/benefits.d.cts +1 -1
- package/dist/2026-04/services/benefits.d.cts.map +1 -1
- package/dist/2026-04/services/benefits.d.mts +1 -1
- package/dist/2026-04/services/benefits.d.mts.map +1 -1
- package/dist/2026-04/services/checkout_links.d.cts +1 -1
- package/dist/2026-04/services/checkout_links.d.cts.map +1 -1
- package/dist/2026-04/services/checkout_links.d.mts +1 -1
- package/dist/2026-04/services/checkout_links.d.mts.map +1 -1
- package/dist/2026-04/services/checkouts.d.cts +1 -1
- package/dist/2026-04/services/checkouts.d.cts.map +1 -1
- package/dist/2026-04/services/checkouts.d.mts +1 -1
- package/dist/2026-04/services/checkouts.d.mts.map +1 -1
- package/dist/2026-04/services/custom_fields.d.cts +1 -1
- package/dist/2026-04/services/custom_fields.d.cts.map +1 -1
- package/dist/2026-04/services/custom_fields.d.mts +1 -1
- package/dist/2026-04/services/custom_fields.d.mts.map +1 -1
- package/dist/2026-04/services/customer_meters.d.cts +1 -1
- package/dist/2026-04/services/customer_meters.d.cts.map +1 -1
- package/dist/2026-04/services/customer_meters.d.mts +1 -1
- package/dist/2026-04/services/customer_meters.d.mts.map +1 -1
- package/dist/2026-04/services/customer_portal/benefit_grants.d.cts +1 -1
- package/dist/2026-04/services/customer_portal/benefit_grants.d.cts.map +1 -1
- package/dist/2026-04/services/customer_portal/benefit_grants.d.mts +1 -1
- package/dist/2026-04/services/customer_portal/benefit_grants.d.mts.map +1 -1
- package/dist/2026-04/services/customer_portal/customer_meters.d.cts +1 -1
- package/dist/2026-04/services/customer_portal/customer_meters.d.cts.map +1 -1
- package/dist/2026-04/services/customer_portal/customer_meters.d.mts +1 -1
- package/dist/2026-04/services/customer_portal/customer_meters.d.mts.map +1 -1
- package/dist/2026-04/services/customer_portal/customer_session.d.cts +1 -1
- package/dist/2026-04/services/customer_portal/customer_session.d.cts.map +1 -1
- package/dist/2026-04/services/customer_portal/customer_session.d.mts +1 -1
- package/dist/2026-04/services/customer_portal/customer_session.d.mts.map +1 -1
- package/dist/2026-04/services/customer_portal/customers.d.cts +1 -1
- package/dist/2026-04/services/customer_portal/customers.d.cts.map +1 -1
- package/dist/2026-04/services/customer_portal/customers.d.mts +1 -1
- package/dist/2026-04/services/customer_portal/customers.d.mts.map +1 -1
- package/dist/2026-04/services/customer_portal/downloadables.d.cts +1 -1
- package/dist/2026-04/services/customer_portal/downloadables.d.cts.map +1 -1
- package/dist/2026-04/services/customer_portal/downloadables.d.mts +1 -1
- package/dist/2026-04/services/customer_portal/downloadables.d.mts.map +1 -1
- package/dist/2026-04/services/customer_portal/index.d.cts +1 -1
- package/dist/2026-04/services/customer_portal/index.d.cts.map +1 -1
- package/dist/2026-04/services/customer_portal/index.d.mts +1 -1
- package/dist/2026-04/services/customer_portal/index.d.mts.map +1 -1
- package/dist/2026-04/services/customer_portal/license_keys.d.cts +1 -1
- package/dist/2026-04/services/customer_portal/license_keys.d.cts.map +1 -1
- package/dist/2026-04/services/customer_portal/license_keys.d.mts +1 -1
- package/dist/2026-04/services/customer_portal/license_keys.d.mts.map +1 -1
- package/dist/2026-04/services/customer_portal/members.d.cts +1 -1
- package/dist/2026-04/services/customer_portal/members.d.cts.map +1 -1
- package/dist/2026-04/services/customer_portal/members.d.mts +1 -1
- package/dist/2026-04/services/customer_portal/members.d.mts.map +1 -1
- package/dist/2026-04/services/customer_portal/orders.d.cts +1 -1
- package/dist/2026-04/services/customer_portal/orders.d.cts.map +1 -1
- package/dist/2026-04/services/customer_portal/orders.d.mts +1 -1
- package/dist/2026-04/services/customer_portal/orders.d.mts.map +1 -1
- package/dist/2026-04/services/customer_portal/organizations.d.cts +1 -1
- package/dist/2026-04/services/customer_portal/organizations.d.cts.map +1 -1
- package/dist/2026-04/services/customer_portal/organizations.d.mts +1 -1
- package/dist/2026-04/services/customer_portal/organizations.d.mts.map +1 -1
- package/dist/2026-04/services/customer_portal/seats.d.cts +1 -1
- package/dist/2026-04/services/customer_portal/seats.d.cts.map +1 -1
- package/dist/2026-04/services/customer_portal/seats.d.mts +1 -1
- package/dist/2026-04/services/customer_portal/seats.d.mts.map +1 -1
- package/dist/2026-04/services/customer_portal/subscriptions.d.cts +1 -1
- package/dist/2026-04/services/customer_portal/subscriptions.d.cts.map +1 -1
- package/dist/2026-04/services/customer_portal/subscriptions.d.mts +1 -1
- package/dist/2026-04/services/customer_portal/subscriptions.d.mts.map +1 -1
- package/dist/2026-04/services/customer_portal/wallets.d.cts +1 -1
- package/dist/2026-04/services/customer_portal/wallets.d.cts.map +1 -1
- package/dist/2026-04/services/customer_portal/wallets.d.mts +1 -1
- package/dist/2026-04/services/customer_portal/wallets.d.mts.map +1 -1
- package/dist/2026-04/services/customer_seats.d.cts +1 -1
- package/dist/2026-04/services/customer_seats.d.cts.map +1 -1
- package/dist/2026-04/services/customer_seats.d.mts +1 -1
- package/dist/2026-04/services/customer_seats.d.mts.map +1 -1
- package/dist/2026-04/services/customer_sessions.d.cts +1 -1
- package/dist/2026-04/services/customer_sessions.d.cts.map +1 -1
- package/dist/2026-04/services/customer_sessions.d.mts +1 -1
- package/dist/2026-04/services/customer_sessions.d.mts.map +1 -1
- package/dist/2026-04/services/customers/index.d.cts +1 -1
- package/dist/2026-04/services/customers/index.d.cts.map +1 -1
- package/dist/2026-04/services/customers/index.d.mts +1 -1
- package/dist/2026-04/services/customers/index.d.mts.map +1 -1
- package/dist/2026-04/services/customers/members.d.cts +1 -1
- package/dist/2026-04/services/customers/members.d.cts.map +1 -1
- package/dist/2026-04/services/customers/members.d.mts +1 -1
- package/dist/2026-04/services/customers/members.d.mts.map +1 -1
- package/dist/2026-04/services/discounts.d.cts +1 -1
- package/dist/2026-04/services/discounts.d.cts.map +1 -1
- package/dist/2026-04/services/discounts.d.mts +1 -1
- package/dist/2026-04/services/discounts.d.mts.map +1 -1
- package/dist/2026-04/services/disputes.d.cts +1 -1
- package/dist/2026-04/services/disputes.d.cts.map +1 -1
- package/dist/2026-04/services/disputes.d.mts +1 -1
- package/dist/2026-04/services/disputes.d.mts.map +1 -1
- package/dist/2026-04/services/event_types.d.cts +1 -1
- package/dist/2026-04/services/event_types.d.cts.map +1 -1
- package/dist/2026-04/services/event_types.d.mts +1 -1
- package/dist/2026-04/services/event_types.d.mts.map +1 -1
- package/dist/2026-04/services/events.d.cts +1 -1
- package/dist/2026-04/services/events.d.cts.map +1 -1
- package/dist/2026-04/services/events.d.mts +1 -1
- package/dist/2026-04/services/events.d.mts.map +1 -1
- package/dist/2026-04/services/files.d.cts +1 -1
- package/dist/2026-04/services/files.d.cts.map +1 -1
- package/dist/2026-04/services/files.d.mts +1 -1
- package/dist/2026-04/services/files.d.mts.map +1 -1
- package/dist/2026-04/services/license_keys.d.cts +1 -1
- package/dist/2026-04/services/license_keys.d.cts.map +1 -1
- package/dist/2026-04/services/license_keys.d.mts +1 -1
- package/dist/2026-04/services/license_keys.d.mts.map +1 -1
- package/dist/2026-04/services/members.d.cts +1 -1
- package/dist/2026-04/services/members.d.cts.map +1 -1
- package/dist/2026-04/services/members.d.mts +1 -1
- package/dist/2026-04/services/members.d.mts.map +1 -1
- package/dist/2026-04/services/meters.d.cts +1 -1
- package/dist/2026-04/services/meters.d.cts.map +1 -1
- package/dist/2026-04/services/meters.d.mts +1 -1
- package/dist/2026-04/services/meters.d.mts.map +1 -1
- package/dist/2026-04/services/metrics.d.cts +1 -1
- package/dist/2026-04/services/metrics.d.cts.map +1 -1
- package/dist/2026-04/services/metrics.d.mts +1 -1
- package/dist/2026-04/services/metrics.d.mts.map +1 -1
- package/dist/2026-04/services/oauth2/clients/index.d.cts +1 -1
- package/dist/2026-04/services/oauth2/clients/index.d.mts +1 -1
- package/dist/2026-04/services/oauth2/clients/oauth2.d.cts +1 -1
- package/dist/2026-04/services/oauth2/clients/oauth2.d.cts.map +1 -1
- package/dist/2026-04/services/oauth2/clients/oauth2.d.mts +1 -1
- package/dist/2026-04/services/oauth2/clients/oauth2.d.mts.map +1 -1
- package/dist/2026-04/services/oauth2/index.d.cts +1 -1
- package/dist/2026-04/services/oauth2/index.d.cts.map +1 -1
- package/dist/2026-04/services/oauth2/index.d.mts +1 -1
- package/dist/2026-04/services/oauth2/index.d.mts.map +1 -1
- package/dist/2026-04/services/orders.d.cts +1 -1
- package/dist/2026-04/services/orders.d.cts.map +1 -1
- package/dist/2026-04/services/orders.d.mts +1 -1
- package/dist/2026-04/services/orders.d.mts.map +1 -1
- package/dist/2026-04/services/organizations.d.cts +1 -1
- package/dist/2026-04/services/organizations.d.cts.map +1 -1
- package/dist/2026-04/services/organizations.d.mts +1 -1
- package/dist/2026-04/services/organizations.d.mts.map +1 -1
- package/dist/2026-04/services/payments.d.cts +1 -1
- package/dist/2026-04/services/payments.d.cts.map +1 -1
- package/dist/2026-04/services/payments.d.mts +1 -1
- package/dist/2026-04/services/payments.d.mts.map +1 -1
- package/dist/2026-04/services/products.d.cts +1 -1
- package/dist/2026-04/services/products.d.cts.map +1 -1
- package/dist/2026-04/services/products.d.mts +1 -1
- package/dist/2026-04/services/products.d.mts.map +1 -1
- package/dist/2026-04/services/refunds.d.cts +1 -1
- package/dist/2026-04/services/refunds.d.cts.map +1 -1
- package/dist/2026-04/services/refunds.d.mts +1 -1
- package/dist/2026-04/services/refunds.d.mts.map +1 -1
- package/dist/2026-04/services/subscriptions.d.cts +1 -1
- package/dist/2026-04/services/subscriptions.d.cts.map +1 -1
- package/dist/2026-04/services/subscriptions.d.mts +1 -1
- package/dist/2026-04/services/subscriptions.d.mts.map +1 -1
- package/dist/2026-04/services/webhooks.d.cts +1 -1
- package/dist/2026-04/services/webhooks.d.cts.map +1 -1
- package/dist/2026-04/services/webhooks.d.mts +1 -1
- package/dist/2026-04/services/webhooks.d.mts.map +1 -1
- package/dist/base-BRp5LBjD.cjs.map +1 -1
- package/dist/base-CmjkkyNf.d.cts.map +1 -1
- package/dist/base-CmjkkyNf.d.mts.map +1 -1
- package/dist/base-Drrd3Jbe.mjs.map +1 -1
- package/dist/{models-ox6b1ecU.d.mts → models-C07UD726.d.mts} +192 -4
- package/dist/models-C07UD726.d.mts.map +1 -0
- package/dist/{models-DN3g8tJh.d.cts → models-Dpu20Rns.d.cts} +192 -4
- package/dist/models-Dpu20Rns.d.cts.map +1 -0
- package/package.json +3 -2
- package/dist/models-DN3g8tJh.d.cts.map +0 -1
- package/dist/models-ox6b1ecU.d.mts.map +0 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: polar-typescript-sdk
|
|
3
|
+
description: Integrate Polar billing in server-side TypeScript applications using the versioned createPolar and createPolarCore clients. Use when implementing Polar customers and external IDs, checkout or customer portal sessions, customer-state entitlement checks, webhook validation and processing, API error handling, tree-shakable SDK calls, usage-event ingestion and metered billing, or migrating an application from the old Polar TypeScript SDK.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Polar TypeScript SDK
|
|
7
|
+
|
|
8
|
+
Use the generated, versioned Polar SDK without inventing methods or parameters.
|
|
9
|
+
|
|
10
|
+
## Inspect the application first
|
|
11
|
+
|
|
12
|
+
1. Identify the installed `@polar-sh/sdk` version and preserve the API version already selected by the application.
|
|
13
|
+
2. Inspect generated service signatures, model interfaces, response types, and endpoint-specific errors before writing calls.
|
|
14
|
+
3. Use `createPolar` for the full service client. Use `createPolarCore` with individual service functions when bundle size or tree-shaking matters.
|
|
15
|
+
4. Identify whether the access token is scoped to an organization. Pass `organization_id` when the selected token does not imply one, and confirm the required endpoint scopes.
|
|
16
|
+
5. Keep client, webhook, and environment configuration in trusted server-side code.
|
|
17
|
+
|
|
18
|
+
## Migrate from the old SDK
|
|
19
|
+
|
|
20
|
+
Read [Migration from the `<1.0.0` SDK](references/migration-from-v0.md) before changing an application that imports an unversioned `Polar` class or uses camelCase request and response fields. Do not mix old and new client, field-casing, pagination, error, or webhook conventions.
|
|
21
|
+
|
|
22
|
+
## Configure and reuse the client
|
|
23
|
+
|
|
24
|
+
Keep production and sandbox access tokens separate. Never expose an organization access token in browser, mobile, or other public client code. Pass `environment: "sandbox"` while testing; omit it or pass `environment: "production"` in production.
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { createPolar } from "@polar-sh/sdk/2026-04";
|
|
28
|
+
|
|
29
|
+
const accessToken = process.env.POLAR_ACCESS_TOKEN;
|
|
30
|
+
if (!accessToken) {
|
|
31
|
+
throw new Error("POLAR_ACCESS_TOKEN is required");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const polar = createPolar({
|
|
35
|
+
accessToken,
|
|
36
|
+
environment: "sandbox",
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const customerState = await polar.customers.getStateExternal("usr_123");
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Create the client once for a long-running server and reuse it. The TypeScript client uses `fetch` and does not expose a connection-closing lifecycle.
|
|
43
|
+
|
|
44
|
+
For tree-shakable individual functions, create a core client and bind only the operations the application uses:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { createPolarCore } from "@polar-sh/sdk/2026-04";
|
|
48
|
+
import { getStateExternalCustomers } from "@polar-sh/sdk/2026-04/services/customers";
|
|
49
|
+
|
|
50
|
+
const polarCore = createPolarCore({ accessToken });
|
|
51
|
+
const getCustomerState = getStateExternalCustomers(polarCore);
|
|
52
|
+
const customerState = await getCustomerState("usr_123");
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Follow generated argument and output conventions
|
|
56
|
+
|
|
57
|
+
- Use camelCase service names and methods such as `benefitGrants`, `getStateExternal`, and `iterList`.
|
|
58
|
+
- Keep request, query, and response field names in API-style `snake_case`, such as `external_customer_id`.
|
|
59
|
+
- Pass path parameters positionally.
|
|
60
|
+
- Pass query parameters as an optional object after path parameters.
|
|
61
|
+
- Pass request bodies as a single typed object.
|
|
62
|
+
- Treat responses as typed plain JavaScript objects.
|
|
63
|
+
- Expect UUID and date-time OpenAPI fields to remain strings.
|
|
64
|
+
- Consume paginated `iter*` methods with `for await`; each additional page performs another API request.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
for await (const customer of polar.customers.iterList({
|
|
68
|
+
sorting: ["email"],
|
|
69
|
+
})) {
|
|
70
|
+
console.log(customer.id);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Correlate customers with stable external IDs
|
|
75
|
+
|
|
76
|
+
Use the application's immutable user or organization identifier as the Polar customer `external_id`. It is unique within a Polar organization. It may be assigned later when initially unset, but it cannot be changed or removed once set.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
const customer = await polar.customers.create({
|
|
80
|
+
external_id: "usr_123",
|
|
81
|
+
email: "user@example.com",
|
|
82
|
+
});
|
|
83
|
+
const customerState = await polar.customers.getStateExternal("usr_123");
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Handle concurrent creation safely. A preflight lookup followed by creation is not atomic; if creation reports an existing email or external ID, fetch and reconcile the existing customer rather than creating a second mapping.
|
|
87
|
+
|
|
88
|
+
## Create checkout and customer portal sessions
|
|
89
|
+
|
|
90
|
+
Create checkout sessions on the server and send only the returned URL to the browser. Prefer `external_customer_id` so a completed checkout is reconciled with the application's customer.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const productId = process.env.POLAR_PRODUCT_ID;
|
|
94
|
+
if (!productId) {
|
|
95
|
+
throw new Error("POLAR_PRODUCT_ID is required");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const checkout = await polar.checkouts.create({
|
|
99
|
+
products: [productId],
|
|
100
|
+
external_customer_id: "usr_123",
|
|
101
|
+
success_url: "https://example.com/billing/success?checkout_id={CHECKOUT_ID}",
|
|
102
|
+
return_url: "https://example.com/settings/billing",
|
|
103
|
+
});
|
|
104
|
+
redirect(checkout.url);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Do not treat the success redirect as proof of payment or entitlement. Confirm access through customer state or a verified webhook.
|
|
108
|
+
|
|
109
|
+
Create short-lived customer portal sessions only for an authenticated customer:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const session = await polar.customerSessions.create({
|
|
113
|
+
external_customer_id: "usr_123",
|
|
114
|
+
return_url: "https://example.com/settings/billing",
|
|
115
|
+
});
|
|
116
|
+
redirect(session.customer_portal_url);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Validate return URLs and never let an untrusted caller choose another customer's external ID.
|
|
120
|
+
|
|
121
|
+
## Reconcile feature access from customer state
|
|
122
|
+
|
|
123
|
+
Prefer a `feature_flag` benefit attached to the relevant products over inferring access from subscription statuses. Reconcile both access grants and access revocations.
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const benefitId = process.env.POLAR_BENEFIT_ID;
|
|
127
|
+
if (!benefitId) {
|
|
128
|
+
throw new Error("POLAR_BENEFIT_ID is required");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const customerState = await polar.customers.getStateExternal("usr_123");
|
|
132
|
+
const enabled = customerState.granted_benefits.some(
|
|
133
|
+
(grant) => grant.benefit_id === benefitId,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
await setFeatureAccess({
|
|
137
|
+
customerExternalId: "usr_123",
|
|
138
|
+
enabled,
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Make `setFeatureAccess` an idempotent application write. A missing benefit must disable access, not merely skip a grant operation.
|
|
143
|
+
|
|
144
|
+
For webhook-driven synchronization, use benefit-grant events for an incremental local grant ledger or `customer.state_changed` for complete snapshot reconciliation. Read [Webhook processing](references/webhooks.md) before implementing or modifying a receiver.
|
|
145
|
+
|
|
146
|
+
## Ingest metered usage events
|
|
147
|
+
|
|
148
|
+
Read [Usage-event ingestion](references/usage-events.md) before implementing metered billing. In particular:
|
|
149
|
+
|
|
150
|
+
- Match the event name and metadata to the configured meter.
|
|
151
|
+
- Associate the event with exactly one `customer_id` or `external_customer_id`.
|
|
152
|
+
- Assign every logical event a stable, unique `external_id`.
|
|
153
|
+
- Reuse the same `external_id` when retrying so Polar can deduplicate the event.
|
|
154
|
+
- Inspect both `inserted` and `duplicates` in the ingestion result.
|
|
155
|
+
- Enforce usage limits in the application; ingestion does not reject an action because a customer has exhausted a balance.
|
|
156
|
+
|
|
157
|
+
## Handle API failures intentionally
|
|
158
|
+
|
|
159
|
+
Handle generated endpoint-specific errors before broad SDK errors. Import base errors from `@polar-sh/sdk` and version-specific endpoint errors from the selected API version's `errors` namespace.
|
|
160
|
+
|
|
161
|
+
- `PolarNetworkError`: retry only when the operation is safe or carries a stable deduplication identifier.
|
|
162
|
+
- `PolarRateLimitError`: apply bounded backoff and honor `retryAfter` when present.
|
|
163
|
+
- `PolarServerError`: retry selected 5xx failures with bounded exponential backoff and jitter.
|
|
164
|
+
- `PolarClientError`: do not blindly retry authentication, authorization, validation, or not-found failures.
|
|
165
|
+
|
|
166
|
+
Never create a new usage-event `external_id` during a retry. For other mutations, use an endpoint-supported idempotency mechanism or application-level reconciliation before retrying.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Migrate from the `<1.0.0` SDK
|
|
2
|
+
|
|
3
|
+
Use these instructions when an application depends on `@polar-sh/sdk<1.0.0`, imports the unversioned `Polar` class, or uses camelCase API fields. Migrate one integration boundary at a time and use the installed new SDK's generated signatures as the source of truth.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [Migration workflow](#migration-workflow)
|
|
8
|
+
- [Keep the casing boundary explicit](#keep-the-casing-boundary-explicit)
|
|
9
|
+
- [Replace client construction](#replace-client-construction)
|
|
10
|
+
- [Migrate operation calls](#migrate-operation-calls)
|
|
11
|
+
- [Replace pagination](#replace-pagination)
|
|
12
|
+
- [Migrate errors, retries, and webhooks](#migrate-errors-retries-and-webhooks)
|
|
13
|
+
|
|
14
|
+
## Migration workflow
|
|
15
|
+
|
|
16
|
+
1. Inventory every SDK import, client construction, operation call, request object, response access, error handler, webhook receiver, retry configuration, and pagination loop.
|
|
17
|
+
2. Select the new API version once and use its versioned imports consistently.
|
|
18
|
+
3. Replace client setup before migrating operation calls.
|
|
19
|
+
4. Convert API request and response fields from camelCase to `snake_case`.
|
|
20
|
+
5. Replace pagination, error handling, and webhook validation explicitly.
|
|
21
|
+
6. Remove all remaining unversioned SDK imports and old retry or pagination helpers.
|
|
22
|
+
|
|
23
|
+
Do not run old and new clients against the same mutation as a comparison strategy. That can create duplicate customers, checkouts, orders, subscriptions, or usage events.
|
|
24
|
+
|
|
25
|
+
## Keep the casing boundary explicit
|
|
26
|
+
|
|
27
|
+
The old SDK automatically converted API request and response fields to camelCase. The new SDK does not perform field-name conversion:
|
|
28
|
+
|
|
29
|
+
- Keep service and operation names in camelCase, such as `customerSessions`, `getStateExternal`, and `iterList`.
|
|
30
|
+
- Write request, query, and response fields in API-style `snake_case`.
|
|
31
|
+
- Do not recursively rename arbitrary `metadata`, custom-field data, or other user-defined object keys.
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// Before: @polar-sh/sdk<1.0.0
|
|
35
|
+
const checkout = await polar.checkouts.create({
|
|
36
|
+
products: [productId],
|
|
37
|
+
externalCustomerId: "usr_123",
|
|
38
|
+
successUrl,
|
|
39
|
+
});
|
|
40
|
+
console.log(checkout.customerId);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// After
|
|
45
|
+
const checkout = await polar.checkouts.create({
|
|
46
|
+
products: [productId],
|
|
47
|
+
external_customer_id: "usr_123",
|
|
48
|
+
success_url: successUrl,
|
|
49
|
+
});
|
|
50
|
+
console.log(checkout.customer_id);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Search for camelCase field access throughout application code, persistence mapping, webhook handling, and mocks. Type checking catches many request mistakes, but response values passed through `any`, JSON fixtures, and property-string access need deliberate review.
|
|
54
|
+
|
|
55
|
+
## Replace client construction
|
|
56
|
+
|
|
57
|
+
Replace the old class with the versioned `createPolar` factory:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// Before: @polar-sh/sdk<1.0.0
|
|
61
|
+
import { Polar } from "@polar-sh/sdk";
|
|
62
|
+
|
|
63
|
+
const polar = new Polar({ accessToken });
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// After
|
|
68
|
+
import { createPolar } from "@polar-sh/sdk/2026-04";
|
|
69
|
+
|
|
70
|
+
const polar = createPolar({ accessToken });
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Use `createPolarCore` and individual functions from the selected version's `services` paths only when tree-shaking matters. Do not carry forward constructor options that are absent from `PolarOptions`.
|
|
74
|
+
|
|
75
|
+
## Migrate operation calls
|
|
76
|
+
|
|
77
|
+
- Expect normalized operation names such as `organizations.list` instead of `organizations.listOrganizations`.
|
|
78
|
+
- Pass path parameters positionally.
|
|
79
|
+
- Pass query parameters as an optional object after path parameters.
|
|
80
|
+
- Pass request bodies as one typed object.
|
|
81
|
+
- Inspect every selected service signature rather than assuming a mechanical rename.
|
|
82
|
+
|
|
83
|
+
The old standalone functions and the new service factories have different names and binding conventions. Rebuild tree-shakable imports from the selected version's `services` exports instead of renaming the old functions.
|
|
84
|
+
|
|
85
|
+
## Replace pagination
|
|
86
|
+
|
|
87
|
+
The old SDK made a list response async iterable and yielded pages. The new SDK exposes `iter*` methods that yield individual resources.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// Before: @polar-sh/sdk<1.0.0
|
|
91
|
+
const pages = await polar.organizations.listOrganizations({});
|
|
92
|
+
for await (const page of pages) {
|
|
93
|
+
processPage(page);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// After
|
|
99
|
+
for await (const organization of polar.organizations.iterList()) {
|
|
100
|
+
processOrganization(organization);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Call `list` directly when the application needs page metadata. Do not wrap an `iter*` call in an extra `await` before iterating it.
|
|
105
|
+
|
|
106
|
+
## Migrate errors, retries, and webhooks
|
|
107
|
+
|
|
108
|
+
- Replace old SDK error classes with `PolarClientError`, `PolarNetworkError`, `PolarRateLimitError`, and `PolarServerError`, then handle version-specific endpoint errors before those broad classes.
|
|
109
|
+
- Remove the old `retryConfig`; apply bounded application-level retries and honor `retryAfter` for rate limits. Retry mutations only when the endpoint is idempotent or the application can reconcile the result.
|
|
110
|
+
- Replace imports from `@polar-sh/sdk/webhooks` with the versioned `webhooks` namespace.
|
|
111
|
+
- Await `webhooks.validateEvent`, passing the unmodified request body, request headers, and signing secret.
|
|
112
|
+
- Rename old webhook error checks to `webhooks.PolarWebhookVerificationError`, `webhooks.PolarWebhookUnknownTypeError`, and `webhooks.PolarWebhookError`.
|
|
113
|
+
|
|
114
|
+
Preserve the behavior of acknowledging unknown but correctly signed event types. Read [Webhook processing](webhooks.md) when changing receiver behavior, deduplication, or benefit synchronization. Read [Usage-event ingestion](usage-events.md) before migrating metered event delivery or retry logic.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Usage-event ingestion
|
|
2
|
+
|
|
3
|
+
Use these instructions whenever implementing metered usage or sending events used by a Polar meter.
|
|
4
|
+
|
|
5
|
+
## Ingest events
|
|
6
|
+
|
|
7
|
+
Send events from trusted server-side code. Use the application's customer ID as `external_customer_id` when the Polar customer has the same `external_id`.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { createPolar } from "@polar-sh/sdk/2026-04";
|
|
11
|
+
|
|
12
|
+
const accessToken = process.env.POLAR_ACCESS_TOKEN;
|
|
13
|
+
if (!accessToken) {
|
|
14
|
+
throw new Error("POLAR_ACCESS_TOKEN is required");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const polar = createPolar({ accessToken });
|
|
18
|
+
const result = await polar.events.ingest({
|
|
19
|
+
events: [
|
|
20
|
+
{
|
|
21
|
+
name: "ai_generation",
|
|
22
|
+
external_customer_id: "usr_123",
|
|
23
|
+
external_id: "usage_01K0E6Y8W4H7D3F2A1B9C5M6N7",
|
|
24
|
+
metadata: {
|
|
25
|
+
input_tokens: 1200,
|
|
26
|
+
output_tokens: 350,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log({
|
|
33
|
+
inserted: result.inserted,
|
|
34
|
+
duplicates: result.duplicates ?? 0,
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use an event name and metadata keys that exactly match the configured meter filter and aggregation. Preserve numeric values as numbers rather than formatted strings.
|
|
39
|
+
|
|
40
|
+
## Make ingestion retry-safe
|
|
41
|
+
|
|
42
|
+
Assign each logical event a stable, globally unique `external_id` when the application records the usage. Persist that identifier before sending the event.
|
|
43
|
+
|
|
44
|
+
When a request times out or is retried:
|
|
45
|
+
|
|
46
|
+
- Send the same logical event with the same `external_id`.
|
|
47
|
+
- Do not generate a replacement ID.
|
|
48
|
+
- Treat the `duplicates` count as successful deduplication, not as newly billed usage.
|
|
49
|
+
- Reconcile unexpected partial batch results before discarding the local delivery record.
|
|
50
|
+
|
|
51
|
+
Batch events when appropriate, but keep every event independently identifiable. Associate each event with exactly one `customer_id` or `external_customer_id`.
|
|
52
|
+
|
|
53
|
+
## Understand timestamps and balances
|
|
54
|
+
|
|
55
|
+
Events are immutable after ingestion. Set `timestamp` to an ISO 8601 string when preserving the time the usage occurred.
|
|
56
|
+
|
|
57
|
+
Polar attributes an event to a billing period according to when Polar receives it, not only its supplied timestamp. Replaying an old event does not revise an already-closed invoice.
|
|
58
|
+
|
|
59
|
+
Event ingestion records usage regardless of the customer's remaining meter balance. Query or cache customer state and enforce hard usage limits in the application before performing the metered action.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Webhook processing
|
|
2
|
+
|
|
3
|
+
Use these instructions whenever implementing or modifying a Polar webhook receiver.
|
|
4
|
+
|
|
5
|
+
## Receiver requirements
|
|
6
|
+
|
|
7
|
+
1. Run the webhook raw-body parser before any middleware that consumes or transforms the request body.
|
|
8
|
+
2. Read the unmodified request body as a string or `Uint8Array`.
|
|
9
|
+
3. Pass the raw body, request headers, and server-side signing secret to the versioned `webhooks.validateEvent`.
|
|
10
|
+
4. Reject invalid signatures before parsing or processing the payload.
|
|
11
|
+
5. Use the `webhook-id` header as a durable deduplication key.
|
|
12
|
+
6. Persist or enqueue the verified event and respond with a 2xx within two seconds.
|
|
13
|
+
7. Make the worker idempotent because failed deliveries can be retried.
|
|
14
|
+
8. Choose incremental benefit-grant tracking or complete customer-state reconciliation deliberately.
|
|
15
|
+
|
|
16
|
+
## Express receiver
|
|
17
|
+
|
|
18
|
+
Use `express.raw` on the webhook route and register the route before `app.use(express.json())` or any other body parser. Once an earlier parser consumes the stream, `express.raw` cannot recover the signed bytes. If route ordering cannot guarantee this, retain the original `Buffer` through the earlier parser's `verify` option and validate that buffer; never reserialize `request.body`.
|
|
19
|
+
|
|
20
|
+
Replace `enqueueVerifiedWebhook` with a durable, idempotent application operation. Enforce uniqueness on `webhookId`; an in-memory set is not sufficient.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import express from "express";
|
|
24
|
+
import { webhooks } from "@polar-sh/sdk/2026-04";
|
|
25
|
+
|
|
26
|
+
const webhookSecret = process.env.POLAR_WEBHOOK_SECRET;
|
|
27
|
+
if (!webhookSecret) {
|
|
28
|
+
throw new Error("POLAR_WEBHOOK_SECRET is required");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const app = express();
|
|
32
|
+
|
|
33
|
+
// Register this route before app.use(express.json()).
|
|
34
|
+
app.post(
|
|
35
|
+
"/webhooks/polar",
|
|
36
|
+
express.raw({ type: "application/json" }),
|
|
37
|
+
async (request, response, next) => {
|
|
38
|
+
const headers = {
|
|
39
|
+
"webhook-id": request.header("webhook-id") ?? "",
|
|
40
|
+
"webhook-timestamp": request.header("webhook-timestamp") ?? "",
|
|
41
|
+
"webhook-signature": request.header("webhook-signature") ?? "",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const event = await webhooks.validateEvent(
|
|
46
|
+
request.body,
|
|
47
|
+
headers,
|
|
48
|
+
webhookSecret,
|
|
49
|
+
);
|
|
50
|
+
await enqueueVerifiedWebhook({
|
|
51
|
+
webhookId: headers["webhook-id"],
|
|
52
|
+
event,
|
|
53
|
+
});
|
|
54
|
+
response.sendStatus(202);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error instanceof webhooks.PolarWebhookVerificationError) {
|
|
57
|
+
response.status(403).json({ error: "Invalid webhook signature" });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (error instanceof webhooks.PolarWebhookUnknownTypeError) {
|
|
61
|
+
console.warn("Ignoring unsupported signed Polar webhook", {
|
|
62
|
+
eventType: error.eventType,
|
|
63
|
+
});
|
|
64
|
+
response.sendStatus(202);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (error instanceof webhooks.PolarWebhookError) {
|
|
68
|
+
response.status(400).json({ error: "Invalid webhook payload" });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
next(error);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
app.use(express.json());
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Acknowledging an unknown but correctly signed event avoids repeated delivery failures when the selected SDK version does not recognize a newer event type. If the application requires strict processing of every subscribed event, alert on the unknown type and upgrade deliberately.
|
|
80
|
+
|
|
81
|
+
Pass unexpected errors to `next` so Express 4 and Express 5 error middleware can produce a 5xx response. Throwing from an async handler is forwarded automatically only by Express 5.
|
|
82
|
+
|
|
83
|
+
## Track benefit grants
|
|
84
|
+
|
|
85
|
+
Use `benefit_grant.created`, `benefit_grant.updated`, and `benefit_grant.revoked` when the application maintains a local grant ledger. These events carry the grant ID, benefit ID, customer, optional member, source subscription or order, grant state, and benefit-specific properties.
|
|
86
|
+
|
|
87
|
+
- Upsert the grant by `event.data.id` on `benefit_grant.created` and `benefit_grant.updated`.
|
|
88
|
+
- Mark that grant revoked on `benefit_grant.revoked`.
|
|
89
|
+
- Correlate through `event.data.customer.external_id` when set, or a stored Polar customer ID otherwise; do not use email as the durable key.
|
|
90
|
+
- Derive access from whether any grant with `is_granted` and without `is_revoked` remains for the customer, member, and benefit.
|
|
91
|
+
|
|
92
|
+
Do not disable a feature merely because one grant was revoked. The same benefit can be granted through more than one subscription or order, so another active grant may still authorize access.
|
|
93
|
+
|
|
94
|
+
Use `customer.state_changed` instead when the application wants a complete current snapshot without maintaining individual grants. Compute the desired access state from `event.data.granted_benefits`, then idempotently write both enabled and disabled states. Snapshot reconciliation is also useful for repairing an incremental ledger after missed or failed processing.
|
|
95
|
+
|
|
96
|
+
Do not perform slow provisioning, network calls, or other substantial work in the receiver. Perform it in the durable worker after the verified event has been accepted.
|