Une intégration de paiement à tester, rapprocher et automatiser sans confondre les paiements des clients et les versements aux vendeurs.
1. Définis qui fait quoi
Ta boutique gère le catalogue, les comptes vendeurs, le panier, la livraison et les commandes. Wholly Crypto s’occupe du checkout, de la vérification des paiements, des parts des vendeurs et des versements approuvés. Une fiche vendeur n’est pas un compte de connexion ; les plugins existants ne répartissent pas automatiquement les paniers entre plusieurs vendeurs.
- Un panier partagé
- Une facture client
- Des parts vendeurs vérifiées
- Des versements approuvés
Les clients paient d’abord dans les wallets du projet. Tu contrôles les clés et conserves les fonds dus aux vendeurs. Ce n’est ni un partage direct du client aux vendeurs ni un service sans garde de fonds pour eux.
Marketplace prend en charge Bitcoin mainnet, les coins EVM compatibles et les tokens ERC-20 standards. Les vendeurs reçoivent l’actif sur le réseau utilisé par le client, sans conversion automatique en monnaie classique. Les 30 réseaux de réception ne sont pas tous disponibles pour les versements Marketplace.
2. Calcule les montants
Trois vendeurs vendent chacun pour 100 dollars de produits. Règle la commission du projet à 4%, sans valeur spécifique par boutique ou vendeur dans cet exemple.
| Part | Brut | Ta commission | Le vendeur reçoit |
|---|---|---|---|
| Chaque vendeur | $100 | $4 | $96 |
| Les trois | $300 | $12 | $288 |
Ce sont des équivalents au taux figé de la facture, versés en crypto. Leur valeur future en dollars n’est pas garantie. Les frais habituels de 1% utilisent 3 dollars de crédit prépayé sur cette facture de 300 dollars, une seule fois, pas par vendeur. Quand ces frais s’appliquent, ta commission brute de 12 dollars laisse 9 dollars avant les coûts réseau.
Garde des coins natifs libres à part pour les frais Bitcoin ou le gas EVM. Les frais ne doivent pas consommer le principal protégé des vendeurs. Les sweeps classiques ne peuvent pas dépenser les fonds des adresses de réception Marketplace.
3. Prépare les wallets et les vendeurs
- Dans Project → Marketplace → Settings, active Marketplace, choisis les boutiques et fixe la commission. Pendant la configuration, laisse les versements en pause et les règles automatiques désactivées.
- Sauvegarde les wallets du projet et la base de données. Active les méthodes BTC/EVM voulues dans la boutique, vérifie les scanners et ajoute du crédit de traitement ainsi que des fonds natifs séparés pour les frais.
- Ajoute chaque vendeur. Enregistre son UUID avec l’ID vendeur de ta boutique ; external_id peut garder cette référence. Vérifie indépendamment chaque adresse de versement et approuve-la pour la chaîne et le réseau exacts.
Chaque méthode de checkout exige une destination approuvée et compatible pour tous les vendeurs concernés. Modifier ensuite l’adresse d’un vendeur ne redirige pas discrètement les obligations existantes.
La vérification des versements exige un nombre positif de confirmations et deux fournisseurs compatibles indépendants, même si le checkout autorise zéro confirmation ou un seul scanner.
Configuration pas à pas dans la console · Sauvegarde et restauration
4. Connecte le panier partagé
Crée un accès Marketplace limité au projet dans Settings → API access. Donne à ton backend de checkout marketplace.read et invoices.write, limité à sa boutique si nécessaire. N’ajoute pas les permissions d’approbation des adresses ou des versements à cette clé.
Calcule prix, remises, taxes et livraison sur ton serveur, puis répartis-les entre les vendeurs. Envoie de 1 à 100 vendeurs distincts avec des montants positifs sous forme de chaînes décimales ; leurs montants bruts doivent donner exactement le total de la facture. Ne fais jamais confiance à une répartition venant du navigateur et n’utilise pas de virgule flottante pour l’argent.
Remplace le hostname API et les UUID d’exemple ci-dessous. Charge WHOLLY_TOKEN depuis l’environnement du serveur. La requête reprend la commission configurée de 4% ; elle n’a pas besoin du droit de la modifier.
Ouvrir la requête en cURL, JavaScript, PHP ou 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))Enregistre le corps de la requête et l’Idempotency-Key avant l’envoi. Après un timeout, réessaie avec le même corps et la même clé. Enregistre data.invoice_id dans ta commande et redirige vers links.checkout au premier niveau de la réponse. Ne mets jamais de clés API dans le navigateur du client.
Où trouver les UUID du projet et de la boutique →
Tu préfères un SDK ? Commence avec les exemples Marketplace :
5. Sépare encaissements et versements
Le retour depuis le checkout ne prouve pas le paiement. Vérifie les signatures des callbacks sur le corps brut, l’horodatage et le projet attendu, puis enregistre event_id comme unique avant d’accuser réception. Ajoute une protection par commande pour éviter de la traiter deux fois lors des renvois.
| Événement | Ce qu’il t’indique |
|---|---|
invoice.settled | La facture client est réglée. Vérifie les alertes de contrôle et les blocages Marketplace avant de traiter la commande. |
marketplace.allocations.available | Les parts vérifiées sont disponibles pour versement. Cela ne veut pas dire qu’un vendeur a déjà été payé. |
marketplace.payout.confirmed | Le versement a terminé ses contrôles de confirmation. |
L’IPN des factures utilise le secret IPN de la boutique. Les webhooks Marketplace ont leur propre secret de signature par endpoint. Garde les deux récepteurs séparés. Lis l’état actuel de la facture ou du versement via l’API pour rapprocher des événements manquants ou reçus dans le désordre.
Callbacks des factures et vérification des signatures · Événements Marketplace et référence API
6. Vérifie avant d’automatiser
Quand les parts vérifiées sont disponibles, enlève la pause des versements mais laisse les règles automatiques désactivées. Dans Marketplace → Payouts, prépare les parts, vérifie les destinataires et les plafonds de frais natifs et de gas, puis approuve le plan exact une fois. Suis-le jusqu’à Paid, pas seulement Broadcast.
Bitcoin regroupe toutes les parts impayées d’une facture choisie. Les tokens EVM peuvent nécessiter un financement du gas avant leur envoi. Ce sont plusieurs transactions, pas un partage atomique tout ou rien.
Après un petit test réussi, règle une politique automatique par actif : minimum, limites du principal par versement et par jour, budgets de frais natifs et de gas, intervalle et confirmations. L’activer autorise l’envoi sans autre clic. Un crédit faible ou le blocage des transferts côté serveur peut encore mettre les dépenses en pause.
7. Gère les cas compliqués
Sous-paiements, retards, paiements mixtes ou réorganisations
Un checkout réglé ne garantit pas des parts vendeurs entièrement couvertes. La tolérance ne crée pas les fonds manquants. Vérifie les parts retenues, attends le complément, rembourse ou approuve explicitement un partage réduit et entièrement couvert. Un trop-perçu ne devient pas automatiquement un revenu supplémentaire du marketplace.
Un paiement en trop ne bloque pas les versements prévus aux vendeurs en Bitcoin, monnaies EVM ou tokens ERC-20 compatibles. Leurs parts restent les mêmes, et le surplus reste séparé pour le rapprochement.
Un versement bloque ou une requête expire
Vérifie les hashes enregistrés, les soldes sources, le gas et le motif du contrôle. Reprends ou rapproche le versement existant ; ne crée pas un second transfert parce que la réponse s’est perdue. Après une restauration, laisse les versements en pause jusqu’à ce que les résultats on-chain et le registre concordent.
Reprendre ne peut remplacer un transfert EVM échoué que si deux fournisseurs indépendants prouvent un revert confirmé sur la blockchain. Si le résultat reste inconnu, les fonds restent réservés. Les frais des tentatives échouées comptent toujours dans le budget initial.
Le client a besoin d’un remboursement
Vérifie une adresse de remboursement contrôlée par le client. Les remboursements pris en charge sont complets et dans le même actif. Si tous les vendeurs ont déjà été payés, finance le wallet principal séparément ; Wholly Crypto ne peut pas les débiter en retour. Un lot partiellement payé demande un rapprochement manuel, pas un supposé bouton de remboursement partiel.
8. Vérifie avant d’ouvrir
- Teste un petit paiement BTC et/ou EVM jusqu’à la confirmation des versements vendeurs. Vérifie chaîne, contrat du token, destinations, commission et frais séparés.
- Teste un callback répété, un timeout API, un sous-paiement et un versement retenu. Vérifie qu’aucun ne provoque de double traitement de commande ou de double envoi.
- Garde des sauvegardes privées hors du serveur, surveille les échecs de versement et rapproche régulièrement les sommes dues aux vendeurs avec les fonds on-chain.
Commence avec des versements contrôlés et peu de vendeurs. Automatise seulement les actifs, budgets et destinations que tu as testés.