feat: Polar.sh payment integration, replace Shopier store approach

This commit is contained in:
kovakmedya
2026-05-04 17:45:30 +03:00
parent f43818a51a
commit e8a766c60a
9 changed files with 395 additions and 342 deletions
+60 -7
View File
@@ -11,7 +11,8 @@ import { DATABASE_ID, TABLES, type SubscriptionPayment, type TenantPlan } from "
import { createAdminClient } from "./server";
import { requireRole, requireTenant } from "./tenant-guard";
import { PLAN_CATALOG } from "./subscription-types";
import { isShopierEnabled } from "../payments/shopier";
import { getShopierPlanUrl, isShopierEnabled } from "../payments/shopier";
import { createPolarCheckout, isPolarEnabled } from "../payments/polar";
const PRO_VALIDITY_DAYS = 30;
@@ -227,6 +228,9 @@ export async function startShopierCheckoutAction(formData: FormData): Promise<vo
const ctx = await requireTenant();
requireRole(ctx, ["owner"]);
const storeUrl = getShopierPlanUrl(plan);
if (!storeUrl) throw new Error("Shopier mağaza URL'i ayarlanmamış.");
const catalog = PLAN_CATALOG[plan];
const orderId = generateOrderId();
@@ -244,6 +248,8 @@ export async function startShopierCheckoutAction(formData: FormData): Promise<vo
currency: catalog.currency,
status: "pending",
provider: "shopier",
// Webhook'ta tenant eşleştirmek için alıcı emailini sakla
providerPayload: JSON.stringify({ userEmail: ctx.user.email }),
},
teamRowPermissions(ctx.tenantId),
);
@@ -257,14 +263,61 @@ export async function startShopierCheckoutAction(formData: FormData): Promise<vo
changes: { plan, amount: catalog.price, provider: "shopier" },
});
redirect(`/settings/billing/checkout/${orderId}/shopier`);
// Kullanıcıyı doğrudan Shopier mağaza ürün sayfasına yönlendir
redirect(storeUrl);
}
// Unified entry point — branches on PAYMENT_PROVIDER env variable.
// Set PAYMENT_PROVIDER=shopier in production; leave unset (or "mock") for testing.
export async function startPolarCheckoutAction(formData: FormData): Promise<void> {
const plan = String(formData.get("plan") ?? "") as TenantPlan;
if (plan !== "pro") throw new Error("Geçersiz plan.");
const ctx = await requireTenant();
requireRole(ctx, ["owner"]);
const catalog = PLAN_CATALOG[plan];
const orderId = generateOrderId();
const appUrl = process.env.APP_URL?.replace(/\/$/, "") ?? "http://localhost:3000";
const { tablesDB } = createAdminClient();
await tablesDB.createRow(
DATABASE_ID,
TABLES.subscriptionPayments,
ID.unique(),
{
tenantId: ctx.tenantId,
createdBy: ctx.user.id,
orderId,
plan,
amount: catalog.price,
currency: catalog.currency,
status: "pending",
provider: "polar",
},
teamRowPermissions(ctx.tenantId),
);
await logAudit({
tenantId: ctx.tenantId,
userId: ctx.user.id,
action: "create",
entityType: "subscription_payment",
entityId: orderId,
changes: { plan, amount: catalog.price, provider: "polar" },
});
const checkout = await createPolarCheckout({
orderId,
tenantId: ctx.tenantId,
userEmail: ctx.user.email,
successUrl: `${appUrl}/settings/billing?upgraded=1`,
});
redirect(checkout.url);
}
// Unified entry point — PAYMENT_PROVIDER env ile yönlendirir.
export async function startCheckoutAction(formData: FormData): Promise<void> {
if (isShopierEnabled()) {
return startShopierCheckoutAction(formData);
}
if (isPolarEnabled()) return startPolarCheckoutAction(formData);
if (isShopierEnabled()) return startShopierCheckoutAction(formData);
return startMockCheckoutAction(formData);
}