Polar
Polar는 개발자 우선 결제 인프라입니다. 결제, 체크아웃 등을 위한 많은 개발자 우선 통합을 기본적으로 제공합니다. 이 플러그인은 Polar를 Better Auth와 통합하여 인증 + 결제 흐름을 원활하게 만드는 데 도움이 됩니다.
이 플러그인은 Polar 팀에서 관리합니다. 버그, 문제 또는 기능 요청은 Polar GitHub 저장소를 방문하세요.
기능
- 체크아웃 통합
- 고객 포털
- 가입 시 자동 고객 생성
- 유연한 사용량 기반 청구를 위한 이벤트 수집 및 고객 계량기
- 서명 검증을 통한 안전한 Polar 웹훅 처리
- 구매를 조직과 연결하는 참조 시스템
설치
pnpm add better-auth @polar-sh/better-auth @polar-sh/sdk준비
Polar Organization Settings로 이동하여 Organization Access Token을 생성합니다. 환경에 추가하세요.
# .env
POLAR_ACCESS_TOKEN=...BetterAuth 서버 구성
Polar 플러그인은 스택에 기능을 추가하는 여러 추가 플러그인과 함께 제공됩니다.
- Checkout - 원활한 체크아웃 통합 활성화
- Portal - 고객이 주문, 구독 및 부여된 혜택을 관리할 수 있게 함
- Usage - 고객 계량기 나열 및 사용량 기반 청구를 위한 이벤트 수집을 위한 간단한 확장
- Webhooks - 관련 Polar 웹훅 수신
import { betterAuth } from "better-auth";
import { polar, checkout, portal, usage, webhooks } from "@polar-sh/better-auth";
import { Polar } from "@polar-sh/sdk";
const polarClient = new Polar({
accessToken: process.env.POLAR_ACCESS_TOKEN,
// Polar Sandbox 환경을 사용하는 경우 'sandbox' 사용
// 액세스 토큰, 제품 등은 환경 간에 완전히 분리됩니다.
// 예를 들어 Production에서 얻은 액세스 토큰은 Sandbox 환경에서 사용할 수 없습니다.
server: 'sandbox'
});
const auth = betterAuth({
// ... Better Auth 설정
plugins: [
polar({
client: polarClient,
createCustomerOnSignUp: true,
use: [
checkout({
products: [
{
productId: "123-456-789", // Polar Dashboard의 제품 ID
slug: "pro" // Checkout URL에서 쉽게 참조할 수 있는 사용자 정의 슬러그, 예: /checkout/pro
}
],
successUrl: "/success?checkout_id={CHECKOUT_ID}",
authenticatedUsersOnly: true
}),
portal(),
usage(),
webhooks({
secret: process.env.POLAR_WEBHOOK_SECRET,
onCustomerStateChanged: (payload) => // 고객에 관한 모든 것이 변경될 때 트리거됨
onOrderPaid: (payload) => // 주문이 결제될 때 트리거됨 (구매, 구독 갱신 등)
... // 25개 이상의 세분화된 웹훅 핸들러
onPayload: (payload) => // 모든 이벤트에 대한 포괄적 핸들러
})
],
})
]
});BetterAuth 클라이언트 구성
BetterAuth Client를 사용하여 Polar 기능과 상호 작용합니다.
import { createAuthClient } from "better-auth/react";
import { polarClient } from "@polar-sh/better-auth";
// 이것이 필요한 전부입니다
// 모든 Polar 플러그인 등은 서버 측 BetterAuth 설정에 연결되어야 합니다
export const authClient = createAuthClient({
plugins: [polarClient()],
});구성 옵션
import { betterAuth } from "better-auth";
import {
polar,
checkout,
portal,
usage,
webhooks,
} from "@polar-sh/better-auth";
import { Polar } from "@polar-sh/sdk";
const polarClient = new Polar({
accessToken: process.env.POLAR_ACCESS_TOKEN,
// Polar Sandbox 환경을 사용하는 경우 'sandbox' 사용
// 액세스 토큰, 제품 등은 환경 간에 완전히 분리됩니다.
// 예를 들어 Production에서 얻은 액세스 토큰은 Sandbox 환경에서 사용할 수 없습니다.
server: "sandbox",
});
const auth = betterAuth({
// ... Better Auth 설정
plugins: [
polar({
client: polarClient,
createCustomerOnSignUp: true,
getCustomerCreateParams: ({ user }, request) => ({
metadata: {
myCustomProperty: 123,
},
}),
use: [
// 여기에 Polar 플러그인 추가
],
}),
],
});필수 옵션
client: Polar SDK 클라이언트 인스턴스
선택적 옵션
createCustomerOnSignUp: 사용자가 가입할 때 자동으로 Polar 고객 생성getCustomerCreateParams: 추가 고객 생성 메타데이터를 제공하는 사용자 정의 함수
고객
createCustomerOnSignUp이 활성화되면 Better-Auth Database에 새 User가 추가될 때 새 Polar Customer가 자동으로 생성됩니다.
모든 새 고객은 연결된 externalId로 생성되며, 이는 Database에 있는 User의 ID입니다. 이를 통해 Database에서 Polar에서 User로의 매핑을 건너뛸 수 있습니다.
Checkout 플러그인
앱에서 체크아웃을 지원하려면 use 속성에 Checkout 플러그인을 전달하기만 하면 됩니다.
import { polar, checkout } from "@polar-sh/better-auth";
const auth = betterAuth({
// ... Better Auth 설정
plugins: [
polar({
...
use: [
checkout({
// 선택적 필드 - Product ID 대신 슬러그를 체크아웃에 전달할 수 있게 함
products: [ { productId: "123-456-789", slug: "pro" } ],
// 체크아웃이 성공적으로 완료될 때 반환할 상대 URL
successUrl: "/success?checkout_id={CHECKOUT_ID}",
// 인증되지 않은 체크아웃 세션을 허용할지 여부
authenticatedUsersOnly: true
})
],
})
]
});체크아웃이 활성화되면 BetterAuth Client의 checkout 메서드를 사용하여 Checkout Session을 초기화할 수 있습니다. 이렇게 하면 사용자가 Product Checkout으로 리디렉션됩니다.
await authClient.checkout({
// 여기에 모든 Polar Product ID를 전달할 수 있습니다
products: ["e651f46d-ac20-4f26-b769-ad088b123df2"],
// 또는 Checkout Config에서 "products"를 설정한 경우 슬러그를 전달할 수 있습니다
slug: "pro",
});체크아웃은 인증된 User를 고객으로 자동으로 체크아웃에 전달합니다. 이메일 주소는 "고정"됩니다.
authenticatedUsersOnly가 false인 경우 - 연결된 고객 없이 체크아웃 세션을 트리거할 수 있습니다.
Organization 지원
이 플러그인은 Organization 플러그인을 지원합니다. Checkout referenceId에 organization ID를 전달하면 조직 구성원이 한 구매를 추적할 수 있습니다.
const organizationId = (await authClient.organization.list())?.data?.[0]?.id,
await authClient.checkout({
// 여기에 모든 Polar Product ID를 전달할 수 있습니다
products: ["e651f46d-ac20-4f26-b769-ad088b123df2"],
// 또는 Checkout Config에서 "products"를 설정한 경우 슬러그를 전달할 수 있습니다
slug: 'pro',
// Reference ID는 체크아웃, 주문 및 구독 객체의 메타데이터에 `referenceId`로 저장됩니다
referenceId: organizationId
});Portal 플러그인
구매, 주문 및 구독에 대한 고객 관리를 활성화하는 플러그인입니다.
import { polar, checkout, portal } from "@polar-sh/better-auth";
const auth = betterAuth({
// ... Better Auth 설정
plugins: [
polar({
...
use: [
checkout(...),
portal()
],
})
]
});portal 플러그인은 BetterAuth Client에 authClient.customer 아래에 범위가 지정된 고객 관리 메서드 세트를 제공합니다.
고객 포털 관리
다음 메서드는 사용자를 Polar Customer Portal로 리디렉션하여 주문, 구매, 구독, 혜택 등을 볼 수 있습니다.
await authClient.customer.portal();고객 상태
portal 플러그인은 일반 Customer State를 검색하기 위한 편리한 state 메서드도 추가합니다.
const { data: customerState } = await authClient.customer.state();고객 상태 객체에는 다음이 포함됩니다:
- 고객에 대한 모든 데이터.
- 활성 구독 목록
- 참고: 여기에는 상위 조직이 수행한 구독이 포함되지 않습니다. 자세한 내용은 아래 구독 목록 메서드를 참조하세요.
- 부여된 혜택 목록.
- 현재 잔액이 있는 활성 계량기 목록.
따라서 해당 단일 객체로 서비스에 대한 접근을 제공해야 하는지 확인하는 데 필요한 모든 정보를 얻을 수 있습니다.
Polar Docs에서 Polar Customer State에 대해 자세히 알아볼 수 있습니다.
혜택, 주문 및 구독
portal 플러그인은 인증된 사용자/고객과 관련된 혜택, 주문 및 구독을 나열하는 3가지 편리한 메서드를 추가합니다.
이 모든 메서드는 Polar CustomerPortal API를 사용합니다
혜택
이 메서드는 인증된 사용자/고객에 대해 부여된 혜택만 나열합니다.
const { data: benefits } = await authClient.customer.benefits.list({
query: {
page: 1,
limit: 10,
},
});주문
이 메서드는 인증된 사용자/고객의 구매 및 구독 갱신과 같은 주문을 나열합니다.
const { data: orders } = await authClient.customer.orders.list({
query: {
page: 1,
limit: 10,
productBillingType: "one_time", // 또는 'recurring'
},
});구독
이 메서드는 인증된 사용자/고객과 연결된 구독을 나열합니다.
const { data: subscriptions } = await authClient.customer.subscriptions.list({
query: {
page: 1,
limit: 10,
active: true,
},
});중요 - Organization 지원
이것은 상위 조직이 인증된 사용자에게 만든 구독을 반환하지 않습니다.
그러나 이 메서드에 referenceId를 전달할 수 있습니다. 이렇게 하면 사용자와 연결된 구독 대신 해당 referenceId와 연결된 모든 구독이 반환됩니다.
따라서 사용자가 접근 권한을 가져야 하는지 확인하려면 사용자의 organization ID를 전달하여 해당 조직에 대한 활성 구독이 있는지 확인하세요.
const organizationId = (await authClient.organization.list())?.data?.[0]?.id,
const { data: subscriptions } = await authClient.customer.orders.list({
query: {
page: 1,
limit: 10,
active: true,
referenceId: organizationId
},
});
const userShouldHaveAccess = subscriptions.some(
sub => // 구독 제품 등을 확인하는 로직
)Usage 플러그인
사용량 기반 청구를 위한 간단한 플러그인입니다.
import { polar, checkout, portal, usage } from "@polar-sh/better-auth";
const auth = betterAuth({
// ... Better Auth 설정
plugins: [
polar({
...
use: [
checkout(...),
portal(),
usage()
],
})
]
});이벤트 수집
Polar의 사용량 기반 청구는 전적으로 이벤트 수집을 기반으로 합니다. 애플리케이션에서 이벤트를 수집하고, 해당 사용량을 나타내는 Meter를 생성하고, 제품에 계량된 가격을 추가하여 청구하세요.
Polar Docs에서 사용량 기반 청구에 대해 자세히 알아보세요.
const { data: ingested } = await authClient.usage.ingest({
event: "file-uploads",
metadata: {
uploadedFiles: 12,
},
});인증된 사용자는 수집된 이벤트와 자동으로 연결됩니다.
고객 계량기
인증된 사용자의 Usage Meter 또는 Customer Meter를 나열하는 간단한 메서드입니다.
Customer Meter에는 정의된 계량기에 대한 소비에 대한 모든 정보가 포함됩니다.
- 고객 정보
- 계량기 정보
- 고객 계량기 정보
- 소비된 단위
- 크레딧된 단위
- 잔액
const { data: customerMeters } = await authClient.usage.meters.list({
query: {
page: 1,
limit: 10,
},
});Webhooks 플러그인
Webhooks 플러그인은 Polar 조직에서 들어오는 이벤트를 캡처하는 데 사용할 수 있습니다.
import { polar, webhooks } from "@polar-sh/better-auth";
const auth = betterAuth({
// ... Better Auth 설정
plugins: [
polar({
...
use: [
webhooks({
secret: process.env.POLAR_WEBHOOK_SECRET,
onCustomerStateChanged: (payload) => // 고객에 관한 모든 것이 변경될 때 트리거됨
onOrderPaid: (payload) => // 주문이 결제될 때 트리거됨 (구매, 구독 갱신 등)
... // 25개 이상의 세분화된 웹훅 핸들러
onPayload: (payload) => // 모든 이벤트에 대한 포괄적 핸들러
})
],
})
]
});Polar Organization Settings 페이지에서 Webhook 엔드포인트를 구성합니다. Webhook 엔드포인트는 /polar/webhooks에서 구성됩니다.
환경에 시크릿을 추가합니다.
# .env
POLAR_WEBHOOK_SECRET=...플러그인은 모든 Polar 웹훅 이벤트에 대한 핸들러를 지원합니다:
onPayload- 들어오는 모든 Webhook 이벤트에 대한 포괄적 핸들러onCheckoutCreated- 체크아웃이 생성될 때 트리거됨onCheckoutUpdated- 체크아웃이 업데이트될 때 트리거됨onOrderCreated- 주문이 생성될 때 트리거됨onOrderPaid- 주문이 결제될 때 트리거됨onOrderRefunded- 주문이 환불될 때 트리거됨onRefundCreated- 환불이 생성될 때 트리거됨onRefundUpdated- 환불이 업데이트될 때 트리거됨onSubscriptionCreated- 구독이 생성될 때 트리거됨onSubscriptionUpdated- 구독이 업데이트될 때 트리거됨onSubscriptionActive- 구독이 활성화될 때 트리거됨onSubscriptionCanceled- 구독이 취소될 때 트리거됨onSubscriptionRevoked- 구독이 철회될 때 트리거됨onSubscriptionUncanceled- 구독 취소가 취소될 때 트리거됨onProductCreated- 제품이 생성될 때 트리거됨onProductUpdated- 제품이 업데이트될 때 트리거됨onOrganizationUpdated- 조직이 업데이트될 때 트리거됨onBenefitCreated- 혜택이 생성될 때 트리거됨onBenefitUpdated- 혜택이 업데이트될 때 트리거됨onBenefitGrantCreated- 혜택 부여가 생성될 때 트리거됨onBenefitGrantUpdated- 혜택 부여가 업데이트될 때 트리거됨onBenefitGrantRevoked- 혜택 부여가 철회될 때 트리거됨onCustomerCreated- 고객이 생성될 때 트리거됨onCustomerUpdated- 고객이 업데이트될 때 트리거됨onCustomerDeleted- 고객이 삭제될 때 트리거됨onCustomerStateChanged- 고객이 생성될 때 트리거됨