Developer docs v0.1.0

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 & pathDescription
POST /v1/paymentsCreate a standalone payment; supports Idempotency-Key
GET /v1/paymentsList; filters subscription, customer, status, type (standalone or subscription), review_status (none, open, resolved)
GET /v1/payments/:idRetrieve
POST /v1/payments/:id/resolveRecord 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"}'
FieldTypeRequiredNotes
customer_idULIDyes
instrument_idULIDnodefaults to the customer's default method; must belong to the customer and not be detached
amountinteger > 0yesminor units
currencystringyesRON, EUR or USD (case-insensitive, stored uppercase)
descriptionstring (≤500)noalso sent to NETOPIA Payments as the order description
metadataobjectno

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, with error_message); the scheduler never retries it, and payment.failed carries retry_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 pending and 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 (no instrument_id and 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 as failed and 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.