Payment methods

Configuring what a checkout may offer on revenexx — method definitions with their restrictions and fees, the eligibility call, and the provider catalogue with its availability flag.

Two layers of configuration sit in front of a payment: a method is the line a checkout offers, and a provider is the PSP account behind it.

Base path: https://api.revenexx.com/v1/payments.

Methods

Request
curl -X POST https://api.revenexx.com/v1/payments/methods \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{
        "code": "invoice",
        "name": "Invoice",
        "kind": "self_managed",
        "countries": ["DE", "AT"],
        "min_order_value": 50,
        "fee_type": "fixed",
        "fee_amount": 2.5,
        "fee_currency": "EUR",
        "enabled": true
      }'
FieldMeaning
codeRequired. The value every payment, every checkout and every ERP names this method by.
name, labels, descriptionDisplay.
kindself_managed (invoice, prepayment) or psp.
provider, provider_methodWhich PSP account and which of its methods. Only for kind: "psp".
countriesAllow-list. Empty or null means unrestricted.
min_order_value, max_order_valueAmount bounds.
fee_typenone, fixed or percent, with fee_amount and fee_currency.
enabledDefaults to false — a new method reaches no checkout until it is switched on.
positionThe order a checkout prints them in.

Two defaults to know before the first call:

  • enabled is false. Creating a method changes nothing a buyer sees.
  • kind is self_managed. A card or wallet method needs kind: "psp" plus a provider the catalogue carries, or it falls back to the tenant's default_provider at payment time and fails there if none is set.
Choose code once. Once a single payment has been made under it, a rename is refused with 409.

A duplicate code in one tenant is a 409.

Method and pathWhat it does
GET /v1/payments/methods/{id}Reads one method by its id, with every column. It takes no code: to find a method by its code, filter the list with GET /v1/payments/methods?code=invoice, which returns at most one row. Reading a method does not say whether a buyer may use it; that is the eligibility call below.
DELETE /v1/payments/methods/{id}Deletes a method. A payment keeps only the method's code, so if payments name the method the route answers 409 with the number of them (method_in_use) and deletes nothing. To stop offering a method without deleting it, set enabled: false.

Ask what a buyer may pay with

Request
curl -X POST https://api.revenexx.com/v1/payments/methods/eligible \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "X-Revenexx-Market: de" \
  -H "Content-Type: application/json" \
  -d '{"country":"DE","amount":249.90,"currency":"EUR"}'

The checkout's question — what can this buyer pay with? — answered server-side before any PSP is involved, so a storefront never renders a method the create would then refuse.

It reads only: nothing is written and no provider is called. Eligible methods come back sorted by position with their fee already computed for this amount, and fee_display says whether those fees are gross or net per the tenant's setting.

Everything else lands in excluded with the reason in words — "country FR not in [DE, AT]" — which is what makes a support question answerable. That is diagnostics, not something to show a buyer: it is how a merchant finds out why their invoice method is missing from a checkout.

Restriction dimensions are ANDed and entries within one are ORed; an empty dimension means unrestricted. A context matching nothing is 200 with an empty methods list, never 404.

Two things it does not check

It does not check whether the method's PSP is configured and enabled. A method whose provider is switched off is still offered here and fails at POST /v1/payments. (A provider a method names can no longer be deleted, which closes the other half of the same gap.)It knows nothing about the buyer beyond country and amount. No segment, no credit standing, no payment terms.

Eligibility is also enforced at POST /v1/payments — the same rules answer 422 there — so this route is an optimisation for the UI, not the guard.

Providers

Request
curl https://api.revenexx.com/v1/payments/providers/catalog \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..."

Call the catalogue first. It is the closed set of provider codes a configuration accepts — anything else is refused with 400 and a message listing these. It runs to roughly thirty connectors, is identical for every tenant, and changes only with a release of the app, so it is safe to cache hard.

Per entry:

FieldMeaning
codeThe value a configuration takes as provider, and the {slug} the logo route serves.
labelThe PSP's own name, for a picker.
auth_typeThe scheme the connector authenticates with.
credential_fieldsThe key names to put inside credentials — never values, which come from the PSP's own dashboard. This is the list a credentials form renders.
logo_urlThe logo that ships with the app.
availableWhether a driver for this provider exists in this deployment.

GET /v1/payments/logos/{slug} serves the logo for a catalogue code: an SVG document, either the shipped file or a generated monogram tile, cached for one day. For a connector whose brand files sit on the platform CDN it answers 302 to the brand file there instead (SVG or PNG), the full logo by default or the square symbol with ?variant=symbol. The slug is case-insensitive and a trailing .svg is ignored. An unknown code is 404. On api.revenexx.com the route is callable without credentials: no API key and no tenant header. A shipped SVG comes back as image/svg+xml and a redirect lands on the image file, so an <img> can point straight at https://api.revenexx.com/v1/payments/logos/stripe. Every answer of the route, the 302 included, carries Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; img-src data:; sandbox and X-Content-Type-Options: nosniff, so an SVG it serves, opened on its own, runs no script and loads nothing. A redirect target on the CDN comes with that host's own headers.

