Journey 2: Part payment
You prove that your app handles PART_PAID: it shows the remaining amount, and you can either collect the rest or credit the part that arrived.
In production you do not control how much a payer sends. A bank transfer can be short, and a crypto transfer can arrive smaller than the quote.
You need
- Before you start done.
- A new payment request for
1000.00inUNPAIDstatus, with the method you will test inpaymentMethods.
Step 1: Pay part of the amount
- Bank transfer
- Card
- Crypto, no wallet
- Crypto, test wallet
Send POST /v1/collections/simulate-payment with an amount smaller than the total:
{
"paymentRequestReference": "ORDER-10045",
"paymentMethod": "BANK_TRANSFER",
"amount": 400.00
}
Expect paymentRequestStatus: "PART_PAID". The 400 is completed and credited to your wallet at once. Your webhook receives payment.pending, payment.received, payment_request.part_paid and payment.completed.
Send POST /v1/collections/simulate-payment with an amount smaller than the total:
{
"paymentRequestReference": "ORDER-10045",
"paymentMethod": "CARD",
"amount": 400.00
}
Expect paymentRequestStatus: "PART_PAID". The 400 is completed at once, but a simulated card payment does not add to your sandbox wallet balance. Your webhook receives payment.pending, payment.received, payment_request.part_paid and payment.completed.
-
Open the
paymentUrl. Select Cryptocurrency, then a token and network, until the deposit address is shown. Do not send anything. -
Send
POST /v1/collections/simulate-crypto-depositfor half of the quote. Generate a new UUID fordepositId. Save thepaymentIdfrom the response.{"paymentRequestReference": "ORDER-10045","depositId": "<NEW_UUID_A>","percentageOfRemaining": 50,"status": "CONFIRMED"}
Expect paymentRequestStatus: "PART_PAID" and paymentStatus: "RECEIVED". Your webhook receives payment.pending, payment.received and payment_request.part_paid. Nothing is credited to your wallet yet.
Follow the wallet tab of Journey 1 to reach the deposit screen, but in MetaMask send about half of the amount Hubpay shows.
When the blockchain confirms, checkout shows the amount still due. Expect the request to be PART_PAID. Your webhook receives payment.pending, payment.received and payment_request.part_paid. Nothing is credited to your wallet yet.
Send GET /v1/collections/payment-requests/{id} and save the id of the entry in payments. You need it as paymentId below.
Step 2: Choose what happens next
- Bank transfer
- Card
- Crypto, no wallet
- Crypto, test wallet
2a. Collect the rest. Send the same call without amount. Hubpay pays the remaining 600. Expect paymentRequestStatus: "PAID" and payment_request.paid.
2b. Keep what arrived. Do nothing. The 400 is already in your wallet and the request stays PART_PAID. Decide in your app: remind the payer, adjust the invoice, or wait.
2a. Collect the rest. Send the same call without amount. Hubpay pays the remaining 600. Expect paymentRequestStatus: "PAID" and payment_request.paid.
2b. Keep what arrived. Do nothing. The 400 is complete and the request stays PART_PAID.
2a. Collect the rest. Send simulate-crypto-deposit again with a new depositId and no amount fields. This pays what is left on the quote. Expect paymentRequestStatus: "PAID" and payment_request.paid. Then complete with either paymentId as in Journey 1. Both deposits are credited together.
2b. Keep what arrived. A quote that is only partly paid cannot be credited while it is open. In production the quote times out. In sandbox, expire it:
- Send
POST /v1/collections/simulate-expirywith"target": "CRYPTO_QUOTE". Nothing is credited yet and no webhook is sent. - Send
POST /v1/collections/simulate-payment-completionwith thepaymentIdyou saved in Step 1: Pay part of the amount. ExpectpaymentStatus: "COMPLETE"andpaymentRequestStatus: "PART_PAID". Your webhook receivespayment.completed. - Optional. Refresh checkout. The payer can take a new quote for the rest. Pay it with a new
depositId, then complete that payment too.
2a. Collect the rest. Send POST /v1/collections/simulate-expiry with "target": "CRYPTO_QUOTE" to end the quote now. Do not wait for the quote timer: in checkout the quote runs until the request stops accepting new payments (see Payment request events), so after a natural timeout the payer cannot take a new quote on this request. Refresh checkout and take a new quote for the remaining amount. Send that amount from MetaMask. Expect PAID and payment_request.paid. Then complete each payment as in Journey 1.
2b. Keep what arrived. Expire the quote with simulate-expiry and "target": "CRYPTO_QUOTE". Then send POST /v1/collections/simulate-payment-completion with the paymentId you saved. Expect paymentStatus: "COMPLETE", paymentRequestStatus: "PART_PAID" and payment.completed.
Step 3: Prove it
| Check | After 2a | After 2b |
|---|---|---|
GET /v1/collections/payment-requests/{id} | status: "PAID", amountRemaining: 0 | status: "PART_PAID", amountRemaining greater than 0, payment COMPLETE |
| Your webhook URL | payment_request.part_paid, then payment_request.paid | payment_request.part_paid, then payment.completed |
| Your app | Shows the order as paid | Shows the amount still due and the amount received. For bank transfer and crypto, that amount is in your wallet. |
Payout: if the request has payoutDetails, each completed payment can create a payout for its amount. Check it as in Journey 1, step 4.
If it does not work
| Problem | Cause | Fix |
|---|---|---|
400 on simulate-payment | amount is larger than the remaining balance, or you changed the method | Use a smaller amount and the same method |
400 on completion in 2b | The quote is still open | Expire it with CRYPTO_QUOTE first |
400 on completion | A deposit is still PENDING | Send it again with "status": "CONFIRMED" and the same depositId |
| Second deposit rejected | Checkout created a new quote between calls | If the first quote is still open, send its quotePaymentId from the first response to select it. If it has expired, follow 2b to credit the first deposit, then take a new quote for the rest |
Good to know
percentageOfRemainingis a share of what is left on the quote. Sending 50 twice with new deposit IDs pays 50%, then 25%.- The total of pending and confirmed deposits cannot exceed the quote amount. Overpayment cannot be simulated.
- Expiring a quote does not expire the payment request. For that, see Journey 3.