Open Source

开源关键代码

这里展示的是平台最核心、最容易让人关心透明度的部分:资金托管、购买校验、私有文件读取控制和卖家入账逻辑。 我们把关键链路拆开公开,方便检查每一步是怎么工作的。

托管合约

这是一个最小可读的资金托管骨架。买家付款后,资金先进入平台托管层,便于后续分账、退款和风控处理。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract EAMarketVault {
    address public owner;

    event Deposited(address indexed from, uint256 amount);
    event Withdrawn(address indexed to, uint256 amount);

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "not owner");
        _;
    }

    function deposit() external payable {
        require(msg.value > 0, "empty deposit");
        emit Deposited(msg.sender, msg.value);
    }

    function withdraw(address payable to, uint256 amount) external onlyOwner {
        require(to != address(0), "bad to");
        require(address(this).balance >= amount, "insufficient balance");
        (bool ok, ) = to.call{value: amount}("");
        require(ok, "transfer failed");
        emit Withdrawn(to, amount);
    }
}

订单校验

购买完成后,服务器会先查订单状态,再确认该买家是否真的拿到了购买权限。这里的判断是后端进行的,不依赖浏览器状态。

export async function verifyPaidOrder({ orderId, buyerId }) {
  const { data: order } = await supabase
    .from("orders")
    .select("id,status,buyer_id,total_cents,paid_at")
    .eq("id", orderId)
    .single();

  if (!order || order.buyer_id !== buyerId) return { ok: false };
  if (order.status !== "paid") return { ok: false };

  const { data: purchase } = await supabase
    .from("purchases")
    .select("id,granted_at")
    .eq("buyer_id", buyerId)
    .eq("order_id", orderId)
    .maybeSingle();

  return {
    ok: true,
    hasAccess: Boolean(purchase),
  };
}

私有存储策略

这一段展示的是私有文件读取策略的思路。实际项目里我们会把产品文件与商品记录关联起来,再结合购买记录放行。

create policy "read only purchased files"
on storage.objects
for select
using (
  bucket_id = 'private-products'
  and exists (
    select 1
    from purchases
    where purchases.product_id = storage.objects.product_id
      and purchases.buyer_id = auth.uid()
  )
);

卖家入账与出金

卖家收入不是“实时可提”的单一数字,而是拆成累计收入、冻结收入、可提现余额三个部分。 这样可以支持 24 小时申诉期、手动审核、自动打款和余额风控。

export async function settleSellerBalance(sellerId: string, amountCents: number) {
  const available = Math.max(0, amountCents);
  if (available <= 0) return;

  await supabase.from("seller_balances").upsert({
    seller_id: sellerId,
    available_cents: available,
    updated_at: new Date().toISOString(),
  });
}

关键说明

• 平台不把用户文件做成公开直链,买家下载前必须先通过购买权限校验。

• 如果卖家下架商品,已购用户仍然保留历史购买记录和下载权限。

• 订单、投诉、提现、余额和销量都在服务器侧存储,前端只是展示结果。

• 自动打款或手动打款都要先检查余额和风控状态,避免重复出金。

• 开源页面展示的是架构关键点,不是把所有业务配置和密钥直接暴露出来。