Платежи по инвойсу¶
Инвойс показывает итог, платёж — отдельный депозит. GET /api/v1/invoices/{invoiceId}/payments
перечисляет их по одному: транзакция, отправитель, сумма, токен, вердикт скрининга и классификация.
curl "https://api.pay.nullswap.com/api/v1/invoices/b2f4a6c8-.../payments?limit=50" \
-H "x-api-key: sk_live_..."
{
"data": [{
"id": "d4e5f6a7-b8c9-4012-a345-6b7c8d9e0f12",
"invoiceId": "b2f4a6c8-0d1e-4f23-9a45-6b7c8d9e0f11",
"chainTransactionId": "aa11bb22-cc33-4d44-8e55-6f7788990011",
"chainId": "1c9a7f36-5f2b-4a83-9d51-0e6b7c8d9a10",
"tokenId": "3b7e1a48-2c6d-4e9f-8a01-5d4c3b2a1908",
"fromAddress": "TJ8k...2Ba7",
"amount": "25500000",
"kind": "EXPECTED",
"unexpectedReason": null,
"resolution": "PENDING",
"status": "CONFIRMED",
"confirmations": 19,
"refundAddress": null,
"refundTxHash": null,
"amlResultId": "5c6d7e8f-9a0b-41c2-8d34-5e6f7a8b9c0d",
"amlStatus": "PASSED",
"createdAt": "2026-01-01T12:04:11.000Z",
"updatedAt": "2026-01-01T12:07:52.000Z"
}],
"pagination": { "offset": 0, "limit": 50, "total": 1, "order": "ASC" }
}
Фильтры: id, kind, resolution, плюс offset, limit, order и sortBy. В отличие от всех
остальных списков в API, здесь по умолчанию order=ASC — сначала самые старые.
Три поля, которые означают разное¶
У платежа три независимых статуса, и перепутать их между собой — самая частая ошибка на этой странице.
| Поле | Отвечает на вопрос | Значения |
|---|---|---|
status |
Закрепилась ли транзакция? | DETECTED, CONFIRMED, FAILED |
amlStatus |
Пропустил ли её скрининг? | null, PASSED, FAILED, SKIPPED |
kind |
Это те деньги, которые запрашивал инвойс? | EXPECTED, ALTERNATIVE, UNEXPECTED |
Чтобы деньги попали в paidAmount, платёж должен быть CONFIRMED, пройти скрининг (PASSED или
SKIPPED) и иметь kind = EXPECTED. Не выполнено хотя бы одно условие из трёх — в итог
инвойса деньги не попадут.
confirmations — это снимок, а не живой счётчик
Поле фиксирует глубину на момент записи и дальше не растёт. О завершённости платежа судите по
status. А если нужно показывать глубину, берите latestBlockNumber и confirmationBlocks из
GET /api/v1/chains.
Как классифицируется платёж¶
kind присваивается один раз — когда депозит замечен впервые, — и зависит от того, в каком состоянии
инвойс был в этот момент.
flowchart TD
A["Депозит приходит на депозитный адрес инвойса"] --> B{"Инвойс ещё в процессе оплаты?<br/>WAITING · PAYMENT_DETECTED · PARTIALLY_PAID"}
B -- нет --> U["UNEXPECTED"]
B -- да --> C{"Та же сеть и тот же токен,<br/>что и у инвойса?"}
C -- да --> E["EXPECTED<br/>засчитывается в paidAmount"]
C -- нет --> D{"acceptsAnyToken"}
D -- "true" --> F["ALTERNATIVE<br/>записывается, не засчитывается"]
D -- "false" --> U
Если платёж оказался UNEXPECTED, причину объясняет unexpectedReason. Несовпадение сети или токена
всегда перевешивает причину, связанную со временем:
unexpectedReason |
Значение |
|---|---|
WRONG_NETWORK_AND_TOKEN |
Не совпадают ни сеть, ни токен |
WRONG_NETWORK |
Токен верный, сеть неверная |
WRONG_TOKEN |
Сеть верная, токен неверный |
AFTER_EXPIRATION |
Сеть и токен верные, но срок инвойса уже истёк |
AFTER_COMPLETION |
Сеть и токен верные, но инвойс уже вышел из стадии оплаты — PAID, SWAPPING, COMPLETED или FAILED |
Что засчитывается в инвойс¶
kind |
status |
Скрининг | pendingAmount |
paidAmount |
|---|---|---|---|---|
EXPECTED |
DETECTED |
ещё не выполнен | + | — |
EXPECTED |
CONFIRMED |
PASSED / SKIPPED |
− | + |
EXPECTED |
CONFIRMED |
FAILED |
− | — |
EXPECTED |
FAILED |
любой | − | − |
ALTERNATIVE |
любой | любой | — | — |
UNEXPECTED |
любой | любой | — | — |
Отсюда два следствия, которые стоит проговорить вслух:
- Помеченные деньги инвойс не оплатят никогда. Они вычитаются из
pendingAmountи вpaidAmountне попадают — чем бы вы потом ни закрыли такой платёж. - Разрешение платежа не двигает суммы.
ACCEPTEDиREFUNDEDфиксируют ваше решение для журнала аудита, и ни то, ни другое наpaidAmountне влияет.
Неожиданные платежи¶
resolution хранит ваше решение по деньгам, которых вы не просили.
resolution |
Значение |
|---|---|
PENDING |
Ждёт вашего решения |
ACCEPTED |
Вы оставляете его себе |
REFUNDED |
Вы отправляете его обратно |
Платёж UNEXPECTED появляется со статусом PENDING, платёж ALTERNATIVE — сразу с ACCEPTED:
принимать такие токены вы уже согласились флагом acceptsAnyToken, так что решать нечего и разрешить
его нельзя.
Разрешение платежа¶
curl -X POST https://api.pay.nullswap.com/api/v1/invoices/{invoiceId}/payments/{paymentId}/resolve \
-H "x-api-key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "resolution": "REFUNDED", "refundAddress": "TJ8k...2Ba7" }'
| Поле | Обяз. | Примечания |
|---|---|---|
resolution |
да | ACCEPTED или REFUNDED |
refundAddress |
условно | Обязателен при REFUNDED, в остальных случаях отклоняется |
Разрешить платёж можно, пока он PENDING, и при условии, что либо его kind отличается от
EXPECTED, либо amlStatus равен FAILED. Ожидаемые деньги, прошедшие скрининг, расходятся сами —
решать по ним нечего. Помеченные ожидаемые деньги — исключение: в инвойс они так и не попали, и
кто-то должен сказать, куда их деть.
| Статус | message |
Причина |
|---|---|---|
404 |
InvoicePaymentWithIdNotFoundError |
Такого платежа нет, либо он принадлежит другому инвойсу |
422 |
ExpectedInvoicePaymentCannotBeResolvedError |
Платёж EXPECTED, прошедший скрининг |
422 |
InvoicePaymentAlreadyResolvedError |
Уже ACCEPTED или REFUNDED |
422 |
RefundAddressRequiredError |
REFUNDED без refundAddress |
422 |
RefundAddressNotAllowedError |
refundAddress отправлен с разрешением, отличным от REFUNDED |
Регистрация возврата¶
NullSwap не отправляет возвраты
Разрешение REFUNDED фиксирует ваше решение, а не перевод. Деньги вы отправляете обратно сами,
со своего кошелька, а потом сообщаете нам хеш транзакции — чтобы он остался в истории платежа.
curl -X POST https://api.pay.nullswap.com/api/v1/invoices/{invoiceId}/payments/{paymentId}/refund \
-H "x-api-key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "refundTxHash": "0xabcdef...7890" }'
| Статус | message |
Причина |
|---|---|---|
422 |
InvoicePaymentNotRefundedError |
Платёж не был предварительно разрешён как REFUNDED |
422 |
InvoicePaymentRefundAlreadyRecordedError |
refundTxHash уже записан |
Целиком последовательность для платежа, который вы решили вернуть, выглядит так: