← Semua tutorial

TUTORIAL 11 / 12

Bangun marketplace kriptomu.

Dari keranjang bersama hingga vendor dibayar, dengan contoh praktis $300 dan kode sisi server.

Yang akan kamu miliki

Integrasi pembayaran yang dapat kamu uji, rekonsiliasi, dan otomatiskan tanpa mencampur pembayaran pelanggan dengan pembayaran keluar vendor.

1. Tentukan siapa menangani apa

Tokomu mengelola katalog, akun penjual, keranjang, pengiriman, dan pemenuhan pesanan. Wholly Crypto menangani checkout, verifikasi pembayaran, alokasi vendor, dan pembayaran keluar yang disetujui. Catatan vendor bukan login penjual; plugin toko yang ada tidak otomatis membagi keranjang multivendor.

  1. Satu keranjang bersama
  2. Satu invoice pelanggan
  3. Bagian vendor terverifikasi
  4. Pembayaran keluar disetujui

Pelanggan membayar ke dompet proyek terlebih dahulu. Kamu mengendalikan kunci dan memegang dana yang terutang kepada vendor. Ini bukan pembagian langsung pelanggan-ke-vendor atau layanan nonkustodial untuk penjualmu.

Marketplace mendukung mainnet Bitcoin serta koin EVM dan token ERC-20 standar yang didukung. Vendor menerima aset dan jaringan yang dipakai pelanggan, bukan konversi fiat otomatis. Tidak semua 30 chain penerimaan didukung untuk pembayaran keluar Marketplace.

2. Hitung jumlahnya

Tiga vendor masing-masing menjual barang senilai $100. Atur komisi proyek ke 4%, tanpa penggantian tingkat toko atau vendor untuk contoh ini.

BagianBrutoKomisimuVendor menerima
Setiap vendor$100$4$96
Ketiganya$300$12$288

Ini adalah nilai setara pada penawaran terkunci invoice, dibayar dalam kripto. Bukan jaminan nilai dolar di masa depan. Biaya pemrosesan biasa 1% memakai kredit prabayar $3 pada invoice $300 ini, sekali, bukan sekali per vendor. Komisi bruto $12-mu menyisakan $9 sebelum biaya jaringan ketika biaya tersebut berlaku.

Sediakan koin native bebas untuk biaya Bitcoin atau gas EVM secara terpisah. Biaya tidak boleh menghabiskan pokok vendor yang dilindungi. Sweep biasa tidak dapat membelanjakan alamat penerima Marketplace.

3. Siapkan dompet dan vendor

  1. Di Proyek → Marketplace → Pengaturan, aktifkan Marketplace, pilih toko, dan atur komisi. Biarkan pembayaran keluar dijeda dan kebijakan otomatis nonaktif selama penyiapan.
  2. Cadangkan dompet proyek dan database. Aktifkan metode BTC/EVM yang dimaksud di toko, periksa kesiapan pemindai, dan danai kredit pemrosesan serta biaya native terpisah.
  3. Tambahkan setiap vendor. Simpan UUID-nya bersama ID penjual tokomu; external_id dapat menyimpan referensi penjual tersebut. Verifikasi dan setujui secara independen setiap alamat pembayaran keluar pada chain dan jaringan yang tepat.

Metode checkout membutuhkan tujuan cocok yang disetujui untuk setiap vendor yang berpartisipasi. Mengubah alamat vendor nanti tidak diam-diam mengalihkan kewajiban yang ada.

Verifikasi pembayaran keluar membutuhkan konfirmasi positif dan dua penyedia kompatibel yang independen, meskipun checkout mengizinkan nol konfirmasi atau satu pemindai.

Panduan langkah penyiapan konsol · Pencadangan dan pemulihan

4. Hubungkan keranjang bersama

Buat kredensial Marketplace dalam cakupan proyek di Pengaturan → Akses API. Beri backend checkout-mu marketplace.read dan invoices.write, dibatasi ke tokonya jika sesuai. Jangan sertakan izin persetujuan alamat dan persetujuan pembayaran keluar pada kunci tersebut.

Hitung harga, diskon, pajak, dan ongkos kirim di servermu, lalu tetapkan ke bagian vendor. Kirim 1–100 vendor berbeda dengan string desimal positif; jumlah bruto mereka harus tepat sama dengan total invoice. Jangan pernah memercayai pembagian dari browser atau menggunakan aritmetika floating-point untuk uang.

Ganti nama host API dan placeholder UUID di bawah. Muat WHOLLY_TOKEN dari lingkungan servermu. Permintaan mewarisi komisi 4% yang dikonfigurasi; tidak membutuhkan izin untuk mengganti komisi.

