← Wszystkie poradniki

PORADNIK 11 / 12

Zbuduj platformę handlową krypto.

Od wspólnego koszyka do wypłat sprzedającym, z praktycznym przykładem za 300 USD i kodem po stronie serwera.

Co uzyskasz

Integrację płatności, którą możesz testować, uzgadniać i automatyzować bez mylenia płatności klientów z wypłatami dla sprzedających.

1. Ustal, kto czym się zajmuje

Twój sklep odpowiada za katalog, konta sprzedających, koszyk, wysyłkę i realizację zamówień. Wholly Crypto obsługuje stronę płatności, weryfikację płatności, przydziały sprzedających i zatwierdzone wypłaty. Rekord sprzedającego nie jest loginem sprzedawcy; istniejące wtyczki sklepowe nie dzielą automatycznie koszyków wielu sprzedających.

  1. Jeden wspólny koszyk
  2. Jedna faktura klienta
  3. Zweryfikowane udziały sprzedających
  4. Zatwierdzone wypłaty

Klienci najpierw płacą do portfeli projektu. Kontrolujesz klucze i przechowujesz środki należne sprzedającym. To nie jest bezpośredni podział od klienta do sprzedających ani usługa bez powierzenia środków dla Twoich sprzedawców.

Marketplace obsługuje sieć główną Bitcoin oraz obsługiwane monety EVM i standardowe tokeny ERC-20. Sprzedający otrzymują aktywo w sieci użytej przez klienta, nie automatyczną konwersję na fiat. Wypłaty Marketplace nie obsługują wszystkich 30 blockchainów odbiorczych.

2. Oblicz kwoty

Trzech sprzedających sprzedaje towary po 100 USD każdy. Ustaw prowizję projektu na 4%, bez nadpisań sklepu ani sprzedających w tym przykładzie.

UdziałBruttoTwoja prowizjaSprzedający otrzymuje
Każdy sprzedający$100$4$96
Cała trójka$300$12$288

To równowartości według zablokowanej wyceny faktury, wypłacane w krypto. Nie są gwarantowanymi przyszłymi wartościami w dolarach. Zwykła opłata za przetwarzanie 1% zużywa 3 USD środków przedpłaconych dla tej faktury na 300 USD, raz, nie raz na sprzedającego. Z prowizji brutto 12 USD pozostaje 9 USD przed kosztami sieci, gdy ta opłata obowiązuje.

Osobno zachowaj wolne natywne monety na opłaty Bitcoin lub gas EVM. Opłaty nie mogą zużywać chronionej kwoty głównej sprzedających. Zwykłe operacje sweep nie mogą wydawać z adresów odbiorczych Marketplace.

3. Przygotuj portfele i sprzedających

  1. W Projekt → Marketplace → Ustawienia włącz Marketplace, wybierz sklepy i ustaw prowizję. Podczas konfiguracji pozostaw wypłaty wstrzymane, a automatyczne polityki wyłączone.
  2. Zrób kopie portfeli projektu i bazy danych. Włącz zamierzone metody BTC/EVM w sklepie, sprawdź gotowość skanera i zasil środki na przetwarzanie oraz osobno natywne opłaty.
  3. Dodaj każdego sprzedającego. Zapisz jego UUID przy identyfikatorze sprzedawcy w sklepie; external_id może zawierać to odwołanie. Niezależnie zweryfikuj i zatwierdź każdy adres wypłaty w dokładnie właściwym blockchainie i sieci.

Metoda płatności potrzebuje zatwierdzonego pasującego adresu docelowego dla każdego uczestniczącego sprzedającego. Późniejsza zmiana adresu sprzedającego nie przekierowuje po cichu istniejących zobowiązań.

Weryfikacja wypłaty wymaga dodatniej liczby potwierdzeń i dwóch niezależnych zgodnych dostawców, nawet jeśli płatności dopuszczają zero potwierdzeń lub jeden skaner.

Konfiguracja panelu krok po kroku · Kopie zapasowe i odtwarzanie

4. Podłącz wspólny koszyk

Utwórz dane dostępu Marketplace ograniczone do projektu w Ustawienia → Dostęp API. Nadaj backendowi płatności marketplace.read i invoices.write, w razie potrzeby ograniczone do jego sklepu. Nie przyznawaj temu kluczowi uprawnień zatwierdzania adresów ani wypłat.

Oblicz ceny, rabaty, podatek i wysyłkę na serwerze, a następnie przypisz je do udziałów sprzedających. Wyślij 1–100 różnych sprzedających z dodatnimi ciągami dziesiętnymi; ich kwoty brutto muszą dokładnie sumować się do kwoty faktury. Nigdy nie ufaj podziałowi z przeglądarki ani nie używaj arytmetyki zmiennoprzecinkowej dla pieniędzy.

Zastąp poniżej nazwę hosta API i symbole zastępcze UUID. Wczytaj WHOLLY_TOKEN ze środowiska serwera. Żądanie dziedziczy skonfigurowaną prowizję 4%; nie potrzebuje uprawnienia do jej nadpisania.

Otwórz żądanie w cURL, JavaScript, PHP lub Pythonie
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))

Trwale zapisz treść żądania i Idempotency-Key przed wysłaniem. Po przekroczeniu czasu ponów tę samą treść z tym samym kluczem. Zapisz data.invoice_id przy zamówieniu i przekieruj do links.checkout najwyższego poziomu. Nigdy nie umieszczaj kluczy API w przeglądarce klienta.

