Files
homaroma/apps/backend/src/routes/payments.ts
T

173 lines
5.4 KiB
TypeScript

import { Elysia, t } from "elysia";
import prisma from "../lib/prisma";
import { markOrderAsPaid } from "../lib/notifications";
const OMISE_SECRET_KEY = process.env.OMISE_SECRET_KEY || "";
export const paymentsRoute = new Elysia({ prefix: "/payments" })
.post("/charge", async ({ body, set }) => {
console.log("[Payment Charge] Incoming request body:", JSON.stringify(body));
try {
const { orderId, amount, type, token } = body as {
orderId: number;
amount: number;
type: "card" | "promptpay";
token?: string;
};
if (!OMISE_SECRET_KEY) {
console.error("[Payment Charge] Error: OMISE_SECRET_KEY is empty");
set.status = 500;
return { error: "Omise secret key is not configured" };
}
// 1. Check if order exists
const order = await prisma.order.findUnique({
where: { id: orderId }
});
if (!order) {
set.status = 404;
return { error: "Order not found" };
}
// Convert amount to satang (integer)
const amountInSatang = Math.round(amount * 100);
// Setup Basic Auth header for Omise
const authHeader = "Basic " + Buffer.from(OMISE_SECRET_KEY + ":").toString("base64");
if (type === "card") {
if (!token) {
set.status = 400;
return { error: "Card token is required for card payments" };
}
// Create a card charge directly
const chargeRes = await fetch("https://api.omise.co/charges", {
method: "POST",
headers: {
"Authorization": authHeader,
"Content-Type": "application/json"
},
body: JSON.stringify({
amount: amountInSatang,
currency: "THB",
card: token,
metadata: {
orderId: String(orderId)
}
})
});
const charge = await chargeRes.json() as any;
if (charge.error) {
set.status = 400;
return { error: charge.message || "Failed to create credit card charge" };
}
// If card payment is immediately successful (some cards don't require 3DS)
if (charge.status === "successful") {
await markOrderAsPaid(orderId);
}
return {
success: true,
status: charge.status,
chargeId: charge.id,
authorizeUri: charge.authorize_uri || null // For 3DS card validation redirect
};
} else {
// PromptPay:
// 1. Create source
const sourceRes = await fetch("https://api.omise.co/sources", {
method: "POST",
headers: {
"Authorization": authHeader,
"Content-Type": "application/json"
},
body: JSON.stringify({
type: "promptpay",
amount: amountInSatang,
currency: "THB"
})
});
const source = await sourceRes.json() as any;
console.log("[Payment Charge] Omise PromptPay Source response:", JSON.stringify(source));
if (source.error) {
set.status = 400;
return { error: source.message || "Failed to create PromptPay source" };
}
// 2. Create charge
const chargeRes = await fetch("https://api.omise.co/charges", {
method: "POST",
headers: {
"Authorization": authHeader,
"Content-Type": "application/json"
},
body: JSON.stringify({
amount: amountInSatang,
currency: "THB",
source: source.id,
metadata: {
orderId: String(orderId)
}
})
});
const charge = await chargeRes.json() as any;
console.log("[Payment Charge] Omise PromptPay Charge response:", JSON.stringify(charge));
if (charge.error) {
set.status = 400;
return { error: charge.message || "Failed to create PromptPay charge" };
}
// Get QR Code Image URL from scannable_code or references
const qrCodeUrl = charge.source?.scannable_code?.image?.download_uri || charge.source?.references?.barcode || null;
return {
success: true,
status: charge.status,
chargeId: charge.id,
qrCodeUrl: qrCodeUrl
};
}
} catch (err: any) {
console.error("[Payment Charge] Error:", err);
set.status = 500;
return { error: err.message || "Failed to process payment request" };
}
}, {
body: t.Object({
orderId: t.Number(),
amount: t.Number(),
type: t.Union([t.Literal("card"), t.Literal("promptpay")]),
token: t.Optional(t.String())
})
})
.post("/webhook", async ({ body, set }) => {
try {
const event = body as any;
console.log(`[Omise Webhook] Received event: ${event.key}`);
if (event.key === "charge.complete") {
const charge = event.data;
const status = charge.status;
const orderIdStr = charge.metadata?.orderId;
if (orderIdStr && status === "successful") {
const orderId = parseInt(orderIdStr, 10);
console.log(`[Omise Webhook] Payment successful for Order #${orderId}`);
await markOrderAsPaid(orderId);
}
}
return { received: true };
} catch (err: any) {
console.error("[Omise Webhook] Error processing event:", err);
set.status = 500;
return { error: "Failed to process webhook" };
}
});