Fix: Handle consulting orders properly in handle-order-paid edge function

Critical bug fix: Consulting orders were not being processed after payment because
the function checked order_items for consulting products, but consulting orders
don't have order_items - they have consulting_slots instead.

Changes:
- Fetch consulting_slots along with order_items in the query
- Check for consulting_slots.length > 0 to detect consulting orders
- Update consulting_slots status from 'pending_payment' to 'confirmed'
- Create Google Meet events for each consulting slot
- Send consulting_scheduled notification

This fixes the issue where:
- Consulting slots stayed in 'pending_payment' status after payment
- No meet links were generated
- No access was granted
- Schedules didn't show up in admin or member dashboard
This commit is contained in:
dwindown
2025-12-26 23:25:55 +07:00
parent 1743f95000
commit 390fde9bf2

View File

@@ -29,7 +29,7 @@ serve(async (req: Request): Promise<Response> => {
const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!; const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
const supabase = createClient(supabaseUrl, supabaseServiceKey); const supabase = createClient(supabaseUrl, supabaseServiceKey);
// Get full order details with items // Get full order details with items AND consulting slots
const { data: order, error: orderError } = await supabase const { data: order, error: orderError } = await supabase
.from("orders") .from("orders")
.select(` .select(`
@@ -38,6 +38,13 @@ serve(async (req: Request): Promise<Response> => {
order_items ( order_items (
product_id, product_id,
product:products (title, type) product:products (title, type)
),
consulting_slots (
id,
date,
start_time,
end_time,
status
) )
`) `)
.eq("id", order_id) .eq("id", order_id)
@@ -58,30 +65,33 @@ serve(async (req: Request): Promise<Response> => {
product: { title: string; type: string }; product: { title: string; type: string };
}>; }>;
// Check if this is a consulting order // Check if this is a consulting order by checking consulting_slots
const hasConsulting = orderItems.some(item => item.product.type === "consulting"); const consultingSlots = order.consulting_slots as Array<{
id: string;
date: string;
start_time: string;
end_time: string;
status: string;
meet_link?: string;
}>;
const isConsultingOrder = consultingSlots && consultingSlots.length > 0;
if (hasConsulting) { if (isConsultingOrder) {
console.log("[HANDLE-PAID] Consulting order detected, processing slots"); console.log("[HANDLE-PAID] Consulting order detected, processing slots");
// Update consulting slots status // Update consulting slots status from pending_payment to confirmed
await supabase await supabase
.from("consulting_slots") .from("consulting_slots")
.update({ status: "confirmed" }) .update({ status: "confirmed" })
.eq("order_id", order_id); .eq("order_id", order_id)
.in("status", ["pending_payment"]);
// Create Google Meet events for each slot
const { data: consultingSlots } = await supabase
.from("consulting_slots")
.select("*")
.eq("order_id", order_id);
if (consultingSlots && consultingSlots.length > 0) { if (consultingSlots && consultingSlots.length > 0) {
for (const slot of consultingSlots) { for (const slot of consultingSlots) {
try { try {
console.log("[HANDLE-PAID] Creating Google Meet for slot:", slot.id); console.log("[HANDLE-PAID] Creating Google Meet for slot:", slot.id);
const topic = orderItems.find(i => i.product.type === "consulting")?.product.title || "Konsultasi 1-on-1"; const topic = "Konsultasi 1-on-1";
const meetResponse = await fetch( const meetResponse = await fetch(
`${supabaseUrl}/functions/v1/create-google-meet-event`, `${supabaseUrl}/functions/v1/create-google-meet-event`,
@@ -115,40 +125,23 @@ serve(async (req: Request): Promise<Response> => {
} }
} }
// Refresh slots to get meet_link // Send consulting notification with the consultingSlots data
const { data: updatedSlots } = await supabase await sendNotification(supabase, "consulting_scheduled", {
.from("consulting_slots") nama: userName,
.select("*") email: userEmail,
.eq("order_id", order_id); order_id: order_id.substring(0, 8),
tanggal_pesanan: new Date().toLocaleDateString("id-ID"),
const slots = (updatedSlots || []) as Array<{ total: `Rp ${order.total_amount.toLocaleString("id-ID")}`,
date: string; metode_pembayaran: order.payment_method || "Unknown",
start_time: string; tanggal_konsultasi: consultingSlots[0]?.date || "",
meet_link?: string; jam_konsultasi: consultingSlots.map(s => s.start_time.substring(0, 5)).join(", "),
}>; link_meet: consultingSlots[0]?.meet_link || "Akan dikirim terpisah",
event: "consulting_scheduled",
// Send consulting notification order_id,
await sendNotification(supabase, "consulting_scheduled", userEmail, { user_id: order.user_id,
nama: userName, user_name: userName,
email: userEmail, slots: consultingSlots,
order_id: order_id.substring(0, 8), });
tanggal_pesanan: new Date().toLocaleDateString("id-ID"),
total: `Rp ${order.total_amount.toLocaleString("id-ID")}`,
metode_pembayaran: order.payment_method || "Unknown",
tanggal_konsultasi: slots[0]?.date || "",
jam_konsultasi: slots.map(s => s.start_time.substring(0, 5)).join(", "),
link_meet: slots[0]?.meet_link || "Akan dikirim terpisah",
}, {
event: "consulting_scheduled",
order_id,
user_id: order.user_id,
user_email: userEmail,
user_name: userName,
total_amount: order.total_amount,
payment_method: order.payment_method,
slots: updatedSlots,
});
}
} else { } else {
// Regular product order - grant access // Regular product order - grant access
console.log("[HANDLE-PAID] Regular product order, granting access"); console.log("[HANDLE-PAID] Regular product order, granting access");
@@ -176,7 +169,7 @@ serve(async (req: Request): Promise<Response> => {
const productTitles = orderItems.map(i => i.product.title); const productTitles = orderItems.map(i => i.product.title);
// Send payment success notification // Send payment success notification
await sendNotification(supabase, "payment_success", userEmail, { await sendNotification(supabase, "payment_success", {
nama: userName, nama: userName,
email: userEmail, email: userEmail,
order_id: order_id.substring(0, 8), order_id: order_id.substring(0, 8),
@@ -185,26 +178,21 @@ serve(async (req: Request): Promise<Response> => {
metode_pembayaran: order.payment_method || "Unknown", metode_pembayaran: order.payment_method || "Unknown",
produk: productTitles.join(", "), produk: productTitles.join(", "),
link_akses: `${Deno.env.get("SITE_URL") || ""}/access`, link_akses: `${Deno.env.get("SITE_URL") || ""}/access`,
}, {
event: "payment_success", event: "payment_success",
order_id, order_id,
user_id: order.user_id, user_id: order.user_id,
user_email: userEmail,
user_name: userName, user_name: userName,
total_amount: order.total_amount,
payment_method: order.payment_method,
products: productTitles, products: productTitles,
}); });
// Send access granted notification // Send access granted notification
await sendNotification(supabase, "access_granted", userEmail, { await sendNotification(supabase, "access_granted", {
nama: userName, nama: userName,
email: userEmail,
produk: productTitles.join(", "), produk: productTitles.join(", "),
}, {
event: "access_granted", event: "access_granted",
order_id, order_id,
user_id: order.user_id, user_id: order.user_id,
user_email: userEmail,
user_name: userName, user_name: userName,
products: productTitles, products: productTitles,
}); });
@@ -231,8 +219,7 @@ serve(async (req: Request): Promise<Response> => {
async function sendNotification( async function sendNotification(
supabase: any, supabase: any,
templateKey: string, templateKey: string,
shortcodeData: Record<string, string>, data: Record<string, any>
webhookPayload: Record<string, unknown>
): Promise<void> { ): Promise<void> {
console.log("[HANDLE-PAID] Sending notification:", templateKey); console.log("[HANDLE-PAID] Sending notification:", templateKey);
@@ -254,7 +241,7 @@ async function sendNotification(
await fetch(template.webhook_url, { await fetch(template.webhook_url, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify(webhookPayload), body: JSON.stringify(data),
}); });
console.log("[HANDLE-PAID] Webhook sent to:", template.webhook_url); console.log("[HANDLE-PAID] Webhook sent to:", template.webhook_url);
} catch (error) { } catch (error) {
@@ -276,10 +263,10 @@ async function sendNotification(
"Authorization": `Bearer ${Deno.env.get("SUPABASE_ANON_KEY")}`, "Authorization": `Bearer ${Deno.env.get("SUPABASE_ANON_KEY")}`,
}, },
body: JSON.stringify({ body: JSON.stringify({
to: shortcodeData.email, to: data.email,
subject: template.email_subject, subject: template.email_subject,
html: template.email_body_html, html: template.email_body_html,
shortcodeData, shortcodeData: data,
}), }),
}); });
} }