Three providers cannot transact

A stored row can name a provider the catalogue no longer offers. The catalogue lists only the providers it offers, and every one of them has a driver. PAYONE (payone), Computop Paygate (computop) and Unzer (unzer) are not offered and not listed. A row stored for one of them earlier keeps its provider code, and every transaction through it fails with provider_unavailable. Move such a row to a provider in the catalogue.Compare a row's provider with the catalogue rather than hard-coding this list — it changes with a release.

Which providers can be reached

available: true says the provider integration exists, not that the provider can be reached. Payments may call out to the sandbox and live API hosts of every provider the catalog offers (the app's outbound permissions), so a configured row of any of them can take a payment through its provider. Reaching the host is not a credentials check: use the connection check under Configuring an account.

Configuring an account

Stripe, PayPal and Novalnet already have a disabled row on a seeded tenant (below), so for those three update the seeded row with PUT /v1/payments/providers/{id} instead of creating one; a second create for the same provider answers 409. A catalog provider with no row yet is created as shown.

Request
curl -X POST https://api.revenexx.com/v1/payments/providers \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{
        "provider": "stripe",
        "name": "Stripe (live)",
        "test_mode": false,
        "enabled": true,
        "credentials": { "api_key": "…" }
      }'
PSP secrets are write-only.credentials and webhook_secret are accepted on create and update, stored, and never returned by any route — responses carry the public columns only (id, provider, name, enabled, test_mode, options, timestamps). To rotate a secret, write the new value; there is no way to read the current one back.

To check that a configuration's credentials work, POST /v1/payments/providers/{id}/check makes one authenticated call to the provider. Stripe and PayPal use the environment the configuration names (sandbox in test mode, live otherwise). Novalnet's check always calls its live host. Nothing is charged and nothing is created at the provider. The outcome is stored as options.last_check (at, ok, code, environment) and returned with the configuration. code is ok, auth_failed, environment_mismatch (a live key on a test-mode provider, or the reverse), missing_credentials, unreachable or provider_error. Credentials appear in no answer and no log. Most catalog providers can be checked, Mollie included; a provider without a check answers 422 not_testable.

Method and pathWhat it does
DELETE /v1/payments/providers/{id}Removes the account row and its stored secrets, but only once nothing depends on it. Payments do not carry a foreign key to a provider, so the database would not stop it; the route counts instead and answers 409 provider_in_use with both counts, payments and methods. The payments counted are those still created, requires_action, authorized or captured, because only those can still be captured, cancelled or refunded. The methods counted are those naming this provider. Failed, cancelled and refunded payments do not block the delete. To stop new payments without deleting, set enabled: false: transitions on payments already held keep working.

Seeding a fresh shop

Request
curl -X POST https://api.revenexx.com/v1/payments/methods/defaults \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{}'

Writes the four methods a shop starts with — invoice and prepayment as self-managed, card and PayPal routed at the built-in mock PSP so a fresh install can complete a checkout end to end — plus the four provider rows behind them: the mock, Stripe, PayPal and Novalnet.

Stripe, PayPal and Novalnet arrive disabled, in test mode and without credentials; the mock arrives enabled, because it moves no money.

The app runs this itself on install, so calling the route is for the second time and after: a method someone deleted, or a row a later release added that an existing install never got. Re-running is safe by design — it never duplicates and never overwrites, so nothing an operator has set can be undone. Only genuinely missing option keys are filled, and those rows are reported as updated rather than created.

The tax-class usage check

Request
curl "https://api.revenexx.com/v1/payments/tax-classes/reduced/usage" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "X-Revenexx-Market: at"

Markets is the source of record for a tax rate, and this app points at it by code from two places: a method's own tax_class, and the market's payment_fee_tax_class setting, the class every method that names none is taxed as. Neither is a foreign key. So markets asks this route before it deletes a tax class, and a count above zero, or a fallback setting that names the code, stops the delete.

The answer carries in_use, count (methods naming the code, capped at 500), fallback_setting and samples (the first 20, with id, code and name). count and samples are the field names every app's usage answer shares. A tax class is unique per market, so send X-Revenexx-Market to count only the methods visible in that market: its own, plus the unassigned ones. Without the header every method of the tenant counts. Every code answers: one nobody points at is in_use: false, never a 404.

Vocabularies

The app's own value sets (among them statuses, method kinds, fee types and dunning stages) are readable, so a client can draw a status chip without copying them.

Method and pathWhat it does
GET /v1/payments/vocabulariesLists the sets, each with a name, a title and a description, without their values. Use it to see which sets exist without paying for all of their values.
GET /v1/payments/vocabularies/{name}Reads one set in full: every value it permits, the label for each and the badge tone to draw it in. The values are the ones the app accepts, so a value the app adds appears here without a release of the client. A permitted value with no label still appears, titled from its own key.

Where to go next

Was this page helpful?