Files
kovakemlak-crm/src/app/api/payments/paytr/callback/route.ts
T
egecankomur 7660901eb0 fix(paytr): encode hyphens in tenantId as Z in merchantOid
PayTR rejects merchant_oid with non-alphanumeric chars. Real Appwrite
tenant IDs are hex (a-z0-9) and safe, but demo-tenant-001 contains
hyphens. Encode - as Z (never appears in Appwrite hex IDs) in the
merchantOid; decode back in the callback.
2026-05-14 23:12:36 +03:00

46 lines
1.7 KiB
TypeScript

import { verifyPayTRCallback } from "@/lib/payments/paytr";
import { activatePlanInDb } from "@/lib/appwrite/subscription-actions";
import type { TenantPlan, PlanPeriod } from "@/lib/appwrite/schema";
export async function POST(req: Request): Promise<Response> {
const rawBody = await req.text();
const params = new URLSearchParams(rawBody);
const merchantOid = params.get("merchant_oid") ?? "";
const status = params.get("status") ?? "";
const totalAmount = params.get("total_amount") ?? "";
const hash = params.get("hash") ?? "";
if (!verifyPayTRCallback({ merchantOid, status, totalAmount, hash })) {
return new Response("FAILED", { status: 400 });
}
if (status === "success") {
// merchant_oid: {encodedTenantId}T{timestamp}{random}P{plan}X{period}
// Hyphens were encoded as Z — decode back
const tenantId = (merchantOid.split("T")[0] ?? "").replace(/Z/g, "-");
const planPart = merchantOid.split("P")[1]; // "{plan}X{period}"
const plan = (planPart?.split("X")[0] ?? "pro") as TenantPlan;
const period = (planPart?.split("X")[1] ?? "monthly") as PlanPeriod;
if (!tenantId) {
return new Response("FAILED", { status: 400 });
}
try {
// totalAmount kuruş cinsinden gelir → TRY'ye çevir
const amountTRY = Math.round(Number(totalAmount) / 100);
const orderId = merchantOid;
await activatePlanInDb(tenantId, plan, "paytr", period, { amount: amountTRY, orderId });
} catch (e) {
console.error("[paytr-callback]", e);
return new Response("FAILED", { status: 500 });
}
}
// PayTR düz metin "OK" bekliyor — BOM veya whitespace olmayacak
return new Response("OK", {
status: 200,
headers: { "Content-Type": "text/plain" },
});
}