← Tutti i tutorial

GUIDA 11 / 11

Crea il tuo marketplace crypto.

Dal carrello condiviso ai venditori pagati, con un esempio da 300 dollari e codice per il server.

Cosa otterrai

Un’integrazione da testare, riconciliare e automatizzare senza confondere i pagamenti dei clienti con quelli ai venditori.

1. Decidi chi gestisce cosa

Il tuo negozio gestisce catalogo, account dei venditori, carrello, spedizioni ed evasione degli ordini. Wholly Crypto gestisce checkout, verifica dei pagamenti, quote dei venditori e pagamenti approvati. Un record venditore non è un account di accesso; i plugin esistenti non dividono automaticamente un carrello tra più venditori.

  1. Un carrello condiviso
  2. Una fattura al cliente
  3. Quote verificate dei venditori
  4. Pagamenti approvati

I clienti pagano prima nei wallet del progetto. Tu controlli le chiavi e custodisci fondi dovuti ai venditori. Non è una divisione diretta dal cliente ai venditori né un servizio senza custodia per loro.

Marketplace supporta Bitcoin mainnet, le coin EVM compatibili e i token ERC-20 standard. I venditori ricevono l’asset sulla rete usata dal cliente, senza conversione automatica in valuta tradizionale. Non tutte le 30 reti di ricezione supportano i pagamenti Marketplace.

2. Calcola gli importi

Tre venditori vendono prodotti per 100 dollari ciascuno. Imposta la commissione del progetto al 4%, senza personalizzazioni per negozio o venditore in questo esempio.

QuotaLordoLa tua commissioneIl venditore riceve
Ogni venditore$100$4$96
Tutti e tre$300$12$288

Sono equivalenti al cambio bloccato sulla fattura, pagati in crypto. Il valore futuro in dollari non è garantito. La normale commissione dell’1% usa 3 dollari di credito prepagato su questa fattura da 300 dollari, una volta sola, non per venditore. Quando si applica, dai tuoi 12 dollari di commissione lorda restano 9 prima dei costi di rete.

Tieni coin native libere separate per le fee Bitcoin o il gas EVM. Le fee non devono consumare il capitale protetto dei venditori. Gli sweep normali non possono spendere dai recapiti di ricezione Marketplace.

3. Prepara wallet e venditori

  1. In Project → Marketplace → Settings, attiva Marketplace, scegli i negozi e imposta la commissione. Durante la configurazione lascia i pagamenti in pausa e le regole automatiche disattivate.
  2. Fai un backup dei wallet del progetto e del database. Attiva i metodi BTC/EVM desiderati nel negozio, controlla gli scanner e aggiungi credito di elaborazione e fondi nativi separati per le fee.
  3. Aggiungi ogni venditore. Salva il suo UUID accanto all’ID venditore del tuo negozio; external_id può contenere quel riferimento. Verifica in modo indipendente ogni indirizzo di pagamento e approvalo sulla chain e rete esatte.

Ogni metodo di checkout richiede una destinazione approvata e compatibile per tutti i venditori coinvolti. Cambiare poi l’indirizzo di un venditore non reindirizza di nascosto gli obblighi già registrati.

La verifica dei pagamenti ai venditori richiede conferme positive e due provider compatibili indipendenti, anche se il checkout permette zero conferme o un solo scanner.

Configurazione guidata nella console · Backup e ripristino

4. Collega il carrello condiviso

Crea una credenziale Marketplace limitata al progetto in Settings → API access. Assegna al backend del checkout marketplace.read e invoices.write, limitandola al negozio se necessario. Non includere i permessi per approvare indirizzi o pagamenti in questa chiave.

Calcola prezzi, sconti, imposte e spedizione sul server, poi assegnali alle quote dei venditori. Invia da 1 a 100 venditori distinti con importi positivi come stringhe decimali; i lordi devono sommare esattamente il totale della fattura. Non fidarti di una divisione inviata dal browser e non usare la virgola mobile per il denaro.

Sostituisci hostname API e UUID segnaposto. Carica WHOLLY_TOKEN dall’ambiente del server. La richiesta eredita la commissione del 4% configurata; non serve il permesso di modificarla.

Apri la richiesta in cURL, JavaScript, PHP o 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))

Salva corpo della richiesta e Idempotency-Key prima di inviare. Dopo un timeout, riprova con lo stesso corpo e la stessa chiave. Salva data.invoice_id nell’ordine e reindirizza al links.checkout al livello principale della risposta. Non mettere mai chiavi API nel browser del cliente.

