feat: add wallet receipt review loop
All checks were successful
CI and Production Smoke / smoke (push) Successful in 7s

This commit is contained in:
OG T
2026-06-11 20:47:35 +08:00
parent 98acab1693
commit 80e8f3f322
8 changed files with 468 additions and 0 deletions

View File

@@ -24,6 +24,33 @@ type TreasuryStats = {
direction?: "in" | "out";
date: string;
}>;
pending_wallet_receipts?: Array<{
id: string;
task_id: string;
payment_reference: string;
raw_payment_reference: string;
payer_wallet?: string | null;
network?: string | null;
amount_cents?: number;
package_id?: string | null;
referral_agent?: string | null;
note?: string | null;
submitted_at: string;
}>;
pending_affiliate_payouts?: Array<{
id: string;
scout_id: string;
scout_wallet?: string | null;
scout_status: string;
task_id: string;
task_title: string;
proposal_status: string;
payment_reference?: string | null;
amount_cents: number;
currency: string;
status: string;
created_at: string;
}>;
};
export default function TreasuryDashboard() {
@@ -41,6 +68,8 @@ export default function TreasuryDashboard() {
const [verifyingWallet, setVerifyingWallet] = useState(false);
const [walletMessage, setWalletMessage] = useState("");
const cents = (value: number | undefined) => value ?? 0;
const pendingWalletReceipts = stats?.pending_wallet_receipts || [];
const pendingAffiliatePayouts = stats?.pending_affiliate_payouts || [];
const loadStats = useCallback(async () => {
try {
@@ -114,6 +143,22 @@ export default function TreasuryDashboard() {
}
};
const selectWalletReceipt = (receipt: NonNullable<TreasuryStats["pending_wallet_receipts"]>[number]) => {
setWalletTaskId(receipt.task_id);
setWalletReference(receipt.raw_payment_reference || receipt.payment_reference.replace(/^wallet:/, ""));
setWalletAmountUsd(receipt.amount_cents ? (receipt.amount_cents / 100).toFixed(2) : "");
setWalletNote(
[
receipt.network ? `network=${receipt.network}` : "",
receipt.payer_wallet ? `payer=${receipt.payer_wallet}` : "",
receipt.note ? `note=${receipt.note}` : "",
]
.filter(Boolean)
.join("; ")
);
setWalletMessage("Pending receipt loaded. Verify only after checking the transaction on-chain.");
};
const handleWithdraw = async () => {
if (!stats) {
setMessage("⚠️ Treasury stats are unavailable.");
@@ -246,6 +291,40 @@ export default function TreasuryDashboard() {
Verify USDC Proposal Receipt
</h2>
<div className="mb-6 rounded-xl border border-amber-500/20 bg-amber-500/5 p-4">
<div className="mb-3 flex items-center justify-between gap-3">
<h3 className="text-sm font-semibold text-amber-100">Pending wallet receipts</h3>
<span className="rounded bg-amber-400/10 px-2 py-1 text-xs font-mono text-amber-200">
{pendingWalletReceipts.length}
</span>
</div>
<div className="space-y-2 max-h-52 overflow-auto">
{pendingWalletReceipts.map((receipt) => (
<button
key={receipt.id}
type="button"
onClick={() => selectWalletReceipt(receipt)}
className="grid w-full gap-2 rounded-lg border border-white/10 bg-black/30 p-3 text-left hover:border-amber-300/50 md:grid-cols-[1fr_auto]"
>
<span className="min-w-0">
<span className="block truncate text-sm font-mono text-white">{receipt.task_id}</span>
<span className="block truncate text-xs text-gray-400">
{receipt.raw_payment_reference} · {receipt.network || "network n/a"} · {receipt.referral_agent || "direct"}
</span>
</span>
<span className="text-sm font-mono text-amber-200">
${((receipt.amount_cents || 0) / 100).toFixed(2)}
</span>
</button>
))}
{pendingWalletReceipts.length === 0 ? (
<div className="rounded-lg border border-dashed border-[#333] p-3 text-sm text-gray-500">
No submitted wallet receipts are waiting for verification.
</div>
) : null}
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm text-gray-400 mb-2">Proposal ID</label>
@@ -360,6 +439,33 @@ export default function TreasuryDashboard() {
{/* Activity Feed */}
<div className="bg-[#111] border border-[#333] rounded-2xl p-6">
<h3 className="text-lg font-semibold text-gray-300 mb-4">Pending Affiliate Payouts</h3>
<div className="mb-8 space-y-3">
{pendingAffiliatePayouts.map((row) => (
<div key={row.id} className="rounded-xl border border-white/10 bg-black/30 p-3">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<p className="truncate text-sm font-semibold text-white">{row.scout_id}</p>
<p className="mt-1 truncate text-xs text-gray-500">{row.task_title}</p>
</div>
<p className="shrink-0 text-sm font-mono text-emerald-300">
${(row.amount_cents / 100).toFixed(2)} {row.currency}
</p>
</div>
<div className="mt-2 flex flex-wrap gap-2 text-[11px] text-gray-400">
<span>{row.scout_status}</span>
<span>{row.proposal_status}</span>
<span>{row.scout_wallet ? "wallet bound" : "wallet missing"}</span>
</div>
</div>
))}
{pendingAffiliatePayouts.length === 0 ? (
<div className="rounded-xl border border-dashed border-[#333] p-4 text-sm text-gray-500">
No pending affiliate payouts.
</div>
) : null}
</div>
<h3 className="text-lg font-semibold text-gray-300 mb-6">Recent Cashflows</h3>
<div className="space-y-4">
{stats.recent_transactions?.map((tx, idx) => (

View File

@@ -14,6 +14,7 @@ const TRACKED_REFERRAL_ACTIONS = [
"EXTERNAL_DEMAND_PROPOSAL_INTAKE_CREATED",
"EXTERNAL_DEMAND_PROPOSAL_CHECKOUT_STARTED",
"EXTERNAL_DEMAND_PROPOSAL_WALLET_PAYMENT_PENDING",
"EXTERNAL_DEMAND_PROPOSAL_WALLET_RECEIPT_SUBMITTED",
"EXTERNAL_DEMAND_PROPOSAL_FEE_CAPTURED",
] as const;
@@ -248,6 +249,7 @@ export async function GET(request: NextRequest) {
proposal_created_events: actionCounts.EXTERNAL_DEMAND_PROPOSAL_INTAKE_CREATED || 0,
proposal_checkout_events: actionCounts.EXTERNAL_DEMAND_PROPOSAL_CHECKOUT_STARTED || 0,
proposal_wallet_pending_events: actionCounts.EXTERNAL_DEMAND_PROPOSAL_WALLET_PAYMENT_PENDING || 0,
proposal_wallet_receipt_events: actionCounts.EXTERNAL_DEMAND_PROPOSAL_WALLET_RECEIPT_SUBMITTED || 0,
proposal_paid_events: actionCounts.EXTERNAL_DEMAND_PROPOSAL_FEE_CAPTURED || 0,
latest_event_at: latestTrafficAt ? latestTrafficAt.toISOString() : null,
},

View File

@@ -443,6 +443,7 @@ export async function GET(request: NextRequest) {
const proposalCreatedEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_INTAKE_CREATED"] || 0;
const proposalCheckoutEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_CHECKOUT_STARTED"] || 0;
const proposalWalletPendingEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_WALLET_PAYMENT_PENDING"] || 0;
const proposalWalletReceiptEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_WALLET_RECEIPT_SUBMITTED"] || 0;
const proposalPaidEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_FEE_CAPTURED"] || 0;
const claimEvents = realExternalActionSummary["EXTERNAL_CLAIM_TASK_SUCCESS"] || 0;
const submitEvents = realExternalActionSummary["EXTERNAL_SUBMIT_SOLUTION_SUCCESS"] || 0;
@@ -467,6 +468,7 @@ export async function GET(request: NextRequest) {
proposal_created_events: proposalCreatedEvents,
proposal_checkout_events: proposalCheckoutEvents,
proposal_wallet_pending_events: proposalWalletPendingEvents,
proposal_wallet_receipt_events: proposalWalletReceiptEvents,
proposal_paid_events: proposalPaidEvents,
claim_events: claimEvents,
submit_events: submitEvents,

View File

@@ -1,4 +1,5 @@
import { prisma } from "@/lib/prisma";
import { submitWalletReceipt } from "@/app/propose/wallet/actions";
import {
getProposalPackage,
TREASURY_USDC_ADDRESS,
@@ -25,6 +26,7 @@ export default async function ProposalSuccessPage({ searchParams }: { searchPara
const params = searchParams ? await searchParams : {};
const taskId = getParam(params, "task_id");
const payment = getParam(params, "payment") || "stripe";
const receiptStatus = getParam(params, "receipt");
const proposalPackage = getProposalPackage(getParam(params, "package"));
const task = taskId
? await prisma.task.findUnique({
@@ -40,8 +42,34 @@ export default async function ProposalSuccessPage({ searchParams }: { searchPara
},
})
: null;
const latestWalletReceipt = taskId
? await prisma.auditEvent.findFirst({
where: {
action: "DEMAND_PROPOSAL_WALLET_RECEIPT_SUBMITTED",
entityType: "TASK",
entityId: taskId,
},
orderBy: { createdAt: "desc" },
select: {
metadata: true,
createdAt: true,
},
})
: null;
const stripeCaptured = payment === "stripe" && Boolean(task?.stripe_payment_intent_id);
const walletCaptured = payment === "wallet" && Boolean(task?.stripe_payment_intent_id?.startsWith("wallet:"));
const receiptMetadata =
typeof latestWalletReceipt?.metadata === "object" &&
latestWalletReceipt.metadata !== null &&
!Array.isArray(latestWalletReceipt.metadata)
? (latestWalletReceipt.metadata as Record<string, unknown>)
: {};
const latestReceiptReference =
typeof receiptMetadata.raw_payment_reference === "string"
? receiptMetadata.raw_payment_reference
: typeof receiptMetadata.payment_reference === "string"
? receiptMetadata.payment_reference.replace(/^wallet:/, "")
: "";
return (
<main className="min-h-screen bg-zinc-950 px-5 py-10 text-zinc-100">
@@ -138,6 +166,76 @@ export default async function ProposalSuccessPage({ searchParams }: { searchPara
Proposal ID referral ledger
</div>
) : null}
{!walletCaptured ? (
<div className="mt-4 rounded-md border border-zinc-700 bg-zinc-950/80 p-4">
<h2 className="text-sm font-semibold text-white"> receipt</h2>
<p className="mt-1 text-xs leading-5 text-zinc-400">
tx hash paid conversion referral ledger
</p>
{receiptStatus === "submitted" || latestWalletReceipt ? (
<div className="mt-3 rounded-md border border-amber-300/30 bg-amber-300/10 px-3 py-2 text-xs leading-5 text-amber-100">
wallet receipt
{latestReceiptReference ? `${latestReceiptReference}` : ""} VibeWork
</div>
) : null}
{receiptStatus === "already_verified" ? (
<div className="mt-3 rounded-md border border-emerald-300/30 bg-emerald-300/10 px-3 py-2 text-xs leading-5 text-emerald-100">
wallet receipt
</div>
) : null}
<form action={submitWalletReceipt} className="mt-4 grid gap-3">
<input type="hidden" name="taskId" value={task?.id || taskId} />
<input type="hidden" name="amountUsdc" value={formatUsdcAmount(proposalPackage.feeCents)} />
<label className="grid gap-2 text-xs font-medium text-zinc-300">
Tx hash / payment reference
<input
required
name="paymentReference"
defaultValue={latestReceiptReference}
className="h-10 rounded-md border border-zinc-700 bg-black px-3 text-sm text-white outline-none focus:border-emerald-400"
placeholder="0x... 或交易 reference"
/>
</label>
<div className="grid gap-3 md:grid-cols-2">
<label className="grid gap-2 text-xs font-medium text-zinc-300">
Payer wallet
<input
name="payerWallet"
className="h-10 rounded-md border border-zinc-700 bg-black px-3 text-sm text-white outline-none focus:border-emerald-400"
placeholder="0x..."
/>
</label>
<label className="grid gap-2 text-xs font-medium text-zinc-300">
Network
<input
name="network"
defaultValue={TREASURY_USDC_NETWORK || ""}
className="h-10 rounded-md border border-zinc-700 bg-black px-3 text-sm text-white outline-none focus:border-emerald-400"
placeholder="Base / Ethereum / Polygon"
/>
</label>
</div>
<label className="grid gap-2 text-xs font-medium text-zinc-300">
Note
<input
name="note"
className="h-10 rounded-md border border-zinc-700 bg-black px-3 text-sm text-white outline-none focus:border-emerald-400"
placeholder="可填交易時間、付款人名稱或補充說明"
/>
</label>
<button
type="submit"
className="inline-flex h-10 items-center justify-center rounded-md bg-emerald-400 px-4 text-sm font-semibold text-zinc-950 hover:bg-emerald-300"
>
receipt
</button>
</form>
</div>
) : null}
</div>
) : null}

View File

@@ -0,0 +1,167 @@
"use server";
import { prisma } from "@/lib/prisma";
import { logA2aTrafficEvent } from "@/lib/a2a-traffic";
import { sanitizeAgentId } from "@/lib/a2a-growth";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
function getString(formData: FormData, key: string) {
const value = formData.get(key);
return typeof value === "string" ? value.trim() : "";
}
function normalizePaymentReference(value: string) {
return value.trim().replace(/\s+/g, "").slice(0, 180);
}
function cleanText(value: string, maxLength: number) {
return value.replace(/\r/g, "").replace(/[^\S\n]+/g, " ").trim().slice(0, maxLength);
}
function positiveInteger(value: unknown) {
const parsed = typeof value === "number" ? value : Number.parseInt(String(value || ""), 10);
if (!Number.isFinite(parsed) || parsed <= 0) return 0;
return Math.floor(parsed);
}
function parseAmountCents(value: string) {
const normalized = value.replace(/[,$]/g, "").trim();
if (!normalized) return 0;
const amount = Number.parseFloat(normalized);
if (!Number.isFinite(amount) || amount <= 0) return 0;
return Math.round(amount * 100);
}
function asRecord(value: unknown) {
return typeof value === "object" && value !== null && !Array.isArray(value)
? (value as Record<string, unknown>)
: {};
}
function buildWalletSuccessUrl(taskId: string, packageId: string, receipt: string) {
const params = new URLSearchParams({
task_id: taskId,
payment: "wallet",
receipt,
});
if (packageId) params.set("package", packageId);
return `/propose/success?${params.toString()}`;
}
export async function submitWalletReceipt(formData: FormData) {
const requestHeaders = await headers();
const taskId = getString(formData, "taskId");
const paymentReference = normalizePaymentReference(getString(formData, "paymentReference"));
const payerWallet = cleanText(getString(formData, "payerWallet"), 180);
const network = cleanText(getString(formData, "network"), 80);
const amountCents = parseAmountCents(getString(formData, "amountUsdc"));
const note = cleanText(getString(formData, "note"), 500);
if (!taskId) {
throw new Error("缺少 Proposal ID。");
}
if (!paymentReference || paymentReference.length < 8) {
throw new Error("請提供有效的 tx hash 或付款 reference。");
}
const task = await prisma.task.findUnique({
where: { id: taskId },
select: {
id: true,
referred_by_agent: true,
scout_id: true,
stripe_payment_intent_id: true,
},
});
if (!task) {
throw new Error("找不到 proposal draft。");
}
const intakeEvent = await prisma.auditEvent.findFirst({
where: {
action: "DEMAND_PROPOSAL_INTAKE_CREATED",
entityType: "TASK",
entityId: task.id,
},
orderBy: { createdAt: "desc" },
select: { metadata: true },
});
const metadata = asRecord(intakeEvent?.metadata);
const packageId = typeof metadata.package_id === "string" ? metadata.package_id : "";
const proposalFeeCents = positiveInteger(metadata.proposal_fee_cents);
const referralAgent = sanitizeAgentId(
task.referred_by_agent || task.scout_id || (typeof metadata.referral_agent === "string" ? metadata.referral_agent : "")
);
const source = typeof metadata.source === "string" ? metadata.source : "wallet-receipt";
const campaign = typeof metadata.campaign === "string" ? metadata.campaign : null;
const normalizedAmountCents = proposalFeeCents || amountCents;
const idempotencyKey = `proposal-wallet-receipt:${paymentReference}`;
const capturedReference = `wallet:${paymentReference}`;
const existingCapture = await prisma.ledgerEntry.findFirst({
where: {
OR: [
{ idempotency_key: `proposal-wallet-fee:${paymentReference}` },
{ stripe_object_id: capturedReference },
],
},
select: { id: true },
});
await prisma.auditEvent.create({
data: {
actorType: "USER",
actorId: "wallet-proposer",
action: "DEMAND_PROPOSAL_WALLET_RECEIPT_SUBMITTED",
entityType: "TASK",
entityId: task.id,
metadata: {
payment_provider: "wallet",
payment_reference: capturedReference,
raw_payment_reference: paymentReference,
payer_wallet: payerWallet || null,
network: network || null,
amount_cents: normalizedAmountCents || null,
expected_fee_cents: proposalFeeCents || null,
package_id: packageId || null,
referral_agent: referralAgent || null,
source,
campaign,
note: note || null,
idempotency_key: idempotencyKey,
already_verified: Boolean(existingCapture),
},
},
});
if (referralAgent) {
await logA2aTrafficEvent({
headers: requestHeaders,
fallbackAgentId: referralAgent,
action: "EXTERNAL_DEMAND_PROPOSAL_WALLET_RECEIPT_SUBMITTED",
surface: "propose/wallet-receipt",
entityType: "TASK",
entityId: task.id,
reason: "wallet_receipt_submitted_by_referred_demand_proposer",
metadata: {
payment_provider: "wallet",
payment_reference: capturedReference,
amount_cents: normalizedAmountCents || null,
expected_fee_cents: proposalFeeCents || null,
package_id: packageId || null,
referral_agent: referralAgent,
source,
campaign,
response_status: 202,
response_summary: existingCapture
? "wallet_receipt_already_verified"
: "wallet_receipt_pending_manual_verification",
},
});
}
redirect(buildWalletSuccessUrl(task.id, packageId, existingCapture ? "already_verified" : "submitted"));
}

View File

@@ -26,6 +26,7 @@ const EVENT_LABELS: Record<string, string> = {
EXTERNAL_DEMAND_PROPOSAL_INTAKE_CREATED: "外部導流需求方建立提案",
EXTERNAL_DEMAND_PROPOSAL_CHECKOUT_STARTED: "外部導流需求方開始線上結帳",
EXTERNAL_DEMAND_PROPOSAL_WALLET_PAYMENT_PENDING: "外部導流需求方取得錢包付款指示",
EXTERNAL_DEMAND_PROPOSAL_WALLET_RECEIPT_SUBMITTED: "外部導流需求方提交錢包 receipt",
EXTERNAL_DEMAND_PROPOSAL_FEE_CAPTURED: "外部導流提案費付款成功",
EXTERNAL_LIST_OPEN_TASKS_SURGE: "外部公開流量突增告警",
EXTERNAL_LIST_OPEN_TASKS_MCP: "外部整合入口查看可接需求",
@@ -347,6 +348,7 @@ async function getTrafficSummary(minutes: number) {
const proposalCreatedEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_INTAKE_CREATED"] || 0;
const proposalCheckoutEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_CHECKOUT_STARTED"] || 0;
const proposalWalletPendingEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_WALLET_PAYMENT_PENDING"] || 0;
const proposalWalletReceiptEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_WALLET_RECEIPT_SUBMITTED"] || 0;
const proposalPaidEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_FEE_CAPTURED"] || 0;
const claimEvents = realExternalActionSummary["EXTERNAL_CLAIM_TASK_SUCCESS"] || 0;
const submitEvents = realExternalActionSummary["EXTERNAL_SUBMIT_SOLUTION_SUCCESS"] || 0;
@@ -366,6 +368,7 @@ async function getTrafficSummary(minutes: number) {
proposal_created_events: proposalCreatedEvents,
proposal_checkout_events: proposalCheckoutEvents,
proposal_wallet_pending_events: proposalWalletPendingEvents,
proposal_wallet_receipt_events: proposalWalletReceiptEvents,
proposal_paid_events: proposalPaidEvents,
claim_events: claimEvents,
submit_events: submitEvents,
@@ -828,6 +831,7 @@ export default async function TrafficDashboard({
<div className="mt-4 text-sm text-gray-300 space-y-1">
<div className="flex justify-between"><span></span><span>{conversionSummary.proposal_checkout_events}</span></div>
<div className="flex justify-between"><span></span><span>{conversionSummary.proposal_wallet_pending_events}</span></div>
<div className="flex justify-between"><span> receipt </span><span>{conversionSummary.proposal_wallet_receipt_events}</span></div>
<div className="flex justify-between"><span></span><span>{conversionSummary.judge_pass_events}</span></div>
<div className="flex justify-between"><span></span><span>{conversionSummary.judge_fail_events}</span></div>
<div className="flex justify-between"><span></span><span>{conversionSummary.payout_captured}</span></div>

View File

@@ -236,6 +236,7 @@ export function buildAgentDemandCampaignKit(params: {
"EXTERNAL_DEMAND_PROPOSAL_VIEW",
"EXTERNAL_DEMAND_PROPOSAL_INTAKE_CREATED",
"EXTERNAL_DEMAND_PROPOSAL_CHECKOUT_STARTED",
"EXTERNAL_DEMAND_PROPOSAL_WALLET_RECEIPT_SUBMITTED",
"EXTERNAL_DEMAND_PROPOSAL_FEE_CAPTURED",
],
payout_boundaries: {

View File

@@ -49,6 +49,8 @@ export async function getTreasurySnapshot() {
completedTasks,
arbitrationCount,
proposalFeeEvents,
walletReceiptEvents,
pendingAffiliateRows,
withdrawalEvents,
recentAuditEvents,
] = await Promise.all([
@@ -65,6 +67,40 @@ export async function getTreasurySnapshot() {
select: { id: true, entityId: true, metadata: true, createdAt: true },
orderBy: { createdAt: "desc" },
}),
prisma.auditEvent.findMany({
where: { action: "DEMAND_PROPOSAL_WALLET_RECEIPT_SUBMITTED" },
select: { id: true, entityId: true, metadata: true, createdAt: true },
orderBy: { createdAt: "desc" },
take: 50,
}),
prisma.affiliateLedger.findMany({
where: { status: "PENDING" },
orderBy: { created_at: "desc" },
take: 25,
select: {
id: true,
scout_id: true,
task_id: true,
amount: true,
currency: true,
status: true,
created_at: true,
updated_at: true,
scout_agent: {
select: {
wallet_address: true,
status: true,
},
},
task: {
select: {
title: true,
status: true,
stripe_payment_intent_id: true,
},
},
},
}),
prisma.auditEvent.findMany({
where: { action: "TREASURY_WITHDRAWAL" },
select: { metadata: true },
@@ -90,6 +126,11 @@ export async function getTreasurySnapshot() {
const proposalFeeRevenue = emptyBucket();
const arbitrationRevenue = emptyBucket();
const withdrawn = emptyBucket();
const capturedWalletReferences = new Set(
proposalFeeEvents
.map((event) => stringValue(asRecord(event.metadata).payment_reference))
.filter((reference) => reference.startsWith("wallet:"))
);
for (const task of allTasks) {
addToBucket(gmv, task.reward_currency === "USDC" ? "usdc" : "fiat", task.reward_amount);
@@ -144,6 +185,51 @@ export async function getTreasurySnapshot() {
date: event.createdAt.toISOString(),
};
});
const seenPendingWalletReceipts = new Set<string>();
const pendingWalletReceipts = walletReceiptEvents
.map((event) => {
const metadata = asRecord(event.metadata);
const paymentReference = stringValue(metadata.payment_reference);
const rawPaymentReference = stringValue(metadata.raw_payment_reference) || paymentReference.replace(/^wallet:/, "");
return {
id: event.id,
task_id: event.entityId,
payment_reference: paymentReference,
raw_payment_reference: rawPaymentReference,
payer_wallet: stringValue(metadata.payer_wallet) || null,
network: stringValue(metadata.network) || null,
amount_cents: numberValue(metadata.amount_cents) || numberValue(metadata.expected_fee_cents),
package_id: stringValue(metadata.package_id) || null,
referral_agent: stringValue(metadata.referral_agent) || null,
note: stringValue(metadata.note) || null,
already_verified: capturedWalletReferences.has(paymentReference) || metadata.already_verified === true,
submitted_at: event.createdAt.toISOString(),
};
})
.filter((receipt) => {
if (receipt.already_verified) return false;
const receiptKey = receipt.payment_reference || `${receipt.task_id}:${receipt.raw_payment_reference}` || receipt.id;
if (seenPendingWalletReceipts.has(receiptKey)) return false;
seenPendingWalletReceipts.add(receiptKey);
return true;
})
.slice(0, 20);
const pendingAffiliatePayouts = pendingAffiliateRows.map((row) => ({
id: row.id,
scout_id: row.scout_id,
scout_wallet: row.scout_agent?.wallet_address || null,
scout_status: row.scout_agent?.status || "UNKNOWN",
task_id: row.task_id,
task_title: row.task?.title || row.task_id,
proposal_status: row.task?.status || "UNKNOWN",
payment_reference: row.task?.stripe_payment_intent_id || null,
amount_cents: row.amount,
currency: row.currency,
status: row.status,
created_at: row.created_at.toISOString(),
updated_at: row.updated_at.toISOString(),
}));
return {
gmv,
@@ -154,5 +240,7 @@ export async function getTreasurySnapshot() {
task_platform_fee_revenue: taskPlatformFees,
arbitration_fee_revenue: arbitrationRevenue,
recent_transactions: recentTransactions,
pending_wallet_receipts: pendingWalletReceipts,
pending_affiliate_payouts: pendingAffiliatePayouts,
};
}