#!/bin/bash # This file is in the public domain. set -eu function clean_wallet() { rm -f "${WALLET_DB}" exit_cleanup } # Replace with 0 for nexus... USE_FAKEBANK=1 if [ 1 = "$USE_FAKEBANK" ] then ACCOUNT="exchange-account-2" BANK_FLAGS="-f -d x-taler-bank -u $ACCOUNT" BANK_URL="http://localhost:8082/taler-bank-access/" else ACCOUNT="exchange-account-1" BANK_FLAGS="-ns -d iban -u $ACCOUNT" BANK_URL="http://localhost:18082/demobanks/default/access-api/" fi . setup.sh # Launch exchange, merchant and bank. setup -c "test_template.conf" \ -em \ $BANK_FLAGS LAST_RESPONSE=$(mktemp -p "${TMPDIR:-/tmp}" test_response.conf-XXXXXX) CONF="test_template.conf.edited" WALLET_DB=$(mktemp -p "${TMPDIR:-/tmp}" test_wallet.json-XXXXXX) EXCHANGE_URL="http://localhost:8081/" # Install cleanup handler (except for kill -9) trap clean_wallet EXIT echo -n "First prepare wallet with coins ..." rm -f "$WALLET_DB" taler-wallet-cli \ --no-throttle \ --wallet-db="$WALLET_DB" \ api \ --expect-success 'withdrawTestBalance' \ "$(jq -n ' { amount: "TESTKUDOS:99", bankAccessApiBaseUrl: $BANK_URL, exchangeBaseUrl: $EXCHANGE_URL }' \ --arg BANK_URL "${BANK_URL}" \ --arg EXCHANGE_URL "$EXCHANGE_URL" )" 2>wallet-withdraw-1.err >wallet-withdraw-1.out echo -n "." # FIXME-MS: add logic to have nexus check immediately here. # sleep 10 echo -n "." # NOTE: once libeufin can do long-polling, we should # be able to reduce the delay here and run wirewatch # always in the background via setup taler-exchange-wirewatch \ -a "$ACCOUNT" \ -L "INFO" \ -c "$CONF" \ -t &> taler-exchange-wirewatch.out echo -n "." taler-wallet-cli \ --wallet-db="$WALLET_DB" \ run-until-done \ 2>wallet-withdraw-finish-1.err \ >wallet-withdraw-finish-1.out echo " OK" CURRENCY_COUNT=$(taler-wallet-cli --wallet-db="$WALLET_DB" balance | jq '.balances|length') if [ "$CURRENCY_COUNT" = "0" ] then exit_fail "Expected least one currency, withdrawal failed. check log." fi # # CREATE INSTANCE FOR TESTING # echo -n "Configuring merchant instance ..." if [ 1 = "$USE_FAKEBANK" ] then FORTYTHREE="payto://x-taler-bank/localhost/fortythree?receiver-name=fortythree" else FORTYTHREE=$(get_payto_uri fortythree x) fi # create with 2 bank account addresses STATUS=$(curl -H "Content-Type: application/json" -X POST \ -H 'Authorization: Bearer secret-token:super_secret' \ http://localhost:9966/management/instances \ -d '{"auth":{"method":"external"},"accounts":[{"payto_uri":"'"$FORTYTHREE"'"},{"payto_uri":"payto://iban/SANDBOXX/DE270744?receiver-name=Forty+Four"}],"id":"default","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000000},"default_pay_delay":{"d_us": 60000000000}}' \ -w "%{http_code}" -s -o /dev/null) if [ "$STATUS" != "204" ] then exit_fail "Expected '204 No content' response. Got instead $STATUS" fi echo -n "." # remove one account address STATUS=$(curl -H "Content-Type: application/json" -X PATCH \ -H 'Authorization: Bearer secret-token:super_secret' \ http://localhost:9966/instances/default/private/ \ -d '{"auth":{"method":"external"},"accounts":[{"payto_uri":"'"$FORTYTHREE"'"}],"id":"default","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000000},"default_pay_delay":{"d_us": 60000000000}}' \ -w "%{http_code}" -s -o /dev/null) if [ "$STATUS" != "204" ] then exit_fail "Expected '204 No content' response. Got instead: $STATUS" fi echo "OK" RANDOM_IMG='data:image/png;base64,abcdefg' # # CREATE AN ORDER WITHOUT TOKEN # echo -n "Creating order without TOKEN..." STATUS=$(curl 'http://localhost:9966/instances/default/private/orders' \ -d '{"create_token":false,"order":{"amount":"TESTKUDOS:7","summary":"3","products":[{"description":"desct","image":"'"$RANDOM_IMG"'","price":"TESTKUDOS:1","taxes":[],"unit":"u","quantity":1}]}}' \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "200" ] then cat "$LAST_RESPONSE" >&2 exit_fail "Expected 200, order created. got: $STATUS" fi ORDER_ID=$(jq -r .order_id < "$LAST_RESPONSE") TOKEN=$(jq -r .token < "$LAST_RESPONSE") if [ "$TOKEN" != "null" ] then exit_fail "token should be null, got: $TOKEN" fi echo "OK" echo -n "Checking created order without TOKEN..." STATUS=$(curl http://localhost:9966/orders/"$ORDER_ID" \ -w "%{http_code}" -s -o "$LAST_RESPONSE") PAY_URI=$(jq -r .taler_pay_uri < "$LAST_RESPONSE") if [ "$PAY_URI" == "null" ] then cat "$LAST_RESPONSE" >&2 exit_fail "Expected non-NULL payuri. got $PAY_URI" fi echo "OK" # # CREATE AN ORDER WITHOUT TOKEN WITH FULLFILMENT URL # echo -n "Creating order without TOKEN and fullfilment URL..." STATUS=$(curl 'http://localhost:9966/instances/default/private/orders' \ -d '{"create_token":false,"order":{"fulfillment_url":"go_here_please", "amount":"TESTKUDOS:7","summary":"3","products":[{"description":"desct","image":"'"$RANDOM_IMG"'","price":"TESTKUDOS:1","taxes":[],"unit":"u","quantity":1}]}}' \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "200" ] then cat "$LAST_RESPONSE" >&2 exit_fail "Expected 200, order created. got: $STATUS" fi ORDER_ID=$(jq -r .order_id < "$LAST_RESPONSE") TOKEN=$(jq -r .token < "$LAST_RESPONSE") if [ "$TOKEN" != "null" ] then exit_fail "Token should be null, got: $TOKEN" fi STATUS=$(curl http://localhost:9966/orders/"$ORDER_ID" \ -w "%{http_code}" -s -o "$LAST_RESPONSE") PAY_URI=$(jq -r .taler_pay_uri < "$LAST_RESPONSE") FULLFILMENT_URL=$(jq -r .fulfillment_url < "$LAST_RESPONSE") if [ "$FULLFILMENT_URL" != "go_here_please" ] then cat "$LAST_RESPONSE" >&2 exit_fail "Expected a pay URI. got: $PAY_URI" fi if [ "$PAY_URI" == "null" ] then cat "$LAST_RESPONSE" >&2 exit_fail "Expected non-NULL pay URI. Got: $PAY_URI" fi echo "OK" # # CREATE ORDER WITH NON-INVENTORY AND CHECK # echo -n "Creating order with non-inventory products..." STATUS=$(curl 'http://localhost:9966/instances/default/private/orders' \ -d '{"order":{"amount":"TESTKUDOS:7","summary":"3","products":[{"description":"desct","image":"'"$RANDOM_IMG"'","price":"TESTKUDOS:1","taxes":[],"unit":"u","quantity":1}]}}' \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "200" ] then cat "$LAST_RESPONSE" >&2 exit_fail "Expected 200, order created. got: $STATUS" fi ORDER_ID=$(jq -r .order_id < "$LAST_RESPONSE") TOKEN=$(jq -r .token < "$LAST_RESPONSE") STATUS=$(curl http://localhost:9966/orders/"$ORDER_ID"/claim \ -d '{"nonce":"","token":"'"$TOKEN"'"}' \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "200" ] then cat "$LAST_RESPONSE" >&2 exit_fail "Expected 200, order claimed. got: $STATUS" fi QUANTITY=$(jq -r .contract_terms.products[0].quantity < "$LAST_RESPONSE") if [ "$QUANTITY" != "1" ] then exit_fail "Expected quantity 1. got: $QUANTITY" fi IMAGE=$(jq -r .contract_terms.products[0].image < "$LAST_RESPONSE") if [ "$IMAGE" != "$RANDOM_IMG" ] then exit_fail "Expected $RANDOM_IMG but got something else: $IMAGE" fi echo "OK" # # CREATE INVENTORY PRODUCT AND CLAIM IT # echo -n "Creating product..." STATUS=$(curl 'http://localhost:9966/instances/default/private/products' \ -d '{"product_id":"2","description":"product with id 2 and price :15","price":"TESTKUDOS:15","total_stock":2,"description_i18n":{},"unit":"","image":"'$RANDOM_IMG'","taxes":[],"address":{},"next_restock":{"t_s":"never"}}' \ -w "%{http_code}" -s -o /dev/null) if [ "$STATUS" != "204" ] then exit_fail "Expected 204, product created. got: $STATUS" fi echo "OK" echo -n "Creating order with inventory products..." STATUS=$(curl 'http://localhost:9966/instances/default/private/orders' \ -d '{"order":{"amount":"TESTKUDOS:7","summary":"3"},"inventory_products":[{"product_id":"2","quantity":1}]}' \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "200" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected 200 OK, order created response. Got: $STATUS" fi ORDER_ID=$(jq -e -r .order_id < "$LAST_RESPONSE") TOKEN=$(jq -e -r .token < "$LAST_RESPONSE") STATUS=$(curl http://localhost:9966/orders/"$ORDER_ID"/claim \ -d '{"nonce":"","token":"'"$TOKEN"'"}' \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "200" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected 200, order claimed. got: $STATUS" fi QUANTITY=$(jq -r .contract_terms.products[0].quantity < "$LAST_RESPONSE") if [ "$QUANTITY" != "1" ] then exit_fail "Expected quantity 1. got: $QUANTITY" fi echo "OK" # # CREATE INVALID ORDER # STATUS=$(curl 'http://localhost:9966/instances/default/private/products' \ -d '{"product_id":"1","description":"product with id 1 and price :15","price":"USD:15","total_stock":1,"description_i18n":{},"unit":"","image":"","taxes":[],"address":{},"next_restock":{"t_s":"never"}}' \ -w "%{http_code}" -s -o /dev/null) if [ "$STATUS" != "400" ] then exit_fail "Expected 400 bad request, product price is in another currency. got: $STATUS" fi # # CREATE ORDER AND SELL IT # echo -n "Creating order to be paid..." STATUS=$(curl 'http://localhost:9966/instances/default/private/orders' \ -d '{"order":{"amount":"TESTKUDOS:1","summary":"payme"},"inventory_products":[{"product_id":"2","quantity":1}]}' \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "200" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected 200, order created. got: $STATUS" fi ORDER_ID=$(jq -e -r .order_id < "$LAST_RESPONSE") TOKEN=$(jq -e -r .token < "$LAST_RESPONSE") STATUS=$(curl "http://localhost:9966/instances/default/private/orders/${ORDER_ID}" \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "200" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected 200, getting order info before claming it. got: $STATUS" fi PAY_URL=$(jq -e -r .taler_pay_uri < "$LAST_RESPONSE") echo "OK" NOW=$(date +%s) echo -n "Pay first order ${PAY_URL} ..." taler-wallet-cli --no-throttle --wallet-db="$WALLET_DB" handle-uri "${PAY_URL}" -y 2> wallet-pay1.err > wallet-pay1.log taler-wallet-cli --no-throttle --wallet-db="$WALLET_DB" run-until-done 2> wallet-finish-pay1.err > wallet-finish-pay1.log NOW2=$(date +%s) echo " OK (took $(( NOW2 - NOW )) secs )" STATUS=$(curl "http://localhost:9966/instances/default/private/orders/${ORDER_ID}" \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "200" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected 200, after pay. got: $STATUS" fi ORDER_STATUS=$(jq -r .order_status < "$LAST_RESPONSE") if [ "$ORDER_STATUS" != "paid" ] then jq . < "$LAST_RESPONSE" exit_fail "Order status should be 'paid'. got: $ORDER_STATUS" fi # # WIRE TRANSFER TO MERCHANT AND NOTIFY BACKEND # # PAY_DEADLINE=$(jq -r .contract_terms.pay_deadline.t_s < "$LAST_RESPONSE") WIRE_DEADLINE=$(jq -r .contract_terms.wire_transfer_deadline.t_s < "$LAST_RESPONSE") NOW=$(date +%s) TO_SLEEP=$(( WIRE_DEADLINE - NOW )) echo "Waiting $TO_SLEEP secs for wire transfer" echo -n "Perform wire transfers ..." taler-exchange-aggregator -y -c "$CONF" -T "${TO_SLEEP}"000000 -t -L INFO &> aggregator.log taler-exchange-transfer -c "$CONF" -t -L INFO &> transfer.log echo " DONE" echo -n "Give time to Nexus to route the payment to Sandbox..." # FIXME-MS: trigger immediate update at nexus # NOTE: once libeufin can do long-polling, we should # be able to reduce the delay here and run aggregator/transfer # always in the background via setup sleep 3 echo " DONE" echo -n "Obtaining wire transfer details from bank..." if [ 1 = "$USE_FAKEBANK" ] then BANKDATA="$(curl 'http://localhost:8082/exchange/history/outgoing?delta=1' -s)" WTID=$(echo "$BANKDATA" | jq -r .outgoing_transactions[0].wtid) WURL=$(echo "$BANKDATA" | jq -r .outgoing_transactions[0].exchange_base_url) CREDIT_AMOUNT=$(echo "$BANKDATA" | jq -r .outgoing_transactions[0].amount) TARGET_PAYTO=$(echo "$BANKDATA" | jq -r .outgoing_transactions[0].credit_account) else # Emulating the previous pybank-based logic of getting # the wire transfer information _via the exchange_ bank # account. NOTE: grabbing tx == 0, since the latest # transaction appear first in the bank's history. BANKDATA=$(get_bankaccount_transactions exchange x | jq '.transactions[0]') SUBJECT=$(echo "$BANKDATA" | jq -r .subject) WTID=$(echo "$SUBJECT" | awk '{print $1}') WURL=$(echo "$SUBJECT" | awk '{print $2}') CREDIT_AMOUNT="$(echo "$BANKDATA" | jq -r .currency):$(echo "$BANKDATA" | jq -r .amount)" TARGET=$(echo "$BANKDATA" | jq -r .creditorIban) # 'TARGET' is now the IBAN. TARGET_PAYTO="payto://iban/SANDBOXX/$TARGET?receiver-name=Forty+Three" fi if [ "$EXCHANGE_URL" != "$WURL" ] then exit_fail "Wrong exchange URL in subject '$SUBJECT', expected '$EXCHANGE_URL'" fi echo " OK" set +e echo -n "Notifying merchant of bogus wire transfer ..." STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \ -d '{"credit_amount":"'"$CREDIT_AMOUNT"'1","wtid":"'"$WTID"'","payto_uri":"'"$TARGET_PAYTO"'","exchange_url":"'"$WURL"'"}' \ -m 3 \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "204" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected to fail since the amount is not valid. got: $STATUS" fi echo "OK" echo -n "Notifying merchant of correct wire transfer (conflicting with old data)..." STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \ -d '{"credit_amount":"'"$CREDIT_AMOUNT"'","wtid":"'"$WTID"'","payto_uri":"'"$TARGET_PAYTO"'","exchange_url":"'"$WURL"'"}' \ -m 3 \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "409" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected response conflict, after providing conflicting transfer data. got: $STATUS" fi echo " OK" echo -n "Deleting bogus wire transfer ..." TID=$(curl -s http://localhost:9966/instances/default/private/transfers | jq -r .transfers[0].transfer_serial_id) STATUS=$(curl -H "Content-Type: application/json" -X DELETE \ "http://localhost:9966/instances/default/private/transfers/$TID" \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "204" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected response 204 No Content, after deleting valid TID. got: $STATUS" fi STATUS=$(curl -H "Content-Type: application/json" -X DELETE \ "http://localhost:9966/instances/default/private/transfers/$TID" \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "404" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected response 404 Not found, after deleting TID again. got: $STATUS" fi echo " OK" echo -n "Notifying merchant of correct wire transfer (now working)..." STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \ -d '{"credit_amount":"'"$CREDIT_AMOUNT"'","wtid":"'"$WTID"'","payto_uri":"'"$TARGET_PAYTO"'","exchange_url":"'"$WURL"'"}' \ -m 3 \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "204" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected response 204 No content, after providing transfer data. got: $STATUS" fi echo " OK" echo -n "Testing idempotence ..." set -e # Test idempotence: do it again! STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \ -d '{"credit_amount":"'"$CREDIT_AMOUNT"'","wtid":"'"$WTID"'","payto_uri":"'"$TARGET_PAYTO"'","exchange_url":"'"$WURL"'"}' \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "204" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected response No Content, after providing transfer data. got: $STATUS" fi echo " OK" echo -n "Testing taler-merchant-exchange ..." set -e taler-merchant-exchange -L INFO -c "$CONF" -t &> taler-merchant-exchange.log echo " OK" echo -n "Fetching wire transfers ..." STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "200" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected response 200 Ok. got: $STATUS" fi TRANSFERS_LIST_SIZE=$(jq -r '.transfers | length' < "$LAST_RESPONSE") if [ "$TRANSFERS_LIST_SIZE" != "1" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected 1 entry in transfer list. Got: $TRANSFERS_LIST_SIZE" fi echo "OK" echo -n "Checking order status ..." STATUS=$(curl "http://localhost:9966/instances/default/private/orders/${ORDER_ID}?transfer=YES" \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "200" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected 200, after order inquiry. got: $STATUS" fi DEPOSIT_TOTAL=$(jq -r .deposit_total < "$LAST_RESPONSE") if [ "$DEPOSIT_TOTAL" == "TESTKUDOS:0" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected non-zero deposit total. got: $DEPOSIT_TOTAL" fi echo " OK" echo -n "Checking bank account status ..." if [ 1 = "$USE_FAKEBANK" ] then STATUS=$(curl "http://localhost:8082/taler-bank-access/accounts/fortythree" \ -w "%{http_code}" \ -s \ -o "$LAST_RESPONSE") if [ "$STATUS" != "200" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected response 200 Ok, getting account status. Got: $STATUS" fi BALANCE=$(jq -r .balance.amount < "$LAST_RESPONSE") if [ "$BALANCE" == "TESTKUDOS:0" ] then jq . < "$LAST_RESPONSE" exit_fail "Wire transfer did not happen. Got: $BALANCE" fi else ACCOUNT_PASSWORD="fortythree:x" BANK_HOST="localhost:18082" # Can be replaced by the libeufin-cli way. STATUS=$(curl "http://$ACCOUNT_PASSWORD@$BANK_HOST/demobanks/default/access-api/accounts/fortythree" \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "200" ] then jq . < "$LAST_RESPONSE" exit_fail "Expected response 200 Ok, getting account status. Got: $STATUS" fi BALANCE=$(jq -r .balance.amount < "$LAST_RESPONSE") if [ "$BALANCE" == "TESTKUDOS:0" ] then jq . < "$LAST_RESPONSE" exit_fail "Wire transfer did not happen. Got: $BALANCE" fi fi echo " OK" echo -n "Getting information about kyc ..." STATUS=$(curl -H "Content-Type: application/json" -X GET \ http://localhost:9966/instances/default/private/kyc \ -w "%{http_code}" -s -o /dev/null) if [ "$STATUS" != "204" ] then exit_fail "Expected 204. Got: $STATUS" fi echo " OK" exit 0