Dove trovare gli UUID di progetto e negozio →

Preferisci un SDK? Parti dagli esempi Marketplace:

5. Separa incassi e pagamenti ai venditori

Il ritorno dal checkout non prova il pagamento. Verifica le firme dei callback sul corpo originale, controlla timestamp e progetto atteso e salva event_id come univoco prima di confermare la ricezione. Aggiungi una protezione a livello di ordine per evitare doppie evasioni nei tentativi ripetuti.

EventoCosa ti dice
invoice.settledLa fattura del cliente è saldata. Controlla gli avvisi di revisione e i blocchi Marketplace prima di evadere l’ordine.
marketplace.allocations.availableLe quote verificate sono disponibili per il pagamento. Non significa che un venditore sia già stato pagato.
marketplace.payout.confirmedIl pagamento ai venditori ha completato i controlli di conferma.

L’IPN delle fatture usa il segreto IPN del negozio. I webhook Marketplace hanno un proprio segreto di firma per endpoint. Tieni separati i due ricevitori. Per riconciliare eventi mancanti o fuori ordine, leggi dalla API lo stato attuale della fattura o del pagamento.

Callback delle fatture e verifica delle firme · Eventi Marketplace e riferimento API

6. Prima controlla, poi automatizza

Quando le quote verificate sono disponibili, togli la pausa ai pagamenti ma lascia le regole automatiche disattivate. In Marketplace → Payouts prepara le quote, controlla destinatari e limiti di fee native e gas, poi approva una volta il piano esatto. Seguilo fino a Paid, non solo Broadcast.

Bitcoin raggruppa tutte le quote non pagate di una fattura selezionata. I token EVM possono richiedere prima il finanziamento del gas. Sono più transazioni, non una divisione atomica tutto-o-niente.

Dopo un piccolo test riuscito, imposta una regola automatica per asset: minimo, limiti del capitale per pagamento e giornalieri, budget per fee native e gas, intervallo e conferme. Attivarla autorizza l’invio senza altri clic. Credito insufficiente o un blocco trasferimenti del server possono comunque fermare le uscite.

7. Gestisci i casi difficili

Pagamenti insufficienti, tardivi, misti o con riorganizzazioni

Un checkout saldato non garantisce quote completamente coperte. La tolleranza non crea i fondi mancanti. Controlla le quote bloccate, attendi il pagamento, rimborsa o approva esplicitamente una divisione minore e interamente coperta. Gli eccessi non diventano automaticamente ricavi extra del marketplace.

I pagamenti in eccesso non bloccano gli importi concordati per i venditori in Bitcoin, monete EVM o token ERC-20 supportati. Le quote restano uguali e l’eccedenza resta separata per la riconciliazione.

Un pagamento si blocca o una richiesta scade

Controlla hash salvati, saldi di origine, gas e motivo della revisione. Riprendi o riconcilia il pagamento esistente; non creare un secondo trasferimento perché la risposta è andata persa. Dopo un ripristino, lascia i pagamenti in pausa finché blockchain e registro coincidono.

Riprendi può sostituire un trasferimento EVM fallito solo dopo che due provider indipendenti provano un revert confermato sulla blockchain. Se l’esito è incerto, i fondi restano riservati e le commissioni dei tentativi falliti contano ancora nel budget originale.

Il cliente ha bisogno di un rimborso

Verifica un indirizzo di rimborso controllato dal cliente. I rimborsi supportati sono completi e nello stesso asset. Se tutti i venditori sono già stati pagati, finanzia separatamente il wallet principale; Wholly Crypto non può addebitarli di nuovo. Un lotto pagato solo in parte richiede riconciliazione manuale, non un presunto pulsante di rimborso parziale.

8. Controlla prima di aprire

  • Prova un piccolo pagamento BTC e/o EVM fino alle conferme dei pagamenti ai venditori. Controlla chain, contratto del token, destinazioni, commissione e fee separate.
  • Prova un callback ripetuto, un timeout API, un pagamento insufficiente e un pagamento bloccato. Verifica che non causino doppie evasioni o doppi invii.
  • Conserva backup privati fuori dal server, monitora i pagamenti falliti e riconcilia regolarmente gli obblighi verso i venditori con i fondi on-chain.

Inizia con pagamenti controllati e pochi venditori. Automatizza solo gli asset, i budget e le destinazioni che hai testato.