Gdzie znaleźć UUID projektu i sklepu →

Wolisz SDK? Zacznij od przykładów Marketplace:

5. Śledź płatność i wypłatę osobno

URL powrotu ze strony płatności nie jest dowodem zapłaty. Weryfikuj podpisy callbacków na surowej treści, sprawdzaj znacznik czasu i oczekiwany projekt oraz zapisuj event_id jako unikalny przed potwierdzeniem. Użyj osobnej ochrony na poziomie zamówienia, aby ponowienia nie mogły zrealizować go dwa razy.

ZdarzenieO czym informuje
invoice.settledFaktura klienta została rozliczona. Przed realizacją sprawdź oznaczenia wymagające weryfikacji i blokady Marketplace.
marketplace.allocations.availableZweryfikowane udziały są dostępne do wypłaty. Niekoniecznie wypłacono już jakiemukolwiek sprzedającemu.
marketplace.payout.confirmedWypłata zakończyła kontrole potwierdzeń.

IPN faktury używa sekretu IPN sklepu. Webhooki cyklu życia Marketplace używają własnego sekretu podpisującego endpointa. Rozdziel oba odbiorniki. Odczytaj aktualną fakturę lub wypłatę przez API podczas uzgadniania pominiętych zdarzeń lub zdarzeń poza kolejnością.

Callbacki faktur i weryfikacja podpisów · Zdarzenia Marketplace i dokumentacja API

6. Najpierw sprawdzaj, potem automatyzuj

Gdy zweryfikowane udziały będą dostępne, wznów wypłaty, lecz pozostaw automatyczne polityki wyłączone. W Marketplace → Wypłaty przygotuj przydziały, sprawdź odbiorców i limity natywnych opłat/gasu, a następnie zatwierdź dokładny plan raz. Śledź do stanu Wypłacono, nie tylko Nadano.

Bitcoin grupuje razem wszystkie niewypłacone udziały wybranej faktury. Wypłaty tokenów EVM mogą potrzebować zasilenia gasem przed przelewami tokenów. To kilka transakcji, nie atomowy podział wszystko albo nic.

Po udanym małym teście ustaw jedną automatyczną politykę na aktywo: minimum wypłaty, limity kwoty głównej na wypłatę i dziennie, budżety natywnych opłat/gasu, interwał i potwierdzenia. Włączenie upoważnia do wysyłania bez kolejnych kliknięć. Niskie saldo lub wyłączona bramka przelewów serwera nadal mogą wstrzymać wydawanie.

7. Obsłuż trudne przypadki

Niedopłacone, spóźnione, mieszane płatności lub płatności objęte reorganizacją

Rozliczona płatność nie gwarantuje pełnego pokrycia podziału dla sprzedających. Tolerancja nie może stworzyć brakujących środków. Sprawdź wstrzymane przydziały, poczekaj na płatność, zwróć środki lub jawnie zatwierdź mniejszy podział z pełnym pokryciem. Nadpłaty nie są automatycznie dodatkowym przychodem platformy.

Nadpłaty nie blokują wycenionych wypłat sprzedającym w Bitcoin, monetach EVM ani obsługiwanych tokenach ERC-20. Udziały sprzedających pozostają bez zmian, a nadwyżka pozostaje oddzielna do uzgodnienia.

Wypłata utknęła lub żądanie przekroczyło czas

Sprawdź zapisane hashe transakcji, salda źródłowe, gas i powód weryfikacji. Wznów lub uzgodnij istniejącą wypłatę; nie twórz drugiego przelewu, bo odpowiedź zaginęła. Po odtworzeniu kopii pozostaw wypłaty wstrzymane, aż wyniki on-chain i rejestr będą zgodne.

Wznowienie może zastąpić nieudany przelew EVM tylko wtedy, gdy dwóch niezależnych dostawców potwierdzi wykonane on-chain cofnięcie. Nieznane wyniki pozostają zarezerwowane, a opłaty nieudanej próby nadal liczą się do pierwotnego budżetu.

Klient potrzebuje zwrotu

Zweryfikuj adres zwrotu kontrolowany przez klienta. Obsługiwane zwroty są pełne i w tym samym aktywie. Po wypłaceniu wszystkim sprzedającym osobno zasil główny portfel; Wholly Crypto nie może odebrać im środków. Częściowo wypłacona partia sprzedających wymaga ręcznego uzgodnienia, nie zakładania istnienia przycisku częściowego zwrotu.

8. Sprawdź przed otwarciem

  • Przeprowadź małą płatność BTC i/lub EVM aż do potwierdzonych wypłat dla sprzedających. Potwierdź blockchain, kontrakt tokena, adresy docelowe, prowizję i osobne opłaty.
  • Przetestuj powtórzony callback, przekroczenie czasu odpowiedzi API, niedopłatę i wstrzymaną wypłatę. Sprawdź, czy żaden przypadek nie powoduje podwójnej realizacji ani wysyłki.
  • Przechowuj prywatne kopie poza serwerem, monitoruj nieudane wypłaty i regularnie uzgadniaj zobowiązania wobec sprzedających ze środkami on-chain.

Zacznij od sprawdzanych wypłat i niewielkiej liczby sprzedających. Automatyzuj tylko przetestowane aktywa, budżety i adresy docelowe.