init
This commit is contained in:
436
.opencode/skills/payment-integration/references/polar/sdk.md
Normal file
436
.opencode/skills/payment-integration/references/polar/sdk.md
Normal file
@@ -0,0 +1,436 @@
|
||||
# Polar SDK Usage
|
||||
|
||||
Multi-language SDKs and framework adapters.
|
||||
|
||||
## TypeScript/JavaScript
|
||||
|
||||
**Installation:**
|
||||
```bash
|
||||
npm install @polar-sh/sdk
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
```typescript
|
||||
import { Polar } from '@polar-sh/sdk';
|
||||
|
||||
const polar = new Polar({
|
||||
accessToken: process.env.POLAR_ACCESS_TOKEN,
|
||||
server: "production" // or "sandbox"
|
||||
});
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```typescript
|
||||
// Products
|
||||
const products = await polar.products.list({ organization_id: "org_xxx" });
|
||||
const product = await polar.products.create({ name: "Pro Plan", ... });
|
||||
|
||||
// Checkouts
|
||||
const checkout = await polar.checkouts.create({
|
||||
product_price_id: "price_xxx",
|
||||
success_url: "https://example.com/success"
|
||||
});
|
||||
|
||||
// Subscriptions
|
||||
const subs = await polar.subscriptions.list({ customer_id: "cust_xxx" });
|
||||
await polar.subscriptions.update(subId, { metadata: { plan: "pro" } });
|
||||
|
||||
// Orders
|
||||
const orders = await polar.orders.list({ organization_id: "org_xxx" });
|
||||
const order = await polar.orders.get(orderId);
|
||||
|
||||
// Customers
|
||||
const customer = await polar.customers.get({ external_id: "user_123" });
|
||||
|
||||
// Events (usage-based)
|
||||
await polar.events.create({
|
||||
external_customer_id: "user_123",
|
||||
event_name: "api_call",
|
||||
properties: { tokens: 1000 }
|
||||
});
|
||||
```
|
||||
|
||||
**Pagination:**
|
||||
```typescript
|
||||
// Automatic pagination
|
||||
for await (const product of polar.products.listAutoPaging()) {
|
||||
console.log(product.name);
|
||||
}
|
||||
|
||||
// Manual pagination
|
||||
let page = 1;
|
||||
while (true) {
|
||||
const response = await polar.products.list({ page, limit: 100 });
|
||||
if (response.items.length === 0) break;
|
||||
// Process items
|
||||
page++;
|
||||
}
|
||||
```
|
||||
|
||||
## Python
|
||||
|
||||
**Installation:**
|
||||
```bash
|
||||
pip install polar-sdk
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
```python
|
||||
from polar_sdk import Polar
|
||||
|
||||
polar = Polar(
|
||||
access_token=os.environ["POLAR_ACCESS_TOKEN"],
|
||||
server="production" # or "sandbox"
|
||||
)
|
||||
```
|
||||
|
||||
**Sync Usage:**
|
||||
```python
|
||||
# Products
|
||||
products = polar.products.list(organization_id="org_xxx")
|
||||
product = polar.products.create(name="Pro Plan", ...)
|
||||
|
||||
# Checkouts
|
||||
checkout = polar.checkouts.create(
|
||||
product_price_id="price_xxx",
|
||||
success_url="https://example.com/success"
|
||||
)
|
||||
|
||||
# Subscriptions
|
||||
subs = polar.subscriptions.list(customer_id="cust_xxx")
|
||||
polar.subscriptions.update(sub_id, metadata={"plan": "pro"})
|
||||
|
||||
# Orders
|
||||
orders = polar.orders.list(organization_id="org_xxx")
|
||||
order = polar.orders.get(order_id)
|
||||
|
||||
# Events
|
||||
polar.events.create(
|
||||
external_customer_id="user_123",
|
||||
event_name="api_call",
|
||||
properties={"tokens": 1000}
|
||||
)
|
||||
```
|
||||
|
||||
**Async Usage:**
|
||||
```python
|
||||
import asyncio
|
||||
from polar_sdk import AsyncPolar
|
||||
|
||||
async def main():
|
||||
polar = AsyncPolar(access_token=os.environ["POLAR_ACCESS_TOKEN"])
|
||||
|
||||
products = await polar.products.list(organization_id="org_xxx")
|
||||
checkout = await polar.checkouts.create(...)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## PHP
|
||||
|
||||
**Installation:**
|
||||
```bash
|
||||
composer require polar-sh/sdk
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
```php
|
||||
use Polar\Polar;
|
||||
|
||||
$polar = new Polar(
|
||||
accessToken: $_ENV['POLAR_ACCESS_TOKEN'],
|
||||
server: 'production' // or 'sandbox'
|
||||
);
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```php
|
||||
// Products
|
||||
$products = $polar->products->list(['organization_id' => 'org_xxx']);
|
||||
$product = $polar->products->create(['name' => 'Pro Plan', ...]);
|
||||
|
||||
// Checkouts
|
||||
$checkout = $polar->checkouts->create([
|
||||
'product_price_id' => 'price_xxx',
|
||||
'success_url' => 'https://example.com/success'
|
||||
]);
|
||||
|
||||
// Subscriptions
|
||||
$subs = $polar->subscriptions->list(['customer_id' => 'cust_xxx']);
|
||||
$polar->subscriptions->update($subId, ['metadata' => ['plan' => 'pro']]);
|
||||
|
||||
// Orders
|
||||
$orders = $polar->orders->list(['organization_id' => 'org_xxx']);
|
||||
$order = $polar->orders->get($orderId);
|
||||
|
||||
// Events
|
||||
$polar->events->create([
|
||||
'external_customer_id' => 'user_123',
|
||||
'event_name' => 'api_call',
|
||||
'properties' => ['tokens' => 1000]
|
||||
]);
|
||||
```
|
||||
|
||||
## Go
|
||||
|
||||
**Installation:**
|
||||
```bash
|
||||
go get github.com/polarsource/polar-go
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```go
|
||||
import (
|
||||
"github.com/polarsource/polar-go"
|
||||
)
|
||||
|
||||
client := polar.NewClient(
|
||||
polar.WithAccessToken(os.Getenv("POLAR_ACCESS_TOKEN")),
|
||||
polar.WithEnvironment("production"),
|
||||
)
|
||||
|
||||
// Products
|
||||
products, err := client.Products.List(ctx, &polar.ProductListParams{
|
||||
OrganizationID: "org_xxx",
|
||||
})
|
||||
|
||||
// Checkouts
|
||||
checkout, err := client.Checkouts.Create(ctx, &polar.CheckoutCreateParams{
|
||||
ProductPriceID: "price_xxx",
|
||||
SuccessURL: "https://example.com/success",
|
||||
})
|
||||
```
|
||||
|
||||
## Framework Adapters
|
||||
|
||||
### Next.js (@polar-sh/nextjs)
|
||||
|
||||
**Quick Start:**
|
||||
```bash
|
||||
npx polar-init
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
```typescript
|
||||
// lib/polar.ts
|
||||
import { PolarClient } from '@polar-sh/nextjs';
|
||||
|
||||
export const polar = new PolarClient({
|
||||
accessToken: process.env.POLAR_ACCESS_TOKEN!,
|
||||
webhookSecret: process.env.POLAR_WEBHOOK_SECRET!
|
||||
});
|
||||
```
|
||||
|
||||
**Checkout Handler:**
|
||||
```typescript
|
||||
// app/actions/checkout.ts
|
||||
'use server'
|
||||
|
||||
import { polar } from '@/lib/polar';
|
||||
|
||||
export async function createCheckout(priceId: string) {
|
||||
const session = await polar.checkouts.create({
|
||||
product_price_id: priceId,
|
||||
success_url: `${process.env.NEXT_PUBLIC_URL}/success?checkout_id={CHECKOUT_ID}`
|
||||
});
|
||||
|
||||
return session.url;
|
||||
}
|
||||
```
|
||||
|
||||
**Webhook Handler:**
|
||||
```typescript
|
||||
// app/api/webhook/polar/route.ts
|
||||
import { polar } from '@/lib/polar';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const event = await polar.webhooks.validate(req);
|
||||
|
||||
switch (event.type) {
|
||||
case 'order.paid':
|
||||
await handleOrderPaid(event.data);
|
||||
break;
|
||||
// ... other events
|
||||
}
|
||||
|
||||
return Response.json({ received: true });
|
||||
}
|
||||
```
|
||||
|
||||
### Laravel (polar-sh/laravel)
|
||||
|
||||
**Installation:**
|
||||
```bash
|
||||
composer require polar-sh/laravel
|
||||
php artisan vendor:publish --tag=polar-config
|
||||
php artisan vendor:publish --tag=polar-migrations
|
||||
php artisan migrate
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
```php
|
||||
// config/polar.php
|
||||
return [
|
||||
'access_token' => env('POLAR_ACCESS_TOKEN'),
|
||||
'webhook_secret' => env('POLAR_WEBHOOK_SECRET'),
|
||||
];
|
||||
```
|
||||
|
||||
**Checkout:**
|
||||
```php
|
||||
use Polar\Facades\Polar;
|
||||
|
||||
Route::post('/checkout', function (Request $request) {
|
||||
$checkout = Polar::checkouts()->create([
|
||||
'product_price_id' => $request->input('price_id'),
|
||||
'success_url' => route('checkout.success'),
|
||||
'external_customer_id' => auth()->id(),
|
||||
]);
|
||||
|
||||
return redirect($checkout['url']);
|
||||
});
|
||||
```
|
||||
|
||||
**Webhook:**
|
||||
```php
|
||||
use Polar\Events\WebhookReceived;
|
||||
|
||||
// app/Listeners/PolarWebhookHandler.php
|
||||
class PolarWebhookHandler
|
||||
{
|
||||
public function handle(WebhookReceived $event)
|
||||
{
|
||||
match ($event->payload['type']) {
|
||||
'order.paid' => $this->handleOrderPaid($event->payload['data']),
|
||||
'subscription.revoked' => $this->handleRevoked($event->payload['data']),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Express
|
||||
|
||||
```javascript
|
||||
const express = require('express');
|
||||
const { Polar } = require('@polar-sh/sdk');
|
||||
const { validateEvent } = require('@polar-sh/sdk/webhooks');
|
||||
|
||||
const app = express();
|
||||
const polar = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN });
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
app.post('/checkout', async (req, res) => {
|
||||
const session = await polar.checkouts.create({
|
||||
product_price_id: req.body.priceId,
|
||||
success_url: 'https://example.com/success',
|
||||
external_customer_id: req.user.id
|
||||
});
|
||||
|
||||
res.json({ url: session.url });
|
||||
});
|
||||
|
||||
app.post('/webhook/polar', (req, res) => {
|
||||
const event = validateEvent(
|
||||
req.body,
|
||||
req.headers,
|
||||
process.env.POLAR_WEBHOOK_SECRET
|
||||
);
|
||||
|
||||
handleEvent(event);
|
||||
res.json({ received: true });
|
||||
});
|
||||
```
|
||||
|
||||
### Remix
|
||||
|
||||
```typescript
|
||||
import { Polar } from '@polar-sh/sdk';
|
||||
|
||||
const polar = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN });
|
||||
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
const formData = await request.formData();
|
||||
const priceId = formData.get('priceId');
|
||||
|
||||
const session = await polar.checkouts.create({
|
||||
product_price_id: priceId,
|
||||
success_url: `${request.url}/success`
|
||||
});
|
||||
|
||||
return redirect(session.url);
|
||||
}
|
||||
```
|
||||
|
||||
## BetterAuth Integration
|
||||
|
||||
**Installation:**
|
||||
```bash
|
||||
npm install @polar-sh/better-auth
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
```typescript
|
||||
import { betterAuth } from 'better-auth';
|
||||
import { polarPlugin } from '@polar-sh/better-auth';
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: db,
|
||||
plugins: [
|
||||
polarPlugin({
|
||||
organizationId: process.env.POLAR_ORG_ID!,
|
||||
accessToken: process.env.POLAR_ACCESS_TOKEN!
|
||||
})
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Auto-create Polar customers on signup
|
||||
- Automatic external_id mapping
|
||||
- User-customer sync
|
||||
- Access customer data in auth session
|
||||
|
||||
## Error Handling
|
||||
|
||||
**TypeScript:**
|
||||
```typescript
|
||||
try {
|
||||
const product = await polar.products.get(productId);
|
||||
} catch (error) {
|
||||
if (error.statusCode === 404) {
|
||||
console.error('Product not found');
|
||||
} else if (error.statusCode === 429) {
|
||||
console.error('Rate limit exceeded');
|
||||
} else {
|
||||
console.error('API error:', error.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Python:**
|
||||
```python
|
||||
from polar_sdk.exceptions import PolarException
|
||||
|
||||
try:
|
||||
product = polar.products.get(product_id)
|
||||
except PolarException as e:
|
||||
if e.status_code == 404:
|
||||
print("Product not found")
|
||||
elif e.status_code == 429:
|
||||
print("Rate limit exceeded")
|
||||
else:
|
||||
print(f"API error: {e.message}")
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Environment Variables:** Store credentials securely
|
||||
2. **Error Handling:** Catch and handle API errors appropriately
|
||||
3. **Rate Limiting:** Implement backoff for 429 responses
|
||||
4. **Pagination:** Use auto-paging for large datasets
|
||||
5. **Webhooks:** Always verify signatures
|
||||
6. **Testing:** Use sandbox for development
|
||||
7. **Logging:** Log API calls for debugging
|
||||
8. **Retry Logic:** Implement for transient failures
|
||||
Reference in New Issue
Block a user