Payments
A payment is one attempt to charge a stored card. It is either a subscription charge
(it covers one billing period, period_start–period_end) or a standalone payment (no
subscription, subscription_id and both period fields are null). Status is pending (outcome
unknown), succeeded or failed (refunded is reserved).
| Method & path | Description |
|---|---|
POST /v1/payments | Create a standalone payment; supports Idempotency-Key |
GET /v1/payments | List; filters subscription, customer, status, type (standalone or subscription), review_status (none, open, resolved) |
GET /v1/payments/:id | Retrieve |
POST /v1/payments/:id/resolve | Record a completed external review disposition; supports Idempotency-Key |
A payment made through a payment link has customer_id: null, instrument_id: null, plus payment_link_id and payer_email.
{ "object":"payment", "id":"01…", "subscription_id":"01…", "customer_id":"01…",
"instrument_id":"01…", "amount":4900, "currency":"RON", "status":"succeeded",
"netopia_ntp_id":"…", "netopia_status_code":200, "error_message":null,
"period_start":"…", "period_end":"…", "description":null, "metadata":{},
"proration":false, "review":null, "created_at":"…" }
Standalone payments
Charge a customer's stored card once, without a subscription: a setup fee, a top-up, an invoice settled outside the plan. The card must have completed hosted enrollment (see Instruments); there is no way to charge a raw card or token.
curl -sS $BASE/v1/payments "${auth[@]}" -H 'idempotency-key: setup-fee-ana-1' \
-d '{"customer_id":"<customer id>","amount":2500,"currency":"RON","description":"Setup fee"}'
| Field | Type | Required | Notes |
|---|---|---|---|
customer_id | ULID | yes | |
instrument_id | ULID | no | defaults to the customer's default method; must belong to the customer and not be detached |
amount | integer > 0 | yes | minor units |
currency | string | yes | RON, EUR or USD (case-insensitive, stored uppercase) |
description | string (≤500) | no | also sent to NETOPIA Payments as the order description |
metadata | object | no |
Returns 201 with the payment transaction. A 201 does not mean the customer has paid: read
status and listen for payment.succeeded / payment.failed. Without an Idempotency-Key the
charge is submitted before the response, so status is normally final. With one, the intent is
committed with the response and submitted right after it, so the first response reports pending
and the transaction settles moments later; poll GET /v1/payments/:id or use the
events. A retry with the same key returns the stored response and never charges twice.
- No retries. A declined standalone payment is final (
failed, witherror_message); the scheduler never retries it, andpayment.failedcarriesretry_at: null. Create a new payment (with a new key) to try again, ideally after the customer fixes the card. - Ambiguous results (NETOPIA Payments timeout or 5xx) stay
pendingand are settled by the NETOPIA Payments callback or the reconciliation job, exactly like subscription charges. - No refunds. Payware has no refund call; a succeeded payment stays
succeeded. - Errors:
404 resource_missing(customer, or instrument not found/detached/another customer's),400 instrument_required(noinstrument_idand no default card),400 instrument_enrollment_required(remote modes: card not verified),400 validation_failed. - In remote modes NETOPIA Payments also needs the customer's complete
metadata.billing(NETOPIA Payments configuration); without it the payment is recorded asfailedand nothing is sent to NETOPIA Payments. - Standalone payments never change a subscription, and are listed with
?type=standalone.
Payment reviews
A newly confirmed success for incomplete_expired or canceled subscriptions remains succeeded
and opens a review atomically with settlement and payment.review_required. Subscriptions stay
closed, including when the customer was deleted. Historical succeeded/expired rows are discovered
by the billing worker; previously paid subscriptions canceled later are ordinary history and do
not open reviews. Duplicate confirmations neither duplicate nor reopen reviews.
review is null for ordinary payments, otherwise:
{ "status":"open", "reason":"subscription_expired", "opened_at":"…",
"resolution":null, "note":null, "resolved_at":null, "resolved_by_api_key_id":null }
Reasons are subscription_expired and subscription_canceled. Open reviews persist independently
of webhook delivery; list them with ?review_status=open even if no endpoint was configured.
Monitor that queue or payment.review_required and reconcile the payment against provider records.
After arranging an external refund or providing the agreed replacement service, record it:
POST /v1/payments/:id/resolve
Authorization: Bearer <merchant API key>
Idempotency-Key: review-disposition-123
Content-Type: application/json
{"resolution":"refunded_externally","note":"Provider refund receipt external-123"}
resolution accepts refunded_externally or service_provided. note is required, trimmed,
1–1000 characters, and should identify the external action/reference. The API records the active
merchant key ID and resolution timestamp and atomically queues payment.review_resolved. It
returns the payment with review.status: resolved. This is the merchant's attestation: no refund
is submitted or independently verified, payment status stays succeeded, and entitlement is not
restored automatically. Arrange that action before recording the disposition.
An identical unkeyed resolution returns the existing result; a different disposition returns
409 payment_review_already_resolved. A payment without an open review returns
409 payment_review_not_open; another merchant's payment returns 404. Resolved reviews are
immutable and remain closed under repeated callbacks or worker recovery. Keep a billing worker
enabled and monitor open reviews until each has a recorded disposition.