x
This commit is contained in:
1 parent
c6bf2f70ac
commit
d3e817d8ba
4 files changed
+556
-70
No files matched your search
@@ -0,0 +1,173 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
|
||||
const OMISE_SECRET_KEY = process.env.OMISE_SECRET_KEY || "";
|
||||
|
||||
export const paymentsRoute = new Elysia({ prefix: "/payments" })
|
||||
.post("/charge", async ({ body, set }) => {
|
||||
try {
|
||||
const { orderId, amount, type, token } = body as {
|
||||
orderId: number;
|
||||
amount: number;
|
||||
type: "card" | "promptpay";
|
||||
token?: string;
|
||||
};
|
||||
|
||||
if (!OMISE_SECRET_KEY) {
|
||||
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 prisma.order.update({
|
||||
where: { id: orderId },
|
||||
data: { status: "PAID" }
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
if (charge.error) {
|
||||
set.status = 400;
|
||||
return { error: charge.message || "Failed to create PromptPay charge" };
|
||||
}
|
||||
|
||||
// Get QR Code Image URL from the charge's source references
|
||||
const qrCodeUrl = 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 prisma.order.update({
|
||||
where: { id: orderId },
|
||||
data: { status: "PAID" }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { received: true };
|
||||
} catch (err: any) {
|
||||
console.error("[Omise Webhook] Error processing event:", err);
|
||||
set.status = 500;
|
||||
return { error: "Failed to process webhook" };
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user