Buka permintaan dalam cURL, JavaScript, PHP, atau Python
cURL
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
  --request POST \
  --url "https://api.example.com/v1/marketplace/invoices" \
  --header "Authorization: Bearer $WHOLLY_TOKEN" \
  --header 'Idempotency-Key: cart-1042-marketplace-v1' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "project_id": "YOUR_PROJECT_ID",
  "store_id": "YOUR_STORE_ID",
  "amount": "300.00",
  "currency": "USD",
  "order_id": "cart-1042",
  "description": "One order from three vendors",
  "ipn_url": "https://shop.example.com/payments/wholly-ipn",
  "metadata": {
    "cart_id": "cart-1042"
  },
  "allocations": [
    {
      "vendor_id": "VENDOR_A_UUID",
      "gross_amount": "100.00"
    },
    {
      "vendor_id": "VENDOR_B_UUID",
      "gross_amount": "100.00"
    },
    {
      "vendor_id": "VENDOR_C_UUID",
      "gross_amount": "100.00"
    }
  ]
}'
JavaScript
// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
  "project_id": "YOUR_PROJECT_ID",
  "store_id": "YOUR_STORE_ID",
  "amount": "300.00",
  "currency": "USD",
  "order_id": "cart-1042",
  "description": "One order from three vendors",
  "ipn_url": "https://shop.example.com/payments/wholly-ipn",
  "metadata": {
    "cart_id": "cart-1042"
  },
  "allocations": [
    {
      "vendor_id": "VENDOR_A_UUID",
      "gross_amount": "100.00"
    },
    {
      "vendor_id": "VENDOR_B_UUID",
      "gross_amount": "100.00"
    },
    {
      "vendor_id": "VENDOR_C_UUID",
      "gross_amount": "100.00"
    }
  ]
}`;
const response = await fetch("https://api.example.com/v1/marketplace/invoices", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${token}`,
    "Idempotency-Key": "cart-1042-marketplace-v1",
    "Content-Type": "application/json"
  },
  body,
  redirect: "error",
  signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());
PHP
<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
  "project_id": "YOUR_PROJECT_ID",
  "store_id": "YOUR_STORE_ID",
  "amount": "300.00",
  "currency": "USD",
  "order_id": "cart-1042",
  "description": "One order from three vendors",
  "ipn_url": "https://shop.example.com/payments/wholly-ipn",
  "metadata": {
    "cart_id": "cart-1042"
  },
  "allocations": [
    {
      "vendor_id": "VENDOR_A_UUID",
      "gross_amount": "100.00"
    },
    {
      "vendor_id": "VENDOR_B_UUID",
      "gross_amount": "100.00"
    },
    {
      "vendor_id": "VENDOR_C_UUID",
      "gross_amount": "100.00"
    }
  ]
}
JSON;
$ch = curl_init("https://api.example.com/v1/marketplace/invoices");
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => false,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: cart-1042-marketplace-v1", "Content-Type: application/json"],
    CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));
Python
# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler

