Files
kovakemlak-crm/src/app/api/payments/shopier/callback/route.ts
T
egecankomur 3cce632eb3 feat(billing): payment infrastructure pre-prep
db: add plan, planExpiresAt, planProvider to tenant_settings (Appwrite MCP)

- schema.ts: TenantPlan type, TenantSettings plan fields
- subscription-types.ts: Emlak plan catalog (Free / Pro 499₺/ay)
- plan-limits.ts: resource limits (properties/customers/members/presentations)
  + getPlanUsage, requirePlanCapacity, PlanLimitError helpers
- subscription-actions.ts: startCheckoutAction (Polar→Shopier→mock fallback),
  activatePlanInDb / deactivatePlanInDb for webhook handlers,
  downgradeToFreeAction, getCurrentPlanAction
- /api/payments/polar/callback: verify webhook → activatePlanInDb on order/subscription events
- /api/payments/shopier/callback: verify HMAC → activate on fulfilled+paid (tenant
  email-matching TODO pending Shopier metadata support)
- /settings/billing: CurrentPlanCard with usage progress bars + UpgradeSection
- sidebar: Plan & Faturalama nav item
- PlanLimitDialog: Emlak-specific feature list
2026-05-08 15:26:18 +03:00

51 lines
2.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextResponse } from "next/server";
import { verifyShopierWebhookSignature, type ShopierWebhookOrder } from "@/lib/payments/shopier";
import { activatePlanInDb } from "@/lib/appwrite/subscription-actions";
import type { TenantPlan } from "@/lib/appwrite/schema";
export async function POST(req: Request): Promise<NextResponse> {
const rawBody = await req.text();
const signature = req.headers.get("x-shopier-signature") ?? "";
if (!verifyShopierWebhookSignature(signature, rawBody)) {
return new NextResponse("Webhook imzası geçersiz.", { status: 400 });
}
let order: ShopierWebhookOrder;
try {
order = JSON.parse(rawBody) as ShopierWebhookOrder;
} catch {
return new NextResponse("JSON parse hatası.", { status: 400 });
}
// Sadece fulfilled + paid siparişlerde planı aktive et
if (order.status === "fulfilled" && order.paymentStatus === "paid") {
// Shopier'da tenant eşleştirme: buyer email → Appwrite kullanıcısı → tenantId
// Shopier'ın metadata alanı yok, bu yüzden email üzerinden bağlıyoruz.
// Not: Shopier entegrasyonu tamamlanınca burada email → tenantId çözümlemesi yapılacak.
const buyerEmail = order.shippingInfo.email;
if (!buyerEmail) {
return new NextResponse("Alıcı emaili eksik.", { status: 400 });
}
try {
// TODO: email ile Appwrite Users'dan userId bul → tenant_settings'ten tenantId al
// Şimdilik log bırakıyoruz, tam implementasyon Shopier'ın buyer_note veya
// custom field desteğine göre tamamlanacak.
console.log("[shopier-webhook] ödeme alındı:", buyerEmail, order.id);
// Eğer Shopier custom metadata destekliyorsa:
// const tenantId = order.customField?.tenant_id;
// await activatePlanInDb(tenantId, "pro", "shopier");
void activatePlanInDb; // import kullanılıyor
void ("pro" as TenantPlan);
} catch (e) {
console.error("[shopier-webhook]", e);
return new NextResponse("İşlem hatası.", { status: 500 });
}
}
return new NextResponse("OK", { status: 200 });
}