merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit 2b714201944476486e91965f63b4e2cc8b18b427
parent de3eab29eebec8d1c1b1bf0ea2adc39ba647691a
Author: Florian Dold <dold@taler.net>
Date:   Fri, 11 Sep 2026 19:30:59 +0200

merchant auth: allow order-pos tokens to grant refunds

Issue: https://bugs.taler.net/n/11767

Diffstat:
Msrc/backend/taler-merchant-httpd.h | 2+-
Msrc/backend/taler-merchant-httpd_auth.c | 4++--
Msrc/testing/test_merchant_instance_auth.sh | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 55 insertions(+), 3 deletions(-)

diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h @@ -64,7 +64,7 @@ enum TMH_AuthScope TMH_AS_ORDER_SIMPLE = 3, /** - * Order creation and inventory locking, + * Order creation, inventory locking and refunds, * includes #TMH_AS_ORDER_SIMPLE */ TMH_AS_ORDER_POS = 4, diff --git a/src/backend/taler-merchant-httpd_auth.c b/src/backend/taler-merchant-httpd_auth.c @@ -93,11 +93,11 @@ static struct ScopePermissionMap scope_permissions[] = { .name = "order-simple", .permissions = "orders-read,orders-write" }, - /* Simple order management for PoS, also allows inventory locking */ + /* Order management for PoS, allows inventory locking and refunds */ { .as = TMH_AS_ORDER_POS, .name = "order-pos", - .permissions = "orders-read,orders-write,pos-read,products-read,products-lock" + .permissions = "orders-read,orders-write,pos-read,products-read,products-lock,orders-refund" }, /* Simple order management, also allows refunding */ { diff --git a/src/testing/test_merchant_instance_auth.sh b/src/testing/test_merchant_instance_auth.sh @@ -84,6 +84,58 @@ fi echo " OK" >&2 +# Check both refund endpoints with scoped bearer tokens. An unknown order +# must reach the refund handler (404/2005) only when the scope permits refunds. +for SCOPE in readonly order-simple order-pos order-pos:refreshable order-mgmt order-full +do + echo -n "Checking refund access for ${SCOPE} ..." >&2 + STATUS=$(curl -H "Content-Type: application/json" \ + -H "Authorization: Basic $BASIC_AUTH" \ + http://localhost:9966/private/token \ + -d "$(jq -n --arg scope "$SCOPE" '{scope: $scope}')" \ + -w "%{http_code}" -s -o "$LAST_RESPONSE") + if [ "$STATUS" != "200" ] + then + cat "$LAST_RESPONSE" >&2 + exit_fail "Expected 200, ${SCOPE} token created. Got: $STATUS" + fi + SCOPED_TOKEN=$(jq -e -r .access_token < "$LAST_RESPONSE") + + case "$SCOPE" in + readonly|order-simple) + EXPECTED_STATUS=401 + EXPECTED_CODE=2015 # TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED + ;; + *) + EXPECTED_STATUS=404 + EXPECTED_CODE=2005 # TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN + ;; + esac + for ENDPOINT in refund refund-external + do + case "$ENDPOINT" in + refund) + REFUND_BODY='{"refund":"TESTKUDOS:1","reason":"scope test"}' + ;; + refund-external) + REFUND_BODY='{"method":"cash","id":"scope-test","amount":"TESTKUDOS:1","reason":"scope test"}' + ;; + esac + STATUS=$(curl -H "Content-Type: application/json" \ + -H "Authorization: Bearer $SCOPED_TOKEN" \ + "http://localhost:9966/private/orders/unknown-order/${ENDPOINT}" \ + -d "$REFUND_BODY" \ + -w "%{http_code}" -s -o "$LAST_RESPONSE") + if [ "$STATUS" != "$EXPECTED_STATUS" ] || + [ "$(jq -r .code < "$LAST_RESPONSE")" != "$EXPECTED_CODE" ] + then + cat "$LAST_RESPONSE" >&2 + exit_fail "Expected ${EXPECTED_STATUS}/${EXPECTED_CODE} for ${SCOPE} on ${ENDPOINT}. Got: $STATUS" + fi + done + echo " OK" >&2 +done + # Kill merchant kill -TERM "$SETUP_PID" wait