├── packages ├── polar-nextjs │ ├── .gitattributes │ ├── example │ │ ├── .env.template │ │ ├── postcss.config.mjs │ │ ├── public │ │ │ ├── vercel.svg │ │ │ ├── window.svg │ │ │ ├── file.svg │ │ │ ├── globe.svg │ │ │ └── next.svg │ │ ├── eslint.config.mjs │ │ ├── tsconfig.json │ │ ├── .gitignore │ │ ├── package.json │ │ └── README.md │ ├── .gitignore │ ├── .npmrc │ ├── src │ │ ├── index.ts │ │ ├── webhooks │ │ │ └── webhooks.ts │ │ ├── customerPortal │ │ │ └── customerPortal.ts │ │ └── checkout │ │ │ └── checkout.ts │ ├── vitest.config.ts │ ├── .editorconfig │ ├── tsconfig.json │ └── package.json ├── polar-supabase │ ├── .gitattributes │ ├── .gitignore │ ├── .vscode │ │ ├── extensions.json │ │ └── settings.json │ ├── .npmrc │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ ├── customerPortal │ │ │ └── customerPortal.ts │ │ ├── webhooks │ │ │ └── webhooks.ts │ │ └── checkout │ │ │ └── checkout.ts │ ├── .editorconfig │ ├── deno.json │ ├── package.json │ ├── CHANGELOG.md │ └── deno.lock ├── polar-astro │ ├── env.d.ts │ ├── .npmrc │ ├── src │ │ ├── index.ts │ │ ├── customerPortal │ │ │ ├── customerPortal.ts │ │ │ └── customerPortal.test.ts │ │ ├── webhooks │ │ │ ├── webhooks.ts │ │ │ └── webhooks.test.ts │ │ └── checkout │ │ │ ├── checkout.test.ts │ │ │ └── checkout.ts │ ├── tsconfig.json │ ├── package.json │ ├── .gitignore │ └── README.md ├── polar-nuxt │ ├── playground │ │ ├── tsconfig.json │ │ ├── server │ │ │ ├── tsconfig.json │ │ │ └── routes │ │ │ │ └── api │ │ │ │ ├── checkout.get.ts │ │ │ │ ├── webhook │ │ │ │ └── polar.post.ts │ │ │ │ └── portal.get.ts │ │ ├── app.vue │ │ ├── .env.sample │ │ ├── package.json │ │ └── nuxt.config.ts │ ├── .vscode │ │ └── settings.json │ ├── src │ │ ├── runtime │ │ │ └── server │ │ │ │ ├── tsconfig.json │ │ │ │ ├── index.ts │ │ │ │ ├── customerPortalHandler.ts │ │ │ │ └── webhookHandler.ts │ │ └── module.ts │ ├── test │ │ ├── fixtures │ │ │ └── basic │ │ │ │ ├── package.json │ │ │ │ ├── app.vue │ │ │ │ └── nuxt.config.ts │ │ └── basic.test.ts │ ├── tsconfig.json │ ├── .npmrc │ ├── eslint.config.mjs │ ├── .gitignore │ └── package.json ├── eslint-config │ ├── README.md │ ├── package.json │ ├── base.js │ ├── react-internal.js │ └── next.js ├── adapter-utils │ ├── src │ │ ├── index.ts │ │ └── entitlement │ │ │ └── entitlement.ts │ ├── .npmrc │ ├── tsconfig.json │ └── package.json ├── polar-tanstack-start │ ├── .gitignore │ ├── .npmrc │ ├── src │ │ ├── index.ts │ │ ├── customerPortal │ │ │ └── customerPortal.ts │ │ ├── webhooks │ │ │ └── webhooks.ts │ │ └── checkout │ │ │ └── checkout.ts │ ├── tsconfig.json │ ├── package.json │ └── CHANGELOG.md ├── polar-betterauth │ ├── example │ │ ├── .env.template │ │ ├── postcss.config.mjs │ │ ├── src │ │ │ ├── app │ │ │ │ ├── favicon.ico │ │ │ │ ├── api │ │ │ │ │ └── auth │ │ │ │ │ │ └── [...all] │ │ │ │ │ │ └── route.ts │ │ │ │ ├── layout.tsx │ │ │ │ ├── globals.css │ │ │ │ └── page.tsx │ │ │ ├── lib │ │ │ │ ├── polar.ts │ │ │ │ ├── auth-client.ts │ │ │ │ └── auth.ts │ │ │ └── components │ │ │ │ ├── Me.tsx │ │ │ │ ├── Login.tsx │ │ │ │ └── Register.tsx │ │ ├── public │ │ │ ├── vercel.svg │ │ │ ├── window.svg │ │ │ ├── file.svg │ │ │ ├── globe.svg │ │ │ └── next.svg │ │ ├── eslint.config.mjs │ │ ├── .gitignore │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── better-auth_migrations │ │ │ └── 2025-03-03T15-33-48.743Z.sql │ │ └── README.md │ ├── .npmrc │ ├── tsconfig.json │ ├── vitest.config.ts │ ├── src │ │ ├── __tests__ │ │ │ ├── utils │ │ │ │ └── helpers.ts │ │ │ └── client.test.ts │ │ ├── index.ts │ │ ├── client.ts │ │ ├── types.ts │ │ └── plugins │ │ │ └── usage.ts │ ├── .gitignore │ └── package.json ├── polar-deno │ ├── CHANGELOG.md │ ├── main.importmap.json │ ├── mocks │ │ ├── webhook.ts │ │ └── sdk.ts │ └── deno.json ├── polar-elysia │ ├── .npmrc │ ├── src │ │ ├── index.ts │ │ ├── customerPortal │ │ │ ├── customerPortal.ts │ │ │ └── customerPortal.test.ts │ │ ├── webhooks │ │ │ ├── webhooks.ts │ │ │ └── webhooks.test.ts │ │ └── checkout │ │ │ ├── checkout.test.ts │ │ │ └── checkout.ts │ ├── types.d.ts │ ├── tsconfig.json │ ├── package.json │ └── .gitignore ├── polar-hono │ ├── .npmrc │ ├── types.d.ts │ ├── tsconfig.json │ ├── package.json │ └── .gitignore ├── polar-remix │ ├── .npmrc │ ├── src │ │ ├── index.ts │ │ ├── types.ts │ │ ├── customerPortal │ │ │ ├── customerPortal.ts │ │ │ └── customerPortal.test.ts │ │ ├── webhooks │ │ │ ├── webhooks.ts │ │ │ └── webhooks.test.ts │ │ └── checkout │ │ │ ├── checkout.test.ts │ │ │ └── checkout.ts │ ├── tsconfig.json │ ├── package.json │ ├── .gitignore │ └── README.md ├── polar-express │ ├── .npmrc │ ├── src │ │ ├── index.ts │ │ ├── customerPortal │ │ │ ├── customerPortal.ts │ │ │ └── customerPortal.test.ts │ │ ├── webhooks │ │ │ ├── webhooks.ts │ │ │ └── webhooks.test.ts │ │ └── checkout │ │ │ ├── checkout.test.ts │ │ │ └── checkout.ts │ ├── types.d.ts │ ├── tsconfig.json │ ├── package.json │ └── .gitignore ├── polar-fastify │ ├── .npmrc │ ├── src │ │ ├── index.ts │ │ ├── customerPortal │ │ │ ├── customerPortal.ts │ │ │ └── customerPortal.test.ts │ │ ├── webhooks │ │ │ ├── webhooks.ts │ │ │ └── webhooks.test.ts │ │ └── checkout │ │ │ ├── checkout.test.ts │ │ │ └── checkout.ts │ ├── types.d.ts │ ├── tsconfig.json │ ├── package.json │ └── .gitignore ├── polar-sveltekit │ ├── .npmrc │ ├── vite.config.ts │ ├── svelte.config.js │ ├── tsconfig.json │ ├── package.json │ └── .gitignore └── typescript-config │ ├── react-library.json │ ├── package.json │ ├── nextjs.json │ └── base.json ├── pnpm-workspace.yaml ├── .github ├── ISSUE_TEMPLATE │ └── config.yml └── workflows │ ├── ci.yml │ └── publish.yml ├── .changeset ├── config.json └── README.md ├── .vscode └── settings.json ├── .gitignore ├── package.json ├── turbo.json ├── biome.json └── README.md /packages/polar-nextjs/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /packages/polar-supabase/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /packages/polar-astro/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/polar-supabase/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .turbo 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "apps/*" 3 | - "packages/*" 4 | -------------------------------------------------------------------------------- /packages/polar-nextjs/example/.env.template: -------------------------------------------------------------------------------- 1 | POLAR_ACCESS_TOKEN= 2 | POLAR_WEBHOOK_SECRET= -------------------------------------------------------------------------------- /packages/polar-nuxt/playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/polar-nuxt/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.experimental.useFlatConfig": true 3 | } 4 | -------------------------------------------------------------------------------- /packages/polar-nuxt/playground/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/polar-supabase/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["denoland.vscode-deno"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/eslint-config/README.md: -------------------------------------------------------------------------------- 1 | # `@turbo/eslint-config` 2 | 3 | Collection of internal eslint configurations. 4 | -------------------------------------------------------------------------------- /packages/polar-nuxt/src/runtime/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/adapter-utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./webhooks/webhooks"; 2 | export * from "./entitlement/entitlement"; 3 | -------------------------------------------------------------------------------- /packages/polar-nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | 4 | .env* 5 | 6 | app 7 | polar.ts 8 | 9 | .idea/ 10 | -------------------------------------------------------------------------------- /packages/polar-nuxt/playground/app.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /packages/polar-nuxt/test/fixtures/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "basic", 4 | "type": "module" 5 | } 6 | -------------------------------------------------------------------------------- /packages/polar-tanstack-start/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | 4 | .env* 5 | 6 | app 7 | polar.ts 8 | 9 | .idea/ 10 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/.env.template: -------------------------------------------------------------------------------- 1 | BETTER_AUTH_SECRET= 2 | BETTER_AUTH_URL= 3 | POLAR_ACCESS_TOKEN= 4 | POLAR_WEBHOOK_SECRET= -------------------------------------------------------------------------------- /packages/polar-nuxt/test/fixtures/basic/app.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /packages/polar-deno/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @polar-sh/deno 2 | 3 | ## 0.1.1 4 | 5 | ### Patch Changes 6 | 7 | - First release of the Deno adapter. 8 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /packages/polar-nextjs/example/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /packages/polar-astro/.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true 4 | engine-strict=true 5 | -------------------------------------------------------------------------------- /packages/polar-elysia/.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true 4 | engine-strict=true 5 | -------------------------------------------------------------------------------- /packages/polar-hono/.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true 4 | engine-strict=true 5 | -------------------------------------------------------------------------------- /packages/polar-nextjs/.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true 4 | engine-strict=true 5 | -------------------------------------------------------------------------------- /packages/polar-remix/.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true 4 | engine-strict=true 5 | -------------------------------------------------------------------------------- /packages/adapter-utils/.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true 4 | engine-strict=true 5 | -------------------------------------------------------------------------------- /packages/polar-astro/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./checkout/checkout"; 2 | export * from "./customerPortal/customerPortal"; 3 | export * from "./webhooks/webhooks"; 4 | -------------------------------------------------------------------------------- /packages/polar-betterauth/.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true 4 | engine-strict=true 5 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polarsource/polar-adapters/HEAD/packages/polar-betterauth/example/src/app/favicon.ico -------------------------------------------------------------------------------- /packages/polar-express/.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true 4 | engine-strict=true 5 | -------------------------------------------------------------------------------- /packages/polar-fastify/.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true 4 | engine-strict=true 5 | -------------------------------------------------------------------------------- /packages/polar-remix/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./checkout/checkout"; 2 | export * from "./customerPortal/customerPortal"; 3 | export * from "./webhooks/webhooks"; 4 | -------------------------------------------------------------------------------- /packages/polar-supabase/.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true 4 | engine-strict=true 5 | -------------------------------------------------------------------------------- /packages/polar-sveltekit/.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true 4 | engine-strict=true 5 | -------------------------------------------------------------------------------- /packages/polar-elysia/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./checkout/checkout"; 2 | export * from "./customerPortal/customerPortal"; 3 | export * from "./webhooks/webhooks"; 4 | -------------------------------------------------------------------------------- /packages/polar-express/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./checkout/checkout"; 2 | export * from "./customerPortal/customerPortal"; 3 | export * from "./webhooks/webhooks"; 4 | -------------------------------------------------------------------------------- /packages/polar-fastify/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./checkout/checkout"; 2 | export * from "./customerPortal/customerPortal"; 3 | export * from "./webhooks/webhooks"; 4 | -------------------------------------------------------------------------------- /packages/polar-hono/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@polar-sh/hono" { 2 | interface Context { 3 | env: { 4 | POLAR_ACCESS_TOKEN: string; 5 | }; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/polar-nextjs/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./checkout/checkout"; 2 | export * from "./customerPortal/customerPortal"; 3 | export * from "./webhooks/webhooks"; 4 | -------------------------------------------------------------------------------- /packages/polar-nuxt/src/runtime/server/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./checkoutHandler"; 2 | export * from "./customerPortalHandler"; 3 | export * from "./webhookHandler"; 4 | -------------------------------------------------------------------------------- /packages/polar-supabase/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sindresorhus/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/polar-tanstack-start/.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true 4 | engine-strict=true 5 | -------------------------------------------------------------------------------- /packages/polar-elysia/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@polar-sh/hono" { 2 | interface Context { 3 | env: { 4 | POLAR_ACCESS_TOKEN: string; 5 | }; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/polar-express/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@polar-sh/hono" { 2 | interface Context { 3 | env: { 4 | POLAR_ACCESS_TOKEN: string; 5 | }; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/polar-fastify/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@polar-sh/hono" { 2 | interface Context { 3 | env: { 4 | POLAR_ACCESS_TOKEN: string; 5 | }; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/polar-nextjs/example/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/polar-nuxt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json", 3 | "exclude": [ 4 | "dist", 5 | "node_modules", 6 | "playground", 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/polar-tanstack-start/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./checkout/checkout"; 2 | export * from "./customerPortal/customerPortal"; 3 | export * from "./webhooks/webhooks"; 4 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/polar-deno/main.importmap.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "npm:@polar-sh/sdk": "./mocks/sdk.ts", 4 | "npm:@polar-sh/sdk/webhooks": "./mocks/webhook.ts" 5 | } 6 | } -------------------------------------------------------------------------------- /packages/polar-nuxt/test/fixtures/basic/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import MyModule from "../../../src/module"; 2 | 3 | export default defineNuxtConfig({ 4 | modules: [MyModule], 5 | }); 6 | -------------------------------------------------------------------------------- /packages/polar-supabase/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./checkout/checkout.js"; 2 | export * from "./customerPortal/customerPortal.js"; 3 | export * from "./webhooks/webhooks.js"; 4 | -------------------------------------------------------------------------------- /packages/polar-nuxt/playground/.env.sample: -------------------------------------------------------------------------------- 1 | NUXT_PRIVATE_POLAR_ACCESS_TOKEN= 2 | NUXT_PRIVATE_POLAR_WEBHOOK_SECRET= 3 | NUXT_PRIVATE_POLAR_CHECKOUT_SUCCESS_URL= 4 | NUXT_PRIVATE_POLAR_SERVER= -------------------------------------------------------------------------------- /packages/polar-nextjs/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | environment: 'node', 6 | globals: true, 7 | }, 8 | }) -------------------------------------------------------------------------------- /packages/polar-sveltekit/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from "@sveltejs/kit/vite"; 2 | import { defineConfig } from "vite"; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | }); 7 | -------------------------------------------------------------------------------- /packages/typescript-config/react-library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "./base.json", 4 | "compilerOptions": { 5 | "jsx": "react-jsx" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/polar-nuxt/.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true 4 | engine-strict=true 5 | shamefully-hoist=true 6 | strict-peer-dependencies=false 7 | -------------------------------------------------------------------------------- /packages/polar-supabase/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/src/lib/polar.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | 3 | export const polarSDK = new Polar({ 4 | accessToken: process.env["POLAR_ACCESS_TOKEN"] as string, 5 | server: "sandbox", 6 | }); 7 | -------------------------------------------------------------------------------- /packages/typescript-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@repo/typescript-config", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "publishConfig": { 7 | "access": "public" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/src/app/api/auth/[...all]/route.ts: -------------------------------------------------------------------------------- 1 | import { auth } from "@/lib/auth"; // path to your auth file 2 | import { toNextJsHandler } from "better-auth/next-js"; 3 | 4 | export const { POST, GET } = toNextJsHandler(auth); 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Bug reports & feature requests 4 | url: https://github.com/polarsource/polar/issues/new/choose 5 | about: Please open all issues in our main issue tracker. 6 | -------------------------------------------------------------------------------- /packages/polar-nextjs/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /packages/polar-nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sindresorhus/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "skipLibCheck": true, 6 | "declaration": true, 7 | "module": "esnext", 8 | "moduleResolution": "bundler" 9 | }, 10 | "include": ["src"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/polar-tanstack-start/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sindresorhus/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "skipLibCheck": true, 6 | "declaration": true, 7 | "module": "esnext", 8 | "moduleResolution": "bundler" 9 | }, 10 | "include": ["src"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/polar-nuxt/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "polar-nuxt-playground", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "nuxi dev", 7 | "build": "nuxi build", 8 | "generate": "nuxi generate" 9 | }, 10 | "dependencies": { 11 | "nuxt": "^3.15.4" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/polar-deno/mocks/webhook.ts: -------------------------------------------------------------------------------- 1 | import sinon from "https://cdn.skypack.dev/sinon@11.1.2?dts"; 2 | 3 | export const WebhookVerificationError = sinon.stub(); 4 | export const validateEvent = function ( 5 | v: string, 6 | _headers: Record, 7 | _secret: string, 8 | ) { 9 | return JSON.parse(v); 10 | }; 11 | -------------------------------------------------------------------------------- /packages/adapter-utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sindresorhus/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "skipLibCheck": true, 6 | "declaration": true, 7 | "module": "esnext", 8 | "moduleResolution": "bundler", 9 | "allowSyntheticDefaultImports": true 10 | }, 11 | "include": ["src"] 12 | } 13 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.5/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /packages/polar-hono/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sindresorhus/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "skipLibCheck": true, 6 | "declaration": true, 7 | "module": "esnext", 8 | "moduleResolution": "bundler", 9 | "typeRoots": ["./types", "./node_modules/@types"] 10 | }, 11 | "include": ["src"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/polar-remix/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sindresorhus/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "skipLibCheck": true, 6 | "declaration": true, 7 | "module": "esnext", 8 | "moduleResolution": "bundler", 9 | "typeRoots": ["./types", "./node_modules/@types"] 10 | }, 11 | "include": ["src"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/polar-betterauth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sindresorhus/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "skipLibCheck": true, 6 | "declaration": true, 7 | "module": "esnext", 8 | "moduleResolution": "bundler", 9 | "typeRoots": ["./types", "./node_modules/@types"] 10 | }, 11 | "include": ["src"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/polar-astro/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sindresorhus/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "skipLibCheck": true, 6 | "declaration": true, 7 | "module": "esnext", 8 | "moduleResolution": "bundler", 9 | "typeRoots": ["./types", "./node_modules/@types"] 10 | }, 11 | "include": ["src", "env.d.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/typescript-config/nextjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "./base.json", 4 | "compilerOptions": { 5 | "plugins": [{ "name": "next" }], 6 | "module": "ESNext", 7 | "moduleResolution": "Bundler", 8 | "allowJs": true, 9 | "jsx": "preserve", 10 | "noEmit": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "biomejs.biome", 3 | "editor.formatOnPaste": false, 4 | "editor.formatOnSave": true, 5 | "editor.codeActionsOnSave": { 6 | "quickfix.biome": "explicit" 7 | }, 8 | "typescript.tsdk": "node_modules/typescript/lib", 9 | "deno.enable": true, 10 | "typescript.tsserver.experimental.enableProjectDiagnostics": false 11 | } 12 | -------------------------------------------------------------------------------- /packages/polar-deno/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polar-sh/deno", 3 | "version": "0.1.3", 4 | "exports": "./main.ts", 5 | "tasks": { 6 | "dev": "deno run --watch main.ts", 7 | "test": "deno test --allow-net --allow-env --allow-import --importmap=main.importmap.json" 8 | }, 9 | "imports": { 10 | "@std/assert": "jsr:@std/assert@1" 11 | }, 12 | "license": "Apache-2.0" 13 | } 14 | -------------------------------------------------------------------------------- /packages/polar-elysia/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sindresorhus/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "skipLibCheck": true, 6 | "declaration": true, 7 | "module": "esnext", 8 | "moduleResolution": "bundler", 9 | "allowSyntheticDefaultImports": true, 10 | "typeRoots": ["./types", "./node_modules/@types"] 11 | }, 12 | "include": ["src"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/polar-express/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sindresorhus/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "skipLibCheck": true, 6 | "declaration": true, 7 | "module": "esnext", 8 | "moduleResolution": "bundler", 9 | "allowSyntheticDefaultImports": true, 10 | "typeRoots": ["./types", "./node_modules/@types"] 11 | }, 12 | "include": ["src"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/polar-fastify/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sindresorhus/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "skipLibCheck": true, 6 | "declaration": true, 7 | "module": "esnext", 8 | "moduleResolution": "bundler", 9 | "allowSyntheticDefaultImports": true, 10 | "typeRoots": ["./types", "./node_modules/@types"] 11 | }, 12 | "include": ["src"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/src/components/Me.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useSession } from "@/lib/auth-client"; 4 | 5 | export const Me = () => { 6 | const { data: session } = useSession(); 7 | 8 | if (!session) { 9 | return
Not authenticated
; 10 | } 11 | 12 | return ( 13 |
14 |

Welcome {session.user.name}

15 |
16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/polar-nuxt/playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | modules: ["../src/module"], 3 | polar: {}, 4 | devtools: { enabled: true }, 5 | compatibilityDate: "2025-02-25", 6 | runtimeConfig: { 7 | private: { 8 | polarAccessToken: "", 9 | polarServer: "", 10 | polarCheckoutSuccessUrl: "", 11 | polarWebhookSecret: "", 12 | }, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /packages/polar-remix/src/types.ts: -------------------------------------------------------------------------------- 1 | type Params = { 2 | readonly [key in Key]: string | undefined; 3 | }; 4 | 5 | type DataFunctionArgs = { 6 | request: Request; 7 | context: unknown; 8 | params: Params; 9 | }; 10 | 11 | export type LoaderFunction = (args: DataFunctionArgs) => Promise; 12 | export type ActionFunction = (args: DataFunctionArgs) => Promise; 13 | -------------------------------------------------------------------------------- /packages/polar-nextjs/example/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/polar-nextjs/example/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/polar-nuxt/playground/server/routes/api/checkout.get.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler((event) => { 2 | const { 3 | private: { polarAccessToken, polarCheckoutSuccessUrl, polarServer }, 4 | } = useRuntimeConfig(); 5 | 6 | const checkoutHandler = Checkout({ 7 | accessToken: polarAccessToken, 8 | successUrl: polarCheckoutSuccessUrl, 9 | server: polarServer as "sandbox" | "production", 10 | }); 11 | 12 | return checkoutHandler(event); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/polar-nuxt/playground/server/routes/api/webhook/polar.post.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler((event) => { 2 | const { 3 | private: { polarWebhookSecret }, 4 | } = useRuntimeConfig(); 5 | 6 | const webhooksHandler = Webhooks({ 7 | webhookSecret: polarWebhookSecret, 8 | onPayload: async () => { 9 | // Handle the payload 10 | // No need to return an acknowledge response 11 | }, 12 | }); 13 | 14 | return webhooksHandler(event); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/src/lib/auth-client.ts: -------------------------------------------------------------------------------- 1 | import { createAuthClient } from "better-auth/react"; 2 | import { polarClient } from "@polar-sh/better-auth"; 3 | import { organizationClient } from "better-auth/client/plugins"; 4 | 5 | export const authClient = createAuthClient({ 6 | baseURL: "http://localhost:3001", // the base url of your auth server 7 | plugins: [organizationClient(), polarClient()], 8 | }); 9 | 10 | export const { signIn, signUp, useSession } = authClient; 11 | -------------------------------------------------------------------------------- /packages/polar-sveltekit/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from "@sveltejs/adapter-auto"; 2 | import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: [vitePreprocess({})], 9 | 10 | kit: { 11 | adapter: adapter(), 12 | }, 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /packages/polar-betterauth/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | environment: 'node', 6 | globals: true, 7 | coverage: { 8 | provider: 'v8', 9 | reporter: ['text', 'json', 'html'], 10 | exclude: ['node_modules/', 'dist/', 'example/', '**/*.d.ts'], 11 | }, 12 | }, 13 | resolve: { 14 | alias: { 15 | '@': '/Users/ewidlund/dev/polar-adapters/packages/polar-betterauth/src', 16 | }, 17 | }, 18 | }) -------------------------------------------------------------------------------- /packages/polar-nextjs/example/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /packages/polar-nuxt/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { createConfigForNuxt } from "@nuxt/eslint-config/flat"; 3 | 4 | // Run `npx @eslint/config-inspector` to inspect the resolved config interactively 5 | export default createConfigForNuxt({ 6 | features: { 7 | // Rules for module authors 8 | tooling: true, 9 | // Rules for formatting 10 | stylistic: false, 11 | }, 12 | dirs: { 13 | src: ["./playground"], 14 | }, 15 | }).append( 16 | // your custom flat config here... 17 | ); 18 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "./globals.css"; 3 | 4 | export const metadata: Metadata = { 5 | title: "Create Next App", 6 | description: "Generated by create next app", 7 | }; 8 | 9 | export default function RootLayout({ 10 | children, 11 | }: Readonly<{ 12 | children: React.ReactNode; 13 | }>) { 14 | return ( 15 | 16 | {children} 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /packages/polar-supabase/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enablePaths": [ 3 | "supabase/functions" 4 | ], 5 | "deno.lint": true, 6 | "deno.unstable": [ 7 | "bare-node-builtins", 8 | "byonm", 9 | "sloppy-imports", 10 | "unsafe-proto", 11 | "webgpu", 12 | "broadcast-channel", 13 | "worker-options", 14 | "cron", 15 | "kv", 16 | "ffi", 17 | "fs", 18 | "http", 19 | "net" 20 | ], 21 | "[typescript]": { 22 | "editor.defaultFormatter": "denoland.vscode-deno" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /packages/polar-nuxt/playground/server/routes/api/portal.get.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler((event) => { 2 | const { 3 | private: { polarAccessToken, polarServer }, 4 | } = useRuntimeConfig(); 5 | 6 | const customerPortalHandler = CustomerPortal({ 7 | accessToken: polarAccessToken, 8 | server: polarServer as "sandbox" | "production", 9 | getCustomerId: () => { 10 | return Promise.resolve("9d89909b-216d-475e-8005-053dba7cff07"); 11 | }, 12 | }); 13 | 14 | return customerPortalHandler(event); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | @theme { 4 | --font-sans: var(--font-geist-sans); 5 | --font-mono: var(--font-geist-mono); 6 | } 7 | 8 | :root { 9 | --background: #ffffff; 10 | --foreground: #171717; 11 | } 12 | 13 | @media (prefers-color-scheme: dark) { 14 | :root { 15 | --background: #0a0a0a; 16 | --foreground: #ededed; 17 | } 18 | } 19 | 20 | body { 21 | color: var(--foreground); 22 | background: var(--background); 23 | font-family: Arial, Helvetica, sans-serif; 24 | } 25 | -------------------------------------------------------------------------------- /packages/polar-sveltekit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "module": "NodeNext", 13 | "moduleResolution": "NodeNext" 14 | }, 15 | "include": ["src/**/*.ts", "src/**/*.tsx"], 16 | "exclude": ["dist", "svelte.config.js", ".svelte-kit"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/polar-nuxt/test/basic.test.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from "node:url"; 2 | import { describe, it, expect } from "vitest"; 3 | import { setup, $fetch } from "@nuxt/test-utils/e2e"; 4 | 5 | describe("ssr", async () => { 6 | await setup({ 7 | rootDir: fileURLToPath(new URL("./fixtures/basic", import.meta.url)), 8 | }); 9 | 10 | it("renders the index page", async () => { 11 | // Get response to a server-rendered page with `$fetch`. 12 | const html = await $fetch("/"); 13 | expect(html).toContain("
basic
"); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | quality: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | - name: Install pnpm 16 | uses: pnpm/action-setup@v4 17 | - name: Setup Biome 18 | uses: biomejs/setup-biome@v2 19 | with: 20 | version: latest 21 | - name: Install Deps 22 | run: pnpm install 23 | - name: Run Biome 24 | run: pnpm run ci 25 | -------------------------------------------------------------------------------- /packages/polar-supabase/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "test": "deno test --allow-env --allow-net", 4 | "test:watch": "deno test --allow-env --allow-net --watch", 5 | "test:coverage": "deno test --allow-env --allow-net --coverage=coverage", 6 | "build": "deno task build:npm", 7 | "build:npm": "deno run -A npm:tsup ./src/index.ts --format esm,cjs --dts --clean --sourcemap" 8 | }, 9 | "imports": { 10 | "@std/assert": "jsr:@std/assert@^1", 11 | "@std/testing": "jsr:@std/testing@^1" 12 | }, 13 | "exclude": [ 14 | "node_modules", 15 | "dist", 16 | "example" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/typescript-config/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "declarationMap": true, 6 | "esModuleInterop": true, 7 | "incremental": false, 8 | "isolatedModules": true, 9 | "lib": ["es2022", "DOM", "DOM.Iterable"], 10 | "module": "NodeNext", 11 | "moduleDetection": "force", 12 | "moduleResolution": "NodeNext", 13 | "noUncheckedIndexedAccess": true, 14 | "resolveJsonModule": true, 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "target": "ES2022" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/polar-nuxt/src/module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | addServerImportsDir, 3 | createResolver, 4 | defineNuxtModule, 5 | } from "@nuxt/kit"; 6 | 7 | // Module options TypeScript interface definition 8 | export type ModuleOptions = Record; 9 | 10 | export default defineNuxtModule({ 11 | meta: { 12 | name: "@polar-sh/nuxt", 13 | configKey: "polar", 14 | }, 15 | // Default configuration options of the Nuxt module 16 | defaults: {}, 17 | setup(_options, _nuxt) { 18 | const resolver = createResolver(import.meta.url); 19 | 20 | addServerImportsDir(resolver.resolve("./runtime/server")); 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # Local env files 9 | .env 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | # Testing 16 | coverage 17 | 18 | # Turbo 19 | .turbo 20 | 21 | # Vercel 22 | .vercel 23 | 24 | # Build Outputs 25 | .next/ 26 | out/ 27 | build 28 | dist 29 | 30 | 31 | # Debug 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # Misc 37 | .DS_Store 38 | *.pem 39 | 40 | # Nuxt 41 | package-lock.json 42 | .nuxt 43 | .output 44 | .idea/ 45 | playground/.nuxtrc 46 | .temp* -------------------------------------------------------------------------------- /packages/polar-deno/mocks/sdk.ts: -------------------------------------------------------------------------------- 1 | import sinon from "https://cdn.skypack.dev/sinon@11.1.2?dts"; 2 | 3 | const mockCustomerPortalUrl = "https://mock-customer-portal-url.com"; 4 | const mockCheckoutUrl = "https://mock-checkout-url.com"; 5 | const mockSessionCreate = sinon 6 | .stub() 7 | .returns({ customerPortalUrl: mockCustomerPortalUrl }); 8 | 9 | const mockCheckoutCreate = sinon.stub().returns({ url: mockCheckoutUrl }); 10 | 11 | export class Polar { 12 | config: any; 13 | constructor(config: any) { 14 | this.config = config; 15 | } 16 | customerSessions = { 17 | create: mockSessionCreate, 18 | }; 19 | 20 | checkouts = { 21 | create: mockCheckoutCreate, 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /packages/polar-nextjs/example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polar-sh/adapters", 3 | "private": true, 4 | "scripts": { 5 | "build": "turbo build", 6 | "dev": "turbo dev", 7 | "lint": "turbo lint", 8 | "check": "turbo check", 9 | "format": "turbo format", 10 | "ci": "turbo build && turbo check && turbo test", 11 | "test": "turbo test -- run", 12 | "publish-packages": "turbo run lint check build && changeset publish" 13 | }, 14 | "devDependencies": { 15 | "@biomejs/biome": "1.9.4", 16 | "prettier": "^3.7.4", 17 | "turbo": "^2.6.3", 18 | "typescript": "5.9.3" 19 | }, 20 | "packageManager": "pnpm@10.19.0", 21 | "engines": { 22 | "node": ">=18" 23 | }, 24 | "dependencies": { 25 | "@changesets/cli": "^2.29.8" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "ui": "tui", 4 | "tasks": { 5 | "build": { 6 | "dependsOn": ["^build"], 7 | "inputs": ["$TURBO_DEFAULT$", ".env*"], 8 | "outputs": [".next/**", "!.next/cache/**", "dist/**"] 9 | }, 10 | "lint": { 11 | "dependsOn": ["^lint"] 12 | }, 13 | "check-types": { 14 | "dependsOn": ["^check-types"] 15 | }, 16 | "format": { 17 | "dependsOn": ["lint", "check-types"] 18 | }, 19 | "dev": { 20 | "cache": false, 21 | "persistent": true 22 | }, 23 | "check": { 24 | "dependsOn": ["lint", "check-types"] 25 | }, 26 | "ci": { 27 | "dependsOn": ["check", "build"] 28 | }, 29 | "test": { 30 | "dependsOn": ["check"] 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/polar-nextjs/example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | !.env.template 36 | 37 | # vercel 38 | .vercel 39 | 40 | # typescript 41 | *.tsbuildinfo 42 | next-env.d.ts 43 | 44 | *.db 45 | *.sqlite -------------------------------------------------------------------------------- /packages/polar-betterauth/example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | !.env.template 36 | 37 | # vercel 38 | .vercel 39 | 40 | # typescript 41 | *.tsbuildinfo 42 | next-env.d.ts 43 | 44 | *.db 45 | *.sqlite -------------------------------------------------------------------------------- /packages/eslint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@repo/eslint-config", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "exports": { 7 | "./base": "./base.js", 8 | "./next-js": "./next.js", 9 | "./react-internal": "./react-internal.js" 10 | }, 11 | "devDependencies": { 12 | "@eslint/js": "^9.38.0", 13 | "@next/eslint-plugin-next": "^15.5.6", 14 | "eslint": "^9.38.0", 15 | "eslint-config-prettier": "^9.1.0", 16 | "eslint-plugin-only-warn": "^1.1.0", 17 | "eslint-plugin-react": "^7.37.5", 18 | "eslint-plugin-react-hooks": "^5.0.0", 19 | "eslint-plugin-turbo": "^2.5.8", 20 | "globals": "^15.12.0", 21 | "typescript": "^5.9.3", 22 | "typescript-eslint": "^8.46.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/eslint-config/base.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import eslintConfigPrettier from "eslint-config-prettier"; 3 | import turboPlugin from "eslint-plugin-turbo"; 4 | import tseslint from "typescript-eslint"; 5 | import onlyWarn from "eslint-plugin-only-warn"; 6 | 7 | /** 8 | * A shared ESLint configuration for the repository. 9 | * 10 | * @type {import("eslint").Linter.Config} 11 | * */ 12 | export const config = [ 13 | js.configs.recommended, 14 | eslintConfigPrettier, 15 | ...tseslint.configs.recommended, 16 | { 17 | plugins: { 18 | turbo: turboPlugin, 19 | }, 20 | rules: { 21 | "turbo/no-undeclared-env-vars": "warn", 22 | }, 23 | }, 24 | { 25 | plugins: { 26 | onlyWarn, 27 | }, 28 | }, 29 | { 30 | ignores: ["dist/**"], 31 | }, 32 | ]; 33 | -------------------------------------------------------------------------------- /packages/polar-nuxt/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Logs 5 | *.log* 6 | 7 | # Temp directories 8 | .temp 9 | .tmp 10 | .cache 11 | 12 | # Yarn 13 | **/.yarn/cache 14 | **/.yarn/*state* 15 | 16 | # Generated dirs 17 | dist 18 | 19 | # Nuxt 20 | .nuxt 21 | .output 22 | .data 23 | .vercel_build_output 24 | .build-* 25 | .netlify 26 | 27 | # Env 28 | .env 29 | 30 | # Testing 31 | reports 32 | coverage 33 | *.lcov 34 | .nyc_output 35 | 36 | # VSCode 37 | .vscode/* 38 | !.vscode/settings.json 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | !.vscode/extensions.json 42 | !.vscode/*.code-snippets 43 | 44 | # Intellij idea 45 | *.iml 46 | .idea 47 | 48 | # OSX 49 | .DS_Store 50 | .AppleDouble 51 | .LSOverride 52 | .AppleDB 53 | .AppleDesktop 54 | Network Trash Folder 55 | Temporary Items 56 | .apdisk 57 | -------------------------------------------------------------------------------- /packages/polar-nextjs/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@polar-sh/nextjs": "workspace:*", 13 | "@polar-sh/sdk": "^0.36.2", 14 | "better-auth": "1.2.7", 15 | "better-sqlite3": "^11.8.1", 16 | "next": "14.2.28", 17 | "react": "^19.0.0", 18 | "react-dom": "^19.0.0" 19 | }, 20 | "devDependencies": { 21 | "@eslint/eslintrc": "^3", 22 | "@tailwindcss/postcss": "^4", 23 | "@types/better-sqlite3": "^7.6.12", 24 | "@types/node": "^20", 25 | "@types/react": "^19.1.8", 26 | "@types/react-dom": "^19", 27 | "eslint": "^9", 28 | "eslint-config-next": "15.2.0", 29 | "tailwindcss": "^4", 30 | "typescript": "^5" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@polar-sh/better-auth": "workspace:*", 13 | "@polar-sh/sdk": "^0.42.1", 14 | "better-auth": "1.4.6", 15 | "better-sqlite3": "^11.10.0", 16 | "next": "16.0.8", 17 | "react": "^19.2.1", 18 | "react-dom": "^19.2.1" 19 | }, 20 | "devDependencies": { 21 | "@eslint/eslintrc": "^3", 22 | "@tailwindcss/postcss": "^4.1.17", 23 | "@types/better-sqlite3": "^7.6.13", 24 | "@types/node": "^20", 25 | "@types/react": "^19.2.7", 26 | "@types/react-dom": "^19", 27 | "eslint": "^9", 28 | "eslint-config-next": "15.2.0", 29 | "tailwindcss": "^4", 30 | "typescript": "^5" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "noEmit": true, 13 | "esModuleInterop": true, 14 | "module": "esnext", 15 | "moduleResolution": "bundler", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "react-jsx", 19 | "incremental": true, 20 | "plugins": [ 21 | { 22 | "name": "next" 23 | } 24 | ], 25 | "paths": { 26 | "@/*": [ 27 | "./src/*" 28 | ] 29 | } 30 | }, 31 | "include": [ 32 | "next-env.d.ts", 33 | "**/*.ts", 34 | "**/*.tsx", 35 | ".next/types/**/*.ts", 36 | ".next/dev/types/**/*.ts" 37 | ], 38 | "exclude": [ 39 | "node_modules" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": false 7 | }, 8 | "files": { 9 | "ignoreUnknown": false, 10 | "ignore": [] 11 | }, 12 | "formatter": { 13 | "enabled": true, 14 | "indentStyle": "tab" 15 | }, 16 | "organizeImports": { 17 | "enabled": true 18 | }, 19 | "linter": { 20 | "enabled": true, 21 | "rules": { 22 | "recommended": true, 23 | "complexity": { 24 | "useLiteralKeys": "off" 25 | } 26 | } 27 | }, 28 | "javascript": { 29 | "formatter": { 30 | "quoteStyle": "double" 31 | } 32 | }, 33 | "overrides": [ 34 | { 35 | "include": ["**/__tests__/**"], 36 | "linter": { 37 | "rules": { 38 | "suspicious": { 39 | "noExplicitAny": "off" 40 | }, 41 | "complexity": { 42 | "noBannedTypes": "off" 43 | } 44 | } 45 | } 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /packages/adapter-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polar-sh/adapter-utils", 3 | "version": "0.4.3", 4 | "description": "Core package for Polar adapters", 5 | "main": "./dist/index.cjs", 6 | "module": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "type": "module", 9 | "exports": { 10 | "import": { 11 | "types": "./dist/index.d.ts", 12 | "import": "./dist/index.js" 13 | }, 14 | "require": { 15 | "types": "./dist/index.d.cts", 16 | "require": "./dist/index.cjs" 17 | } 18 | }, 19 | "scripts": { 20 | "build": "tsup src/index.ts --format esm,cjs --dts --outDir dist", 21 | "test": "vitest" 22 | }, 23 | "keywords": [], 24 | "author": "", 25 | "license": "Apache-2.0", 26 | "dependencies": { 27 | "@polar-sh/sdk": "^0.42.1" 28 | }, 29 | "devDependencies": { 30 | "@sindresorhus/tsconfig": "^7.0.0", 31 | "tsup": "^8.5.1", 32 | "typescript": "^5.9.3", 33 | "vitest": "^2.1.8" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/polar-betterauth/src/__tests__/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | import { vi } from "vitest"; 2 | import type { PolarOptions } from "../../types"; 3 | import { createMockPolarClient } from "./mocks"; 4 | 5 | export const createTestPolarOptions = ( 6 | overrides: Partial = {}, 7 | ): PolarOptions => ({ 8 | client: createMockPolarClient(), 9 | createCustomerOnSignUp: true, 10 | use: [], 11 | ...overrides, 12 | }); 13 | 14 | export { createMockPolarClient }; 15 | 16 | export const mockApiError = (status: number, message: string) => { 17 | const error = new Error(message) as any; 18 | error.status = status; 19 | error.response = { 20 | status, 21 | data: { error: { message } }, 22 | }; 23 | return error; 24 | }; 25 | 26 | export const mockApiResponse = (data: T) => Promise.resolve({ data }); 27 | 28 | export const createMockMiddleware = () => { 29 | const middleware = vi.fn(); 30 | middleware.mockImplementation((context, next) => next()); 31 | return middleware; 32 | }; 33 | -------------------------------------------------------------------------------- /packages/polar-nextjs/example/public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-22.04 13 | strategy: 14 | matrix: 15 | node-version: [20] 16 | permissions: 17 | contents: 'write' 18 | pull-requests: 'write' 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Install pnpm 23 | uses: pnpm/action-setup@v4 24 | 25 | - name: Use Node.js ${{ matrix.node-version }} 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | cache: 'pnpm' 30 | 31 | - run: pnpm install --frozen-lockfile 32 | 33 | - name: Publish to NPM 34 | id: changesets 35 | uses: changesets/action@v1 36 | with: 37 | publish: pnpm publish-packages 38 | env: 39 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/better-auth_migrations/2025-03-03T15-33-48.743Z.sql: -------------------------------------------------------------------------------- 1 | create table "user" ("id" text not null primary key, "name" text not null, "email" text not null unique, "emailVerified" integer not null, "image" text, "createdAt" date not null, "updatedAt" date not null); 2 | 3 | create table "session" ("id" text not null primary key, "expiresAt" date not null, "token" text not null unique, "createdAt" date not null, "updatedAt" date not null, "ipAddress" text, "userAgent" text, "userId" text not null references "user" ("id")); 4 | 5 | create table "account" ("id" text not null primary key, "accountId" text not null, "providerId" text not null, "userId" text not null references "user" ("id"), "accessToken" text, "refreshToken" text, "idToken" text, "accessTokenExpiresAt" date, "refreshTokenExpiresAt" date, "scope" text, "password" text, "createdAt" date not null, "updatedAt" date not null); 6 | 7 | create table "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expiresAt" date not null, "createdAt" date, "updatedAt" date); -------------------------------------------------------------------------------- /packages/polar-betterauth/example/src/components/Login.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { signIn } from "@/lib/auth-client"; 4 | import { useState } from "react"; 5 | 6 | export const Login = () => { 7 | const [email, setEmail] = useState(""); 8 | const [password, setPassword] = useState(""); 9 | 10 | return ( 11 |
12 |

Login

13 | setEmail(e.target.value)} 18 | /> 19 | setPassword(e.target.value)} 24 | /> 25 | 39 |
40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /packages/polar-supabase/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polar-sh/supabase", 3 | "version": "0.4.2", 4 | "main": "./dist/index.cjs", 5 | "module": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "exports": { 8 | "import": { 9 | "types": "./dist/index.d.ts", 10 | "import": "./dist/index.js" 11 | }, 12 | "require": { 13 | "types": "./dist/index.d.cts", 14 | "require": "./dist/index.cjs" 15 | } 16 | }, 17 | "type": "module", 18 | "engines": { 19 | "node": ">=16" 20 | }, 21 | "scripts": { 22 | "build": "tsup ./src/index.ts --format esm,cjs --dts --clean --sourcemap", 23 | "dev": "tsc --watch", 24 | "check": "biome check --write ./src", 25 | "test": "vitest", 26 | "test:watch": "vitest --watch", 27 | "test:coverage": "vitest --coverage" 28 | }, 29 | "files": [ 30 | "dist" 31 | ], 32 | "dependencies": { 33 | "@polar-sh/sdk": "^0.42.1" 34 | }, 35 | "devDependencies": { 36 | "@biomejs/biome": "1.9.4", 37 | "@polar-sh/adapter-utils": "workspace:*", 38 | "@sindresorhus/tsconfig": "^7.0.0", 39 | "tsup": "^8.3.5", 40 | "vitest": "^3.2.4" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/polar-supabase/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @polar-sh/supabase 2 | 3 | ## 0.4.2 4 | 5 | ### Patch Changes 6 | 7 | - 3906ed9: Bump @polar-sh/sdk 8 | 9 | ## 0.4.1 10 | 11 | ### Patch Changes 12 | 13 | - c591126: Bump @polar-sh/sdk 14 | 15 | ## 0.4.0 16 | 17 | ### Minor Changes 18 | 19 | - 4512e82: Bump Polar SDK 20 | 21 | ## 0.3.0 22 | 23 | ### Minor Changes 24 | 25 | - 6c46bd1: feat: add seats param to checkout 26 | 27 | ## 0.2.0 28 | 29 | ### Minor Changes 30 | 31 | - 1deb3b3: Update sdk to 0.40.2 32 | 33 | ## 0.1.2 34 | 35 | ### Patch Changes 36 | 37 | - a84c64c: Add support for returnUrl 38 | 39 | ## 0.1.1 40 | 41 | ### Patch Changes 42 | 43 | - 017e7c3: New Supabase adapter version 44 | 45 | ## 0.1.2 46 | 47 | ### Patch Changes 48 | 49 | - 2299d7a: new supabase version 50 | 51 | ## 0.1.1 52 | 53 | ### Patch Changes 54 | 55 | - 6e03516: init supabase adapter 56 | 57 | ## 0.1.0 58 | 59 | ### Minor Changes 60 | 61 | - Initial release of Supabase adapter for Polar 62 | - Support for Checkout creation with full parameter support 63 | - Customer Portal session management 64 | - Webhook handling with signature validation 65 | - Entitlements API integration 66 | - Full compatibility with Supabase Edge Functions and Deno runtime 67 | - Comprehensive test coverage 68 | -------------------------------------------------------------------------------- /packages/eslint-config/react-internal.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import eslintConfigPrettier from "eslint-config-prettier"; 3 | import tseslint from "typescript-eslint"; 4 | import pluginReactHooks from "eslint-plugin-react-hooks"; 5 | import pluginReact from "eslint-plugin-react"; 6 | import globals from "globals"; 7 | import { config as baseConfig } from "./base.js"; 8 | 9 | /** 10 | * A custom ESLint configuration for libraries that use React. 11 | * 12 | * @type {import("eslint").Linter.Config} */ 13 | export const config = [ 14 | ...baseConfig, 15 | js.configs.recommended, 16 | eslintConfigPrettier, 17 | ...tseslint.configs.recommended, 18 | pluginReact.configs.flat.recommended, 19 | { 20 | languageOptions: { 21 | ...pluginReact.configs.flat.recommended.languageOptions, 22 | globals: { 23 | ...globals.serviceworker, 24 | ...globals.browser, 25 | }, 26 | }, 27 | }, 28 | { 29 | plugins: { 30 | "react-hooks": pluginReactHooks, 31 | }, 32 | settings: { react: { version: "detect" } }, 33 | rules: { 34 | ...pluginReactHooks.configs.recommended.rules, 35 | // React scope no longer necessary with new JSX transform. 36 | "react/react-in-jsx-scope": "off", 37 | }, 38 | }, 39 | ]; 40 | -------------------------------------------------------------------------------- /packages/polar-tanstack-start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polar-sh/tanstack-start", 3 | "version": "0.4.3", 4 | "main": "./dist/index.cjs", 5 | "module": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "exports": { 8 | "import": { 9 | "types": "./dist/index.d.ts", 10 | "import": "./dist/index.js" 11 | }, 12 | "require": { 13 | "types": "./dist/index.d.cts", 14 | "require": "./dist/index.cjs" 15 | } 16 | }, 17 | "type": "module", 18 | "engines": { 19 | "node": ">=16" 20 | }, 21 | "scripts": { 22 | "build": "tsup ./src/index.ts --format esm,cjs --dts --clean --sourcemap", 23 | "dev": "tsc --watch", 24 | "check": "biome check --write ./src" 25 | }, 26 | "files": [ 27 | "dist" 28 | ], 29 | "dependencies": { 30 | "@polar-sh/adapter-utils": "workspace:*", 31 | "@polar-sh/sdk": "^0.42.1", 32 | "@tanstack/react-start": "^1.140.0" 33 | }, 34 | "devDependencies": { 35 | "@biomejs/biome": "1.9.4", 36 | "@sindresorhus/tsconfig": "^7.0.0", 37 | "tsup": "^8.5.1", 38 | "vitest": "^2.1.8" 39 | }, 40 | "ava": { 41 | "extensions": { 42 | "ts": "module", 43 | "tsx": "module" 44 | }, 45 | "nodeArguments": [ 46 | "--loader=ts-node/esm" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/src/lib/auth.ts: -------------------------------------------------------------------------------- 1 | import { betterAuth } from "better-auth"; 2 | import { 3 | polar, 4 | checkout, 5 | webhooks, 6 | usage, 7 | portal, 8 | } from "@polar-sh/better-auth"; 9 | import Database from "better-sqlite3"; 10 | import { polarSDK } from "./polar"; 11 | import { organization } from "better-auth/plugins"; 12 | 13 | export const auth = betterAuth({ 14 | emailAndPassword: { 15 | enabled: true, 16 | }, 17 | plugins: [ 18 | organization(), 19 | polar({ 20 | client: polarSDK, 21 | createCustomerOnSignUp: true, 22 | async getCustomerCreateParams() { 23 | return { 24 | metadata: { 25 | hello: "world", 26 | }, 27 | }; 28 | }, 29 | use: [ 30 | checkout({ 31 | theme: "dark", 32 | products: [ 33 | { 34 | productId: "e651f46d-ac20-4f26-b769-ad088b123df2", 35 | slug: "pro", 36 | }, 37 | ], 38 | returnUrl: "https://myapp.com", 39 | }), 40 | usage(), 41 | portal({ 42 | returnUrl: "https://myapp.com", 43 | }), 44 | webhooks({ 45 | secret: process.env["POLAR_WEBHOOK_SECRET"] as string, 46 | onOrganizationUpdated: async (payload) => { 47 | console.log(payload); 48 | }, 49 | }), 50 | ], 51 | }), 52 | ], 53 | database: new Database("sqlite.db"), 54 | }); 55 | -------------------------------------------------------------------------------- /packages/polar-express/src/customerPortal/customerPortal.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | import type { Request, Response } from "express"; 3 | 4 | export interface CustomerPortalConfig { 5 | accessToken?: string; 6 | getCustomerId: (req: Request) => Promise; 7 | server?: "sandbox" | "production"; 8 | returnUrl?: string; 9 | } 10 | 11 | export const CustomerPortal = ({ 12 | accessToken, 13 | server, 14 | getCustomerId, 15 | returnUrl, 16 | }: CustomerPortalConfig) => { 17 | const polar = new Polar({ 18 | accessToken: accessToken ?? process.env["POLAR_ACCESS_TOKEN"], 19 | server, 20 | }); 21 | 22 | return async (req: Request, res: Response) => { 23 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 24 | 25 | const customerId = await getCustomerId(req); 26 | 27 | if (!customerId) { 28 | res.status(400).json({ error: "customerId not defined" }); 29 | return; 30 | } 31 | 32 | try { 33 | const result = await polar.customerSessions.create({ 34 | customerId, 35 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 36 | }); 37 | 38 | res.redirect(result.customerPortalUrl); 39 | return; 40 | } catch (error) { 41 | console.error(error); 42 | res.status(500).json({ error: "Internal server error" }); 43 | return; 44 | } 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /packages/polar-betterauth/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { BetterAuthPlugin } from "better-auth"; 2 | import { 3 | onAfterUserCreate, 4 | onBeforeUserCreate, 5 | onUserDelete, 6 | onUserUpdate, 7 | } from "./hooks/customer"; 8 | import type { PolarEndpoints, PolarOptions } from "./types"; 9 | 10 | export { polarClient } from "./client"; 11 | 12 | export * from "./plugins/portal"; 13 | export * from "./plugins/checkout"; 14 | export * from "./plugins/usage"; 15 | export * from "./plugins/webhooks"; 16 | 17 | export const polar = (options: O) => { 18 | const plugins = options.use 19 | .map((use) => use(options.client)) 20 | .reduce((acc, plugin) => { 21 | Object.assign(acc, plugin); 22 | return acc; 23 | }, {} as PolarEndpoints); 24 | 25 | return { 26 | id: "polar", 27 | endpoints: { 28 | ...plugins, 29 | }, 30 | init() { 31 | return { 32 | options: { 33 | databaseHooks: { 34 | user: { 35 | create: { 36 | before: onBeforeUserCreate(options), 37 | after: onAfterUserCreate(options), 38 | }, 39 | update: { 40 | after: onUserUpdate(options), 41 | }, 42 | delete: { 43 | after: onUserDelete(options), 44 | }, 45 | }, 46 | }, 47 | }, 48 | }; 49 | }, 50 | } satisfies BetterAuthPlugin; 51 | }; 52 | -------------------------------------------------------------------------------- /packages/polar-nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polar-sh/nextjs", 3 | "version": "0.9.3", 4 | "main": "./dist/index.cjs", 5 | "module": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "exports": { 8 | "import": { 9 | "types": "./dist/index.d.ts", 10 | "import": "./dist/index.js" 11 | }, 12 | "require": { 13 | "types": "./dist/index.d.cts", 14 | "require": "./dist/index.cjs" 15 | } 16 | }, 17 | "type": "module", 18 | "engines": { 19 | "node": ">=16" 20 | }, 21 | "scripts": { 22 | "build": "tsup ./src/index.ts --format esm,cjs --dts --clean --sourcemap", 23 | "dev": "tsc --watch", 24 | "check": "biome check --write ./src", 25 | "test": "vitest" 26 | }, 27 | "files": [ 28 | "dist" 29 | ], 30 | "dependencies": { 31 | "@polar-sh/adapter-utils": "workspace:*", 32 | "@polar-sh/sdk": "^0.42.1" 33 | }, 34 | "devDependencies": { 35 | "@biomejs/biome": "1.9.4", 36 | "@sindresorhus/tsconfig": "^7.0.0", 37 | "tsup": "^8.5.1", 38 | "vitest": "^2.1.8" 39 | }, 40 | "ava": { 41 | "extensions": { 42 | "ts": "module", 43 | "tsx": "module" 44 | }, 45 | "nodeArguments": [ 46 | "--loader=ts-node/esm" 47 | ] 48 | }, 49 | "peerDependencies": { 50 | "next": "^15.0.0 || ^16.0.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/polar-elysia/src/customerPortal/customerPortal.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | import { type Context, status } from "elysia"; 3 | import type { InlineHandler } from "elysia/types"; 4 | 5 | export interface CustomerPortalConfig { 6 | accessToken?: string; 7 | getCustomerId: (req: Request) => Promise; 8 | server?: "sandbox" | "production"; 9 | returnUrl?: string; 10 | } 11 | 12 | export const CustomerPortal = ({ 13 | accessToken, 14 | server, 15 | getCustomerId, 16 | returnUrl, 17 | }: CustomerPortalConfig): InlineHandler => { 18 | const polar = new Polar({ 19 | accessToken: accessToken ?? process.env["POLAR_ACCESS_TOKEN"], 20 | server, 21 | }); 22 | 23 | return async (ctx: Context) => { 24 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 25 | 26 | const customerId = await getCustomerId(ctx.request); 27 | 28 | if (!customerId) { 29 | return status(400, { error: "customerId not defined" }); 30 | } 31 | 32 | try { 33 | const result = await polar.customerSessions.create({ 34 | customerId, 35 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 36 | }); 37 | 38 | return ctx.redirect(result.customerPortalUrl); 39 | } catch (error) { 40 | console.error(error); 41 | return status(500, { error: "Internal server error" }); 42 | } 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /packages/polar-remix/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polar-sh/remix", 3 | "version": "0.6.3", 4 | "description": "Polar 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 | "polar", 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 | "@polar-sh/adapter-utils": "workspace:*", 48 | "@polar-sh/sdk": "^0.42.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/polar-remix/src/customerPortal/customerPortal.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | import type { LoaderFunction } from "../types"; 3 | 4 | export interface CustomerPortalConfig { 5 | accessToken?: string; 6 | getCustomerId: (req: Request) => Promise; 7 | server?: "sandbox" | "production"; 8 | returnUrl?: string; 9 | } 10 | 11 | export const CustomerPortal = ({ 12 | accessToken, 13 | server, 14 | getCustomerId, 15 | returnUrl, 16 | }: CustomerPortalConfig): LoaderFunction => { 17 | const polar = new Polar({ 18 | accessToken: accessToken ?? process.env["POLAR_ACCESS_TOKEN"], 19 | server, 20 | }); 21 | 22 | return async ({ request }) => { 23 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 24 | 25 | const customerId = await getCustomerId(request); 26 | 27 | if (!customerId) { 28 | return Response.json( 29 | { error: "customerId not defined" }, 30 | { status: 400 }, 31 | ); 32 | } 33 | 34 | try { 35 | const result = await polar.customerSessions.create({ 36 | customerId, 37 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 38 | }); 39 | 40 | return Response.redirect(result.customerPortalUrl); 41 | } catch (error) { 42 | console.error(error); 43 | return Response.json({ error: "Internal server error" }, { status: 500 }); 44 | } 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /packages/polar-supabase/src/customerPortal/customerPortal.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | 3 | export interface CustomerPortalConfig { 4 | accessToken: string; 5 | getCustomerId: (req: Request) => Promise; 6 | server: "sandbox" | "production"; 7 | returnUrl?: string; 8 | } 9 | 10 | export const CustomerPortal = ({ 11 | accessToken, 12 | server, 13 | getCustomerId, 14 | returnUrl, 15 | }: CustomerPortalConfig) => { 16 | const polar = new Polar({ 17 | accessToken, 18 | server, 19 | }); 20 | 21 | return async (req: Request) => { 22 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 23 | 24 | const customerId = await getCustomerId(req); 25 | 26 | if (!customerId) { 27 | return new Response(JSON.stringify({ error: "customerId not defined" }), { 28 | status: 400, 29 | headers: { "Content-Type": "application/json" }, 30 | }); 31 | } 32 | 33 | try { 34 | const result = await polar.customerSessions.create({ 35 | customerId, 36 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 37 | }); 38 | 39 | return new Response(null, { 40 | status: 302, 41 | headers: { 42 | Location: result.customerPortalUrl, 43 | }, 44 | }); 45 | } catch (error) { 46 | console.error(error); 47 | return new Response(null, { status: 500 }); 48 | } 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /packages/polar-fastify/src/customerPortal/customerPortal.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | import type { FastifyReply, FastifyRequest, RouteHandler } from "fastify"; 3 | export interface CustomerPortalConfig { 4 | accessToken?: string; 5 | getCustomerId: (req: FastifyRequest) => Promise; 6 | server?: "sandbox" | "production"; 7 | returnUrl?: string; 8 | } 9 | 10 | export const CustomerPortal = ({ 11 | accessToken, 12 | server, 13 | getCustomerId, 14 | returnUrl, 15 | }: CustomerPortalConfig): RouteHandler => { 16 | const polar = new Polar({ 17 | accessToken: accessToken ?? process.env["POLAR_ACCESS_TOKEN"], 18 | server, 19 | }); 20 | 21 | return async (request: FastifyRequest, reply: FastifyReply) => { 22 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 23 | 24 | const customerId = await getCustomerId(request); 25 | 26 | if (!customerId) { 27 | return reply.status(400).send({ error: "customerId not defined" }); 28 | } 29 | 30 | try { 31 | const result = await polar.customerSessions.create({ 32 | customerId, 33 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 34 | }); 35 | 36 | return reply.redirect(result.customerPortalUrl); 37 | } catch (error) { 38 | console.error(error); 39 | return reply.status(500).send({ error: "Internal server error" }); 40 | } 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /packages/polar-supabase/deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4", 3 | "specifiers": { 4 | "jsr:@std/assert@1": "1.0.14", 5 | "jsr:@std/assert@^1.0.13": "1.0.14", 6 | "jsr:@std/internal@^1.0.10": "1.0.10", 7 | "jsr:@std/testing@1": "1.0.15" 8 | }, 9 | "jsr": { 10 | "@std/assert@1.0.10": { 11 | "integrity": "59b5cbac5bd55459a19045d95cc7c2ff787b4f8527c0dd195078ff6f9481fbb3" 12 | }, 13 | "@std/assert@1.0.14": { 14 | "integrity": "68d0d4a43b365abc927f45a9b85c639ea18a9fab96ad92281e493e4ed84abaa4", 15 | "dependencies": [ 16 | "jsr:@std/internal" 17 | ] 18 | }, 19 | "@std/internal@1.0.10": { 20 | "integrity": "e3be62ce42cab0e177c27698e5d9800122f67b766a0bea6ca4867886cbde8cf7" 21 | }, 22 | "@std/testing@1.0.15": { 23 | "integrity": "a490169f5ccb0f3ae9c94fbc69d2cd43603f2cffb41713a85f99bbb0e3087cbc", 24 | "dependencies": [ 25 | "jsr:@std/assert@^1.0.13", 26 | "jsr:@std/internal" 27 | ] 28 | } 29 | }, 30 | "workspace": { 31 | "dependencies": [ 32 | "jsr:@std/assert@1", 33 | "jsr:@std/testing@1" 34 | ], 35 | "packageJson": { 36 | "dependencies": [ 37 | "npm:@biomejs/biome@1.9.4", 38 | "npm:@polar-sh/sdk@~0.35.3", 39 | "npm:@sindresorhus/tsconfig@7", 40 | "npm:tsup@^8.3.5" 41 | ] 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/polar-nextjs/example/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/polar-hono/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polar-sh/hono", 3 | "version": "0.5.3", 4 | "description": "Polar integration for Hono", 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 | "polar", 33 | "hono", 34 | "payments", 35 | "subscriptions" 36 | ], 37 | "peerDependencies": { 38 | "hono": "^4.6.16" 39 | }, 40 | "devDependencies": { 41 | "@biomejs/biome": "1.9.4", 42 | "@sindresorhus/tsconfig": "^7.0.0", 43 | "@types/node": "^20.0.0", 44 | "hono": "^4.10.7", 45 | "prettier": "^3.7.4", 46 | "tsup": "^8.5.1", 47 | "vitest": "^2.1.8" 48 | }, 49 | "dependencies": { 50 | "@polar-sh/adapter-utils": "workspace:*", 51 | "@polar-sh/sdk": "^0.42.1" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/polar-astro/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polar-sh/astro", 3 | "version": "0.7.3", 4 | "description": "Polar integration for Astro", 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 --external 'astro:env/server'", 25 | "dev": "tsc --watch", 26 | "check": "biome check --write ./src" 27 | }, 28 | "files": [ 29 | "dist" 30 | ], 31 | "keywords": [ 32 | "polar", 33 | "withastro", 34 | "payments", 35 | "subscriptions" 36 | ], 37 | "peerDependencies": { 38 | "astro": "^5.0.0" 39 | }, 40 | "devDependencies": { 41 | "@biomejs/biome": "1.9.4", 42 | "@sindresorhus/tsconfig": "^7.0.0", 43 | "@types/node": "^20.0.0", 44 | "prettier": "^3.7.4", 45 | "tsup": "^8.5.1", 46 | "vitest": "^2.1.8" 47 | }, 48 | "dependencies": { 49 | "@polar-sh/adapter-utils": "workspace:*", 50 | "@polar-sh/sdk": "^0.42.1" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/polar-fastify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polar-sh/fastify", 3 | "version": "0.5.3", 4 | "description": "Polar integration for Fastify", 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 | "engines": { 19 | "node": ">=18" 20 | }, 21 | "type": "module", 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 | "polar", 33 | "fastify", 34 | "payments", 35 | "subscriptions" 36 | ], 37 | "devDependencies": { 38 | "@biomejs/biome": "1.9.4", 39 | "@sindresorhus/tsconfig": "^7.0.0", 40 | "@types/node": "^20.0.0", 41 | "fastify": "^5.6.2", 42 | "prettier": "^3.7.4", 43 | "tsup": "^8.5.1", 44 | "vitest": "^2.1.8" 45 | }, 46 | "dependencies": { 47 | "@polar-sh/adapter-utils": "workspace:*", 48 | "@polar-sh/sdk": "^0.42.1" 49 | }, 50 | "peerDependencies": { 51 | "fastify": "^5.2.1" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/polar-tanstack-start/src/customerPortal/customerPortal.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | // @ts-expect-error - TODO: fix this 3 | import type { StartAPIMethodCallback } from "@tanstack/react-start/api"; 4 | 5 | export interface CustomerPortalConfig { 6 | accessToken: string; 7 | getCustomerId: (req: Request) => Promise; 8 | server: "sandbox" | "production"; 9 | returnUrl?: string; 10 | } 11 | 12 | export const CustomerPortal = ({ 13 | accessToken, 14 | server, 15 | getCustomerId, 16 | returnUrl, 17 | }: CustomerPortalConfig): StartAPIMethodCallback => { 18 | const polar = new Polar({ 19 | accessToken, 20 | server, 21 | }); 22 | 23 | // @ts-expect-error - TODO: fix this 24 | return async ({ request }) => { 25 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 26 | 27 | const customerId = await getCustomerId(request); 28 | 29 | if (!customerId) { 30 | return Response.json( 31 | { error: "customerId not defined" }, 32 | { status: 400 }, 33 | ); 34 | } 35 | 36 | try { 37 | const result = await polar.customerSessions.create({ 38 | customerId, 39 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 40 | }); 41 | 42 | return Response.redirect(result.customerPortalUrl); 43 | } catch (error) { 44 | console.error(error); 45 | return Response.error(); 46 | } 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Polar Adapters 2 | 3 | This repository hosts a wide array of Polar adapters for your TypeScript framework. Our Adapters are built to make it as easy as possible to integrate Polar in your application. 4 | 5 | ### Adapters 6 | 7 | - [BetterAuth](./packages/polar-betterauth) 8 | - [Supabase](./packages/polar-supabase/) 9 | - [Deno](./packages//polar-deno/) 10 | - [Astro](./packages/polar-astro) 11 | - [Elysia](./packages/polar-elysia) 12 | - [Express](./packages/polar-express) 13 | - [Fastify](./packages/polar-fastify) 14 | - [Hono](./packages/polar-hono) 15 | - [Next.js](./packages/polar-nextjs) 16 | - [Nuxt](./packages/polar-nuxt) 17 | - [Remix](./packages/polar-remix) 18 | - [Sveltekit](./packages/polar-sveltekit) 19 | - [TanStack Start](./packages/polar-tanstack-start) 20 | 21 | 22 | ### Deploying Adapters 23 | 24 | 1. To deploy the adapters, you need to create a new changeset. You can do this by running and follow the instructions in the terminal: 25 | 26 | ```bash 27 | npx @changesets/cli 28 | ``` 29 | 30 | 2. After you have created the changeset, you should create a pull request to the main branch. 31 | 3. Once the pull request is merged, a new pull request will be created that will bump the version of the adapters. 32 | 4. Merge it to the main branch and the adapters will be published to npm. 33 | 34 | 35 | > [!WARNING] 36 | > Deno package is published to JSR registry, not npm. At the moment this is done manually. -------------------------------------------------------------------------------- /packages/polar-elysia/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polar-sh/elysia", 3 | "version": "0.5.3", 4 | "description": "Polar integration for Elysia", 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 | "engines": { 19 | "node": ">=18" 20 | }, 21 | "type": "module", 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 | "polar", 33 | "elysia", 34 | "payments", 35 | "subscriptions" 36 | ], 37 | "devDependencies": { 38 | "@biomejs/biome": "1.9.4", 39 | "@sindresorhus/tsconfig": "^7.0.0", 40 | "@types/node": "^20.0.0", 41 | "elysia": "^1.4.18", 42 | "memoirist": "^0.4.0", 43 | "prettier": "^3.7.4", 44 | "tsup": "^8.5.1", 45 | "vitest": "^2.1.8" 46 | }, 47 | "dependencies": { 48 | "@polar-sh/adapter-utils": "workspace:*", 49 | "@polar-sh/sdk": "^0.42.1" 50 | }, 51 | "peerDependencies": { 52 | "elysia": "^1.2.10" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/polar-astro/src/customerPortal/customerPortal.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | import type { APIRoute } from "astro"; 3 | 4 | export interface CustomerPortalConfig { 5 | accessToken?: string; 6 | getCustomerId: (req: Request) => Promise; 7 | returnUrl?: string; 8 | server?: "sandbox" | "production"; 9 | } 10 | 11 | export const CustomerPortal = ({ 12 | accessToken, 13 | server, 14 | getCustomerId, 15 | returnUrl, 16 | }: CustomerPortalConfig): APIRoute => { 17 | return async ({ request }) => { 18 | if (!accessToken) { 19 | const { getSecret } = await import("astro:env/server"); 20 | accessToken = getSecret("POLAR_ACCESS_TOKEN"); 21 | } 22 | 23 | const polar = new Polar({ 24 | accessToken, 25 | server, 26 | }); 27 | 28 | const customerId = await getCustomerId(request); 29 | 30 | if (!customerId) { 31 | return Response.json( 32 | { error: "customerId not defined" }, 33 | { status: 400 }, 34 | ); 35 | } 36 | 37 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 38 | 39 | try { 40 | const result = await polar.customerSessions.create({ 41 | customerId, 42 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 43 | }); 44 | 45 | return Response.redirect(result.customerPortalUrl); 46 | } catch (error) { 47 | console.error(error); 48 | return Response.json({ error: "Internal server error" }, { status: 500 }); 49 | } 50 | }; 51 | }; 52 | -------------------------------------------------------------------------------- /packages/polar-express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polar-sh/express", 3 | "version": "0.6.3", 4 | "description": "Polar integration for Express", 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 | "engines": { 19 | "node": ">=16" 20 | }, 21 | "type": "module", 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 | "polar", 33 | "express", 34 | "payments", 35 | "subscriptions" 36 | ], 37 | "peerDependencies": { 38 | "express": "^4.21.2" 39 | }, 40 | "devDependencies": { 41 | "@biomejs/biome": "1.9.4", 42 | "@sindresorhus/tsconfig": "^7.0.0", 43 | "@types/express": "^5.0.6", 44 | "@types/node": "^20.0.0", 45 | "@types/supertest": "^6.0.3", 46 | "express": "^4.21.2", 47 | "prettier": "^3.7.4", 48 | "supertest": "^7.1.4", 49 | "tsup": "^8.5.1", 50 | "vitest": "^2.1.8" 51 | }, 52 | "dependencies": { 53 | "@polar-sh/adapter-utils": "workspace:*", 54 | "@polar-sh/sdk": "^0.42.1" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/polar-sveltekit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polar-sh/sveltekit", 3 | "version": "0.7.3", 4 | "description": "Polar integration for SvelteKit", 5 | "main": "dist/index.js", 6 | "module": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "type": "module", 9 | "engines": { 10 | "node": ">=16" 11 | }, 12 | "scripts": { 13 | "dev": "pnpm watch", 14 | "preview": "vite preview", 15 | "prepublishOnly": "pnpm run package", 16 | "build": "pnpm run package", 17 | "package": "svelte-kit sync && svelte-package", 18 | "watch": "svelte-package --watch" 19 | }, 20 | "exports": { 21 | ".": { 22 | "types": "./dist/index.d.ts", 23 | "svelte": "./dist/index.js" 24 | } 25 | }, 26 | "files": [ 27 | "dist" 28 | ], 29 | "keywords": [ 30 | "polar", 31 | "sveltekit", 32 | "payments", 33 | "subscriptions" 34 | ], 35 | "peerDependencies": { 36 | "@sveltejs/kit": "^2.0.0" 37 | }, 38 | "devDependencies": { 39 | "@playwright/test": "^1.57.0", 40 | "@sveltejs/adapter-auto": "^3.3.1", 41 | "@sveltejs/kit": "^2.49.2", 42 | "@sveltejs/package": "^2.5.7", 43 | "@sveltejs/vite-plugin-svelte": "^5.0.3", 44 | "@types/node": "^20.0.0", 45 | "prettier": "^3.7.4", 46 | "svelte": "^5.45.8", 47 | "svelte-check": "4.3.4", 48 | "vite": "^6.0.7", 49 | "vite-plugin-dts": "^3.0.0" 50 | }, 51 | "dependencies": { 52 | "@polar-sh/adapter-utils": "workspace:*", 53 | "@polar-sh/sdk": "^0.42.1" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/eslint-config/next.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import eslintConfigPrettier from "eslint-config-prettier"; 3 | import tseslint from "typescript-eslint"; 4 | import pluginReactHooks from "eslint-plugin-react-hooks"; 5 | import pluginReact from "eslint-plugin-react"; 6 | import globals from "globals"; 7 | import pluginNext from "@next/eslint-plugin-next"; 8 | import { config as baseConfig } from "./base.js"; 9 | 10 | /** 11 | * A custom ESLint configuration for libraries that use Next.js. 12 | * 13 | * @type {import("eslint").Linter.Config} 14 | * */ 15 | export const nextJsConfig = [ 16 | ...baseConfig, 17 | js.configs.recommended, 18 | eslintConfigPrettier, 19 | ...tseslint.configs.recommended, 20 | { 21 | ...pluginReact.configs.flat.recommended, 22 | languageOptions: { 23 | ...pluginReact.configs.flat.recommended.languageOptions, 24 | globals: { 25 | ...globals.serviceworker, 26 | }, 27 | }, 28 | }, 29 | { 30 | plugins: { 31 | "@next/next": pluginNext, 32 | }, 33 | rules: { 34 | ...pluginNext.configs.recommended.rules, 35 | ...pluginNext.configs["core-web-vitals"].rules, 36 | }, 37 | }, 38 | { 39 | plugins: { 40 | "react-hooks": pluginReactHooks, 41 | }, 42 | settings: { react: { version: "detect" } }, 43 | rules: { 44 | ...pluginReactHooks.configs.recommended.rules, 45 | // React scope no longer necessary with new JSX transform. 46 | "react/react-in-jsx-scope": "off", 47 | }, 48 | }, 49 | ]; 50 | -------------------------------------------------------------------------------- /packages/polar-betterauth/src/client.ts: -------------------------------------------------------------------------------- 1 | import { PolarEmbedCheckout } from "@polar-sh/checkout/embed"; 2 | import type { PolarEmbedCheckout as PolarEmbedCheckoutType } from "@polar-sh/checkout/embed"; 3 | import type { BetterAuthClientPlugin } from "better-auth"; 4 | import type { BetterFetchOption } from "better-auth/client"; 5 | import type { CheckoutParams, polar } from "./index"; 6 | 7 | export type { PolarEmbedCheckoutType as PolarEmbedCheckout }; 8 | 9 | export const polarClient = () => { 10 | return { 11 | id: "polar-client", 12 | $InferServerPlugin: {} as ReturnType, 13 | getActions: ($fetch) => { 14 | return { 15 | checkoutEmbed: async ( 16 | data: Omit, 17 | fetchOptions?: BetterFetchOption, 18 | ): Promise => { 19 | const res = await $fetch("/checkout", { 20 | method: "POST", 21 | body: { 22 | ...data, 23 | redirect: false, 24 | embedOrigin: window.location.origin, 25 | }, 26 | ...fetchOptions, 27 | }); 28 | 29 | if (res.error) { 30 | throw new Error(res.error.message); 31 | } 32 | 33 | const checkout = res.data as { url: string }; 34 | 35 | const theme = 36 | (new URL(checkout.url).searchParams.get("theme") as 37 | | "light" 38 | | "dark" 39 | | undefined) ?? "light"; 40 | 41 | return await PolarEmbedCheckout.create(checkout.url, theme); 42 | }, 43 | }; 44 | }, 45 | } satisfies BetterAuthClientPlugin; 46 | }; 47 | -------------------------------------------------------------------------------- /packages/polar-betterauth/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { Polar } from "@polar-sh/sdk"; 2 | 3 | import type { UnionToIntersection, User } from "better-auth"; 4 | import type { checkout } from "./plugins/checkout"; 5 | import type { portal } from "./plugins/portal"; 6 | import type { usage } from "./plugins/usage"; 7 | import type { webhooks } from "./plugins/webhooks"; 8 | 9 | export type Product = { 10 | /** 11 | * Product Id from Polar Product 12 | */ 13 | productId: string; 14 | /** 15 | * Easily identifiable slug for the product 16 | */ 17 | slug: string; 18 | }; 19 | 20 | export type PolarPlugin = 21 | | ReturnType 22 | | ReturnType 23 | | ReturnType 24 | | ReturnType; 25 | 26 | export type PolarPlugins = [PolarPlugin, ...PolarPlugin[]]; 27 | 28 | export type PolarEndpoints = UnionToIntersection>; 29 | 30 | export interface PolarOptions { 31 | /** 32 | * Polar Client 33 | */ 34 | client: Polar; 35 | /** 36 | * Enable customer creation when a user signs up 37 | */ 38 | createCustomerOnSignUp?: boolean; 39 | /** 40 | * A custom function to get the customer create 41 | * params 42 | * @param data - data containing user and session 43 | * @returns 44 | */ 45 | getCustomerCreateParams?: ( 46 | data: { 47 | user: Partial; 48 | }, 49 | request?: Request, 50 | ) => Promise<{ 51 | metadata?: Record; 52 | }>; 53 | /** 54 | * Use Polar plugins 55 | */ 56 | use: PolarPlugins; 57 | } 58 | -------------------------------------------------------------------------------- /packages/polar-nuxt/src/runtime/server/customerPortalHandler.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | import { createError, sendRedirect } from "h3"; 3 | import type { H3Event } from "h3"; 4 | 5 | export interface CustomerPortalConfig { 6 | accessToken: string; 7 | server?: "sandbox" | "production"; 8 | getCustomerId: (event: H3Event) => Promise; 9 | returnUrl?: string; 10 | } 11 | 12 | export const CustomerPortal = ({ 13 | accessToken, 14 | server, 15 | getCustomerId, 16 | returnUrl, 17 | }: CustomerPortalConfig) => { 18 | return async (event: H3Event) => { 19 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 20 | 21 | const customerId = await getCustomerId(event); 22 | 23 | if (!customerId) { 24 | console.error( 25 | "Failed to redirect to customer portal, customerId not defined", 26 | ); 27 | throw createError({ 28 | statusCode: 400, 29 | message: "customerId not defined", 30 | }); 31 | } 32 | 33 | try { 34 | const polar = new Polar({ 35 | accessToken, 36 | server, 37 | }); 38 | 39 | const result = await polar.customerSessions.create({ 40 | customerId, 41 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 42 | }); 43 | 44 | return sendRedirect(event, result.customerPortalUrl); 45 | } catch (error) { 46 | console.error("Failed to redirect to customer portal", error); 47 | throw createError({ 48 | statusCode: 500, 49 | statusMessage: (error as Error).message, 50 | message: (error as Error).message ?? "Internal server error", 51 | }); 52 | } 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /packages/polar-astro/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | dist 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | .pnpm-debug.log* 16 | 17 | .svelte-kit 18 | 19 | # Diagnostic reports (https://nodejs.org/api/report.html) 20 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 21 | 22 | # Runtime data 23 | pids 24 | *.pid 25 | *.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | coverage 33 | *.lcov 34 | 35 | # nyc test coverage 36 | .nyc_output 37 | 38 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 39 | .grunt 40 | 41 | # Bower dependency directory (https://bower.io/) 42 | bower_components 43 | 44 | # node-waf configuration 45 | .lock-wscript 46 | 47 | # Compiled binary addons (https://nodejs.org/api/addons.html) 48 | build/Release 49 | 50 | # Dependency directories 51 | node_modules/ 52 | jspm_packages/ 53 | 54 | # Snowpack dependency directory (https://snowpack.dev/) 55 | web_modules/ 56 | 57 | # TypeScript cache 58 | *.tsbuildinfo 59 | 60 | # Optional npm cache directory 61 | .npm 62 | 63 | # Optional eslint cache 64 | .eslintcache 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variable files 73 | .env 74 | .env.development.local 75 | .env.test.local 76 | .env.production.local 77 | .env.local 78 | 79 | sites/docs/.vercel 80 | packages/formsnap/doc 81 | docs/.velite -------------------------------------------------------------------------------- /packages/polar-elysia/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | dist 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | .pnpm-debug.log* 16 | 17 | .svelte-kit 18 | 19 | # Diagnostic reports (https://nodejs.org/api/report.html) 20 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 21 | 22 | # Runtime data 23 | pids 24 | *.pid 25 | *.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | coverage 33 | *.lcov 34 | 35 | # nyc test coverage 36 | .nyc_output 37 | 38 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 39 | .grunt 40 | 41 | # Bower dependency directory (https://bower.io/) 42 | bower_components 43 | 44 | # node-waf configuration 45 | .lock-wscript 46 | 47 | # Compiled binary addons (https://nodejs.org/api/addons.html) 48 | build/Release 49 | 50 | # Dependency directories 51 | node_modules/ 52 | jspm_packages/ 53 | 54 | # Snowpack dependency directory (https://snowpack.dev/) 55 | web_modules/ 56 | 57 | # TypeScript cache 58 | *.tsbuildinfo 59 | 60 | # Optional npm cache directory 61 | .npm 62 | 63 | # Optional eslint cache 64 | .eslintcache 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variable files 73 | .env 74 | .env.development.local 75 | .env.test.local 76 | .env.production.local 77 | .env.local 78 | 79 | sites/docs/.vercel 80 | packages/formsnap/doc 81 | docs/.velite -------------------------------------------------------------------------------- /packages/polar-hono/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | dist 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | .pnpm-debug.log* 16 | 17 | .svelte-kit 18 | 19 | # Diagnostic reports (https://nodejs.org/api/report.html) 20 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 21 | 22 | # Runtime data 23 | pids 24 | *.pid 25 | *.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | coverage 33 | *.lcov 34 | 35 | # nyc test coverage 36 | .nyc_output 37 | 38 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 39 | .grunt 40 | 41 | # Bower dependency directory (https://bower.io/) 42 | bower_components 43 | 44 | # node-waf configuration 45 | .lock-wscript 46 | 47 | # Compiled binary addons (https://nodejs.org/api/addons.html) 48 | build/Release 49 | 50 | # Dependency directories 51 | node_modules/ 52 | jspm_packages/ 53 | 54 | # Snowpack dependency directory (https://snowpack.dev/) 55 | web_modules/ 56 | 57 | # TypeScript cache 58 | *.tsbuildinfo 59 | 60 | # Optional npm cache directory 61 | .npm 62 | 63 | # Optional eslint cache 64 | .eslintcache 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variable files 73 | .env 74 | .env.development.local 75 | .env.test.local 76 | .env.production.local 77 | .env.local 78 | 79 | sites/docs/.vercel 80 | packages/formsnap/doc 81 | docs/.velite -------------------------------------------------------------------------------- /packages/polar-remix/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | dist 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | .pnpm-debug.log* 16 | 17 | .svelte-kit 18 | 19 | # Diagnostic reports (https://nodejs.org/api/report.html) 20 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 21 | 22 | # Runtime data 23 | pids 24 | *.pid 25 | *.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | coverage 33 | *.lcov 34 | 35 | # nyc test coverage 36 | .nyc_output 37 | 38 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 39 | .grunt 40 | 41 | # Bower dependency directory (https://bower.io/) 42 | bower_components 43 | 44 | # node-waf configuration 45 | .lock-wscript 46 | 47 | # Compiled binary addons (https://nodejs.org/api/addons.html) 48 | build/Release 49 | 50 | # Dependency directories 51 | node_modules/ 52 | jspm_packages/ 53 | 54 | # Snowpack dependency directory (https://snowpack.dev/) 55 | web_modules/ 56 | 57 | # TypeScript cache 58 | *.tsbuildinfo 59 | 60 | # Optional npm cache directory 61 | .npm 62 | 63 | # Optional eslint cache 64 | .eslintcache 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variable files 73 | .env 74 | .env.development.local 75 | .env.test.local 76 | .env.production.local 77 | .env.local 78 | 79 | sites/docs/.vercel 80 | packages/formsnap/doc 81 | docs/.velite -------------------------------------------------------------------------------- /packages/polar-betterauth/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | dist 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | .pnpm-debug.log* 16 | 17 | .svelte-kit 18 | 19 | # Diagnostic reports (https://nodejs.org/api/report.html) 20 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 21 | 22 | # Runtime data 23 | pids 24 | *.pid 25 | *.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | coverage 33 | *.lcov 34 | 35 | # nyc test coverage 36 | .nyc_output 37 | 38 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 39 | .grunt 40 | 41 | # Bower dependency directory (https://bower.io/) 42 | bower_components 43 | 44 | # node-waf configuration 45 | .lock-wscript 46 | 47 | # Compiled binary addons (https://nodejs.org/api/addons.html) 48 | build/Release 49 | 50 | # Dependency directories 51 | node_modules/ 52 | jspm_packages/ 53 | 54 | # Snowpack dependency directory (https://snowpack.dev/) 55 | web_modules/ 56 | 57 | # TypeScript cache 58 | *.tsbuildinfo 59 | 60 | # Optional npm cache directory 61 | .npm 62 | 63 | # Optional eslint cache 64 | .eslintcache 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variable files 73 | .env 74 | .env.development.local 75 | .env.test.local 76 | .env.production.local 77 | .env.local 78 | 79 | sites/docs/.vercel 80 | packages/formsnap/doc 81 | docs/.velite -------------------------------------------------------------------------------- /packages/polar-express/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | dist 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | .pnpm-debug.log* 16 | 17 | .svelte-kit 18 | 19 | # Diagnostic reports (https://nodejs.org/api/report.html) 20 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 21 | 22 | # Runtime data 23 | pids 24 | *.pid 25 | *.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | coverage 33 | *.lcov 34 | 35 | # nyc test coverage 36 | .nyc_output 37 | 38 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 39 | .grunt 40 | 41 | # Bower dependency directory (https://bower.io/) 42 | bower_components 43 | 44 | # node-waf configuration 45 | .lock-wscript 46 | 47 | # Compiled binary addons (https://nodejs.org/api/addons.html) 48 | build/Release 49 | 50 | # Dependency directories 51 | node_modules/ 52 | jspm_packages/ 53 | 54 | # Snowpack dependency directory (https://snowpack.dev/) 55 | web_modules/ 56 | 57 | # TypeScript cache 58 | *.tsbuildinfo 59 | 60 | # Optional npm cache directory 61 | .npm 62 | 63 | # Optional eslint cache 64 | .eslintcache 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variable files 73 | .env 74 | .env.development.local 75 | .env.test.local 76 | .env.production.local 77 | .env.local 78 | 79 | sites/docs/.vercel 80 | packages/formsnap/doc 81 | docs/.velite -------------------------------------------------------------------------------- /packages/polar-fastify/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | dist 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | .pnpm-debug.log* 16 | 17 | .svelte-kit 18 | 19 | # Diagnostic reports (https://nodejs.org/api/report.html) 20 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 21 | 22 | # Runtime data 23 | pids 24 | *.pid 25 | *.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | coverage 33 | *.lcov 34 | 35 | # nyc test coverage 36 | .nyc_output 37 | 38 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 39 | .grunt 40 | 41 | # Bower dependency directory (https://bower.io/) 42 | bower_components 43 | 44 | # node-waf configuration 45 | .lock-wscript 46 | 47 | # Compiled binary addons (https://nodejs.org/api/addons.html) 48 | build/Release 49 | 50 | # Dependency directories 51 | node_modules/ 52 | jspm_packages/ 53 | 54 | # Snowpack dependency directory (https://snowpack.dev/) 55 | web_modules/ 56 | 57 | # TypeScript cache 58 | *.tsbuildinfo 59 | 60 | # Optional npm cache directory 61 | .npm 62 | 63 | # Optional eslint cache 64 | .eslintcache 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variable files 73 | .env 74 | .env.development.local 75 | .env.test.local 76 | .env.production.local 77 | .env.local 78 | 79 | sites/docs/.vercel 80 | packages/formsnap/doc 81 | docs/.velite -------------------------------------------------------------------------------- /packages/polar-nextjs/example/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /packages/polar-sveltekit/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | dist 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | .pnpm-debug.log* 16 | 17 | .svelte-kit 18 | 19 | # Diagnostic reports (https://nodejs.org/api/report.html) 20 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 21 | 22 | # Runtime data 23 | pids 24 | *.pid 25 | *.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | coverage 33 | *.lcov 34 | 35 | # nyc test coverage 36 | .nyc_output 37 | 38 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 39 | .grunt 40 | 41 | # Bower dependency directory (https://bower.io/) 42 | bower_components 43 | 44 | # node-waf configuration 45 | .lock-wscript 46 | 47 | # Compiled binary addons (https://nodejs.org/api/addons.html) 48 | build/Release 49 | 50 | # Dependency directories 51 | node_modules/ 52 | jspm_packages/ 53 | 54 | # Snowpack dependency directory (https://snowpack.dev/) 55 | web_modules/ 56 | 57 | # TypeScript cache 58 | *.tsbuildinfo 59 | 60 | # Optional npm cache directory 61 | .npm 62 | 63 | # Optional eslint cache 64 | .eslintcache 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variable files 73 | .env 74 | .env.development.local 75 | .env.test.local 76 | .env.production.local 77 | .env.local 78 | 79 | sites/docs/.vercel 80 | packages/formsnap/doc 81 | docs/.velite -------------------------------------------------------------------------------- /packages/polar-betterauth/example/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /packages/polar-nextjs/src/webhooks/webhooks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type WebhooksConfig, 3 | handleWebhookPayload, 4 | } from "@polar-sh/adapter-utils"; 5 | import { 6 | WebhookVerificationError, 7 | validateEvent, 8 | } from "@polar-sh/sdk/webhooks"; 9 | import { type NextRequest, NextResponse } from "next/server"; 10 | 11 | export { 12 | type EntitlementContext, 13 | type EntitlementHandler, 14 | type EntitlementProperties, 15 | EntitlementStrategy, 16 | Entitlements, 17 | } from "@polar-sh/adapter-utils"; 18 | 19 | export const Webhooks = ({ 20 | webhookSecret, 21 | entitlements, 22 | onPayload, 23 | ...eventHandlers 24 | }: WebhooksConfig) => { 25 | return async (request: NextRequest) => { 26 | const requestBody = await request.text(); 27 | 28 | const webhookHeaders = { 29 | "webhook-id": request.headers.get("webhook-id") ?? "", 30 | "webhook-timestamp": request.headers.get("webhook-timestamp") ?? "", 31 | "webhook-signature": request.headers.get("webhook-signature") ?? "", 32 | }; 33 | 34 | let webhookPayload: ReturnType; 35 | try { 36 | webhookPayload = validateEvent( 37 | requestBody, 38 | webhookHeaders, 39 | webhookSecret, 40 | ); 41 | } catch (error) { 42 | if (error instanceof WebhookVerificationError) { 43 | return NextResponse.json({ received: false }, { status: 403 }); 44 | } 45 | 46 | throw error; 47 | } 48 | 49 | await handleWebhookPayload(webhookPayload, { 50 | webhookSecret, 51 | entitlements, 52 | onPayload, 53 | ...eventHandlers, 54 | }); 55 | 56 | return NextResponse.json({ received: true }); 57 | }; 58 | }; 59 | -------------------------------------------------------------------------------- /packages/polar-elysia/src/webhooks/webhooks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type WebhooksConfig, 3 | handleWebhookPayload, 4 | } from "@polar-sh/adapter-utils"; 5 | import { 6 | WebhookVerificationError, 7 | validateEvent, 8 | } from "@polar-sh/sdk/webhooks"; 9 | import { type Context, status } from "elysia"; 10 | import type { InlineHandler } from "elysia/types"; 11 | 12 | export { 13 | type EntitlementContext, 14 | type EntitlementHandler, 15 | type EntitlementProperties, 16 | EntitlementStrategy, 17 | Entitlements, 18 | } from "@polar-sh/adapter-utils"; 19 | 20 | export const Webhooks = ({ 21 | webhookSecret, 22 | onPayload, 23 | entitlements, 24 | ...eventHandlers 25 | }: WebhooksConfig): InlineHandler => { 26 | return async (ctx: Context) => { 27 | const requestBody = await ctx.request.text(); 28 | 29 | const webhookHeaders: Record = { 30 | "webhook-id": ctx.request.headers.get("webhook-id") ?? "", 31 | "webhook-timestamp": ctx.request.headers.get("webhook-timestamp") ?? "", 32 | "webhook-signature": ctx.request.headers.get("webhook-signature") ?? "", 33 | }; 34 | 35 | let webhookPayload: ReturnType; 36 | try { 37 | webhookPayload = validateEvent( 38 | requestBody, 39 | webhookHeaders, 40 | webhookSecret, 41 | ); 42 | } catch (error) { 43 | console.log(error); 44 | if (error instanceof WebhookVerificationError) { 45 | return status(400, { received: false }); 46 | } 47 | 48 | return status(500, { error: "Internal server error" }); 49 | } 50 | 51 | await handleWebhookPayload(webhookPayload, { 52 | webhookSecret, 53 | entitlements, 54 | onPayload, 55 | ...eventHandlers, 56 | }); 57 | 58 | return { received: true }; 59 | }; 60 | }; 61 | -------------------------------------------------------------------------------- /packages/polar-express/src/webhooks/webhooks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type WebhooksConfig, 3 | handleWebhookPayload, 4 | } from "@polar-sh/adapter-utils"; 5 | import { 6 | WebhookVerificationError, 7 | validateEvent, 8 | } from "@polar-sh/sdk/webhooks"; 9 | import type { Request, RequestHandler, Response } from "express"; 10 | 11 | export { 12 | type EntitlementContext, 13 | type EntitlementHandler, 14 | type EntitlementProperties, 15 | EntitlementStrategy, 16 | Entitlements, 17 | } from "@polar-sh/adapter-utils"; 18 | 19 | export const Webhooks = ({ 20 | webhookSecret, 21 | onPayload, 22 | entitlements, 23 | ...eventHandlers 24 | }: WebhooksConfig): RequestHandler => { 25 | return async (req: Request, res: Response) => { 26 | const requestBody = JSON.stringify(req.body); 27 | 28 | const webhookHeaders: Record = { 29 | "webhook-id": req.headers["webhook-id"] as string, 30 | "webhook-timestamp": req.headers["webhook-timestamp"] as string, 31 | "webhook-signature": req.headers["webhook-signature"] as string, 32 | }; 33 | 34 | let webhookPayload: ReturnType; 35 | try { 36 | webhookPayload = validateEvent( 37 | requestBody, 38 | webhookHeaders, 39 | webhookSecret, 40 | ); 41 | } catch (error) { 42 | console.log(error); 43 | if (error instanceof WebhookVerificationError) { 44 | res.status(403).json({ received: false }); 45 | return; 46 | } 47 | 48 | res.status(500).json({ error: "Internal server error" }); 49 | return; 50 | } 51 | 52 | await handleWebhookPayload(webhookPayload, { 53 | webhookSecret, 54 | entitlements, 55 | onPayload, 56 | ...eventHandlers, 57 | }); 58 | 59 | res.json({ received: true }); 60 | }; 61 | }; 62 | -------------------------------------------------------------------------------- /packages/polar-supabase/src/webhooks/webhooks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type WebhooksConfig, 3 | handleWebhookPayload, 4 | } from "@polar-sh/adapter-utils"; 5 | import { 6 | WebhookVerificationError, 7 | validateEvent, 8 | } from "@polar-sh/sdk/webhooks"; 9 | 10 | export { 11 | type EntitlementContext, 12 | type EntitlementHandler, 13 | type EntitlementProperties, 14 | EntitlementStrategy, 15 | Entitlements, 16 | } from "@polar-sh/adapter-utils"; 17 | 18 | export const Webhooks = ({ 19 | webhookSecret, 20 | entitlements, 21 | onPayload, 22 | ...eventHandlers 23 | }: WebhooksConfig) => { 24 | return async (request: Request) => { 25 | const requestBody = await request.text(); 26 | 27 | const webhookHeaders = { 28 | "webhook-id": request.headers.get("webhook-id") ?? "", 29 | "webhook-timestamp": request.headers.get("webhook-timestamp") ?? "", 30 | "webhook-signature": request.headers.get("webhook-signature") ?? "", 31 | }; 32 | 33 | let webhookPayload: ReturnType; 34 | try { 35 | webhookPayload = validateEvent( 36 | requestBody, 37 | webhookHeaders, 38 | webhookSecret, 39 | ); 40 | } catch (error) { 41 | if (error instanceof WebhookVerificationError) { 42 | return new Response(JSON.stringify({ received: false }), { 43 | status: 403, 44 | headers: { "Content-Type": "application/json" }, 45 | }); 46 | } 47 | 48 | throw error; 49 | } 50 | 51 | await handleWebhookPayload(webhookPayload, { 52 | webhookSecret, 53 | entitlements, 54 | onPayload, 55 | ...eventHandlers, 56 | }); 57 | 58 | return new Response(JSON.stringify({ received: true }), { 59 | status: 200, 60 | headers: { "Content-Type": "application/json" }, 61 | }); 62 | }; 63 | }; 64 | -------------------------------------------------------------------------------- /packages/polar-tanstack-start/src/webhooks/webhooks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type WebhooksConfig, 3 | handleWebhookPayload, 4 | } from "@polar-sh/adapter-utils"; 5 | import { 6 | WebhookVerificationError, 7 | validateEvent, 8 | } from "@polar-sh/sdk/webhooks"; 9 | // @ts-expect-error - TODO: fix this 10 | import type { StartAPIMethodCallback } from "@tanstack/react-start/api"; 11 | 12 | export { 13 | EntitlementStrategy, 14 | Entitlements, 15 | type EntitlementContext, 16 | type EntitlementHandler, 17 | type EntitlementProperties, 18 | } from "@polar-sh/adapter-utils"; 19 | 20 | export const Webhooks = ({ 21 | webhookSecret, 22 | entitlements, 23 | onPayload, 24 | ...eventHandlers 25 | }: WebhooksConfig): StartAPIMethodCallback => { 26 | // @ts-expect-error - TODO: fix this 27 | return async ({ request }) => { 28 | const requestBody = await request.text(); 29 | 30 | const webhookHeaders = { 31 | "webhook-id": request.headers.get("webhook-id") ?? "", 32 | "webhook-timestamp": request.headers.get("webhook-timestamp") ?? "", 33 | "webhook-signature": request.headers.get("webhook-signature") ?? "", 34 | }; 35 | 36 | let webhookPayload: ReturnType; 37 | try { 38 | webhookPayload = validateEvent( 39 | requestBody, 40 | webhookHeaders, 41 | webhookSecret, 42 | ); 43 | } catch (error) { 44 | if (error instanceof WebhookVerificationError) { 45 | return Response.json({ received: false }, { status: 403 }); 46 | } 47 | 48 | throw error; 49 | } 50 | 51 | await handleWebhookPayload(webhookPayload, { 52 | webhookSecret, 53 | entitlements, 54 | onPayload, 55 | ...eventHandlers, 56 | }); 57 | 58 | return Response.json({ received: true }); 59 | }; 60 | }; 61 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/src/components/Register.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { signUp } from "@/lib/auth-client"; 4 | import { useState } from "react"; 5 | 6 | export const Register = () => { 7 | const [name, setName] = useState(""); 8 | const [email, setEmail] = useState(""); 9 | const [password, setPassword] = useState(""); 10 | 11 | const handleRegister = async () => { 12 | await signUp.email({ 13 | email, 14 | password, 15 | name, 16 | }); 17 | }; 18 | 19 | return ( 20 |
21 |

Register

22 | setName(e.target.value)} 31 | /> 32 | setEmail(e.target.value)} 41 | /> 42 | setPassword(e.target.value)} 51 | /> 52 | 55 |
56 | ); 57 | }; 58 | -------------------------------------------------------------------------------- /packages/polar-fastify/src/webhooks/webhooks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type WebhooksConfig, 3 | handleWebhookPayload, 4 | } from "@polar-sh/adapter-utils"; 5 | import { 6 | WebhookVerificationError, 7 | validateEvent, 8 | } from "@polar-sh/sdk/webhooks"; 9 | import type { FastifyReply, FastifyRequest, RouteHandler } from "fastify"; 10 | 11 | export { 12 | type EntitlementContext, 13 | type EntitlementHandler, 14 | type EntitlementProperties, 15 | EntitlementStrategy, 16 | Entitlements, 17 | } from "@polar-sh/adapter-utils"; 18 | 19 | export const Webhooks = ({ 20 | webhookSecret, 21 | onPayload, 22 | entitlements, 23 | ...eventHandlers 24 | }: WebhooksConfig): RouteHandler => { 25 | return async (request: FastifyRequest, reply: FastifyReply) => { 26 | const requestBody = 27 | typeof request.body === "string" 28 | ? request.body 29 | : JSON.stringify(request.body); 30 | 31 | const webhookHeaders: Record = { 32 | "webhook-id": request.headers["webhook-id"] as string, 33 | "webhook-timestamp": request.headers["webhook-timestamp"] as string, 34 | "webhook-signature": request.headers["webhook-signature"] as string, 35 | }; 36 | 37 | let webhookPayload: ReturnType; 38 | try { 39 | webhookPayload = validateEvent( 40 | requestBody, 41 | webhookHeaders, 42 | webhookSecret, 43 | ); 44 | } catch (error) { 45 | console.log(error); 46 | if (error instanceof WebhookVerificationError) { 47 | return reply.status(400).send({ received: false }); 48 | } 49 | 50 | return reply.status(500).send({ error: "Internal server error" }); 51 | } 52 | 53 | await handleWebhookPayload(webhookPayload, { 54 | webhookSecret, 55 | entitlements, 56 | onPayload, 57 | ...eventHandlers, 58 | }); 59 | 60 | return { received: true }; 61 | }; 62 | }; 63 | -------------------------------------------------------------------------------- /packages/polar-astro/src/webhooks/webhooks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type WebhooksConfig, 3 | handleWebhookPayload, 4 | } from "@polar-sh/adapter-utils"; 5 | import { 6 | WebhookVerificationError, 7 | validateEvent, 8 | } from "@polar-sh/sdk/webhooks"; 9 | import type { APIRoute } from "astro"; 10 | 11 | export { 12 | type EntitlementContext, 13 | type EntitlementHandler, 14 | type EntitlementProperties, 15 | EntitlementStrategy, 16 | Entitlements, 17 | } from "@polar-sh/adapter-utils"; 18 | 19 | export const Webhooks = ({ 20 | webhookSecret, 21 | onPayload, 22 | entitlements, 23 | ...eventHandlers 24 | }: WebhooksConfig): APIRoute => { 25 | return async ({ request }) => { 26 | if (request.method !== "POST") { 27 | return Response.json({ message: "Method not allowed" }, { status: 405 }); 28 | } 29 | 30 | const requestBody = await request.text(); 31 | 32 | const webhookHeaders: Record = { 33 | "webhook-id": request.headers.get("webhook-id") ?? "", 34 | "webhook-timestamp": request.headers.get("webhook-timestamp") ?? "", 35 | "webhook-signature": request.headers.get("webhook-signature") ?? "", 36 | }; 37 | 38 | let webhookPayload: ReturnType; 39 | try { 40 | webhookPayload = validateEvent( 41 | requestBody, 42 | webhookHeaders, 43 | webhookSecret, 44 | ); 45 | } catch (error) { 46 | console.log(error); 47 | if (error instanceof WebhookVerificationError) { 48 | return Response.json({ received: false }, { status: 403 }); 49 | } 50 | 51 | return Response.json({ error: "Internal server error" }, { status: 500 }); 52 | } 53 | 54 | await handleWebhookPayload(webhookPayload, { 55 | webhookSecret, 56 | entitlements, 57 | onPayload, 58 | ...eventHandlers, 59 | }); 60 | 61 | return Response.json({ received: true }); 62 | }; 63 | }; 64 | -------------------------------------------------------------------------------- /packages/polar-remix/src/webhooks/webhooks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type WebhooksConfig, 3 | handleWebhookPayload, 4 | } from "@polar-sh/adapter-utils"; 5 | import { 6 | WebhookVerificationError, 7 | validateEvent, 8 | } from "@polar-sh/sdk/webhooks"; 9 | import type { ActionFunction } from "../types"; 10 | 11 | export { 12 | type EntitlementContext, 13 | type EntitlementHandler, 14 | type EntitlementProperties, 15 | EntitlementStrategy, 16 | Entitlements, 17 | } from "@polar-sh/adapter-utils"; 18 | 19 | export const Webhooks = ({ 20 | webhookSecret, 21 | onPayload, 22 | entitlements, 23 | ...eventHandlers 24 | }: WebhooksConfig): ActionFunction => { 25 | return async ({ request }) => { 26 | if (request.method !== "POST") { 27 | return Response.json({ message: "Method not allowed" }, { status: 405 }); 28 | } 29 | 30 | const requestBody = await request.text(); 31 | 32 | const webhookHeaders: Record = { 33 | "webhook-id": request.headers.get("webhook-id") ?? "", 34 | "webhook-timestamp": request.headers.get("webhook-timestamp") ?? "", 35 | "webhook-signature": request.headers.get("webhook-signature") ?? "", 36 | }; 37 | 38 | let webhookPayload: ReturnType; 39 | try { 40 | webhookPayload = validateEvent( 41 | requestBody, 42 | webhookHeaders, 43 | webhookSecret, 44 | ); 45 | } catch (error) { 46 | console.log(error); 47 | if (error instanceof WebhookVerificationError) { 48 | return Response.json({ received: false }, { status: 403 }); 49 | } 50 | 51 | return Response.json({ error: "Internal server error" }, { status: 500 }); 52 | } 53 | 54 | await handleWebhookPayload(webhookPayload, { 55 | webhookSecret, 56 | entitlements, 57 | onPayload, 58 | ...eventHandlers, 59 | }); 60 | 61 | return Response.json({ received: true }); 62 | }; 63 | }; 64 | -------------------------------------------------------------------------------- /packages/polar-express/src/webhooks/webhooks.test.ts: -------------------------------------------------------------------------------- 1 | vi.mock("@polar-sh/sdk/webhooks", async (importOriginal) => { 2 | return { 3 | ...(await importOriginal()), 4 | WebhookVerificationError: vi.fn(), 5 | validateEvent: vi.fn((v) => JSON.parse(v)), 6 | }; 7 | }); 8 | 9 | import express from "express"; 10 | import supertest from "supertest"; 11 | import { describe, expect, it, vi } from "vitest"; 12 | import { Webhooks } from "./webhooks"; 13 | 14 | describe("Webhooks middleware", () => { 15 | it("should call onPayload with the payload", async () => { 16 | const app = express(); 17 | const mockOnPayload = vi.fn(); 18 | 19 | app.use(express.json()).post( 20 | "*", 21 | Webhooks({ 22 | webhookSecret: "mock-secret", 23 | onPayload: mockOnPayload, 24 | }), 25 | ); 26 | 27 | const payload = { event: "mock-event", data: "mock-data" }; 28 | 29 | const res = await supertest(app) 30 | .post("/") 31 | .send(payload) 32 | .set("webhook-id", "mock-id") 33 | .set("webhook-timestamp", "mock-timestamp") 34 | .set("webhook-signature", "mock-signature"); 35 | expect(res.status).toBe(200); 36 | expect(mockOnPayload).toHaveBeenCalledWith(payload); 37 | }); 38 | 39 | it("should acknowledge the webhook", async () => { 40 | const app = express(); 41 | const mockOnPayload = vi.fn(); 42 | 43 | app.use(express.json()).post( 44 | "*", 45 | Webhooks({ 46 | webhookSecret: "mock-secret", 47 | onPayload: mockOnPayload, 48 | }), 49 | ); 50 | 51 | const payload = { event: "mock-event", data: "mock-data" }; 52 | 53 | const res = await supertest(app) 54 | .post("/") 55 | .send(payload) 56 | .set("webhook-id", "mock-id") 57 | .set("webhook-timestamp", "mock-timestamp") 58 | .set("webhook-signature", "mock-signature"); 59 | expect(res.status).toBe(200); 60 | expect(res.body).toEqual({ received: true }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /packages/polar-express/src/checkout/checkout.test.ts: -------------------------------------------------------------------------------- 1 | // Define mock values at the top level 2 | const mockCustomerPortalUrl = "https://mock-customer-portal-url.com/"; 3 | const mockCheckoutUrl = "https://mock-checkout-url.com/"; 4 | const mockSessionCreate = vi 5 | .fn() 6 | .mockResolvedValue({ customerPortalUrl: mockCustomerPortalUrl }); 7 | const mockCheckoutCreate = vi.fn(() => ({ url: mockCheckoutUrl })); 8 | 9 | // Mock the module before any imports 10 | vi.mock("@polar-sh/sdk", async (importOriginal) => { 11 | class Polar { 12 | customerSessions = { 13 | create: mockSessionCreate, 14 | }; 15 | 16 | checkouts = { 17 | create: mockCheckoutCreate, 18 | }; 19 | } 20 | 21 | return { 22 | ...(await importOriginal()), 23 | Polar, 24 | }; 25 | }); 26 | 27 | import express from "express"; 28 | import supertest from "supertest"; 29 | import { describe, it, vi } from "vitest"; 30 | import { Checkout } from "./checkout"; 31 | 32 | describe("Checkout middleware", () => { 33 | it("should redirect to checkout when products is valid", async () => { 34 | const app = express(); 35 | app.get( 36 | "/", 37 | Checkout({ 38 | accessToken: "mock-access-token", 39 | }), 40 | ); 41 | 42 | supertest(app) 43 | .get("/?products=mock-product-id") 44 | .expect(302) 45 | .expect("location", mockCheckoutUrl) 46 | .end((err) => { 47 | if (err) { 48 | throw err; 49 | } 50 | }); 51 | }); 52 | 53 | it("should return 400 when products is not defined", async () => { 54 | const app = express(); 55 | app.get( 56 | "/", 57 | Checkout({ 58 | accessToken: "mock-access-token", 59 | }), 60 | ); 61 | 62 | supertest(app) 63 | .get("/") 64 | .expect(400) 65 | .expect({ 66 | error: "Missing products in query params", 67 | }) 68 | .end((err) => { 69 | if (err) { 70 | throw err; 71 | } 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /packages/polar-nuxt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polar-sh/nuxt", 3 | "version": "0.5.3", 4 | "description": "Polar.sh integration for Nuxt", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/polar-sh/polar.git" 8 | }, 9 | "license": "MIT", 10 | "type": "module", 11 | "exports": { 12 | ".": { 13 | "types": "./dist/types.d.ts", 14 | "import": "./dist/module.mjs", 15 | "require": "./dist/module.cjs" 16 | } 17 | }, 18 | "main": "./dist/module.cjs", 19 | "types": "./dist/types.d.ts", 20 | "files": [ 21 | "dist" 22 | ], 23 | "scripts": { 24 | "build": "nuxt-module-build build", 25 | "prepare": "nuxt-module-build prepare", 26 | "dev": "nuxi dev playground", 27 | "dev:build": "nuxi build playground", 28 | "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground", 29 | "release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags", 30 | "lint": "eslint .", 31 | "test": "vitest", 32 | "test:watch": "vitest watch", 33 | "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit", 34 | "check": "biome check --write ./src" 35 | }, 36 | "dependencies": { 37 | "@nuxt/kit": "^3.15.4", 38 | "@polar-sh/adapter-utils": "workspace:*", 39 | "@polar-sh/sdk": "^0.42.1" 40 | }, 41 | "devDependencies": { 42 | "@biomejs/biome": "1.9.4", 43 | "@nuxt/devtools": "^2.6.5", 44 | "@nuxt/eslint-config": "^1.11.0", 45 | "@nuxt/module-builder": "^0.8.4", 46 | "@nuxt/schema": "^3.15.4", 47 | "@nuxt/test-utils": "^3.21.0", 48 | "@types/node": "latest", 49 | "changelogen": "^0.6.2", 50 | "eslint": "^9.39.1", 51 | "nuxt": "^3.15.4", 52 | "typescript": "5.9.3", 53 | "vitest": "^3.2.4", 54 | "vue-tsc": "2.1.6", 55 | "zod": "^3.24.2" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/polar-fastify/src/checkout/checkout.test.ts: -------------------------------------------------------------------------------- 1 | // Define mock values at the top level 2 | const mockCustomerPortalUrl = "https://mock-customer-portal-url.com"; 3 | const mockCheckoutUrl = "https://mock-checkout-url.com/"; 4 | const mockSessionCreate = vi 5 | .fn() 6 | .mockResolvedValue({ customerPortalUrl: mockCustomerPortalUrl }); 7 | const mockCheckoutCreate = vi.fn(() => ({ url: mockCheckoutUrl })); 8 | 9 | // Mock the module before any imports 10 | vi.mock("@polar-sh/sdk", async (importOriginal) => { 11 | class Polar { 12 | customerSessions = { 13 | create: mockSessionCreate, 14 | }; 15 | 16 | checkouts = { 17 | create: mockCheckoutCreate, 18 | }; 19 | } 20 | 21 | return { 22 | ...(await importOriginal()), 23 | Polar, 24 | }; 25 | }); 26 | 27 | import fastify from "fastify"; 28 | import { describe, expect, it, vi } from "vitest"; 29 | import { Checkout } from "./checkout"; 30 | 31 | describe("Checkout middleware", () => { 32 | it("should redirect to checkout when products is valid", async () => { 33 | const app = fastify(); 34 | app.get( 35 | "/", 36 | Checkout({ 37 | accessToken: "mock-access-token", 38 | }), 39 | ); 40 | 41 | const response = await app.inject({ 42 | url: "http://localhost/?products=mock-product-id", 43 | method: "GET", 44 | }); 45 | 46 | expect(response.statusCode).toBe(302); 47 | expect(response.headers["location"]).toBe(mockCheckoutUrl); 48 | }); 49 | 50 | it("should return 400 when products is not defined", async () => { 51 | const app = fastify(); 52 | app.get( 53 | "/", 54 | Checkout({ 55 | accessToken: "mock-access-token", 56 | }), 57 | ); 58 | 59 | const response = await app.inject({ 60 | url: "http://localhost/", 61 | method: "GET", 62 | }); 63 | 64 | expect(response.statusCode).toBe(400); 65 | expect(response.json()).toEqual({ 66 | error: "Missing products in query params", 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /packages/polar-betterauth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polar-sh/better-auth", 3 | "version": "1.6.3", 4 | "description": "Polar integration for better-auth", 5 | "main": "./dist/index.cjs", 6 | "module": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./dist/index.d.ts", 12 | "default": "./dist/index.js" 13 | }, 14 | "require": { 15 | "types": "./dist/index.d.cts", 16 | "default": "./dist/index.cjs" 17 | } 18 | }, 19 | "./client": { 20 | "import": { 21 | "types": "./dist/client.d.ts", 22 | "default": "./dist/client.js" 23 | }, 24 | "require": { 25 | "types": "./dist/client.d.cts", 26 | "default": "./dist/client.cjs" 27 | } 28 | } 29 | }, 30 | "type": "module", 31 | "engines": { 32 | "node": ">=16" 33 | }, 34 | "scripts": { 35 | "test": "vitest", 36 | "test:watch": "vitest", 37 | "test:coverage": "vitest run --coverage", 38 | "build": "tsup ./src/index.ts ./src/client.ts --format esm,cjs --dts --clean --sourcemap", 39 | "dev": "npm run build -- --watch", 40 | "check": "biome check --write ./src" 41 | }, 42 | "files": [ 43 | "dist" 44 | ], 45 | "keywords": [ 46 | "polar", 47 | "better-auth", 48 | "payments", 49 | "subscriptions" 50 | ], 51 | "devDependencies": { 52 | "@biomejs/biome": "1.9.4", 53 | "@polar-sh/adapter-utils": "workspace:*", 54 | "@polar-sh/sdk": "^0.42.1", 55 | "@sindresorhus/tsconfig": "^7.0.0", 56 | "@types/node": "^20.0.0", 57 | "better-auth": "^1.4.6", 58 | "tsup": "^8.5.1", 59 | "vitest": "^2.1.8" 60 | }, 61 | "peerDependencies": { 62 | "@polar-sh/sdk": "^0.42.1", 63 | "better-auth": "^1.4.6", 64 | "zod": "^3.24.2 || ^4" 65 | }, 66 | "dependencies": { 67 | "@polar-sh/checkout": "^0.1.14" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/polar-betterauth/src/__tests__/client.test.ts: -------------------------------------------------------------------------------- 1 | import type { BetterAuthClientPlugin } from "better-auth"; 2 | import { describe, expect, it } from "vitest"; 3 | import { polarClient } from "../client"; 4 | 5 | describe("polarClient", () => { 6 | it("should create a client plugin with correct id", () => { 7 | const plugin = polarClient(); 8 | 9 | expect(plugin.id).toBe("polar-client"); 10 | }); 11 | 12 | it("should satisfy BetterAuthClientPlugin interface", () => { 13 | const plugin = polarClient(); 14 | 15 | // Check that it has the required properties for BetterAuthClientPlugin 16 | expect(plugin).toHaveProperty("id"); 17 | expect(plugin).toHaveProperty("$InferServerPlugin"); 18 | expect(typeof plugin.id).toBe("string"); 19 | expect(typeof plugin.$InferServerPlugin).toBe("object"); 20 | }); 21 | 22 | it("should have consistent plugin id across multiple calls", () => { 23 | const plugin1 = polarClient(); 24 | const plugin2 = polarClient(); 25 | 26 | expect(plugin1.id).toBe(plugin2.id); 27 | expect(plugin1.id).toBe("polar-client"); 28 | }); 29 | 30 | it("should be a function that returns a plugin object", () => { 31 | expect(typeof polarClient).toBe("function"); 32 | 33 | const plugin = polarClient(); 34 | expect(typeof plugin).toBe("object"); 35 | expect(plugin).not.toBe(null); 36 | }); 37 | 38 | it("should have proper type inference marker", () => { 39 | const plugin = polarClient(); 40 | 41 | // The $InferServerPlugin should be an empty object used for type inference 42 | expect(plugin.$InferServerPlugin).toEqual({}); 43 | }); 44 | 45 | it("should conform to BetterAuthClientPlugin type structure", () => { 46 | const plugin = polarClient(); 47 | 48 | // Type assertion to ensure it matches the expected interface 49 | const clientPlugin: BetterAuthClientPlugin = plugin; 50 | 51 | expect(clientPlugin.id).toBe("polar-client"); 52 | expect(clientPlugin).toHaveProperty("$InferServerPlugin"); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /packages/polar-remix/src/customerPortal/customerPortal.test.ts: -------------------------------------------------------------------------------- 1 | // Define mock values at the top level 2 | const mockCustomerPortalUrl = "https://mock-customer-portal-url.com/"; 3 | const mockCheckoutUrl = "https://mock-checkout-url.com/"; 4 | const mockSessionCreate = vi 5 | .fn() 6 | .mockResolvedValue({ customerPortalUrl: mockCustomerPortalUrl }); 7 | const mockCheckoutCreate = vi.fn(() => ({ url: mockCheckoutUrl })); 8 | 9 | // Mock the module before any imports 10 | vi.mock("@polar-sh/sdk", async (importOriginal) => { 11 | class Polar { 12 | customerSessions = { 13 | create: mockSessionCreate, 14 | }; 15 | 16 | checkouts = { 17 | create: mockCheckoutCreate, 18 | }; 19 | } 20 | 21 | return { 22 | ...(await importOriginal()), 23 | Polar, 24 | }; 25 | }); 26 | 27 | import { describe, expect, it, vi } from "vitest"; 28 | import { CustomerPortal } from "./customerPortal"; 29 | 30 | describe("CustomerPortal middleware", () => { 31 | it("should redirect to customer portal when customerId is valid", async () => { 32 | const response = await CustomerPortal({ 33 | getCustomerId: async () => "valid-customer-id", 34 | })({ 35 | request: new Request("http://localhost:3000/"), 36 | context: {}, 37 | params: {}, 38 | }); 39 | 40 | expect(response).toBeInstanceOf(Response); 41 | expect((response as Response).status).toBe(302); 42 | expect((response as Response).headers.get("Location")).toBe( 43 | mockCustomerPortalUrl, 44 | ); 45 | }); 46 | 47 | it("should return 400 when customerId is not defined", async () => { 48 | const response = await CustomerPortal({ 49 | getCustomerId: async () => "", 50 | })({ 51 | request: new Request("http://localhost:3000/"), 52 | context: {}, 53 | params: {}, 54 | }); 55 | 56 | expect(response).toBeInstanceOf(Response); 57 | expect((response as Response).status).toBe(400); 58 | expect(await (response as Response).json()).toEqual({ 59 | error: "customerId not defined", 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /packages/polar-elysia/src/customerPortal/customerPortal.test.ts: -------------------------------------------------------------------------------- 1 | // Define mock values at the top level 2 | const mockCustomerPortalUrl = "https://mock-customer-portal-url.com/"; 3 | const mockCheckoutUrl = "https://mock-checkout-url.com/"; 4 | const mockSessionCreate = vi 5 | .fn() 6 | .mockResolvedValue({ customerPortalUrl: mockCustomerPortalUrl }); 7 | const mockCheckoutCreate = vi.fn(() => ({ url: mockCheckoutUrl })); 8 | 9 | // Mock the module before any imports 10 | vi.mock("@polar-sh/sdk", async (importOriginal) => { 11 | class Polar { 12 | customerSessions = { 13 | create: mockSessionCreate, 14 | }; 15 | 16 | checkouts = { 17 | create: mockCheckoutCreate, 18 | }; 19 | } 20 | 21 | return { 22 | ...(await importOriginal()), 23 | Polar, 24 | }; 25 | }); 26 | 27 | import Elysia from "elysia"; 28 | import { describe, expect, it, vi } from "vitest"; 29 | import { CustomerPortal } from "./customerPortal"; 30 | 31 | describe("CustomerPortal middleware", () => { 32 | it("should redirect to customer portal when customerId is valid", async () => { 33 | const app = new Elysia(); 34 | const mockGetCustomerId = async () => "valid-customer-id"; 35 | 36 | app.get( 37 | "/", 38 | CustomerPortal({ 39 | getCustomerId: mockGetCustomerId, 40 | }), 41 | ); 42 | 43 | const response = await app.handle(new Request("http://localhost/")); 44 | 45 | expect(response.status).toBe(302); 46 | expect(response.headers.get("location")).toBe(mockCustomerPortalUrl); 47 | }); 48 | 49 | it("should return 400 when customerId is not defined", async () => { 50 | const app = new Elysia(); 51 | const mockGetCustomerId = async () => ""; 52 | app.get( 53 | "/", 54 | CustomerPortal({ 55 | getCustomerId: mockGetCustomerId, 56 | }), 57 | ); 58 | 59 | const response = await app.handle(new Request("http://localhost/")); 60 | 61 | expect(response.status).toBe(400); 62 | expect(await response.json()).toEqual({ error: "customerId not defined" }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /packages/polar-remix/src/checkout/checkout.test.ts: -------------------------------------------------------------------------------- 1 | // Define mock values at the top level 2 | const mockCustomerPortalUrl = "https://mock-customer-portal-url.com/"; 3 | const mockCheckoutUrl = "https://mock-checkout-url.com/"; 4 | const mockSessionCreate = vi 5 | .fn() 6 | .mockResolvedValue({ customerPortalUrl: mockCustomerPortalUrl }); 7 | const mockCheckoutCreate = vi.fn(() => ({ url: mockCheckoutUrl })); 8 | 9 | // Mock the module before any imports 10 | vi.mock("@polar-sh/sdk", async (importOriginal) => { 11 | class Polar { 12 | customerSessions = { 13 | create: mockSessionCreate, 14 | }; 15 | 16 | checkouts = { 17 | create: mockCheckoutCreate, 18 | }; 19 | } 20 | 21 | return { 22 | ...(await importOriginal()), 23 | Polar, 24 | }; 25 | }); 26 | 27 | import { describe, expect, it, vi } from "vitest"; 28 | import { Checkout } from "./checkout"; 29 | 30 | describe("Checkout middleware", () => { 31 | it("should redirect to checkout when products is valid", async () => { 32 | const loader = Checkout({}); 33 | 34 | // Test Loader Function 35 | const response = await loader({ 36 | request: new Request("http://localhost:3000/?products=mock-product-id"), 37 | context: {}, 38 | params: {}, 39 | }); 40 | 41 | expect(response).toBeInstanceOf(Response); 42 | expect((response as Response).status).toBe(302); 43 | expect((response as Response).headers.get("Location")).toBe( 44 | mockCheckoutUrl, 45 | ); 46 | }); 47 | 48 | it("should return 400 when products is not defined", async () => { 49 | const loader = Checkout({ 50 | accessToken: "mock-access-token", 51 | }); 52 | 53 | const response = await loader({ 54 | request: new Request("http://localhost:3000/"), 55 | context: {}, 56 | params: {}, 57 | }); 58 | 59 | expect(response).toBeInstanceOf(Response); 60 | expect((response as Response).status).toBe(400); 61 | expect(await (response as Response).json()).toEqual({ 62 | error: "Missing products in query params", 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /packages/polar-astro/src/checkout/checkout.test.ts: -------------------------------------------------------------------------------- 1 | // Define mock values at the top level 2 | const mockCustomerPortalUrl = "https://mock-customer-portal-url.com/"; 3 | const mockCheckoutUrl = "https://mock-checkout-url.com/"; 4 | const mockSessionCreate = vi 5 | .fn() 6 | .mockResolvedValue({ customerPortalUrl: mockCustomerPortalUrl }); 7 | const mockCheckoutCreate = vi.fn(() => ({ url: mockCheckoutUrl })); 8 | 9 | // Mock the module before any imports 10 | vi.mock("@polar-sh/sdk", async (importOriginal) => { 11 | class Polar { 12 | customerSessions = { 13 | create: mockSessionCreate, 14 | }; 15 | 16 | checkouts = { 17 | create: mockCheckoutCreate, 18 | }; 19 | } 20 | 21 | return { 22 | ...(await importOriginal()), 23 | Polar, 24 | }; 25 | }); 26 | 27 | import type { APIContext } from "astro"; 28 | import { describe, expect, it, vi } from "vitest"; 29 | import { Checkout } from "./checkout"; 30 | 31 | describe("Checkout middleware", () => { 32 | it("should redirect to checkout when products is valid", async () => { 33 | const response = await Checkout({ 34 | accessToken: "mock-access-token", 35 | })({ 36 | url: new URL( 37 | new Request( 38 | "http://localhost:3000/?products=product-1&products=product-2", 39 | ).url, 40 | ), 41 | } as APIContext); 42 | 43 | expect(response).toBeInstanceOf(Response); 44 | expect((response as Response).status).toBe(302); 45 | expect((response as Response).headers.get("Location")).toBe( 46 | mockCheckoutUrl, 47 | ); 48 | }); 49 | 50 | it("should return 400 when products is not defined", async () => { 51 | const response = await Checkout({ 52 | accessToken: "mock-access-token", 53 | })({ 54 | url: new URL(new Request("http://localhost:3000/").url), 55 | } as APIContext); 56 | 57 | expect(response).toBeInstanceOf(Response); 58 | expect((response as Response).status).toBe(400); 59 | expect(await (response as Response).json()).toEqual({ 60 | error: "Missing products in query params", 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /packages/polar-express/src/customerPortal/customerPortal.test.ts: -------------------------------------------------------------------------------- 1 | // Define mock values at the top level 2 | const mockCustomerPortalUrl = "https://mock-customer-portal-url.com"; 3 | const mockCheckoutUrl = "https://mock-checkout-url.com"; 4 | const mockSessionCreate = vi 5 | .fn() 6 | .mockResolvedValue({ customerPortalUrl: mockCustomerPortalUrl }); 7 | const mockCheckoutCreate = vi.fn(() => ({ url: mockCheckoutUrl })); 8 | 9 | // Mock the module before any imports 10 | vi.mock("@polar-sh/sdk", async (importOriginal) => { 11 | class Polar { 12 | customerSessions = { 13 | create: mockSessionCreate, 14 | }; 15 | 16 | checkouts = { 17 | create: mockCheckoutCreate, 18 | }; 19 | } 20 | 21 | return { 22 | ...(await importOriginal()), 23 | Polar, 24 | }; 25 | }); 26 | 27 | import express from "express"; 28 | import supertest from "supertest"; 29 | import { describe, it, vi } from "vitest"; 30 | import { CustomerPortal } from "./customerPortal"; 31 | 32 | describe("CustomerPortal middleware", () => { 33 | it("should redirect to customer portal when customerId is valid", async () => { 34 | const app = express(); 35 | const mockGetCustomerId = async () => "valid-customer-id"; 36 | 37 | app.get( 38 | "/", 39 | CustomerPortal({ 40 | getCustomerId: mockGetCustomerId, 41 | }), 42 | ); 43 | 44 | supertest(app) 45 | .get("/") 46 | .expect(302) 47 | .expect("location", mockCustomerPortalUrl) 48 | .end((err) => { 49 | if (err) { 50 | throw err; 51 | } 52 | }); 53 | }); 54 | 55 | it("should return 400 when customerId is not defined", async () => { 56 | const app = express(); 57 | const mockGetCustomerId = async () => ""; 58 | app.get( 59 | "/", 60 | CustomerPortal({ 61 | getCustomerId: mockGetCustomerId, 62 | }), 63 | ); 64 | 65 | supertest(app) 66 | .get("/") 67 | .expect(400) 68 | .expect({ error: "customerId not defined" }) 69 | .end((err) => { 70 | if (err) { 71 | throw err; 72 | } 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /packages/polar-elysia/src/webhooks/webhooks.test.ts: -------------------------------------------------------------------------------- 1 | vi.mock("@polar-sh/sdk/webhooks", async (importOriginal) => { 2 | return { 3 | ...(await importOriginal()), 4 | WebhookVerificationError: vi.fn(), 5 | validateEvent: vi.fn((v) => JSON.parse(v)), 6 | }; 7 | }); 8 | 9 | import Elysia from "elysia"; 10 | import { describe, expect, it, vi } from "vitest"; 11 | import { Webhooks } from "./webhooks"; 12 | 13 | describe("Webhooks middleware", () => { 14 | it("should call onPayload with the payload", async () => { 15 | const app = new Elysia(); 16 | const mockOnPayload = vi.fn(); 17 | 18 | app.post( 19 | "*", 20 | Webhooks({ 21 | webhookSecret: "mock-secret", 22 | onPayload: mockOnPayload, 23 | }), 24 | ); 25 | 26 | const payload = { event: "mock-event", data: "mock-data" }; 27 | 28 | const response = await app.handle( 29 | new Request("http://localhost/", { 30 | method: "POST", 31 | body: JSON.stringify(payload), 32 | headers: { 33 | "webhook-id": "mock-id", 34 | "webhook-timestamp": "mock-timestamp", 35 | "webhook-signature": "mock-signature", 36 | }, 37 | }), 38 | ); 39 | 40 | expect(response.status).toBe(200); 41 | expect(mockOnPayload).toHaveBeenCalledWith(payload); 42 | }); 43 | 44 | it("should acknowledge the webhook", async () => { 45 | const app = new Elysia(); 46 | const mockOnPayload = vi.fn(); 47 | 48 | app.post( 49 | "*", 50 | Webhooks({ 51 | webhookSecret: "mock-secret", 52 | onPayload: mockOnPayload, 53 | }), 54 | ); 55 | 56 | const payload = { event: "mock-event", data: "mock-data" }; 57 | 58 | const response = await app.handle( 59 | new Request("http://localhost/", { 60 | method: "POST", 61 | body: JSON.stringify(payload), 62 | headers: { 63 | "webhook-id": "mock-id", 64 | "webhook-timestamp": "mock-timestamp", 65 | "webhook-signature": "mock-signature", 66 | }, 67 | }), 68 | ); 69 | expect(response.status).toBe(200); 70 | expect(await response.json()).toEqual({ received: true }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /packages/polar-fastify/src/customerPortal/customerPortal.test.ts: -------------------------------------------------------------------------------- 1 | // Define mock values at the top level 2 | const mockCustomerPortalUrl = "https://mock-customer-portal-url.com/"; 3 | const mockCheckoutUrl = "https://mock-checkout-url.com/"; 4 | const mockSessionCreate = vi 5 | .fn() 6 | .mockResolvedValue({ customerPortalUrl: mockCustomerPortalUrl }); 7 | const mockCheckoutCreate = vi.fn(() => ({ url: mockCheckoutUrl })); 8 | 9 | // Mock the module before any imports 10 | vi.mock("@polar-sh/sdk", async (importOriginal) => { 11 | class Polar { 12 | customerSessions = { 13 | create: mockSessionCreate, 14 | }; 15 | 16 | checkouts = { 17 | create: mockCheckoutCreate, 18 | }; 19 | } 20 | 21 | return { 22 | ...(await importOriginal()), 23 | Polar, 24 | }; 25 | }); 26 | 27 | import fastify from "fastify"; 28 | import { describe, expect, it, vi } from "vitest"; 29 | import { CustomerPortal } from "./customerPortal"; 30 | 31 | describe("CustomerPortal middleware", () => { 32 | it("should redirect to customer portal when customerId is valid", async () => { 33 | const app = fastify(); 34 | const mockGetCustomerId = async () => "valid-customer-id"; 35 | 36 | app.get( 37 | "/", 38 | CustomerPortal({ 39 | getCustomerId: mockGetCustomerId, 40 | }), 41 | ); 42 | 43 | const response = await app.inject({ 44 | url: "http://localhost/", 45 | method: "GET", 46 | }); 47 | 48 | expect(response.statusCode).toBe(302); 49 | expect(response.headers["location"]).toBe(mockCustomerPortalUrl); 50 | }); 51 | 52 | it("should return 400 when customerId is not defined", async () => { 53 | const app = fastify(); 54 | const mockGetCustomerId = async () => ""; 55 | app.get( 56 | "/", 57 | CustomerPortal({ 58 | getCustomerId: mockGetCustomerId, 59 | }), 60 | ); 61 | 62 | const response = await app.inject({ 63 | url: "http://localhost/", 64 | method: "GET", 65 | }); 66 | 67 | expect(response.statusCode).toBe(400); 68 | expect(response.json()).toEqual({ error: "customerId not defined" }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /packages/polar-astro/src/customerPortal/customerPortal.test.ts: -------------------------------------------------------------------------------- 1 | // Define mock values at the top level 2 | const mockCustomerPortalUrl = "https://mock-customer-portal-url.com/"; 3 | const mockCheckoutUrl = "https://mock-checkout-url.com/"; 4 | const mockSessionCreate = vi 5 | .fn() 6 | .mockResolvedValue({ customerPortalUrl: mockCustomerPortalUrl }); 7 | const mockCheckoutCreate = vi.fn(() => ({ url: mockCheckoutUrl })); 8 | 9 | // Mock the module before any imports 10 | vi.mock("@polar-sh/sdk", async (importOriginal) => { 11 | class Polar { 12 | customerSessions = { 13 | create: mockSessionCreate, 14 | }; 15 | 16 | checkouts = { 17 | create: mockCheckoutCreate, 18 | }; 19 | } 20 | 21 | return { 22 | ...(await importOriginal()), 23 | Polar, 24 | }; 25 | }); 26 | 27 | import type { APIContext } from "astro"; 28 | import { describe, expect, it, vi } from "vitest"; 29 | import { CustomerPortal } from "./customerPortal"; 30 | 31 | describe("CustomerPortal middleware", () => { 32 | it("should redirect to customer portal when customerId is valid", async () => { 33 | const response = await CustomerPortal({ 34 | accessToken: "mock-access-token", 35 | getCustomerId: async () => "valid-customer-id", 36 | })({ 37 | request: new Request("http://localhost:3000/"), 38 | } as APIContext); 39 | 40 | expect(response).toBeInstanceOf(Response); 41 | expect((response as Response).status).toBe(302); 42 | expect((response as Response).headers.get("Location")).toBe( 43 | mockCustomerPortalUrl, 44 | ); 45 | }); 46 | 47 | it("should return 400 when customerId is not defined", async () => { 48 | const response = await CustomerPortal({ 49 | accessToken: "mock-access-token", 50 | getCustomerId: async () => "", 51 | })({ 52 | request: new Request("http://localhost:3000/"), 53 | } as APIContext); 54 | 55 | expect(response).toBeInstanceOf(Response); 56 | expect((response as Response).status).toBe(400); 57 | expect(await (response as Response).json()).toEqual({ 58 | error: "customerId not defined", 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/polar-nuxt/src/runtime/server/webhookHandler.ts: -------------------------------------------------------------------------------- 1 | import { handleWebhookPayload } from "@polar-sh/adapter-utils"; 2 | import type { WebhooksConfig } from "@polar-sh/adapter-utils"; 3 | import { 4 | WebhookVerificationError, 5 | validateEvent, 6 | } from "@polar-sh/sdk/webhooks"; 7 | import type { H3Event } from "h3"; 8 | import { createError, getHeader, readBody, setResponseStatus } from "h3"; 9 | 10 | export const Webhooks = ({ 11 | webhookSecret, 12 | onPayload, 13 | entitlements, 14 | ...eventHandlers 15 | }: WebhooksConfig) => { 16 | return async (event: H3Event) => { 17 | const requestBody = await readBody(event); 18 | 19 | const webhookHeaders = { 20 | "webhook-id": getHeader(event, "webhook-id") ?? "", 21 | "webhook-timestamp": getHeader(event, "webhook-timestamp") ?? "", 22 | "webhook-signature": getHeader(event, "webhook-signature") ?? "", 23 | }; 24 | 25 | let webhookPayload: ReturnType; 26 | 27 | try { 28 | webhookPayload = validateEvent( 29 | JSON.stringify(requestBody), 30 | webhookHeaders, 31 | webhookSecret, 32 | ); 33 | } catch (error) { 34 | if (error instanceof WebhookVerificationError) { 35 | console.error("Failed to verify webhook event", error); 36 | setResponseStatus(event, 403); 37 | return { received: false }; 38 | } 39 | 40 | console.error("Failed to validate webhook event", error); 41 | throw createError({ 42 | statusCode: 500, 43 | statusMessage: (error as Error).message, 44 | message: (error as Error).message ?? "Internal server error", 45 | }); 46 | } 47 | 48 | try { 49 | await handleWebhookPayload(webhookPayload, { 50 | webhookSecret, 51 | entitlements, 52 | onPayload, 53 | ...eventHandlers, 54 | }); 55 | 56 | return { received: true }; 57 | } catch (error) { 58 | console.error("Webhook error", error); 59 | throw createError({ 60 | statusCode: 500, 61 | statusMessage: (error as Error).message, 62 | message: (error as Error).message ?? "Internal server error", 63 | }); 64 | } 65 | }; 66 | }; 67 | -------------------------------------------------------------------------------- /packages/polar-fastify/src/webhooks/webhooks.test.ts: -------------------------------------------------------------------------------- 1 | vi.mock("@polar-sh/sdk/webhooks", async (importOriginal) => { 2 | return { 3 | ...(await importOriginal()), 4 | WebhookVerificationError: vi.fn(), 5 | validateEvent: vi.fn((v) => JSON.parse(v)), 6 | }; 7 | }); 8 | 9 | import fastify from "fastify"; 10 | import { describe, expect, it, vi } from "vitest"; 11 | import { Webhooks } from "./webhooks"; 12 | 13 | describe("Webhooks middleware", () => { 14 | it("should call onPayload with the payload", async () => { 15 | const app = fastify(); 16 | const mockOnPayload = vi.fn(); 17 | 18 | app.post( 19 | "*", 20 | Webhooks({ 21 | webhookSecret: "mock-secret", 22 | onPayload: mockOnPayload, 23 | }), 24 | ); 25 | 26 | const payload = { event: "mock-event", data: "mock-data" }; 27 | 28 | const response = await app.inject({ 29 | url: "http://localhost/", 30 | method: "POST", 31 | body: JSON.stringify(payload), 32 | headers: { 33 | "webhook-id": "mock-id", 34 | "webhook-timestamp": "mock-timestamp", 35 | "webhook-signature": "mock-signature", 36 | "content-type": "application/json", 37 | }, 38 | }); 39 | 40 | expect(response.statusCode).toBe(200); 41 | expect(mockOnPayload).toHaveBeenCalledWith(payload); 42 | }); 43 | 44 | it("should acknowledge the webhook", async () => { 45 | const app = fastify(); 46 | const mockOnPayload = vi.fn(); 47 | 48 | app.post( 49 | "*", 50 | Webhooks({ 51 | webhookSecret: "mock-secret", 52 | onPayload: mockOnPayload, 53 | }), 54 | ); 55 | 56 | const payload = { event: "mock-event", data: "mock-data" }; 57 | 58 | const response = await app.inject({ 59 | url: "http://localhost/", 60 | method: "POST", 61 | body: JSON.stringify(payload), 62 | headers: { 63 | "webhook-id": "mock-id", 64 | "webhook-timestamp": "mock-timestamp", 65 | "webhook-signature": "mock-signature", 66 | "content-type": "application/json", 67 | }, 68 | }); 69 | 70 | expect(response.statusCode).toBe(200); 71 | expect(response.json()).toEqual({ received: true }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /packages/polar-betterauth/example/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Login } from "@/components/Login"; 4 | import { Me } from "@/components/Me"; 5 | import { Register } from "@/components/Register"; 6 | import { authClient } from "@/lib/auth-client"; 7 | 8 | export default function Home() { 9 | const onClickCheckout = async () => { 10 | await authClient.checkout({ 11 | slug: "pro", 12 | }); 13 | }; 14 | 15 | const onClickCustomerPortal = async () => { 16 | const { data: state } = await authClient.customer.portal(); 17 | }; 18 | 19 | const onClickCustomerState = async () => { 20 | const { data: state } = await authClient.customer.state(); 21 | console.log(state); 22 | }; 23 | 24 | const onClickOrganization = async () => { 25 | const { data: state } = await authClient.organization.create({ 26 | name: "My Organization", 27 | slug: "my-organization", 28 | }); 29 | }; 30 | 31 | const onClickIngest = async () => { 32 | const { data: ingestion } = await authClient.usage.ingest({ 33 | event: "user.created", 34 | metadata: { 35 | email: "test@test.com", 36 | }, 37 | }); 38 | 39 | console.log(ingestion); 40 | }; 41 | 42 | const onClickSubscriptions = async () => { 43 | const { data: subs } = await authClient.customer.subscriptions.list({ 44 | query: { 45 | page: 1, 46 | limit: 10, 47 | active: true, 48 | }, 49 | }); 50 | console.log(subs); 51 | }; 52 | 53 | return ( 54 |
55 | 56 | 57 | 58 |
59 | 62 | 65 | 68 | 71 | 74 | 77 |
78 |
79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /packages/polar-nextjs/src/customerPortal/customerPortal.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | import { type NextRequest, NextResponse } from "next/server"; 3 | 4 | interface CustomerPortalBaseConfig { 5 | accessToken: string; 6 | server: "sandbox" | "production"; 7 | returnUrl?: string; 8 | } 9 | 10 | interface CustomerPortalCustomerIdConfig extends CustomerPortalBaseConfig { 11 | getCustomerId: (req: NextRequest) => Promise; 12 | getExternalCustomerId?: never; 13 | } 14 | 15 | interface CustomerPortalExternalCustomerIdConfig 16 | extends CustomerPortalBaseConfig { 17 | getCustomerId?: never; 18 | getExternalCustomerId: (req: NextRequest) => Promise; 19 | } 20 | 21 | function configIsExternalCustomerIdConfig( 22 | config: CustomerPortalConfig, 23 | ): config is CustomerPortalExternalCustomerIdConfig { 24 | return typeof config.getExternalCustomerId === "function"; 25 | } 26 | 27 | export type CustomerPortalConfig = 28 | | CustomerPortalCustomerIdConfig 29 | | CustomerPortalExternalCustomerIdConfig; 30 | 31 | export const CustomerPortal = (config: CustomerPortalConfig) => { 32 | const { accessToken, server, returnUrl } = config; 33 | 34 | const polar = new Polar({ 35 | accessToken, 36 | server, 37 | }); 38 | 39 | return async (req: NextRequest) => { 40 | const decodedReturnUrl = returnUrl 41 | ? decodeURI(new URL(returnUrl).toString()) 42 | : undefined; 43 | 44 | if (configIsExternalCustomerIdConfig(config)) { 45 | const externalCustomerId = await config.getExternalCustomerId(req); 46 | 47 | if (!externalCustomerId) { 48 | return NextResponse.json( 49 | { error: "externalCustomerId not defined" }, 50 | { status: 400 }, 51 | ); 52 | } 53 | 54 | try { 55 | const { customerPortalUrl } = await polar.customerSessions.create({ 56 | returnUrl: decodedReturnUrl, 57 | externalCustomerId, 58 | }); 59 | 60 | return NextResponse.redirect(customerPortalUrl); 61 | } catch (error) { 62 | console.error(error); 63 | return NextResponse.error(); 64 | } 65 | } 66 | 67 | const customerId = await config.getCustomerId(req); 68 | 69 | if (!customerId) { 70 | return NextResponse.json( 71 | { error: "customerId not defined" }, 72 | { status: 400 }, 73 | ); 74 | } 75 | 76 | try { 77 | const { customerPortalUrl } = await polar.customerSessions.create({ 78 | returnUrl: decodedReturnUrl, 79 | customerId, 80 | }); 81 | 82 | return NextResponse.redirect(customerPortalUrl); 83 | } catch (error) { 84 | console.error(error); 85 | return NextResponse.error(); 86 | } 87 | }; 88 | }; 89 | -------------------------------------------------------------------------------- /packages/polar-elysia/src/checkout/checkout.test.ts: -------------------------------------------------------------------------------- 1 | // Define mock values at the top level 2 | const mockCustomerPortalUrl = "https://mock-customer-portal-url.com"; 3 | const mockCheckoutUrl = "https://mock-checkout-url.com/"; 4 | const mockSessionCreate = vi 5 | .fn() 6 | .mockResolvedValue({ customerPortalUrl: mockCustomerPortalUrl }); 7 | const mockCheckoutCreate = vi.fn(() => ({ url: mockCheckoutUrl })); 8 | 9 | // Mock the module before any imports 10 | vi.mock("@polar-sh/sdk", async (importOriginal) => { 11 | class Polar { 12 | customerSessions = { 13 | create: mockSessionCreate, 14 | }; 15 | 16 | checkouts = { 17 | create: mockCheckoutCreate, 18 | }; 19 | } 20 | 21 | return { 22 | ...(await importOriginal()), 23 | Polar, 24 | }; 25 | }); 26 | 27 | import { Elysia } from "elysia"; 28 | import { describe, expect, it, vi } from "vitest"; 29 | import { Checkout } from "./checkout"; 30 | 31 | describe("Checkout middleware", () => { 32 | it("should redirect to checkout when products is valid", async () => { 33 | const app = new Elysia(); 34 | app.get( 35 | "/", 36 | Checkout({ 37 | accessToken: "mock-access-token", 38 | }), 39 | ); 40 | 41 | const response = await app.handle( 42 | new Request("http://localhost/?products=mock-product-id"), 43 | ); 44 | 45 | expect(response.status).toBe(302); 46 | expect(response.headers.get("location")).toBe(mockCheckoutUrl); 47 | }); 48 | 49 | it("should return 400 when products are not defined", async () => { 50 | const app = new Elysia(); 51 | app.get( 52 | "/", 53 | Checkout({ 54 | accessToken: "mock-access-token", 55 | }), 56 | ); 57 | 58 | const response = await app.handle(new Request("http://localhost/")); 59 | 60 | expect(response.status).toBe(400); 61 | expect(await response.json()).toEqual({ 62 | error: "Missing products in query params", 63 | }); 64 | }); 65 | 66 | it("should encode metadata JSON properly", async () => { 67 | const app = new Elysia(); 68 | app.get( 69 | "/", 70 | Checkout({ 71 | accessToken: "mock-access-token", 72 | }), 73 | ); 74 | 75 | const metadata = { 76 | foo: "bar", 77 | }; 78 | 79 | const url = new URL("http://localhost/"); 80 | url.searchParams.set("products", "mock-product-id"); 81 | url.searchParams.set("metadata", JSON.stringify(metadata)); 82 | 83 | const response = await app.handle(new Request(url.toString())); 84 | 85 | expect(response.status).toBe(302); 86 | expect(response.headers.get("location")).toBe(mockCheckoutUrl); 87 | expect(mockCheckoutCreate).toHaveBeenCalledWith({ 88 | products: ["mock-product-id"], 89 | metadata, 90 | }); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /packages/polar-astro/src/webhooks/webhooks.test.ts: -------------------------------------------------------------------------------- 1 | vi.mock("@polar-sh/sdk/webhooks", async (importOriginal) => { 2 | return { 3 | ...(await importOriginal()), 4 | WebhookVerificationError: vi.fn(), 5 | validateEvent: vi.fn((v) => JSON.parse(v)), 6 | }; 7 | }); 8 | 9 | import type { APIContext } from "astro"; 10 | import { describe, expect, it, vi } from "vitest"; 11 | import { Webhooks } from "./webhooks"; 12 | 13 | describe("Webhooks middleware", () => { 14 | it("should call onPayload with the payload", async () => { 15 | const onPayload = vi.fn(); 16 | 17 | const response = await Webhooks({ 18 | webhookSecret: "mock-secret", 19 | onPayload, 20 | })({ 21 | request: new Request("http://localhost:3000/", { 22 | method: "POST", 23 | body: JSON.stringify({ event: "mock-event", data: "mock-data" }), 24 | headers: { 25 | "webhook-id": "mock-id", 26 | "webhook-timestamp": "mock-timestamp", 27 | "webhook-signature": "mock-signature", 28 | }, 29 | }), 30 | } as APIContext); 31 | 32 | expect(response).toBeInstanceOf(Response); 33 | expect(onPayload).toHaveBeenCalledWith({ 34 | event: "mock-event", 35 | data: "mock-data", 36 | }); 37 | expect((response as Response).status).toBe(200); 38 | expect(await (response as Response).json()).toEqual({ received: true }); 39 | }); 40 | 41 | it("should acknowledge the webhook", async () => { 42 | const onPayload = vi.fn(); 43 | 44 | const response = await Webhooks({ 45 | webhookSecret: "mock-secret", 46 | onPayload, 47 | })({ 48 | request: new Request("http://localhost:3000/", { 49 | method: "POST", 50 | body: JSON.stringify({ event: "mock-event", data: "mock-data" }), 51 | headers: { 52 | "webhook-id": "mock-id", 53 | "webhook-timestamp": "mock-timestamp", 54 | "webhook-signature": "mock-signature", 55 | }, 56 | }), 57 | } as APIContext); 58 | 59 | expect(response).toBeInstanceOf(Response); 60 | expect((response as Response).status).toBe(200); 61 | expect(onPayload).toHaveBeenCalledWith({ 62 | event: "mock-event", 63 | data: "mock-data", 64 | }); 65 | expect(await (response as Response).json()).toEqual({ received: true }); 66 | }); 67 | 68 | it("should return 405 when the method is not POST", async () => { 69 | const response = await Webhooks({ 70 | webhookSecret: "mock-secret", 71 | onPayload: vi.fn(), 72 | })({ 73 | request: new Request("http://localhost:3000/"), 74 | } as APIContext); 75 | 76 | expect(response).toBeInstanceOf(Response); 77 | expect((response as Response).status).toBe(405); 78 | expect(await (response as Response).json()).toEqual({ 79 | message: "Method not allowed", 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /packages/polar-remix/src/webhooks/webhooks.test.ts: -------------------------------------------------------------------------------- 1 | vi.mock("@polar-sh/sdk/webhooks", async (importOriginal) => { 2 | return { 3 | ...(await importOriginal()), 4 | WebhookVerificationError: vi.fn(), 5 | validateEvent: vi.fn((v) => JSON.parse(v)), 6 | }; 7 | }); 8 | 9 | import { describe, expect, it, vi } from "vitest"; 10 | import { Webhooks } from "./webhooks"; 11 | 12 | describe("Webhooks middleware", () => { 13 | it("should call onPayload with the payload", async () => { 14 | const onPayload = vi.fn(); 15 | 16 | const response = await Webhooks({ 17 | webhookSecret: "mock-secret", 18 | onPayload, 19 | })({ 20 | request: new Request("http://localhost:3000/", { 21 | method: "POST", 22 | body: JSON.stringify({ event: "mock-event", data: "mock-data" }), 23 | headers: { 24 | "webhook-id": "mock-id", 25 | "webhook-timestamp": "mock-timestamp", 26 | "webhook-signature": "mock-signature", 27 | }, 28 | }), 29 | context: {}, 30 | params: {}, 31 | }); 32 | 33 | expect(response).toBeInstanceOf(Response); 34 | expect(onPayload).toHaveBeenCalledWith({ 35 | event: "mock-event", 36 | data: "mock-data", 37 | }); 38 | expect((response as Response).status).toBe(200); 39 | expect(await (response as Response).json()).toEqual({ received: true }); 40 | }); 41 | 42 | it("should acknowledge the webhook", async () => { 43 | const onPayload = vi.fn(); 44 | 45 | const response = await Webhooks({ 46 | webhookSecret: "mock-secret", 47 | onPayload, 48 | })({ 49 | request: new Request("http://localhost:3000/", { 50 | method: "POST", 51 | body: JSON.stringify({ event: "mock-event", data: "mock-data" }), 52 | headers: { 53 | "webhook-id": "mock-id", 54 | "webhook-timestamp": "mock-timestamp", 55 | "webhook-signature": "mock-signature", 56 | }, 57 | }), 58 | context: {}, 59 | params: {}, 60 | }); 61 | 62 | expect(response).toBeInstanceOf(Response); 63 | expect((response as Response).status).toBe(200); 64 | expect(onPayload).toHaveBeenCalledWith({ 65 | event: "mock-event", 66 | data: "mock-data", 67 | }); 68 | expect(await (response as Response).json()).toEqual({ received: true }); 69 | }); 70 | 71 | it("should return 405 when the method is not POST", async () => { 72 | const response = await Webhooks({ 73 | webhookSecret: "mock-secret", 74 | onPayload: vi.fn(), 75 | })({ 76 | request: new Request("http://localhost:3000/"), 77 | context: {}, 78 | params: {}, 79 | }); 80 | 81 | expect(response).toBeInstanceOf(Response); 82 | expect((response as Response).status).toBe(405); 83 | expect(await (response as Response).json()).toEqual({ 84 | message: "Method not allowed", 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /packages/adapter-utils/src/entitlement/entitlement.ts: -------------------------------------------------------------------------------- 1 | import type { Customer } from "@polar-sh/sdk/models/components/customer"; 2 | import type { WebhookBenefitGrantCreatedPayload } from "@polar-sh/sdk/models/components/webhookbenefitgrantcreatedpayload"; 3 | import type { WebhookBenefitGrantRevokedPayload } from "@polar-sh/sdk/models/components/webhookbenefitgrantrevokedpayload"; 4 | 5 | export type EntitlementProperties = Record; 6 | 7 | export type EntitlementHandler = ( 8 | payload: 9 | | WebhookBenefitGrantCreatedPayload 10 | | WebhookBenefitGrantRevokedPayload, 11 | ) => Promise; 12 | 13 | export interface EntitlementContext { 14 | customer: Customer; 15 | properties: T; 16 | payload: 17 | | WebhookBenefitGrantCreatedPayload 18 | | WebhookBenefitGrantRevokedPayload; 19 | } 20 | 21 | export class EntitlementStrategy { 22 | private grantCallbacks: (( 23 | context: EntitlementContext, 24 | ) => Promise)[] = []; 25 | 26 | private revokeCallbacks: (( 27 | context: EntitlementContext, 28 | ) => Promise)[] = []; 29 | 30 | public grant(callback: (context: EntitlementContext) => Promise) { 31 | this.grantCallbacks.push(callback); 32 | return this; 33 | } 34 | 35 | public revoke(callback: (context: EntitlementContext) => Promise) { 36 | this.revokeCallbacks.push(callback); 37 | return this; 38 | } 39 | 40 | public handler(slug: string): EntitlementHandler { 41 | return async ( 42 | payload: 43 | | WebhookBenefitGrantCreatedPayload 44 | | WebhookBenefitGrantRevokedPayload, 45 | ) => { 46 | if (payload.data.benefit.description === slug) { 47 | switch (payload.type) { 48 | case "benefit_grant.created": 49 | await Promise.all( 50 | this.grantCallbacks.map((callback) => 51 | callback({ 52 | customer: payload.data.customer, 53 | properties: payload.data.properties as T, 54 | payload, 55 | }), 56 | ), 57 | ); 58 | break; 59 | case "benefit_grant.revoked": 60 | await Promise.all( 61 | this.revokeCallbacks.map((callback) => 62 | callback({ 63 | customer: payload.data.customer, 64 | properties: payload.data.properties as T, 65 | payload, 66 | }), 67 | ), 68 | ); 69 | break; 70 | } 71 | } 72 | }; 73 | } 74 | } 75 | 76 | export class Entitlements { 77 | static handlers = [] as EntitlementHandler[]; 78 | 79 | static use( 80 | slug: string, 81 | strategy: EntitlementStrategy, 82 | ) { 83 | this.handlers.push(strategy.handler(slug)); 84 | 85 | return this; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /packages/polar-betterauth/src/plugins/usage.ts: -------------------------------------------------------------------------------- 1 | import type { Polar } from "@polar-sh/sdk"; 2 | import { 3 | APIError, 4 | createAuthEndpoint, 5 | sessionMiddleware, 6 | } from "better-auth/api"; 7 | import { z } from "zod"; 8 | import type { Product } from "../types"; 9 | 10 | export interface UsageOptions { 11 | /** 12 | * Products to use for topping up credits 13 | */ 14 | creditProducts?: Product[] | (() => Promise); 15 | } 16 | 17 | export const usage = (_usageOptions?: UsageOptions) => (polar: Polar) => { 18 | return { 19 | meters: createAuthEndpoint( 20 | "/usage/meters/list", 21 | { 22 | method: "GET", 23 | use: [sessionMiddleware], 24 | query: z.object({ 25 | page: z.coerce.number().optional(), 26 | limit: z.coerce.number().optional(), 27 | }), 28 | }, 29 | async (ctx) => { 30 | if (!ctx.context.session.user.id) { 31 | throw new APIError("BAD_REQUEST", { 32 | message: "User not found", 33 | }); 34 | } 35 | 36 | try { 37 | const customerSession = await polar.customerSessions.create({ 38 | externalCustomerId: ctx.context.session.user.id, 39 | }); 40 | 41 | const customerMeters = await polar.customerPortal.customerMeters.list( 42 | { customerSession: customerSession.token }, 43 | { 44 | page: ctx.query?.page, 45 | limit: ctx.query?.limit, 46 | }, 47 | ); 48 | 49 | return ctx.json(customerMeters); 50 | } catch (e: unknown) { 51 | if (e instanceof Error) { 52 | ctx.context.logger.error( 53 | `Polar meters list failed. Error: ${e.message}`, 54 | ); 55 | } 56 | 57 | throw new APIError("INTERNAL_SERVER_ERROR", { 58 | message: "Meters list failed", 59 | }); 60 | } 61 | }, 62 | ), 63 | ingestion: createAuthEndpoint( 64 | "/usage/ingest", 65 | { 66 | method: "POST", 67 | body: z.object({ 68 | event: z.string(), 69 | metadata: z.record( 70 | z.string(), 71 | z.union([z.string(), z.number(), z.boolean()]), 72 | ), 73 | }), 74 | use: [sessionMiddleware], 75 | }, 76 | async (ctx) => { 77 | if (!ctx.context.session.user.id) { 78 | throw new APIError("BAD_REQUEST", { 79 | message: "User not found", 80 | }); 81 | } 82 | 83 | try { 84 | const ingestion = await polar.events.ingest({ 85 | events: [ 86 | { 87 | name: ctx.body.event, 88 | metadata: ctx.body.metadata, 89 | externalCustomerId: ctx.context.session.user.id, 90 | }, 91 | ], 92 | }); 93 | 94 | return ctx.json(ingestion); 95 | } catch (e: unknown) { 96 | if (e instanceof Error) { 97 | ctx.context.logger.error( 98 | `Polar ingestion failed. Error: ${e.message}`, 99 | ); 100 | } 101 | 102 | throw new APIError("INTERNAL_SERVER_ERROR", { 103 | message: "Ingestion failed", 104 | }); 105 | } 106 | }, 107 | ), 108 | }; 109 | }; 110 | -------------------------------------------------------------------------------- /packages/polar-nextjs/src/checkout/checkout.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | import { NextResponse } from "next/server"; 3 | import type { NextRequest } from "next/server"; 4 | 5 | export interface CheckoutConfig { 6 | accessToken?: string; 7 | successUrl?: string; 8 | returnUrl?: string; 9 | includeCheckoutId?: boolean; 10 | server?: "sandbox" | "production"; 11 | theme?: "light" | "dark"; 12 | } 13 | 14 | export const Checkout = ({ 15 | accessToken, 16 | successUrl, 17 | returnUrl, 18 | server, 19 | theme, 20 | includeCheckoutId = true, 21 | }: CheckoutConfig) => { 22 | const polar = new Polar({ 23 | accessToken, 24 | server, 25 | }); 26 | 27 | return async (req: NextRequest) => { 28 | const url = new URL(req.url); 29 | const products = url.searchParams.getAll("products"); 30 | 31 | if (products.length === 0) { 32 | return NextResponse.json( 33 | { error: "Missing products in query params" }, 34 | { status: 400 }, 35 | ); 36 | } 37 | 38 | const success = successUrl ? new URL(successUrl) : undefined; 39 | 40 | if (success && includeCheckoutId) { 41 | success.searchParams.set("checkoutId", "{CHECKOUT_ID}"); 42 | } 43 | 44 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 45 | 46 | try { 47 | const result = await polar.checkouts.create({ 48 | products, 49 | successUrl: success ? decodeURI(success.toString()) : undefined, 50 | customerId: url.searchParams.get("customerId") ?? undefined, 51 | externalCustomerId: 52 | url.searchParams.get("customerExternalId") ?? undefined, 53 | customerEmail: url.searchParams.get("customerEmail") ?? undefined, 54 | customerName: url.searchParams.get("customerName") ?? undefined, 55 | customerBillingAddress: url.searchParams.has("customerBillingAddress") 56 | ? JSON.parse(url.searchParams.get("customerBillingAddress") ?? "{}") 57 | : undefined, 58 | customerTaxId: url.searchParams.get("customerTaxId") ?? undefined, 59 | customerIpAddress: 60 | url.searchParams.get("customerIpAddress") ?? undefined, 61 | customerMetadata: url.searchParams.has("customerMetadata") 62 | ? JSON.parse(url.searchParams.get("customerMetadata") ?? "{}") 63 | : undefined, 64 | allowDiscountCodes: url.searchParams.has("allowDiscountCodes") 65 | ? url.searchParams.get("allowDiscountCodes") === "true" 66 | : undefined, 67 | discountId: url.searchParams.get("discountId") ?? undefined, 68 | metadata: url.searchParams.has("metadata") 69 | ? JSON.parse(url.searchParams.get("metadata") ?? "{}") 70 | : undefined, 71 | seats: url.searchParams.has("seats") 72 | ? Number.parseInt(url.searchParams.get("seats") ?? "1", 10) 73 | : undefined, 74 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 75 | }); 76 | 77 | const redirectUrl = new URL(result.url); 78 | 79 | if (theme) { 80 | redirectUrl.searchParams.set("theme", theme); 81 | } 82 | 83 | return NextResponse.redirect(redirectUrl.toString()); 84 | } catch (error) { 85 | console.error(error); 86 | return NextResponse.error(); 87 | } 88 | }; 89 | }; 90 | -------------------------------------------------------------------------------- /packages/polar-tanstack-start/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @polar-sh/tanstack-start 2 | 3 | ## 0.4.3 4 | 5 | ### Patch Changes 6 | 7 | - 3906ed9: Bump @polar-sh/sdk 8 | - Updated dependencies [3906ed9] 9 | - @polar-sh/adapter-utils@0.4.3 10 | 11 | ## 0.4.2 12 | 13 | ### Patch Changes 14 | 15 | - c591126: Bump @polar-sh/sdk 16 | - Updated dependencies [c591126] 17 | - @polar-sh/adapter-utils@0.4.2 18 | 19 | ## 0.4.1 20 | 21 | ### Patch Changes 22 | 23 | - b658623: Upgrade dependencies 24 | - Updated dependencies [b658623] 25 | - @polar-sh/adapter-utils@0.4.1 26 | 27 | ## 0.4.0 28 | 29 | ### Minor Changes 30 | 31 | - 4512e82: Bump Polar SDK 32 | 33 | ### Patch Changes 34 | 35 | - Updated dependencies [4512e82] 36 | - @polar-sh/adapter-utils@0.4.0 37 | 38 | ## 0.3.0 39 | 40 | ### Minor Changes 41 | 42 | - 6c46bd1: feat: add seats param to checkout 43 | 44 | ## 0.2.0 45 | 46 | ### Minor Changes 47 | 48 | - 1deb3b3: Update sdk to 0.40.2 49 | 50 | ### Patch Changes 51 | 52 | - Updated dependencies [1deb3b3] 53 | - @polar-sh/adapter-utils@0.3.0 54 | 55 | ## 0.1.12 56 | 57 | ### Patch Changes 58 | 59 | - 3250323: Update Polar SDK to 0.38.1 60 | - Updated dependencies [3250323] 61 | - @polar-sh/adapter-utils@0.2.10 62 | 63 | ## 0.1.11 64 | 65 | ### Patch Changes 66 | 67 | - a84c64c: Add support for returnUrl 68 | - Updated dependencies [a84c64c] 69 | - @polar-sh/adapter-utils@0.2.9 70 | 71 | ## 0.1.10 72 | 73 | ### Patch Changes 74 | 75 | - Updated dependencies [bcad48d] 76 | - @polar-sh/adapter-utils@0.2.8 77 | 78 | ## 0.1.9 79 | 80 | ### Patch Changes 81 | 82 | - 3ef623b: Bump SDK version 83 | - Updated dependencies [3ef623b] 84 | - @polar-sh/adapter-utils@0.2.7 85 | 86 | ## 0.1.8 87 | 88 | ### Patch Changes 89 | 90 | - 377e07e: Add refund webhooks 91 | - 54d368c: Bump SDK version 92 | - Updated dependencies [377e07e] 93 | - Updated dependencies [54d368c] 94 | - @polar-sh/adapter-utils@0.2.6 95 | 96 | ## 0.1.7 97 | 98 | ### Patch Changes 99 | 100 | - 78a922c: Add refund webhooks 101 | - Updated dependencies [78a922c] 102 | - Updated dependencies [e98e50b] 103 | - @polar-sh/adapter-utils@0.2.5 104 | 105 | ## 0.1.6 106 | 107 | ### Patch Changes 108 | 109 | - 39f4d39: Update Polar SDK version 110 | - Updated dependencies [39f4d39] 111 | - @polar-sh/adapter-utils@0.2.4 112 | 113 | ## 0.1.5 114 | 115 | ### Patch Changes 116 | 117 | - bdd7635: Bump SDK Dependency 118 | - Updated dependencies [bdd7635] 119 | - @polar-sh/adapter-utils@0.2.3 120 | 121 | ## 0.1.4 122 | 123 | ### Patch Changes 124 | 125 | - bf04d3a: Fix issue with SDK mistakenly resolving Zod v4 126 | - Updated dependencies [bf04d3a] 127 | - @polar-sh/adapter-utils@0.2.2 128 | 129 | ## 0.1.3 130 | 131 | ### Patch Changes 132 | 133 | - 1b25168: Add theme-support to Checkout configs 134 | 135 | ## 0.1.2 136 | 137 | ### Patch Changes 138 | 139 | - 87de0c5: Bump Polar SDK 140 | - Updated dependencies [87de0c5] 141 | - @polar-sh/adapter-utils@0.2.1 142 | 143 | ## 0.1.1 144 | 145 | ### Patch Changes 146 | 147 | - 853c66d: init tanstack 148 | -------------------------------------------------------------------------------- /packages/polar-elysia/src/checkout/checkout.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | import { type Context, status } from "elysia"; 3 | import type { InlineHandler } from "elysia/types"; 4 | 5 | export interface CheckoutConfig { 6 | accessToken?: string; 7 | successUrl?: string; 8 | returnUrl?: string; 9 | includeCheckoutId?: boolean; 10 | server?: "sandbox" | "production"; 11 | theme?: "light" | "dark"; 12 | } 13 | 14 | export const Checkout = ({ 15 | accessToken, 16 | successUrl, 17 | returnUrl, 18 | server, 19 | theme, 20 | includeCheckoutId = true, 21 | }: CheckoutConfig): InlineHandler => { 22 | const polar = new Polar({ 23 | accessToken: accessToken ?? process.env["POLAR_ACCESS_TOKEN"], 24 | server, 25 | }); 26 | 27 | return async (ctx: Context) => { 28 | const url = new URL(ctx.request.url); 29 | const products = url.searchParams.getAll("products"); 30 | 31 | if (products.length === 0) { 32 | return status(400, { 33 | error: "Missing products in query params", 34 | }); 35 | } 36 | 37 | const success = successUrl ? new URL(successUrl) : undefined; 38 | 39 | if (success && includeCheckoutId) { 40 | success.searchParams.set("checkoutId", "{CHECKOUT_ID}"); 41 | } 42 | 43 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 44 | 45 | try { 46 | const result = await polar.checkouts.create({ 47 | products, 48 | successUrl: success ? decodeURI(success.toString()) : undefined, 49 | customerId: url.searchParams.get("customerId") ?? undefined, 50 | externalCustomerId: 51 | url.searchParams.get("customerExternalId") ?? undefined, 52 | customerEmail: url.searchParams.get("customerEmail") ?? undefined, 53 | customerName: url.searchParams.get("customerName") ?? undefined, 54 | customerBillingAddress: url.searchParams.has("customerBillingAddress") 55 | ? JSON.parse(url.searchParams.get("customerBillingAddress") ?? "{}") 56 | : undefined, 57 | customerTaxId: url.searchParams.get("customerTaxId") ?? undefined, 58 | customerIpAddress: 59 | url.searchParams.get("customerIpAddress") ?? undefined, 60 | customerMetadata: url.searchParams.has("customerMetadata") 61 | ? JSON.parse(url.searchParams.get("customerMetadata") ?? "{}") 62 | : undefined, 63 | allowDiscountCodes: url.searchParams.has("allowDiscountCodes") 64 | ? url.searchParams.get("allowDiscountCodes") === "true" 65 | : undefined, 66 | discountId: url.searchParams.get("discountId") ?? undefined, 67 | metadata: url.searchParams.has("metadata") 68 | ? JSON.parse(url.searchParams.get("metadata") ?? "{}") 69 | : undefined, 70 | seats: url.searchParams.has("seats") 71 | ? Number.parseInt(url.searchParams.get("seats") ?? "1", 10) 72 | : undefined, 73 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 74 | }); 75 | 76 | const redirectUrl = new URL(result.url); 77 | 78 | if (theme) { 79 | redirectUrl.searchParams.set("theme", theme); 80 | } 81 | 82 | return ctx.redirect(redirectUrl.toString()); 83 | } catch (error) { 84 | console.error(error); 85 | return { error: "Internal server error" }; 86 | } 87 | }; 88 | }; 89 | -------------------------------------------------------------------------------- /packages/polar-remix/src/checkout/checkout.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | import type { LoaderFunction } from "../types"; 3 | 4 | export interface CheckoutConfig { 5 | accessToken?: string; 6 | successUrl?: string; 7 | returnUrl?: string; 8 | includeCheckoutId?: boolean; 9 | server?: "sandbox" | "production"; 10 | theme?: "light" | "dark"; 11 | } 12 | 13 | export const Checkout = ({ 14 | accessToken, 15 | successUrl, 16 | returnUrl, 17 | server, 18 | theme, 19 | includeCheckoutId = true, 20 | }: CheckoutConfig): LoaderFunction => { 21 | const polar = new Polar({ 22 | accessToken: accessToken ?? process.env["POLAR_ACCESS_TOKEN"], 23 | server, 24 | }); 25 | 26 | return async ({ request }) => { 27 | const url = new URL(request.url); 28 | const products = url.searchParams.getAll("products"); 29 | 30 | if (products.length === 0) { 31 | return Response.json( 32 | { error: "Missing products in query params" }, 33 | { status: 400 }, 34 | ); 35 | } 36 | 37 | const success = successUrl ? new URL(successUrl) : undefined; 38 | 39 | if (success && includeCheckoutId) { 40 | success.searchParams.set("checkoutId", "{CHECKOUT_ID}"); 41 | } 42 | 43 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 44 | 45 | try { 46 | const result = await polar.checkouts.create({ 47 | products, 48 | successUrl: success ? decodeURI(success.toString()) : undefined, 49 | customerId: url.searchParams.get("customerId") ?? undefined, 50 | externalCustomerId: 51 | url.searchParams.get("customerExternalId") ?? undefined, 52 | customerEmail: url.searchParams.get("customerEmail") ?? undefined, 53 | customerName: url.searchParams.get("customerName") ?? undefined, 54 | customerBillingAddress: url.searchParams.has("customerBillingAddress") 55 | ? JSON.parse(url.searchParams.get("customerBillingAddress") ?? "{}") 56 | : undefined, 57 | customerTaxId: url.searchParams.get("customerTaxId") ?? undefined, 58 | customerIpAddress: 59 | url.searchParams.get("customerIpAddress") ?? undefined, 60 | customerMetadata: url.searchParams.has("customerMetadata") 61 | ? JSON.parse(url.searchParams.get("customerMetadata") ?? "{}") 62 | : undefined, 63 | allowDiscountCodes: url.searchParams.has("allowDiscountCodes") 64 | ? url.searchParams.get("allowDiscountCodes") === "true" 65 | : undefined, 66 | discountId: url.searchParams.get("discountId") ?? undefined, 67 | metadata: url.searchParams.has("metadata") 68 | ? JSON.parse(url.searchParams.get("metadata") ?? "{}") 69 | : undefined, 70 | seats: url.searchParams.has("seats") 71 | ? Number.parseInt(url.searchParams.get("seats") ?? "1", 10) 72 | : undefined, 73 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 74 | }); 75 | 76 | const redirectUrl = new URL(result.url); 77 | 78 | if (theme) { 79 | redirectUrl.searchParams.set("theme", theme); 80 | } 81 | 82 | return Response.redirect(redirectUrl.toString()); 83 | } catch (error) { 84 | console.error(error); 85 | return Response.json({ error: "Internal server error" }, { status: 500 }); 86 | } 87 | }; 88 | }; 89 | -------------------------------------------------------------------------------- /packages/polar-express/src/checkout/checkout.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | import type { Request, Response } from "express"; 3 | 4 | export interface CheckoutConfig { 5 | accessToken?: string; 6 | successUrl?: string; 7 | returnUrl?: string; 8 | includeCheckoutId?: boolean; 9 | server?: "sandbox" | "production"; 10 | theme?: "light" | "dark"; 11 | } 12 | 13 | export const Checkout = ({ 14 | accessToken, 15 | successUrl, 16 | returnUrl, 17 | server, 18 | theme, 19 | includeCheckoutId = true, 20 | }: CheckoutConfig) => { 21 | const polar = new Polar({ 22 | accessToken: accessToken ?? process.env["POLAR_ACCESS_TOKEN"], 23 | server, 24 | }); 25 | 26 | return async (req: Request, res: Response) => { 27 | const url = new URL( 28 | `${req.protocol}://${req.get("host")}${req.originalUrl}`, 29 | ); 30 | const products = url.searchParams.getAll("products"); 31 | 32 | if (products.length === 0) { 33 | res.status(400).json({ 34 | error: "Missing products in query params", 35 | }); 36 | return; 37 | } 38 | 39 | const success = successUrl ? new URL(successUrl) : undefined; 40 | 41 | if (success && includeCheckoutId) { 42 | success.searchParams.set("checkoutId", "{CHECKOUT_ID}"); 43 | } 44 | 45 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 46 | 47 | try { 48 | const result = await polar.checkouts.create({ 49 | products, 50 | successUrl: success ? decodeURI(success.toString()) : undefined, 51 | customerId: url.searchParams.get("customerId") ?? undefined, 52 | externalCustomerId: 53 | url.searchParams.get("customerExternalId") ?? undefined, 54 | customerEmail: url.searchParams.get("customerEmail") ?? undefined, 55 | customerName: url.searchParams.get("customerName") ?? undefined, 56 | customerBillingAddress: url.searchParams.has("customerBillingAddress") 57 | ? JSON.parse(url.searchParams.get("customerBillingAddress") ?? "{}") 58 | : undefined, 59 | customerTaxId: url.searchParams.get("customerTaxId") ?? undefined, 60 | customerIpAddress: 61 | url.searchParams.get("customerIpAddress") ?? undefined, 62 | customerMetadata: url.searchParams.has("customerMetadata") 63 | ? JSON.parse(url.searchParams.get("customerMetadata") ?? "{}") 64 | : undefined, 65 | allowDiscountCodes: url.searchParams.has("allowDiscountCodes") 66 | ? url.searchParams.get("allowDiscountCodes") === "true" 67 | : undefined, 68 | discountId: url.searchParams.get("discountId") ?? undefined, 69 | metadata: url.searchParams.has("metadata") 70 | ? JSON.parse(url.searchParams.get("metadata") ?? "{}") 71 | : undefined, 72 | seats: url.searchParams.has("seats") 73 | ? Number.parseInt(url.searchParams.get("seats") ?? "1", 10) 74 | : undefined, 75 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 76 | }); 77 | 78 | const redirectUrl = new URL(result.url); 79 | 80 | if (theme) { 81 | redirectUrl.searchParams.set("theme", theme); 82 | } 83 | 84 | res.redirect(redirectUrl.toString()); 85 | } catch (error) { 86 | console.error(error); 87 | res.status(500).json({ error: "Internal server error" }); 88 | } 89 | }; 90 | }; 91 | -------------------------------------------------------------------------------- /packages/polar-supabase/src/checkout/checkout.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | 3 | export interface CheckoutConfig { 4 | accessToken?: string; 5 | successUrl?: string; 6 | returnUrl?: string; 7 | includeCheckoutId?: boolean; 8 | server?: "sandbox" | "production"; 9 | theme?: "light" | "dark"; 10 | } 11 | 12 | export const Checkout = ({ 13 | accessToken, 14 | successUrl, 15 | returnUrl, 16 | server, 17 | theme, 18 | includeCheckoutId = true, 19 | }: CheckoutConfig) => { 20 | const polar = new Polar({ 21 | accessToken, 22 | server, 23 | }); 24 | 25 | return async (req: Request) => { 26 | const url = new URL(req.url); 27 | const products = url.searchParams.getAll("products"); 28 | 29 | if (products.length === 0) { 30 | return new Response( 31 | JSON.stringify({ error: "Missing products in query params" }), 32 | { 33 | status: 400, 34 | headers: { "Content-Type": "application/json" }, 35 | }, 36 | ); 37 | } 38 | 39 | const success = successUrl ? new URL(successUrl) : undefined; 40 | 41 | if (success && includeCheckoutId) { 42 | success.searchParams.set("checkoutId", "{CHECKOUT_ID}"); 43 | } 44 | 45 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 46 | 47 | try { 48 | const result = await polar.checkouts.create({ 49 | products, 50 | successUrl: success ? decodeURI(success.toString()) : undefined, 51 | customerId: url.searchParams.get("customerId") ?? undefined, 52 | externalCustomerId: 53 | url.searchParams.get("customerExternalId") ?? undefined, 54 | customerEmail: url.searchParams.get("customerEmail") ?? undefined, 55 | customerName: url.searchParams.get("customerName") ?? undefined, 56 | customerBillingAddress: url.searchParams.has("customerBillingAddress") 57 | ? JSON.parse(url.searchParams.get("customerBillingAddress") ?? "{}") 58 | : undefined, 59 | customerTaxId: url.searchParams.get("customerTaxId") ?? undefined, 60 | customerIpAddress: 61 | url.searchParams.get("customerIpAddress") ?? undefined, 62 | customerMetadata: url.searchParams.has("customerMetadata") 63 | ? JSON.parse(url.searchParams.get("customerMetadata") ?? "{}") 64 | : undefined, 65 | allowDiscountCodes: url.searchParams.has("allowDiscountCodes") 66 | ? url.searchParams.get("allowDiscountCodes") === "true" 67 | : undefined, 68 | discountId: url.searchParams.get("discountId") ?? undefined, 69 | metadata: url.searchParams.has("metadata") 70 | ? JSON.parse(url.searchParams.get("metadata") ?? "{}") 71 | : undefined, 72 | seats: url.searchParams.has("seats") 73 | ? Number.parseInt(url.searchParams.get("seats") ?? "1", 10) 74 | : undefined, 75 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 76 | }); 77 | 78 | const redirectUrl = new URL(result.url); 79 | 80 | if (theme) { 81 | redirectUrl.searchParams.set("theme", theme); 82 | } 83 | 84 | return new Response(null, { 85 | status: 302, 86 | headers: { 87 | Location: redirectUrl.toString(), 88 | }, 89 | }); 90 | } catch (error) { 91 | console.error(error); 92 | return new Response(null, { status: 500 }); 93 | } 94 | }; 95 | }; 96 | -------------------------------------------------------------------------------- /packages/polar-astro/src/checkout/checkout.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | import type { APIRoute } from "astro"; 3 | 4 | export interface CheckoutConfig { 5 | accessToken?: string; 6 | successUrl?: string; 7 | returnUrl?: string; 8 | includeCheckoutId?: boolean; 9 | server?: "sandbox" | "production"; 10 | theme?: "light" | "dark"; 11 | } 12 | 13 | export const Checkout = ({ 14 | accessToken, 15 | successUrl, 16 | returnUrl, 17 | server, 18 | theme, 19 | includeCheckoutId = true, 20 | }: CheckoutConfig): APIRoute => { 21 | return async ({ url }) => { 22 | if (!accessToken) { 23 | const { getSecret } = await import("astro:env/server"); 24 | accessToken = getSecret("POLAR_ACCESS_TOKEN"); 25 | } 26 | 27 | const polar = new Polar({ 28 | accessToken, 29 | server, 30 | }); 31 | 32 | const products = url.searchParams.getAll("products"); 33 | 34 | if (products.length === 0) { 35 | return Response.json( 36 | { error: "Missing products in query params" }, 37 | { status: 400 }, 38 | ); 39 | } 40 | 41 | const success = successUrl ? new URL(successUrl) : undefined; 42 | 43 | if (success && includeCheckoutId) { 44 | success.searchParams.set("checkoutId", "{CHECKOUT_ID}"); 45 | } 46 | 47 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 48 | 49 | try { 50 | const result = await polar.checkouts.create({ 51 | products, 52 | successUrl: success ? decodeURI(success.toString()) : undefined, 53 | customerId: url.searchParams.get("customerId") ?? undefined, 54 | externalCustomerId: 55 | url.searchParams.get("customerExternalId") ?? undefined, 56 | customerEmail: url.searchParams.get("customerEmail") ?? undefined, 57 | customerName: url.searchParams.get("customerName") ?? undefined, 58 | customerBillingAddress: url.searchParams.has("customerBillingAddress") 59 | ? JSON.parse(url.searchParams.get("customerBillingAddress") ?? "{}") 60 | : undefined, 61 | customerTaxId: url.searchParams.get("customerTaxId") ?? undefined, 62 | customerIpAddress: 63 | url.searchParams.get("customerIpAddress") ?? undefined, 64 | customerMetadata: url.searchParams.has("customerMetadata") 65 | ? JSON.parse(url.searchParams.get("customerMetadata") ?? "{}") 66 | : undefined, 67 | allowDiscountCodes: url.searchParams.has("allowDiscountCodes") 68 | ? url.searchParams.get("allowDiscountCodes") === "true" 69 | : undefined, 70 | discountId: url.searchParams.get("discountId") ?? undefined, 71 | metadata: url.searchParams.has("metadata") 72 | ? JSON.parse(url.searchParams.get("metadata") ?? "{}") 73 | : undefined, 74 | seats: url.searchParams.has("seats") 75 | ? Number.parseInt(url.searchParams.get("seats") ?? "1", 10) 76 | : undefined, 77 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 78 | }); 79 | 80 | const redirectUrl = new URL(result.url); 81 | 82 | if (theme) { 83 | redirectUrl.searchParams.set("theme", theme); 84 | } 85 | 86 | return Response.redirect(redirectUrl.toString()); 87 | } catch (error) { 88 | console.error(error); 89 | return Response.json({ error: "Internal server error" }, { status: 500 }); 90 | } 91 | }; 92 | }; 93 | -------------------------------------------------------------------------------- /packages/polar-tanstack-start/src/checkout/checkout.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | // @ts-expect-error - TODO: fix this 3 | import type { StartAPIMethodCallback } from "@tanstack/react-start/api"; 4 | 5 | export interface CheckoutConfig { 6 | accessToken?: string; 7 | successUrl?: string; 8 | returnUrl?: string; 9 | includeCheckoutId?: boolean; 10 | server?: "sandbox" | "production"; 11 | theme?: "light" | "dark"; 12 | } 13 | 14 | export const Checkout = ({ 15 | accessToken, 16 | successUrl, 17 | returnUrl, 18 | server, 19 | theme, 20 | includeCheckoutId = true, 21 | }: CheckoutConfig): StartAPIMethodCallback => { 22 | const polar = new Polar({ 23 | accessToken, 24 | server, 25 | }); 26 | 27 | // @ts-expect-error - TODO: fix this 28 | return async ({ request }) => { 29 | const url = new URL(request.url); 30 | const products = url.searchParams.getAll("products"); 31 | 32 | if (products.length === 0) { 33 | return Response.json( 34 | { error: "Missing products in query params" }, 35 | { status: 400 }, 36 | ); 37 | } 38 | 39 | const success = successUrl ? new URL(successUrl) : undefined; 40 | 41 | if (success && includeCheckoutId) { 42 | success.searchParams.set("checkoutId", "{CHECKOUT_ID}"); 43 | } 44 | 45 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 46 | 47 | try { 48 | const result = await polar.checkouts.create({ 49 | products, 50 | successUrl: success ? decodeURI(success.toString()) : undefined, 51 | customerId: url.searchParams.get("customerId") ?? undefined, 52 | externalCustomerId: 53 | url.searchParams.get("customerExternalId") ?? undefined, 54 | customerEmail: url.searchParams.get("customerEmail") ?? undefined, 55 | customerName: url.searchParams.get("customerName") ?? undefined, 56 | customerBillingAddress: url.searchParams.has("customerBillingAddress") 57 | ? JSON.parse(url.searchParams.get("customerBillingAddress") ?? "{}") 58 | : undefined, 59 | customerTaxId: url.searchParams.get("customerTaxId") ?? undefined, 60 | customerIpAddress: 61 | url.searchParams.get("customerIpAddress") ?? undefined, 62 | customerMetadata: url.searchParams.has("customerMetadata") 63 | ? JSON.parse(url.searchParams.get("customerMetadata") ?? "{}") 64 | : undefined, 65 | allowDiscountCodes: url.searchParams.has("allowDiscountCodes") 66 | ? url.searchParams.get("allowDiscountCodes") === "true" 67 | : undefined, 68 | discountId: url.searchParams.get("discountId") ?? undefined, 69 | metadata: url.searchParams.has("metadata") 70 | ? JSON.parse(url.searchParams.get("metadata") ?? "{}") 71 | : undefined, 72 | seats: url.searchParams.has("seats") 73 | ? Number.parseInt(url.searchParams.get("seats") ?? "1", 10) 74 | : undefined, 75 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 76 | }); 77 | 78 | const redirectUrl = new URL(result.url); 79 | 80 | if (theme) { 81 | redirectUrl.searchParams.set("theme", theme); 82 | } 83 | 84 | return Response.redirect(redirectUrl.toString()); 85 | } catch (error) { 86 | console.error(error); 87 | return Response.error(); 88 | } 89 | }; 90 | }; 91 | -------------------------------------------------------------------------------- /packages/polar-fastify/src/checkout/checkout.ts: -------------------------------------------------------------------------------- 1 | import { Polar } from "@polar-sh/sdk"; 2 | import type { FastifyReply, FastifyRequest, RouteHandler } from "fastify"; 3 | 4 | export interface CheckoutConfig { 5 | accessToken?: string; 6 | successUrl?: string; 7 | returnUrl?: string; 8 | includeCheckoutId?: boolean; 9 | server?: "sandbox" | "production"; 10 | theme?: "light" | "dark"; 11 | } 12 | 13 | export const Checkout = ({ 14 | accessToken, 15 | successUrl, 16 | returnUrl, 17 | server, 18 | theme, 19 | includeCheckoutId = true, 20 | }: CheckoutConfig): RouteHandler => { 21 | const polar = new Polar({ 22 | accessToken: accessToken ?? process.env["POLAR_ACCESS_TOKEN"], 23 | server, 24 | }); 25 | 26 | return async (request: FastifyRequest, reply: FastifyReply) => { 27 | const url = new URL( 28 | `${request.protocol}://${request.hostname}${request.url}`, 29 | ); 30 | const products = url.searchParams.getAll("products"); 31 | 32 | if (products.length === 0) { 33 | return reply.status(400).send({ 34 | error: "Missing products in query params", 35 | }); 36 | } 37 | 38 | const success = successUrl ? new URL(successUrl) : undefined; 39 | 40 | if (success && includeCheckoutId) { 41 | success.searchParams.set("checkoutId", "{CHECKOUT_ID}"); 42 | } 43 | 44 | const retUrl = returnUrl ? new URL(returnUrl) : undefined; 45 | 46 | try { 47 | const result = await polar.checkouts.create({ 48 | products, 49 | successUrl: success ? decodeURI(success.toString()) : undefined, 50 | customerId: url.searchParams.get("customerId") ?? undefined, 51 | externalCustomerId: 52 | url.searchParams.get("customerExternalId") ?? undefined, 53 | customerEmail: url.searchParams.get("customerEmail") ?? undefined, 54 | customerName: url.searchParams.get("customerName") ?? undefined, 55 | customerBillingAddress: url.searchParams.has("customerBillingAddress") 56 | ? JSON.parse(url.searchParams.get("customerBillingAddress") ?? "{}") 57 | : undefined, 58 | customerTaxId: url.searchParams.get("customerTaxId") ?? undefined, 59 | customerIpAddress: 60 | url.searchParams.get("customerIpAddress") ?? undefined, 61 | customerMetadata: url.searchParams.has("customerMetadata") 62 | ? JSON.parse(url.searchParams.get("customerMetadata") ?? "{}") 63 | : undefined, 64 | allowDiscountCodes: url.searchParams.has("allowDiscountCodes") 65 | ? url.searchParams.get("allowDiscountCodes") === "true" 66 | : undefined, 67 | discountId: url.searchParams.get("discountId") ?? undefined, 68 | metadata: url.searchParams.has("metadata") 69 | ? JSON.parse(url.searchParams.get("metadata") ?? "{}") 70 | : undefined, 71 | seats: url.searchParams.has("seats") 72 | ? Number.parseInt(url.searchParams.get("seats") ?? "1", 10) 73 | : undefined, 74 | returnUrl: retUrl ? decodeURI(retUrl.toString()) : undefined, 75 | }); 76 | 77 | const redirectUrl = new URL(result.url); 78 | 79 | if (theme) { 80 | redirectUrl.searchParams.set("theme", theme); 81 | } 82 | 83 | return reply.redirect(redirectUrl.toString()); 84 | } catch (error) { 85 | console.error(error); 86 | return reply.status(500).send({ error: "Internal server error" }); 87 | } 88 | }; 89 | }; 90 | -------------------------------------------------------------------------------- /packages/polar-remix/README.md: -------------------------------------------------------------------------------- 1 | # @polar-sh/remix 2 | 3 | Payments and Checkouts made dead simple with Remi and React Router. 4 | 5 | `pnpm install @polar-sh/remix zod` 6 | 7 | ## Checkout 8 | 9 | Create a Checkout handler which takes care of redirections. 10 | 11 | ```typescript 12 | import { Checkout } from "@polar-sh/remix"; 13 | 14 | export const loader = Checkout({ 15 | accessToken: "xxx", // Or set an environment variable to POLAR_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 Polar - 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 "@polar-sh/remix"; 41 | 42 | export const loader = CustomerPortal({ 43 | accessToken: "xxx", // Or set an environment variable to POLAR_ACCESS_TOKEN 44 | getCustomerId: (event) => "", // Fuction to resolve a Polar 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 Polar - 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 '@polar-sh/remix'; 56 | 57 | export const action = Webhooks({ 58 | webhookSecret: process.env.POLAR_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) => 89 | -------------------------------------------------------------------------------- /packages/polar-astro/README.md: -------------------------------------------------------------------------------- 1 | # @polar-sh/astro 2 | 3 | Payments and Checkouts made dead simple with Astro. 4 | 5 | `pnpm install @polar-sh/astro` 6 | 7 | ## Checkout 8 | 9 | Create a Checkout handler which takes care of redirections. 10 | 11 | ```typescript 12 | import { Checkout } from "@polar-sh/astro"; 13 | import { POLAR_ACCESS_TOKEN, POLAR_SUCCESS_URL } from "astro:env/server"; 14 | 15 | export const GET = Checkout({ 16 | accessToken: POLAR_ACCESS_TOKEN, 17 | successUrl: POLAR_SUCCESS_URL, 18 | returnUrl: "https://myapp.com", // Optional Return URL, which renders a Back-button in the Checkout 19 | server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise 20 | theme: "dark" // Enforces the theme - System-preferred theme will be set if left omitted 21 | }); 22 | ``` 23 | 24 | ### Query Params 25 | 26 | Pass query params to this route. 27 | 28 | - products `?products=123` 29 | - customerId (optional) `?products=123&customerId=xxx` 30 | - customerExternalId (optional) `?products=123&customerExternalId=xxx` 31 | - customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com` 32 | - customerName (optional) `?products=123&customerName=Jane` 33 | - seats (optional) `?products=123&seats=5` - Number of seats for seat-based products 34 | - metadata (optional) `URL-Encoded JSON string` 35 | 36 | ## Customer Portal 37 | 38 | Create a customer portal where your customer can view orders and subscriptions. 39 | 40 | ```typescript 41 | import { CustomerPortal } from "@polar-sh/astro"; 42 | import { POLAR_ACCESS_TOKEN } from "astro:env/server"; 43 | 44 | export const GET = CustomerPortal({ 45 | accessToken: POLAR_ACCESS_TOKEN, 46 | getCustomerId: (event) => "", // Fuction to resolve a Polar Customer ID 47 | returnUrl: "https://myapp.com", // Optional Return URL, which renders a Back-button in the Customer Portal 48 | server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise 49 | }); 50 | ``` 51 | 52 | ## Webhooks 53 | 54 | A simple utility which resolves incoming webhook payloads by signing the webhook secret properly. 55 | 56 | ```typescript 57 | import { Webhooks } from '@polar-sh/astro'; 58 | import { POLAR_WEBHOOK_SECRET } from "astro:env/server" 59 | 60 | export const POST = Webhooks({ 61 | webhookSecret: POLAR_WEBHOOK_SECRET, 62 | onPayload: async (payload) => /** Handle payload */, 63 | }) 64 | ``` 65 | 66 | #### Payload Handlers 67 | 68 | The Webhook handler also supports granular handlers for easy integration. 69 | 70 | - onCheckoutCreated: (payload) => 71 | - onCheckoutUpdated: (payload) => 72 | - onOrderCreated: (payload) => 73 | - onOrderUpdated: (payload) => 74 | - onOrderPaid: (payload) => 75 | - onSubscriptionCreated: (payload) => 76 | - onSubscriptionUpdated: (payload) => 77 | - onSubscriptionActive: (payload) => 78 | - onSubscriptionCanceled: (payload) => 79 | - onSubscriptionRevoked: (payload) => 80 | - onProductCreated: (payload) => 81 | - onProductUpdated: (payload) => 82 | - onOrganizationUpdated: (payload) => 83 | - onBenefitCreated: (payload) => 84 | - onBenefitUpdated: (payload) => 85 | - onBenefitGrantCreated: (payload) => 86 | - onBenefitGrantUpdated: (payload) => 87 | - onBenefitGrantRevoked: (payload) => 88 | - onCustomerCreated: (payload) => 89 | - onCustomerUpdated: (payload) => 90 | - onCustomerDeleted: (payload) => 91 | - onCustomerStateChanged: (payload) => 92 | --------------------------------------------------------------------------------