Implement collaboration wallets, withdrawals, and app UI flows
This commit is contained in:
@@ -38,8 +38,10 @@ serve(async (req: Request): Promise<Response> => {
|
||||
*,
|
||||
profiles(email, name),
|
||||
order_items (
|
||||
id,
|
||||
product_id,
|
||||
product:products (title, type)
|
||||
unit_price,
|
||||
product:products (title, type, collaborator_user_id, profit_share_percentage, auto_grant_access)
|
||||
),
|
||||
consulting_sessions (
|
||||
id,
|
||||
@@ -80,8 +82,16 @@ serve(async (req: Request): Promise<Response> => {
|
||||
const userEmail = order.profiles?.email || "";
|
||||
const userName = order.profiles?.name || userEmail.split('@')[0] || "Pelanggan";
|
||||
const orderItems = order.order_items as Array<{
|
||||
id: string;
|
||||
product_id: string;
|
||||
product: { title: string; type: string };
|
||||
unit_price?: number;
|
||||
product: {
|
||||
title: string;
|
||||
type: string;
|
||||
collaborator_user_id?: string | null;
|
||||
profit_share_percentage?: number | null;
|
||||
auto_grant_access?: boolean | null;
|
||||
};
|
||||
}>;
|
||||
|
||||
// Check if this is a consulting order by checking consulting_sessions
|
||||
@@ -218,6 +228,84 @@ serve(async (req: Request): Promise<Response> => {
|
||||
});
|
||||
console.log("[HANDLE-PAID] Access granted for product:", item.product_id);
|
||||
}
|
||||
|
||||
// Collaboration: credit collaborator wallet if this product has a collaborator
|
||||
const collaboratorUserId = item.product?.collaborator_user_id;
|
||||
const profitSharePct = Number(item.product?.profit_share_percentage || 0);
|
||||
const autoGrantAccess = item.product?.auto_grant_access !== false;
|
||||
const itemPrice = Number(item.unit_price || 0);
|
||||
|
||||
if (collaboratorUserId && profitSharePct > 0 && itemPrice > 0) {
|
||||
const hostShare = itemPrice * ((100 - profitSharePct) / 100);
|
||||
const collaboratorShare = itemPrice * (profitSharePct / 100);
|
||||
|
||||
// Save profit split to order_items
|
||||
const { error: splitError } = await supabase
|
||||
.from("order_items")
|
||||
.update({
|
||||
host_share: hostShare,
|
||||
collaborator_share: collaboratorShare,
|
||||
})
|
||||
.eq("id", item.id);
|
||||
|
||||
if (splitError) {
|
||||
console.error("[HANDLE-PAID] Failed to update order item split:", splitError);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Credit collaborator wallet (also stores wallet_transaction_id on order_items)
|
||||
const { data: transactionId, error: creditError } = await supabase
|
||||
.rpc("credit_collaborator_wallet", {
|
||||
p_user_id: collaboratorUserId,
|
||||
p_order_item_id: item.id,
|
||||
p_amount: collaboratorShare,
|
||||
p_description: `Profit from sale: ${item.product?.title || "Product"}`,
|
||||
});
|
||||
|
||||
if (creditError) {
|
||||
console.error("[HANDLE-PAID] Failed to credit collaborator wallet:", creditError);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[HANDLE-PAID] Credited collaborator wallet: ${collaboratorUserId} + Rp ${collaboratorShare}, tx=${transactionId}`
|
||||
);
|
||||
|
||||
// Grant collaborator access to the same product if enabled
|
||||
if (autoGrantAccess) {
|
||||
const { error: collaboratorAccessError } = await supabase
|
||||
.from("user_access")
|
||||
.upsert(
|
||||
{
|
||||
user_id: collaboratorUserId,
|
||||
product_id: item.product_id,
|
||||
access_type: "collaborator",
|
||||
granted_by: order.user_id,
|
||||
},
|
||||
{ onConflict: "user_id,product_id" }
|
||||
);
|
||||
|
||||
if (collaboratorAccessError) {
|
||||
console.error("[HANDLE-PAID] Failed to grant collaborator access:", collaboratorAccessError);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify collaborator about new sale
|
||||
const { error: collabNotifyError } = await supabase.functions.invoke("send-collaboration-notification", {
|
||||
body: {
|
||||
type: "new_sale",
|
||||
collaboratorUserId,
|
||||
productTitle: item.product?.title || "Product",
|
||||
profitAmount: collaboratorShare,
|
||||
profitSharePercentage: profitSharePct,
|
||||
saleDate: order.created_at,
|
||||
},
|
||||
});
|
||||
|
||||
if (collabNotifyError) {
|
||||
console.error("[HANDLE-PAID] Failed to send collaborator notification:", collabNotifyError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const productTitles = orderItems.map(i => i.product.title);
|
||||
@@ -257,12 +345,13 @@ serve(async (req: Request): Promise<Response> => {
|
||||
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||
);
|
||||
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error("[HANDLE-PAID] Error:", error);
|
||||
const message = error instanceof Error ? error.message : "Internal server error";
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: error.message || "Internal server error"
|
||||
error: message
|
||||
}),
|
||||
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||
);
|
||||
@@ -271,9 +360,9 @@ serve(async (req: Request): Promise<Response> => {
|
||||
|
||||
// Helper function to send notification
|
||||
async function sendNotification(
|
||||
supabase: any,
|
||||
supabase: ReturnType<typeof createClient>,
|
||||
templateKey: string,
|
||||
data: Record<string, any>
|
||||
data: Record<string, unknown>
|
||||
): Promise<void> {
|
||||
console.log("[HANDLE-PAID] Sending notification:", templateKey);
|
||||
|
||||
@@ -319,8 +408,8 @@ async function sendNotification(
|
||||
},
|
||||
body: JSON.stringify({
|
||||
template_key: templateKey,
|
||||
recipient_email: data.email,
|
||||
recipient_name: data.user_name || data.nama,
|
||||
recipient_email: String(data.email || ""),
|
||||
recipient_name: String((data.user_name as string) || (data.nama as string) || ""),
|
||||
variables: data,
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user