commit 31771451cedb9784cce621d0af5028897546bebf
parent 1a7dff6c8ddf0a143797f8790887858cf900ee2e
Author: Hernâni Marques <hernani+clementine@vecirex.net>
Date: Sat, 5 Sep 2026 03:20:47 +0200
10989/docs: stage e2e onboarding (API access) + regio notes
Diffstat:
1 file changed, 225 insertions(+), 8 deletions(-)
diff --git a/deployments/tops-stage-devtesting.rst b/deployments/tops-stage-devtesting.rst
@@ -1,15 +1,105 @@
+Testing in the TOPS Staging Environment
+=======================================
+
+This document contains a detailled, step-by-step instructions for testing
+with the staging environment for the Taler Operations deployment.
+
+The staging environment (on host ``rusty``) is almost equivalent to the production
+environment. It is, however, not connected to a real bank account.
+
+Prerequisites
+-------------
+
+An administrator needs to enable your SSH key for testing
+in the staging environment.
+
+
+Withdrawals: Basic Withdrawal
+-----------------------------
+
+1. Scan the following QR code to start a withdrawal from the `stage exchange <https://exchange.stage.taler-ops.ch/>`_:
+
+ .. image:: ./qr-withdraw-tops-stage.png
+
+ ``taler://withdraw-exchange/exchange.stage.taler-ops.ch/``
+
+2. Confirm the withdrawal in the wallet.
+
+3. Run the following command to fake a wire transfer, using a randomly
+ generated creditor IBAN. The ``$AMOUNT`` and ``$SUBJECT`` should be
+ taken from the wire transfer instructions in the wallet UI.
+
+ .. code:: none
+
+ ssh -T devtesting@rusty.taler-ops.ch "fake-incoming --amount $AMOUNT --subject '$SUBJECT'"
+
+4. Check that the withdrawal succeeded in the wallet.
+
+
+Deposits: Basic Deposit
+-----------------------
+
+1. Do a basic withdrawal.
+2. Initiate a deposit in the wallet UI, using the receiver account known to the wallet from the withdrawal.
+3. The deposit transaction should now ask for KYC information. Fill out the corresponding form(s).
+4. The deposit transaction should finish.
+
+Deposits: Deposit with KYC Auth
+-------------------------------
+
+1. Do a basic withdrawal.
+
+2. Generate an IBAN via:
+
+ .. code:: none
+
+ ssh -T devtesting@rusty.taler-ops.ch geniban
+
+3. Initiate a deposit in the wallet UI to the randomly generated IBAN.
+
+4. The deposit transaction should now ask for a KYC auth transfer. Satisfy the auth transfer by running:
+
+ .. code:: none
+
+ ssh -T devtesting@rusty.taler-ops.ch "fake-incoming --amount $AMOUNT --subject '$SUBJECT' --debit-payto '$DEBIT_PAYTO'"
+
+ with the information shown in the wallet UI, where the ``$DEBIT_PAYTO`` must contain the IBAN generated in step (2).
+
+
+5. The deposit transaction should now ask for KYC information. Fill out the corresponding form(s).
+
+6. The deposit transaction should finish.
+
Merchant: End-to-end onboarding (staging)
-----------------------------------------
Stage-specific glue for the merchant path already described elsewhere:
-:ref:`taler-merchant-backend-operator-manual` (SPA) and *Merchant:
-Onboarding* in :doc:`tops` (bank account → KYC auth → accept exchange
-terms of service → accept payments). Wire faking reuses ``geniban`` /
-``fake-incoming`` from *Deposits: Deposit with KYC Auth* above.
+:ref:`taler-merchant-backend-operator-manual` (SPA and HTTP API) and
+*Merchant: Onboarding* in :doc:`tops` (bank account → KYC auth → accept
+exchange terms of service → accept payments). Authentication is
+documented under :ref:`merchant-api-authentication`. Wire faking
+reuses ``geniban`` / ``fake-incoming`` from *Deposits: Deposit with KYC
+Auth* above.
+
+Two variants: **SPA (mostly manual)** below, then **API** (curl /
+scripted self-provision, login tokens, and orders).
-This section is the **mostly manual** path (merchant portal / SPA).
+This page is written for **TOPS stage** (``$BASE`` below). Deployments
+like **regional currencies** — overview at
+`https://regio.taler.net/ <https://regio.taler.net/>`_, setup via
+``taler-deployment.git`` ``regional-currency/`` (see the :doc:`automated
+setup<../regional/regional-automated-manual>` and :doc:`custom
+setup<../regional/regional-custom-manual>` manuals) — can differ.
+Look at the merchant ``/config``: if ``mandatory_tan_channels`` is
+empty, MFA for self-provisioning is effectively off and
+``POST /instances`` often returns **204** (instance exists immediately,
+no ``/challenge/…`` round-trip) — typical for many **regio** setups.
+Creating orders still needs at least one active bank account (otherwise
+the backend rejects with a missing-account error). Wire faking via
+``rusty`` / ``taler-devtesting`` is TOPS-stage specific (not the usual
+regional-bank funding path).
Variables used below (set your own values):
@@ -18,8 +108,15 @@ Variables used below (set your own values):
BASE=https://stage.my.taler-ops.ch
INSTANCE=my-shop # instance id (slug)
INSTANCE_PASSWORD=… # password chosen at signup
+ EMAIL=you@example.com # inbox that can receive stage TAN mail
+ PHONE=+41 7x xxx xx xx # real CH mobile; must match phone_regex
+ # from $BASE/config *and* be accepted by
+ # the SMS helper (dummy numbers fail with
+ # TALER_EC 2132 on /challenge/$ID)
IBAN=… # from geniban
DEBIT_PAYTO=payto://iban/$IBAN
+ TOKEN=… # access_token from /private/token
+ ORDER_ID=… # from PostOrderResponse
``$BASE/`` redirects to ``$BASE/webui/``. Use the
`stage exchange <https://exchange.stage.taler-ops.ch/>`_ for payments.
@@ -36,9 +133,20 @@ Shared bank / KYC steps
IBAN=$(ssh -T devtesting@rusty.taler-ops.ch geniban)
DEBIT_PAYTO="payto://iban/$IBAN"
-2. Register that IBAN in the portal under **Bank account**
- (:ref:`instance-account-setup`). Skip revenue-API credentials
- unless an administrator provided stage ones.
+2. Register that IBAN on the instance (:ref:`instance-account-setup`) —
+ in the portal under **Bank account**, or via API after you have a
+ ``$TOKEN``:
+
+ .. code:: none
+
+ curl -sS \
+ -H "Authorization: Bearer $TOKEN" \
+ -H 'Content-Type: application/json' \
+ -d "{\"payto_uri\":\"$DEBIT_PAYTO\"}" \
+ "$BASE/instances/$INSTANCE/private/accounts"
+
+ Skip revenue-API credentials unless an administrator provided stage
+ ones.
3. When the exchange requires the KYC auth transfer (*Merchant:
Onboarding* in :doc:`tops`), satisfy it like *Deposits: Deposit with
@@ -75,3 +183,112 @@ Normal interactive path (:ref:`Instance-setup`, *Self-provisioning*,
4. Pay from the wallet (shared step 5) and confirm paid status in the
SPA.
+
+API (self-provision → password → login token → Bearer)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Same backend via HTTP
+(:http:post:`/instances`, :ref:`merchant-api-authentication`,
+:http:post:`[/instances/$INSTANCE]/private/token`,
+:http:post:`[/instances/$INSTANCE]/private/orders`).
+Private endpoints need
+``Authorization: Bearer secret-token:…`` (RFC 8959).
+
+On this stage backend, expect the MFA path below (``mandatory_tan_channels``
+is ``email`` and ``sms``). Where that list is empty, skip to step 3 after a
+**204** create.
+
+1. Self-provision (:http:post:`/instances`). Example body
+ (`InstanceConfigurationRequest`; ``auth.method`` = ``token``):
+
+ .. code:: none
+
+ curl -sS -H 'Content-Type: application/json' \
+ -d "{
+ \"id\": \"$INSTANCE\",
+ \"name\": \"Example Shop\",
+ \"email\": \"$EMAIL\",
+ \"phone_number\": \"$PHONE\",
+ \"auth\": {\"method\": \"token\", \"password\": \"$INSTANCE_PASSWORD\"},
+ \"address\": {\"country\": \"CH\"},
+ \"jurisdiction\": {\"country\": \"CH\"},
+ \"use_stefan\": true
+ }" \
+ "$BASE/instances"
+
+ Typical response before MFA: **HTTP 202** `ChallengeResponse` with
+ ``combi_and: true`` and one challenge per TAN channel, e.g.:
+
+ .. code:: none
+
+ {
+ "combi_and": true,
+ "challenges": [
+ {"tan_channel": "email", "tan_info": "…", "challenge_id": "$CHALLENGE_EMAIL"},
+ {"tan_channel": "sms", "tan_info": "…", "challenge_id": "$CHALLENGE_SMS"}
+ ]
+ }
+
+ Until both TANs are solved, ``$INSTANCE`` does **not** exist
+ (``/instances/$INSTANCE/private/…`` → **404**).
+
+2. Request and confirm each TAN on the **unprefixed** challenge URLs
+ (instance path segment optional; before create, only this form
+ works — see *Solving challenges* in the merchant API):
+
+ .. code:: none
+
+ curl -sS -H 'Content-Type: application/json' -d '{}' \
+ "$BASE/challenge/$CHALLENGE_ID"
+ curl -sS -H 'Content-Type: application/json' \
+ -d "{\"tan\":\"$TAN\"}" \
+ "$BASE/challenge/$CHALLENGE_ID/confirm"
+
+ Repeat for every ``challenge_id``. A successful request returns
+ **200**; a rejected SMS/e-mail address returns **400** with code
+ **2132** (fix ``$PHONE`` / ``$EMAIL``). Wrong ``$TAN`` on confirm
+ returns **409**.
+
+ Retry the **same** ``POST $BASE/instances`` body with:
+
+ .. code:: none
+
+ -H "Taler-Challenge-Ids: $CHALLENGE_EMAIL,$CHALLENGE_SMS"
+
+ Expect **200** / **204** when the instance is created. With
+ unsolved challenges the retry stays at **202**.
+
+3. Password → login token (Basic: user ``$INSTANCE``, password
+ ``$INSTANCE_PASSWORD``; `LoginTokenRequest`):
+
+ .. code:: none
+
+ curl -sS -u "$INSTANCE:$INSTANCE_PASSWORD" \
+ -H 'Content-Type: application/json' \
+ -d '{"scope":"order-simple"}' \
+ "$BASE/instances/$INSTANCE/private/token"
+
+ From `LoginTokenSuccessResponse`, set ``TOKEN`` to ``access_token``
+ (already includes the ``secret-token:`` prefix). Scopes such as
+ ``spa`` or ``all`` work the same way if you need broader rights.
+
+4. Create an order with the Bearer token:
+
+ .. code:: none
+
+ curl -sS \
+ -H "Authorization: Bearer $TOKEN" \
+ -H 'Content-Type: application/json' \
+ -d '{"order":{"summary":"stage test","amount":"CHF:1"}}' \
+ "$BASE/instances/$INSTANCE/private/orders"
+
+5. Read ``order_id`` from `PostOrderResponse`. Fetch ``taler_pay_uri``
+ via :http:get:`[/instances/$INSTANCE]/private/orders/$ORDER_ID`
+ (or the public order status URL). Finish *Shared bank / KYC steps*
+ if needed, then pay from the wallet.
+
+.. note::
+
+ Prefer login tokens from ``/private/token``. Long-lived
+ ``Authorization: Bearer secret-token:$INSTANCE_PASSWORD`` is the
+ deprecated ``token`` method and will be phased out.