Implement collaboration wallets, withdrawals, and app UI flows

This commit is contained in:
dwindown
2026-02-03 16:03:11 +07:00
parent 8e64780f72
commit 52b16dce07
16 changed files with 3039 additions and 28 deletions

View File

@@ -0,0 +1,172 @@
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};
interface NotificationPayload {
type: "new_sale" | "withdrawal_requested" | "withdrawal_completed" | "withdrawal_rejected";
collaboratorUserId?: string;
userId?: string;
amount?: number;
productTitle?: string;
profitAmount?: number;
profitSharePercentage?: number;
saleDate?: string;
paymentReference?: string;
reason?: string;
bankInfo?: {
bankName: string;
accountNumber: string;
accountName: string;
};
}
async function sendEmail(recipient: string, subject: string, content: string): Promise<void> {
const response = await fetch(`${Deno.env.get("SUPABASE_URL")}/functions/v1/send-email-v2`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")}`,
},
body: JSON.stringify({
recipient,
subject,
content,
}),
});
if (!response.ok) {
const text = await response.text();
throw new Error(`send-email-v2 failed: ${response.status} ${text}`);
}
}
serve(async (req: Request): Promise<Response> => {
if (req.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
try {
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
{
auth: {
autoRefreshToken: false,
persistSession: false,
},
},
);
const data = await req.json() as NotificationPayload;
const { type } = data;
let recipientEmail = "";
let subject = "";
let htmlContent = "";
if (type === "new_sale") {
const { data: collaborator } = await supabase
.from("profiles")
.select("email, name")
.eq("id", data.collaboratorUserId || "")
.maybeSingle();
recipientEmail = collaborator?.email || "";
subject = `🎉 You earned Rp ${(data.profitAmount || 0).toLocaleString("id-ID")} from ${data.productTitle || "your product"}!`;
htmlContent = `
<h2>Great news, ${collaborator?.name || "Partner"}!</h2>
<p>Your collaborative webinar <strong>${data.productTitle || "-"}</strong> just made a sale.</p>
<ul>
<li>Your Share: ${data.profitSharePercentage || 0}%</li>
<li>Profit Earned: <strong>Rp ${(data.profitAmount || 0).toLocaleString("id-ID")}</strong></li>
<li>Sale Date: ${data.saleDate ? new Date(data.saleDate).toLocaleDateString("id-ID") : "-"}</li>
</ul>
`;
} else if (type === "withdrawal_requested") {
const { data: adminRole } = await supabase
.from("user_roles")
.select("user_id")
.eq("role", "admin")
.limit(1)
.maybeSingle();
const { data: admin } = await supabase
.from("profiles")
.select("email")
.eq("id", adminRole?.user_id || "")
.maybeSingle();
recipientEmail = admin?.email || "";
subject = "💸 New Withdrawal Request";
htmlContent = `
<h2>New Withdrawal Request</h2>
<p>A collaborator has requested withdrawal:</p>
<ul>
<li>Amount: <strong>Rp ${(data.amount || 0).toLocaleString("id-ID")}</strong></li>
<li>Bank: ${data.bankInfo?.bankName || "-"}</li>
<li>Account: ${data.bankInfo?.accountNumber || "-"} (${data.bankInfo?.accountName || "-"})</li>
</ul>
`;
} else if (type === "withdrawal_completed") {
const { data: user } = await supabase
.from("profiles")
.select("email, name")
.eq("id", data.userId || "")
.maybeSingle();
recipientEmail = user?.email || "";
subject = `✅ Withdrawal Completed: Rp ${(data.amount || 0).toLocaleString("id-ID")}`;
htmlContent = `
<h2>Withdrawal Completed, ${user?.name || "Partner"}!</h2>
<ul>
<li>Amount: <strong>Rp ${(data.amount || 0).toLocaleString("id-ID")}</strong></li>
<li>Payment Reference: ${data.paymentReference || "-"}</li>
</ul>
`;
} else if (type === "withdrawal_rejected") {
const { data: user } = await supabase
.from("profiles")
.select("email, name")
.eq("id", data.userId || "")
.maybeSingle();
recipientEmail = user?.email || "";
subject = "❌ Withdrawal Request Returned";
htmlContent = `
<h2>Withdrawal Request Returned</h2>
<p>Hi ${user?.name || "Partner"},</p>
<p>Your withdrawal request of <strong>Rp ${(data.amount || 0).toLocaleString("id-ID")}</strong> has been returned to your wallet.</p>
<p>Reason: ${data.reason || "Contact admin for details"}</p>
`;
} else {
return new Response(
JSON.stringify({ error: "Unknown notification type" }),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } },
);
}
if (!recipientEmail) {
return new Response(
JSON.stringify({ error: "Recipient email not found" }),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } },
);
}
await sendEmail(recipientEmail, subject, htmlContent);
return new Response(
JSON.stringify({ success: true }),
{ status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } },
);
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "Failed to send notification";
return new Response(
JSON.stringify({ error: message }),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } },
);
}
});