class NoRedirect(HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None

# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
    "Idempotency-Key": "cart-1042-marketplace-v1",
    "Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
  "project_id": "YOUR_PROJECT_ID",
  "store_id": "YOUR_STORE_ID",
  "amount": "300.00",
  "currency": "USD",
  "order_id": "cart-1042",
  "description": "One order from three vendors",
  "ipn_url": "https://shop.example.com/payments/wholly-ipn",
  "metadata": {
    "cart_id": "cart-1042"
  },
  "allocations": [
    {
      "vendor_id": "VENDOR_A_UUID",
      "gross_amount": "100.00"
    },
    {
      "vendor_id": "VENDOR_B_UUID",
      "gross_amount": "100.00"
    },
    {
      "vendor_id": "VENDOR_C_UUID",
      "gross_amount": "100.00"
    }
  ]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/marketplace/invoices",
                  method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
    print(json.load(response))

Simpan badan permintaan dan Idempotency-Key secara persisten sebelum mengirim. Setelah timeout, coba ulang badan yang sama dengan kunci yang sama. Simpan data.invoice_id bersama pesananmu dan arahkan ke links.checkout tingkat teratas. Jangan pernah menaruh kunci API di browser pelanggan.

Di mana menemukan UUID proyek dan toko →

Lebih suka SDK? Mulai dengan contoh Marketplace:

5. Lacak pembayaran masuk dan keluar secara terpisah

URL kembali checkout bukan bukti pembayaran. Verifikasi tanda tangan callback atas badan mentah, periksa stempel waktu serta proyek yang diharapkan, dan simpan event_id secara unik sebelum memberi pengakuan penerimaan. Gunakan pengaman tingkat pesanan terpisah agar percobaan ulang tidak dapat memenuhi pesanan dua kali.

PeristiwaInformasi yang diberikan
invoice.settledInvoice pelanggan telah diselesaikan. Periksa penanda peninjauan dan penahanan Marketplace sebelum pemenuhan pesanan.
marketplace.allocations.availableBagian terverifikasi tersedia untuk pembayaran keluar. Belum tentu ada vendor yang sudah dibayar.
marketplace.payout.confirmedPembayaran keluar telah menyelesaikan pemeriksaan konfirmasinya.

IPN invoice memakai rahasia IPN toko. Webhook siklus hidup Marketplace memakai rahasia penandatanganan endpoint-nya sendiri. Pisahkan kedua penerima. Baca invoice atau pembayaran keluar saat ini melalui API saat merekonsiliasi event yang terlewat atau tidak berurutan.

Callback invoice dan verifikasi tanda tangan · Event Marketplace dan referensi API

6. Tinjau dulu, otomatiskan nanti

Setelah bagian terverifikasi tersedia, lanjutkan pembayaran keluar dari jeda tetapi biarkan kebijakan otomatis nonaktif. Di Marketplace → Pembayaran keluar, siapkan alokasi, tinjau penerima dan batas biaya native/gas, lalu setujui rencana tepat tersebut sekali. Ikuti sampai Dibayar, bukan hanya Disiarkan.

Bitcoin mengelompokkan setiap bagian yang belum dibayar dari invoice terpilih bersama. Pembayaran keluar token EVM mungkin memerlukan pendanaan gas sebelum transfer token. Ini merupakan beberapa transaksi, bukan pembagian atomik yang seluruhnya berhasil atau seluruhnya gagal.

Setelah uji kecil berhasil, atur satu kebijakan otomatis per aset: pembayaran keluar minimum, batas pokok per pembayaran dan harian, anggaran biaya native/gas, interval, dan konfirmasi. Mengaktifkannya mengotorisasi pengiriman tanpa klik lagi. Kredit rendah atau gerbang transfer server yang dinonaktifkan tetap dapat menjeda pembelanjaan.

7. Tangani kasus yang rumit

Pembayaran kurang, terlambat, campuran, atau terkena reorg

Checkout yang selesai tidak menjamin pembagian vendor didanai penuh. Toleransi tidak dapat menciptakan dana yang kurang. Periksa alokasi yang ditahan, tunggu pembayaran, kembalikan dana, atau setujui secara eksplisit pembagian lebih kecil yang didukung dana penuh. Kelebihan pembayaran tidak otomatis menjadi pendapatan marketplace tambahan.

Kelebihan pembayaran tidak memblokir pembayaran vendor sesuai kuotasi pada Bitcoin, koin EVM, atau token ERC-20 yang didukung. Bagian vendor tetap sama dan kelebihannya tetap terpisah untuk rekonsiliasi.

Pembayaran keluar macet atau permintaan timeout

Periksa hash transaksi tersimpan, saldo sumber, gas, dan alasan peninjauannya. Lanjutkan atau rekonsiliasi pembayaran keluar yang ada; jangan membuat transfer kedua karena respons hilang. Setelah memulihkan cadangan, biarkan pembayaran keluar dijeda sampai hasil on-chain dan buku besar selaras.

Lanjutkan dapat mengganti transfer EVM gagal hanya setelah dua penyedia independen membuktikan revert on-chain yang terkonfirmasi. Hasil yang belum diketahui tetap direservasi, dan biaya percobaan gagal tetap dihitung dalam anggaran awal.

Pelanggan membutuhkan pengembalian dana

Verifikasi alamat pengembalian dana yang dikendalikan pelanggan. Pengembalian dana yang didukung bersifat penuh dan dalam aset yang sama. Setelah semua vendor dibayar, danai dompet utama secara terpisah; Wholly Crypto tidak dapat mendebit mereka kembali. Batch vendor yang baru dibayar sebagian membutuhkan rekonsiliasi manual, bukan asumsi adanya tombol pengembalian dana parsial.

8. Periksa sebelum membuka

  • Jalankan pembayaran BTC dan/atau EVM kecil sampai pembayaran keluar vendor terkonfirmasi. Pastikan chain, kontrak token, tujuan, komisi, dan biaya terpisah.
  • Uji callback berulang, respons API yang timeout, pembayaran kurang, dan pembayaran keluar yang ditahan. Pastikan tidak ada yang menyebabkan pemenuhan atau pengiriman duplikat.
  • Simpan cadangan privat di luar server, pantau kegagalan pembayaran keluar, dan rekonsiliasi kewajiban vendor terhadap dana on-chain secara berkala.

Mulai dengan pembayaran keluar yang ditinjau dan sedikit vendor. Otomatiskan hanya aset, anggaran, dan tujuan yang sudah kamu uji.