feat: implement Phase 1 LINE Message Integration (Order Created, Payment Confirmed, Shipped, Cancelled)
This commit is contained in:
1 parent
852d9c1925
commit
59363616c4
4 files changed
+630
-34
No files matched your search
@@ -0,0 +1,573 @@
|
||||
import prisma from "./prisma";
|
||||
import { lineClient } from "./lineClient";
|
||||
|
||||
export async function sendOrderCreatedNotification(orderId: number) {
|
||||
try {
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id: orderId },
|
||||
include: { items: true, customer: true }
|
||||
});
|
||||
|
||||
if (!order || !order.customer?.lineUserId) {
|
||||
console.log(`[Notification] Order #${orderId} has no associated lineUserId or does not exist.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = await prisma.storeSettings.findFirst();
|
||||
const bankName = settings?.bankName || "KASIKORNBANK (KBANK)";
|
||||
const bankAccountNumber = settings?.bankAccountNumber || "012-3-45678-9";
|
||||
const bankAccountName = settings?.bankAccountName || "HomAroma Co., Ltd.";
|
||||
|
||||
const itemContents = order.items.map(item => ({
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: `${item.productName}${item.variantSize ? ` (${item.variantSize})` : ""} x ${item.quantity}`,
|
||||
size: "sm",
|
||||
color: "#333333",
|
||||
flex: 3,
|
||||
wrap: true
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `฿${item.priceAtPurchase * item.quantity}`,
|
||||
size: "sm",
|
||||
color: "#1C3F3A",
|
||||
weight: "bold",
|
||||
align: "end",
|
||||
flex: 1
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
const flexMessage: any = {
|
||||
type: "flex",
|
||||
altText: `HomAroma: คำสั่งซื้อ #${order.id} ได้รับแล้ว กรุณาชำระเงิน`,
|
||||
contents: {
|
||||
type: "bubble",
|
||||
header: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
backgroundColor: "#1C3F3A",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "HomAroma Official",
|
||||
color: "#D4B16A",
|
||||
weight: "bold",
|
||||
size: "xs"
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "ได้รับคำสั่งซื้อของคุณแล้ว",
|
||||
color: "#FFFFFF",
|
||||
weight: "bold",
|
||||
size: "md",
|
||||
margin: "sm"
|
||||
}
|
||||
]
|
||||
},
|
||||
body: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: `หมายเลขออเดอร์: #HM-${order.id}`,
|
||||
weight: "bold",
|
||||
size: "sm",
|
||||
color: "#333333"
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
margin: "md"
|
||||
},
|
||||
{
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
margin: "md",
|
||||
spacing: "sm",
|
||||
contents: [
|
||||
{
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "รายการสินค้า",
|
||||
size: "xs",
|
||||
color: "#888888",
|
||||
flex: 3
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "ยอดรวม",
|
||||
size: "xs",
|
||||
color: "#888888",
|
||||
align: "end",
|
||||
flex: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
...itemContents
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
margin: "md"
|
||||
},
|
||||
{
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
margin: "md",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "ยอดชำระสุทธิ (รวมค่าส่ง)",
|
||||
size: "sm",
|
||||
color: "#555555",
|
||||
weight: "bold",
|
||||
flex: 3
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `฿${order.totalAmount}`,
|
||||
size: "sm",
|
||||
color: "#1C3F3A",
|
||||
weight: "bold",
|
||||
align: "end",
|
||||
flex: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
margin: "md"
|
||||
},
|
||||
{
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
margin: "md",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "🏦 ช่องทางการชำระเงินโอนเงิน:",
|
||||
size: "xs",
|
||||
color: "#555555",
|
||||
weight: "bold"
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `${bankName} (${bankAccountNumber})`,
|
||||
size: "xs",
|
||||
color: "#1C3F3A",
|
||||
margin: "xs",
|
||||
weight: "bold"
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `ชื่อบัญชี: ${bankAccountName}`,
|
||||
size: "xs",
|
||||
color: "#555555"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
footer: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
spacing: "sm",
|
||||
contents: [
|
||||
{
|
||||
type: "button",
|
||||
style: "primary",
|
||||
color: "#1C3F3A",
|
||||
action: {
|
||||
"type": "uri",
|
||||
"label": "แนบสลิปชำระเงิน",
|
||||
"uri": `https://spa.homaroma.shop/order-success?id=${order.id}`
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "button",
|
||||
style: "link",
|
||||
height: "sm",
|
||||
action: {
|
||||
"type": "uri",
|
||||
"label": "ติดตามสถานะออเดอร์",
|
||||
"uri": "https://spa.homaroma.shop/track-order"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await lineClient.pushMessage({
|
||||
to: order.customer.lineUserId,
|
||||
messages: [flexMessage]
|
||||
});
|
||||
console.log(`[Notification] OrderCreated notification sent for Order #${orderId}`);
|
||||
} catch (err) {
|
||||
console.error(`[Notification] Failed to send OrderCreated notification for Order #${orderId}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendPaymentConfirmedNotification(orderId: number) {
|
||||
try {
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id: orderId },
|
||||
include: { customer: true }
|
||||
});
|
||||
|
||||
if (!order || !order.customer?.lineUserId) {
|
||||
console.log(`[Notification] Order #${orderId} has no associated lineUserId or does not exist.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const flexMessage: any = {
|
||||
type: "flex",
|
||||
altText: "HomAroma: ได้รับการชำระเงินเรียบร้อยแล้ว",
|
||||
contents: {
|
||||
type: "bubble",
|
||||
body: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
contents: [
|
||||
{
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
alignItems: "center",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "✨ ชำระเงินสำเร็จ ✨",
|
||||
weight: "bold",
|
||||
size: "xl",
|
||||
color: "#1C3F3A"
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `ขอขอบคุณสำหรับคำสั่งซื้อออเดอร์ #HM-${order.id}`,
|
||||
size: "xs",
|
||||
color: "#888888",
|
||||
margin: "xs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
backgroundColor: "#F4F6F5",
|
||||
margin: "lg",
|
||||
paddingAll: "md",
|
||||
cornerRadius: "md",
|
||||
contents: [
|
||||
{
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "ได้รับแต้มครั้งนี้",
|
||||
size: "sm",
|
||||
color: "#555555"
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `+${order.pointsEarned} แต้ม`,
|
||||
weight: "bold",
|
||||
size: "sm",
|
||||
color: "#1C3F3A",
|
||||
align: "end"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
margin: "xs",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "แต้มสะสมรวมล่าสุด",
|
||||
size: "sm",
|
||||
color: "#555555"
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `${order.customer.points} แต้ม`,
|
||||
weight: "bold",
|
||||
size: "sm",
|
||||
color: "#D4B16A",
|
||||
align: "end"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
footer: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
contents: [
|
||||
{
|
||||
type: "button",
|
||||
style: "primary",
|
||||
color: "#1C3F3A",
|
||||
action: {
|
||||
"type": "uri",
|
||||
"label": "แลกของรางวัล & คูปองสะสม",
|
||||
"uri": "https://spa.homaroma.shop/profile"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await lineClient.pushMessage({
|
||||
to: order.customer.lineUserId,
|
||||
messages: [flexMessage]
|
||||
});
|
||||
console.log(`[Notification] PaymentConfirmed notification sent for Order #${orderId}`);
|
||||
} catch (err) {
|
||||
console.error(`[Notification] Failed to send PaymentConfirmed notification for Order #${orderId}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendOrderShippedNotification(orderId: number) {
|
||||
try {
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id: orderId },
|
||||
include: { customer: true }
|
||||
});
|
||||
|
||||
if (!order || !order.customer?.lineUserId) {
|
||||
console.log(`[Notification] Order #${orderId} has no associated lineUserId or does not exist.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const flexMessage: any = {
|
||||
type: "flex",
|
||||
altText: "HomAroma: สินค้าของคุณอยู่ระหว่างจัดส่ง",
|
||||
contents: {
|
||||
type: "bubble",
|
||||
header: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
backgroundColor: "#1C3F3A",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "ออเดอร์ของคุณกำลังเดินทาง 🚚",
|
||||
color: "#FFFFFF",
|
||||
weight: "bold",
|
||||
size: "md"
|
||||
}
|
||||
]
|
||||
},
|
||||
body: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
spacing: "md",
|
||||
contents: [
|
||||
{
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "หมายเลขออเดอร์:",
|
||||
size: "sm",
|
||||
color: "#888888",
|
||||
flex: 2
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `#HM-${order.id}`,
|
||||
size: "sm",
|
||||
color: "#333333",
|
||||
weight: "bold",
|
||||
flex: 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "หมายเลขพัสดุ:",
|
||||
size: "sm",
|
||||
color: "#888888",
|
||||
flex: 2
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: order.trackingNo || "อยู่ระหว่างเข้ารับพัสดุ",
|
||||
size: "sm",
|
||||
color: "#1C3F3A",
|
||||
weight: "bold",
|
||||
flex: 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
footer: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
contents: [
|
||||
{
|
||||
type: "button",
|
||||
style: "primary",
|
||||
color: "#1C3F3A",
|
||||
action: {
|
||||
"type": "uri",
|
||||
"label": "เช็คสถานะการจัดส่ง",
|
||||
"uri": "https://spa.homaroma.shop/track-order"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await lineClient.pushMessage({
|
||||
to: order.customer.lineUserId,
|
||||
messages: [flexMessage]
|
||||
});
|
||||
console.log(`[Notification] OrderShipped notification sent for Order #${orderId}`);
|
||||
} catch (err) {
|
||||
console.error(`[Notification] Failed to send OrderShipped notification for Order #${orderId}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendOrderCancelledNotification(orderId: number) {
|
||||
try {
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id: orderId },
|
||||
include: { customer: true }
|
||||
});
|
||||
|
||||
if (!order || !order.customer?.lineUserId) {
|
||||
console.log(`[Notification] Order #${orderId} has no associated lineUserId or does not exist.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const flexMessage: any = {
|
||||
type: "flex",
|
||||
altText: "HomAroma: คำสั่งซื้อของคุณถูกยกเลิกแล้ว",
|
||||
contents: {
|
||||
type: "bubble",
|
||||
header: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
backgroundColor: "#8C2A2A",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "คำสั่งซื้อถูกยกเลิก 🚫",
|
||||
color: "#FFFFFF",
|
||||
weight: "bold",
|
||||
size: "md"
|
||||
}
|
||||
]
|
||||
},
|
||||
body: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
spacing: "md",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: `คำสั่งซื้อหมายเลข #HM-${order.id} ได้ถูกยกเลิกแล้ว เนื่องจากเกินกำหนดเวลาชำระเงิน หรือสิทธิประโยชน์บางประการ หรือตามความประสงค์ของคุณ`,
|
||||
size: "sm",
|
||||
color: "#333333",
|
||||
wrap: true
|
||||
},
|
||||
...(order.pointsUsed > 0 ? [
|
||||
{
|
||||
type: "text",
|
||||
text: `* ระบบได้ทำการคืนคะแนนสะสมจำนวน ${order.pointsUsed} แต้มเข้าบัญชีของคุณเรียบร้อยแล้ว`,
|
||||
size: "xs",
|
||||
color: "#8C2A2A",
|
||||
weight: "bold",
|
||||
margin: "md"
|
||||
}
|
||||
] : [])
|
||||
]
|
||||
},
|
||||
footer: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
contents: [
|
||||
{
|
||||
type: "button",
|
||||
style: "primary",
|
||||
color: "#1C3F3A",
|
||||
action: {
|
||||
"type": "uri",
|
||||
"label": "ช้อปต่ออีกครั้ง",
|
||||
"uri": "https://spa.homaroma.shop/"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await lineClient.pushMessage({
|
||||
to: order.customer.lineUserId,
|
||||
messages: [flexMessage]
|
||||
});
|
||||
console.log(`[Notification] OrderCancelled notification sent for Order #${orderId}`);
|
||||
} catch (err) {
|
||||
console.error(`[Notification] Failed to send OrderCancelled notification for Order #${orderId}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
export async function markOrderAsPaid(orderId: number) {
|
||||
try {
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id: orderId }
|
||||
});
|
||||
|
||||
if (!order) {
|
||||
console.log(`[Payment] Order #${orderId} not found in markOrderAsPaid.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (order.status === "PAID") {
|
||||
console.log(`[Payment] Order #${orderId} is already PAID.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Update status to PAID
|
||||
await prisma.order.update({
|
||||
where: { id: orderId },
|
||||
data: { status: "PAID" }
|
||||
});
|
||||
|
||||
// 2. Grant points
|
||||
if (order.customerId && order.pointsEarned > 0) {
|
||||
await prisma.customer.update({
|
||||
where: { id: order.customerId },
|
||||
data: { points: { increment: order.pointsEarned } }
|
||||
});
|
||||
console.log(`[Payment] Granted ${order.pointsEarned} points to Customer #${order.customerId} for Order #${orderId}`);
|
||||
}
|
||||
|
||||
// 3. Send payment notification
|
||||
await sendPaymentConfirmedNotification(orderId);
|
||||
} catch (err) {
|
||||
console.error(`[Payment] Error in markOrderAsPaid for Order #${orderId}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user