Uma integração de pagamentos que você pode testar, conciliar e automatizar sem confundir recebimentos de clientes com repasses aos vendedores.
1. Defina quem cuida de cada parte
Sua loja cuida do catálogo, contas dos vendedores, carrinho, frete e pedidos. O Wholly Crypto cuida do checkout, verificação dos pagamentos, divisão dos valores e repasses aprovados. Um registro de vendedor não é um login; os plugins de loja existentes não dividem automaticamente carrinhos entre vários vendedores.
- Um carrinho compartilhado
- Uma fatura para o cliente
- Partes verificadas dos vendedores
- Repasses aprovados
Os clientes pagam primeiro nas carteiras do projeto. Você controla as chaves e guarda valores devidos aos vendedores. Não é uma divisão direta do cliente para cada vendedor nem um serviço sem custódia para eles.
O Marketplace aceita Bitcoin mainnet, moedas EVM compatíveis e tokens ERC-20 padrão. Os vendedores recebem o ativo na rede usada pelo cliente, sem conversão automática para moeda tradicional. Nem todas as 30 redes de recebimento são compatíveis com repasses do Marketplace.
2. Calcule os valores
Três vendedores vendem US$ 100 em produtos cada. Defina a comissão do projeto em 4%, sem ajustes por loja ou vendedor neste exemplo.
| Parte | Bruto | Sua comissão | O vendedor recebe |
|---|---|---|---|
| Cada vendedor | $100 | $4 | $96 |
| Os três | $300 | $12 | $288 |
São valores equivalentes à cotação fixada na fatura, pagos em cripto. Não há garantia do valor futuro em dólares. A taxa usual de 1% usa US$ 3 de crédito pré-pago nesta fatura de US$ 300, uma vez, não por vendedor. Quando essa taxa se aplica, sua comissão bruta de US$ 12 deixa US$ 9 antes dos custos de rede.
Mantenha moedas nativas livres separadas para taxas de Bitcoin ou gas EVM. As taxas não podem consumir o principal protegido dos vendedores. Sweeps comuns não podem gastar os fundos dos endereços de recebimento do Marketplace.
3. Prepare carteiras e vendedores
- Em Project → Marketplace → Settings, ative Marketplace, escolha as lojas e defina a comissão. Durante a configuração, mantenha os repasses pausados e as regras automáticas desligadas.
- Faça backup das carteiras do projeto e do banco de dados. Ative os métodos BTC/EVM desejados na loja, confira os scanners e coloque crédito de processamento e fundos nativos separados para as taxas.
- Adicione cada vendedor. Salve o UUID junto ao ID do vendedor na sua loja; external_id pode guardar essa referência. Verifique de forma independente e aprove cada endereço de repasse na cadeia e rede exatas.
Cada método de checkout precisa de um destino aprovado e compatível para todos os vendedores participantes. Alterar depois o endereço de um vendedor não redireciona silenciosamente obrigações existentes.
A verificação dos repasses exige confirmações positivas e dois provedores compatíveis independentes, mesmo que o checkout permita zero confirmações ou um único scanner.
4. Conecte o carrinho compartilhado
Crie uma credencial de Marketplace limitada ao projeto em Settings → API access. Dê ao backend do checkout marketplace.read e invoices.write, com restrição à loja quando fizer sentido. Não inclua permissões para aprovar endereços ou repasses nessa chave.
Calcule preços, descontos, impostos e frete no servidor e distribua entre os vendedores. Envie de 1 a 100 vendedores distintos com valores positivos em strings decimais; os valores brutos precisam somar exatamente o total da fatura. Nunca confie na divisão enviada pelo navegador nem use ponto flutuante para dinheiro.
Troque o hostname da API e os UUIDs de exemplo abaixo. Carregue WHOLLY_TOKEN do ambiente do servidor. A requisição herda a comissão configurada de 4%; não precisa de permissão para sobrescrevê-la.
Abra a requisição em 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))Salve o corpo da requisição e o Idempotency-Key antes de enviar. Após um timeout, repita o mesmo corpo com a mesma chave. Salve data.invoice_id junto ao pedido e redirecione para links.checkout no nível principal da resposta. Nunca coloque chaves API no navegador do cliente.
Onde encontrar os UUIDs do projeto e da loja →
Prefere um SDK? Comece pelos exemplos de Marketplace:
5. Separe o recebimento do repasse
Voltar do checkout não comprova o pagamento. Verifique as assinaturas do callback sobre o corpo original, confira o horário e o projeto esperado e salve event_id como único antes de confirmar o recebimento. Use também uma proteção por pedido para não processá-lo duas vezes nos reenvios.
| Evento | O que ele indica |
|---|---|
invoice.settled | A fatura do cliente foi liquidada. Confira os avisos de revisão e as retenções do Marketplace antes de atender ao pedido. |
marketplace.allocations.available | As partes verificadas estão disponíveis para repasse. Isso não significa que algum vendedor já recebeu. |
marketplace.payout.confirmed | O repasse concluiu suas verificações de confirmação. |
O IPN de faturas usa o segredo IPN da loja. Os webhooks do Marketplace usam seu próprio segredo de assinatura por endpoint. Mantenha os dois receptores separados. Consulte a fatura ou o repasse atual pela API ao conciliar eventos perdidos ou fora de ordem.
Callbacks de faturas e verificação de assinatura · Eventos do Marketplace e referência da API
6. Revise primeiro, automatize depois
Quando as partes verificadas estiverem disponíveis, retire a pausa dos repasses, mas mantenha as regras automáticas desligadas. Em Marketplace → Payouts, prepare as alocações, confira destinatários e limites de taxas nativas e gas e aprove o plano exato uma vez. Acompanhe até Paid, não apenas Broadcast.
Bitcoin agrupa todas as partes ainda não pagas de uma fatura selecionada. Tokens EVM podem precisar de financiamento de gas antes da transferência. São várias transações, não uma divisão atômica de tudo ou nada.
Depois de um teste pequeno bem-sucedido, configure uma regra automática por ativo: mínimo, limites de principal por repasse e por dia, orçamentos de taxas nativas e gas, intervalo e confirmações. Ativá-la autoriza envios sem novos cliques. Crédito baixo ou uma trava de transferências desligada no servidor ainda podem pausar os gastos.
7. Resolva os casos complicados
Pagamentos a menos, atrasados, mistos ou afetados por reorganização
Um checkout liquidado não garante uma divisão totalmente coberta. A tolerância não cria fundos que faltam. Confira as alocações retidas, espere o pagamento, reembolse ou aprove explicitamente uma divisão menor e totalmente coberta. Valores excedentes não viram automaticamente receita extra do marketplace.
Pagamentos a mais não bloqueiam os repasses cotados aos vendedores em Bitcoin, moedas EVM ou tokens ERC-20 compatíveis. As partes dos vendedores não mudam, e o excedente fica separado para conciliação.
Um repasse trava ou uma requisição expira
Confira os hashes salvos, saldos de origem, gas e motivo da revisão. Retome ou concilie o repasse existente; não crie outra transferência porque a resposta se perdeu. Após restaurar um backup, mantenha os repasses pausados até os resultados na cadeia e o livro contábil baterem.
Retomar só pode substituir uma transferência EVM que falhou depois que dois provedores independentes comprovarem uma reversão confirmada na blockchain. Resultados incertos mantêm os fundos reservados, e as taxas das tentativas que falharam continuam contando no orçamento original.
O cliente precisa de um reembolso
Verifique um endereço de reembolso controlado pelo cliente. Os reembolsos suportados são integrais e no mesmo ativo. Se todos os vendedores já receberam, financie a carteira principal separadamente; o Wholly Crypto não pode cobrar o valor de volta deles. Um lote parcialmente pago precisa de conciliação manual, não de um suposto botão de reembolso parcial.
8. Confira antes de abrir
- Teste um pagamento pequeno em BTC e/ou EVM até confirmar os repasses. Confira rede, contrato do token, destinos, comissão e taxas separadas.
- Teste um callback repetido, uma resposta API com timeout, um pagamento a menos e um repasse retido. Confira que nenhum deles duplica o atendimento do pedido ou o envio.
- Guarde backups privados fora do servidor, acompanhe falhas nos repasses e concilie regularmente os valores devidos aos vendedores com os fundos na cadeia.
Comece com repasses revisados e poucos vendedores. Automatize só os ativos, orçamentos e destinos que você testou.