7660901eb0
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.
46 lines
1.7 KiB
TypeScript
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" },
|
|
});
|
|
}
|