summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.gitmodules1
-rw-r--r--ChangeLog3
-rwxr-xr-xbootstrap19
-rw-r--r--configure.ac279
-rw-r--r--contrib/.gitignore2
-rw-r--r--contrib/Makefile.am39
-rw-r--r--contrib/Makefile.am.in36
-rw-r--r--contrib/ci/Containerfile76
-rwxr-xr-xcontrib/ci/ci.sh34
-rw-r--r--contrib/ci/jobs/0-codespell/config.ini6
-rw-r--r--contrib/ci/jobs/0-codespell/dictionary.txt46
-rwxr-xr-xcontrib/ci/jobs/0-codespell/job.sh101
-rwxr-xr-xcontrib/ci/jobs/1-build/build.sh10
-rwxr-xr-xcontrib/ci/jobs/1-build/job.sh9
-rw-r--r--contrib/ci/jobs/2-test/config.ini6
-rwxr-xr-xcontrib/ci/jobs/2-test/job.sh6
-rwxr-xr-xcontrib/ci/jobs/2-test/test.sh40
-rw-r--r--contrib/ci/jobs/3-docs/config.ini6
-rwxr-xr-xcontrib/ci/jobs/3-docs/docs.sh11
-rwxr-xr-xcontrib/ci/jobs/3-docs/job.sh6
-rwxr-xr-xcontrib/ci/jobs/4-deb-package/job.sh23
-rwxr-xr-xcontrib/ci/jobs/4-deb-package/version.sh17
-rw-r--r--contrib/ci/jobs/5-deploy-package/config.ini6
-rwxr-xr-xcontrib/ci/jobs/5-deploy-package/job.sh14
-rw-r--r--contrib/taler-exchange.tag16
-rwxr-xr-xcontrib/taler-merchant-dbconfig148
-rw-r--r--contrib/uncrustify.cfg4
m---------contrib/wallet-core0
-rw-r--r--debian/.gitignore1
-rw-r--r--debian/changelog98
-rw-r--r--debian/control20
-rwxr-xr-xdebian/db/install/pgsql38
-rw-r--r--debian/etc/nginx/sites-available/taler-merchant6
-rw-r--r--debian/etc/taler/secrets/merchant-db.secret.conf2
-rw-r--r--debian/libtalermerchant-dev.install3
-rw-r--r--debian/libtalermerchant.install1
-rwxr-xr-xdebian/rules5
-rw-r--r--debian/taler-merchant.README.Debian2
-rw-r--r--debian/taler-merchant.config22
-rw-r--r--debian/taler-merchant.install2
-rw-r--r--debian/taler-merchant.postinst22
-rw-r--r--debian/taler-merchant.postrm29
-rw-r--r--debian/taler-merchant.preinst29
-rw-r--r--debian/taler-merchant.prerm12
-rw-r--r--debian/taler-merchant.taler-merchant-depositcheck.service17
-rw-r--r--debian/taler-merchant.taler-merchant-exchange.service17
-rw-r--r--debian/taler-merchant.taler-merchant-httpd.service9
-rw-r--r--debian/taler-merchant.taler-merchant-webhook.service17
-rw-r--r--debian/taler-merchant.taler-merchant-wirewatch.service18
-rw-r--r--debian/taler-merchant.taler-merchant.slice7
-rw-r--r--debian/taler-merchant.taler-merchant.target12
-rw-r--r--doc/Makefile.am34
-rw-r--r--doc/doxygen/taler.doxy5
m---------doc/prebuilt0
-rw-r--r--m4/libgnurl.m4250
-rw-r--r--src/Makefile.am2
-rw-r--r--src/backend/.gitignore4
-rw-r--r--src/backend/Makefile.am153
-rw-r--r--src/backend/kudos.conf13
-rw-r--r--src/backend/merchant.conf4
-rw-r--r--src/backend/taler-merchant-depositcheck.c1071
-rw-r--r--src/backend/taler-merchant-exchange.c1255
-rw-r--r--src/backend/taler-merchant-httpd.c739
-rw-r--r--src/backend/taler-merchant-httpd.h120
-rw-r--r--src/backend/taler-merchant-httpd_auditors.c265
-rw-r--r--src/backend/taler-merchant-httpd_auditors.h76
-rw-r--r--src/backend/taler-merchant-httpd_config.c59
-rw-r--r--src/backend/taler-merchant-httpd_exchanges.c2090
-rw-r--r--src/backend/taler-merchant-httpd_exchanges.h154
-rw-r--r--src/backend/taler-merchant-httpd_get-orders-ID.c1951
-rw-r--r--src/backend/taler-merchant-httpd_get-orders-ID.h39
-rw-r--r--src/backend/taler-merchant-httpd_get-templates-ID.c78
-rw-r--r--src/backend/taler-merchant-httpd_get-templates-ID.h (renamed from src/backend/taler-merchant-httpd_private-get-tips.h)27
-rw-r--r--src/backend/taler-merchant-httpd_get-tips-ID.c292
-rw-r--r--src/backend/taler-merchant-httpd_get-tips-ID.h67
-rw-r--r--src/backend/taler-merchant-httpd_helper.c664
-rw-r--r--src/backend/taler-merchant-httpd_helper.h132
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-abort.c116
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-claim.c2
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-paid.c47
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-pay.c2434
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-refund.c80
-rw-r--r--src/backend/taler-merchant-httpd_post-tips-ID-pickup.c1001
-rw-r--r--src/backend/taler-merchant-httpd_post-tips-ID-pickup.h49
-rw-r--r--src/backend/taler-merchant-httpd_post-using-templates.c322
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-account-ID.c94
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-account-ID.h42
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-instances-ID-token.c110
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-instances-ID-token.h45
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-instances-ID.c12
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-orders-ID.c19
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.c (renamed from src/backend/taler-merchant-httpd_private-delete-reserves-ID.c)57
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.h41
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-templates-ID.c8
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-templates-ID.h4
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.c75
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.h41
-rw-r--r--src/backend/taler-merchant-httpd_private-get-accounts-ID.c103
-rw-r--r--src/backend/taler-merchant-httpd_private-get-accounts-ID.h (renamed from src/backend/taler-merchant-httpd_private-get-tips-ID.h)24
-rw-r--r--src/backend/taler-merchant-httpd_private-get-accounts.c78
-rw-r--r--src/backend/taler-merchant-httpd_private-get-accounts.h (renamed from src/backend/taler-merchant-httpd_private-get-reserves.h)18
-rw-r--r--src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c271
-rw-r--r--src/backend/taler-merchant-httpd_private-get-instances-ID.c17
-rw-r--r--src/backend/taler-merchant-httpd_private-get-instances.c5
-rw-r--r--src/backend/taler-merchant-httpd_private-get-orders-ID.c1991
-rw-r--r--src/backend/taler-merchant-httpd_private-get-orders.c270
-rw-r--r--src/backend/taler-merchant-httpd_private-get-otp-devices-ID.c110
-rw-r--r--src/backend/taler-merchant-httpd_private-get-otp-devices-ID.h (renamed from src/backend/taler-merchant-httpd_private-delete-reserves-ID.h)16
-rw-r--r--src/backend/taler-merchant-httpd_private-get-otp-devices.c80
-rw-r--r--src/backend/taler-merchant-httpd_private-get-otp-devices.h (renamed from src/backend/taler-merchant-httpd_private-get-reserves-ID.h)22
-rw-r--r--src/backend/taler-merchant-httpd_private-get-products.c20
-rw-r--r--src/backend/taler-merchant-httpd_private-get-reserves-ID.c228
-rw-r--r--src/backend/taler-merchant-httpd_private-get-reserves.c146
-rw-r--r--src/backend/taler-merchant-httpd_private-get-templates-ID.c53
-rw-r--r--src/backend/taler-merchant-httpd_private-get-templates-ID.h7
-rw-r--r--src/backend/taler-merchant-httpd_private-get-templates.c3
-rw-r--r--src/backend/taler-merchant-httpd_private-get-templates.h4
-rw-r--r--src/backend/taler-merchant-httpd_private-get-tips-ID.c157
-rw-r--r--src/backend/taler-merchant-httpd_private-get-tips.c157
-rw-r--r--src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c105
-rw-r--r--src/backend/taler-merchant-httpd_private-get-token-families-SLUG.h41
-rw-r--r--src/backend/taler-merchant-httpd_private-get-token-families.c88
-rw-r--r--src/backend/taler-merchant-httpd_private-get-token-families.h41
-rw-r--r--src/backend/taler-merchant-httpd_private-get-transfers.c63
-rw-r--r--src/backend/taler-merchant-httpd_private-get-webhooks-ID.c10
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-accounts-ID.c132
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-accounts-ID.h43
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-instances-ID.c225
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-orders-ID-forget.c24
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-otp-devices-ID.c114
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-otp-devices-ID.h44
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-products-ID.c5
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-products-ID.h2
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-templates-ID.c85
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-templates-ID.h4
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c158
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.h43
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-webhooks-ID.c15
-rw-r--r--src/backend/taler-merchant-httpd_private-post-account.c238
-rw-r--r--src/backend/taler-merchant-httpd_private-post-account.h44
-rw-r--r--src/backend/taler-merchant-httpd_private-post-instances-ID-auth.c1
-rw-r--r--src/backend/taler-merchant-httpd_private-post-instances-ID-token.c149
-rw-r--r--src/backend/taler-merchant-httpd_private-post-instances-ID-token.h45
-rw-r--r--src/backend/taler-merchant-httpd_private-post-instances.c311
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c220
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders.c2687
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders.h8
-rw-r--r--src/backend/taler-merchant-httpd_private-post-otp-devices.c199
-rw-r--r--src/backend/taler-merchant-httpd_private-post-otp-devices.h (renamed from src/backend/taler-merchant-httpd_private-post-reserves.h)26
-rw-r--r--src/backend/taler-merchant-httpd_private-post-products.c5
-rw-r--r--src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c186
-rw-r--r--src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h57
-rw-r--r--src/backend/taler-merchant-httpd_private-post-reserves.c403
-rw-r--r--src/backend/taler-merchant-httpd_private-post-templates.c231
-rw-r--r--src/backend/taler-merchant-httpd_private-post-templates.h4
-rw-r--r--src/backend/taler-merchant-httpd_private-post-token-families.c244
-rw-r--r--src/backend/taler-merchant-httpd_private-post-token-families.h43
-rw-r--r--src/backend/taler-merchant-httpd_private-post-transfers.c1368
-rw-r--r--src/backend/taler-merchant-httpd_private-post-transfers.h9
-rw-r--r--src/backend/taler-merchant-httpd_private-post-webhooks.c31
-rw-r--r--src/backend/taler-merchant-httpd_reserves.c356
-rw-r--r--src/backend/taler-merchant-httpd_reserves.h61
-rw-r--r--src/backend/taler-merchant-httpd_spa.c20
-rwxr-xr-xsrc/backend/taler-merchant-webhook210
-rw-r--r--src/backend/taler-merchant-webhook.c276
-rw-r--r--src/backend/taler-merchant-wirewatch.c756
-rw-r--r--src/backend/test.conf2
-rw-r--r--src/backenddb/.gitignore1
-rw-r--r--src/backenddb/Makefile.am129
-rw-r--r--src/backenddb/drop.sql18
-rw-r--r--src/backenddb/merchant-0001.sql516
-rw-r--r--src/backenddb/merchant-0002.sql157
-rw-r--r--src/backenddb/merchant-0003.sql28
-rw-r--r--src/backenddb/merchant-0004.sql93
-rw-r--r--src/backenddb/merchant-0005.sql36
-rw-r--r--src/backenddb/merchantdb-postgres.conf2
-rw-r--r--src/backenddb/merchantdb_helper.c16
-rw-r--r--src/backenddb/pg_account_kyc_get_status.c195
-rw-r--r--src/backenddb/pg_account_kyc_get_status.h49
-rw-r--r--src/backenddb/pg_account_kyc_set_status.c88
-rw-r--r--src/backenddb/pg_account_kyc_set_status.h57
-rw-r--r--src/backenddb/pg_activate_account.c53
-rw-r--r--src/backenddb/pg_activate_account.h41
-rw-r--r--src/backenddb/pg_check_transfer_exists.c63
-rw-r--r--src/backenddb/pg_check_transfer_exists.h43
-rw-r--r--src/backenddb/pg_delete_contract_terms.c59
-rw-r--r--src/backenddb/pg_delete_contract_terms.h47
-rw-r--r--src/backenddb/pg_delete_exchange_accounts.c48
-rw-r--r--src/backenddb/pg_delete_exchange_accounts.h42
-rw-r--r--src/backenddb/pg_delete_instance_private_key.c50
-rw-r--r--src/backenddb/pg_delete_instance_private_key.h39
-rw-r--r--src/backenddb/pg_delete_login_token.c55
-rw-r--r--src/backenddb/pg_delete_login_token.h44
-rw-r--r--src/backenddb/pg_delete_order.c93
-rw-r--r--src/backenddb/pg_delete_order.h45
-rw-r--r--src/backenddb/pg_delete_otp.c54
-rw-r--r--src/backenddb/pg_delete_otp.h43
-rw-r--r--src/backenddb/pg_delete_pending_webhook.c48
-rw-r--r--src/backenddb/pg_delete_pending_webhook.h40
-rw-r--r--src/backenddb/pg_delete_product.c57
-rw-r--r--src/backenddb/pg_delete_product.h43
-rw-r--r--src/backenddb/pg_delete_template.c55
-rw-r--r--src/backenddb/pg_delete_template.h44
-rw-r--r--src/backenddb/pg_delete_token_family.c53
-rw-r--r--src/backenddb/pg_delete_token_family.h41
-rw-r--r--src/backenddb/pg_delete_transfer.c57
-rw-r--r--src/backenddb/pg_delete_transfer.h43
-rw-r--r--src/backenddb/pg_delete_webhook.c54
-rw-r--r--src/backenddb/pg_delete_webhook.h42
-rw-r--r--src/backenddb/pg_expire_locks.c86
-rw-r--r--src/backenddb/pg_expire_locks.h39
-rw-r--r--src/backenddb/pg_helper.c138
-rw-r--r--src/backenddb/pg_helper.h158
-rw-r--r--src/backenddb/pg_inactivate_account.c53
-rw-r--r--src/backenddb/pg_inactivate_account.h42
-rw-r--r--src/backenddb/pg_increase_refund.c507
-rw-r--r--src/backenddb/pg_increase_refund.h56
-rw-r--r--src/backenddb/pg_insert_account.c68
-rw-r--r--src/backenddb/pg_insert_account.h43
-rw-r--r--src/backenddb/pg_insert_contract_terms.c132
-rw-r--r--src/backenddb/pg_insert_contract_terms.h52
-rw-r--r--src/backenddb/pg_insert_deposit.c76
-rw-r--r--src/backenddb/pg_insert_deposit.h52
-rw-r--r--src/backenddb/pg_insert_deposit_confirmation.c134
-rw-r--r--src/backenddb/pg_insert_deposit_confirmation.h60
-rw-r--r--src/backenddb/pg_insert_deposit_to_transfer.c83
-rw-r--r--src/backenddb/pg_insert_deposit_to_transfer.h44
-rw-r--r--src/backenddb/pg_insert_deposit_to_transfer.sql160
-rw-r--r--src/backenddb/pg_insert_exchange_account.c66
-rw-r--r--src/backenddb/pg_insert_exchange_account.h51
-rw-r--r--src/backenddb/pg_insert_exchange_keys.c67
-rw-r--r--src/backenddb/pg_insert_exchange_keys.h40
-rw-r--r--src/backenddb/pg_insert_exchange_signkey.c66
-rw-r--r--src/backenddb/pg_insert_exchange_signkey.h50
-rw-r--r--src/backenddb/pg_insert_instance.c106
-rw-r--r--src/backenddb/pg_insert_instance.h46
-rw-r--r--src/backenddb/pg_insert_login_token.c64
-rw-r--r--src/backenddb/pg_insert_login_token.h50
-rw-r--r--src/backenddb/pg_insert_order.c95
-rw-r--r--src/backenddb/pg_insert_order.h56
-rw-r--r--src/backenddb/pg_insert_order_lock.c78
-rw-r--r--src/backenddb/pg_insert_order_lock.h47
-rw-r--r--src/backenddb/pg_insert_otp.c74
-rw-r--r--src/backenddb/pg_insert_otp.h45
-rw-r--r--src/backenddb/pg_insert_pending_webhook.c70
-rw-r--r--src/backenddb/pg_insert_pending_webhook.h49
-rw-r--r--src/backenddb/pg_insert_product.c77
-rw-r--r--src/backenddb/pg_insert_product.h43
-rw-r--r--src/backenddb/pg_insert_refund_proof.c58
-rw-r--r--src/backenddb/pg_insert_refund_proof.h43
-rw-r--r--src/backenddb/pg_insert_template.c73
-rw-r--r--src/backenddb/pg_insert_template.h46
-rw-r--r--src/backenddb/pg_insert_token_family.c82
-rw-r--r--src/backenddb/pg_insert_token_family.h43
-rw-r--r--src/backenddb/pg_insert_transfer.c73
-rw-r--r--src/backenddb/pg_insert_transfer.h52
-rw-r--r--src/backenddb/pg_insert_transfer_details.c171
-rw-r--r--src/backenddb/pg_insert_transfer_details.h51
-rw-r--r--src/backenddb/pg_insert_transfer_details.sql229
-rw-r--r--src/backenddb/pg_insert_webhook.c70
-rw-r--r--src/backenddb/pg_insert_webhook.h43
-rw-r--r--src/backenddb/pg_lock_product.c76
-rw-r--r--src/backenddb/pg_lock_product.h49
-rw-r--r--src/backenddb/pg_lookup_account.c62
-rw-r--r--src/backenddb/pg_lookup_account.h43
-rw-r--r--src/backenddb/pg_lookup_contract_terms.c80
-rw-r--r--src/backenddb/pg_lookup_contract_terms.h48
-rw-r--r--src/backenddb/pg_lookup_contract_terms2.c96
-rw-r--r--src/backenddb/pg_lookup_contract_terms2.h54
-rw-r--r--src/backenddb/pg_lookup_contract_terms3.c99
-rw-r--r--src/backenddb/pg_lookup_contract_terms3.h56
-rw-r--r--src/backenddb/pg_lookup_deposits.c165
-rw-r--r--src/backenddb/pg_lookup_deposits.h47
-rw-r--r--src/backenddb/pg_lookup_deposits_by_contract_and_coin.c192
-rw-r--r--src/backenddb/pg_lookup_deposits_by_contract_and_coin.h48
-rw-r--r--src/backenddb/pg_lookup_deposits_by_order.c161
-rw-r--r--src/backenddb/pg_lookup_deposits_by_order.h43
-rw-r--r--src/backenddb/pg_lookup_instance_auth.c59
-rw-r--r--src/backenddb/pg_lookup_instance_auth.h40
-rw-r--r--src/backenddb/pg_lookup_instances.c343
-rw-r--r--src/backenddb/pg_lookup_instances.h60
-rw-r--r--src/backenddb/pg_lookup_order.c96
-rw-r--r--src/backenddb/pg_lookup_order.h49
-rw-r--r--src/backenddb/pg_lookup_order_by_fulfillment.c80
-rw-r--r--src/backenddb/pg_lookup_order_by_fulfillment.h49
-rw-r--r--src/backenddb/pg_lookup_order_status.c73
-rw-r--r--src/backenddb/pg_lookup_order_status.h45
-rw-r--r--src/backenddb/pg_lookup_order_status_by_serial.c77
-rw-r--r--src/backenddb/pg_lookup_order_status_by_serial.h48
-rw-r--r--src/backenddb/pg_lookup_order_summary.c75
-rw-r--r--src/backenddb/pg_lookup_order_summary.h45
-rw-r--r--src/backenddb/pg_lookup_orders.c280
-rw-r--r--src/backenddb/pg_lookup_orders.h45
-rw-r--r--src/backenddb/pg_lookup_otp_devices.c133
-rw-r--r--src/backenddb/pg_lookup_otp_devices.h45
-rw-r--r--src/backenddb/pg_lookup_pending_deposits.c201
-rw-r--r--src/backenddb/pg_lookup_pending_deposits.h50
-rw-r--r--src/backenddb/pg_lookup_pending_webhooks.c261
-rw-r--r--src/backenddb/pg_lookup_pending_webhooks.h78
-rw-r--r--src/backenddb/pg_lookup_product.c109
-rw-r--r--src/backenddb/pg_lookup_product.h44
-rw-r--r--src/backenddb/pg_lookup_products.c155
-rw-r--r--src/backenddb/pg_lookup_products.h48
-rw-r--r--src/backenddb/pg_lookup_refund_proof.c63
-rw-r--r--src/backenddb/pg_lookup_refund_proof.h43
-rw-r--r--src/backenddb/pg_lookup_refunds.c148
-rw-r--r--src/backenddb/pg_lookup_refunds.h46
-rw-r--r--src/backenddb/pg_lookup_refunds_detailed.c185
-rw-r--r--src/backenddb/pg_lookup_refunds_detailed.h45
-rw-r--r--src/backenddb/pg_lookup_template.c109
-rw-r--r--src/backenddb/pg_lookup_template.h46
-rw-r--r--src/backenddb/pg_lookup_templates.c133
-rw-r--r--src/backenddb/pg_lookup_templates.h44
-rw-r--r--src/backenddb/pg_lookup_token_families.c150
-rw-r--r--src/backenddb/pg_lookup_token_families.h43
-rw-r--r--src/backenddb/pg_lookup_token_family.c121
-rw-r--r--src/backenddb/pg_lookup_token_family.h44
-rw-r--r--src/backenddb/pg_lookup_transfer.c125
-rw-r--r--src/backenddb/pg_lookup_transfer.h57
-rw-r--r--src/backenddb/pg_lookup_transfer_details.c155
-rw-r--r--src/backenddb/pg_lookup_transfer_details.h45
-rw-r--r--src/backenddb/pg_lookup_transfer_details_by_order.c172
-rw-r--r--src/backenddb/pg_lookup_transfer_details_by_order.h44
-rw-r--r--src/backenddb/pg_lookup_transfer_summary.c152
-rw-r--r--src/backenddb/pg_lookup_transfer_summary.h45
-rw-r--r--src/backenddb/pg_lookup_transfers.c241
-rw-r--r--src/backenddb/pg_lookup_transfers.h60
-rw-r--r--src/backenddb/pg_lookup_webhook.c92
-rw-r--r--src/backenddb/pg_lookup_webhook.h44
-rw-r--r--src/backenddb/pg_lookup_webhook_by_event.c158
-rw-r--r--src/backenddb/pg_lookup_webhook_by_event.h45
-rw-r--r--src/backenddb/pg_lookup_webhooks.c133
-rw-r--r--src/backenddb/pg_lookup_webhooks.h43
-rw-r--r--src/backenddb/pg_lookup_wire_fee.c83
-rw-r--r--src/backenddb/pg_lookup_wire_fee.h52
-rw-r--r--src/backenddb/pg_mark_contract_paid.c112
-rw-r--r--src/backenddb/pg_mark_contract_paid.h46
-rw-r--r--src/backenddb/pg_mark_order_wired.c48
-rw-r--r--src/backenddb/pg_mark_order_wired.h39
-rw-r--r--src/backenddb/pg_purge_instance.c54
-rw-r--r--src/backenddb/pg_purge_instance.h40
-rw-r--r--src/backenddb/pg_refund_coin.c77
-rw-r--r--src/backenddb/pg_refund_coin.h51
-rw-r--r--src/backenddb/pg_select_account.c79
-rw-r--r--src/backenddb/pg_select_account.h44
-rw-r--r--src/backenddb/pg_select_account_by_uri.c82
-rw-r--r--src/backenddb/pg_select_account_by_uri.h44
-rw-r--r--src/backenddb/pg_select_accounts.c159
-rw-r--r--src/backenddb/pg_select_accounts.h44
-rw-r--r--src/backenddb/pg_select_accounts_by_exchange.c146
-rw-r--r--src/backenddb/pg_select_accounts_by_exchange.h45
-rw-r--r--src/backenddb/pg_select_exchange_keys.c68
-rw-r--r--src/backenddb/pg_select_exchange_keys.h43
-rw-r--r--src/backenddb/pg_select_login_token.c67
-rw-r--r--src/backenddb/pg_select_login_token.h48
-rw-r--r--src/backenddb/pg_select_open_transfers.c167
-rw-r--r--src/backenddb/pg_select_open_transfers.h47
-rw-r--r--src/backenddb/pg_select_otp.c91
-rw-r--r--src/backenddb/pg_select_otp.h45
-rw-r--r--src/backenddb/pg_select_otp_serial.c61
-rw-r--r--src/backenddb/pg_select_otp_serial.h43
-rw-r--r--src/backenddb/pg_select_wirewatch_accounts.c147
-rw-r--r--src/backenddb/pg_select_wirewatch_accounts.h44
-rw-r--r--src/backenddb/pg_set_transfer_status_to_confirmed.c66
-rw-r--r--src/backenddb/pg_set_transfer_status_to_confirmed.h48
-rw-r--r--src/backenddb/pg_store_wire_fee_by_exchange.c76
-rw-r--r--src/backenddb/pg_store_wire_fee_by_exchange.h52
-rw-r--r--src/backenddb/pg_template.c26
-rw-r--r--src/backenddb/pg_template.h29
-rwxr-xr-xsrc/backenddb/pg_template.sh21
-rw-r--r--src/backenddb/pg_unlock_inventory.c47
-rw-r--r--src/backenddb/pg_unlock_inventory.h42
-rw-r--r--src/backenddb/pg_update_account.c64
-rw-r--r--src/backenddb/pg_update_account.h48
-rw-r--r--src/backenddb/pg_update_contract_terms.c103
-rw-r--r--src/backenddb/pg_update_contract_terms.h49
-rw-r--r--src/backenddb/pg_update_deposit_confirmation_status.c66
-rw-r--r--src/backenddb/pg_update_deposit_confirmation_status.h51
-rw-r--r--src/backenddb/pg_update_instance.c77
-rw-r--r--src/backenddb/pg_update_instance.h39
-rw-r--r--src/backenddb/pg_update_instance_auth.c52
-rw-r--r--src/backenddb/pg_update_instance_auth.h43
-rw-r--r--src/backenddb/pg_update_otp.c78
-rw-r--r--src/backenddb/pg_update_otp.h47
-rw-r--r--src/backenddb/pg_update_pending_webhook.c51
-rw-r--r--src/backenddb/pg_update_pending_webhook.h41
-rw-r--r--src/backenddb/pg_update_product.c86
-rw-r--r--src/backenddb/pg_update_product.h53
-rw-r--r--src/backenddb/pg_update_template.c80
-rw-r--r--src/backenddb/pg_update_template.h46
-rw-r--r--src/backenddb/pg_update_token_family.c66
-rw-r--r--src/backenddb/pg_update_token_family.h44
-rw-r--r--src/backenddb/pg_update_transfer_status.c65
-rw-r--r--src/backenddb/pg_update_transfer_status.h51
-rw-r--r--src/backenddb/pg_update_webhook.c68
-rw-r--r--src/backenddb/pg_update_webhook.h45
-rw-r--r--src/backenddb/pg_update_wirewatch_progress.c58
-rw-r--r--src/backenddb/pg_update_wirewatch_progress.h46
-rw-r--r--src/backenddb/plugin_merchantdb_postgres.c10780
-rw-r--r--src/backenddb/procedures.sql.in24
-rw-r--r--src/backenddb/test-merchantdb-postgres.conf2
-rw-r--r--src/backenddb/test.conf172
-rw-r--r--src/backenddb/test_merchantdb.c1992
-rw-r--r--src/backenddb/versioning.sql5
-rw-r--r--src/bank/Makefile.am28
-rw-r--r--src/bank/mb_common.c63
-rw-r--r--src/bank/mb_common.h45
-rw-r--r--src/bank/mb_credit.c340
-rw-r--r--src/bank/mb_parse.c218
-rw-r--r--src/include/Makefile.am1
-rw-r--r--src/include/platform.h30
-rw-r--r--src/include/taler_merchant_bank_lib.h247
-rw-r--r--src/include/taler_merchant_service.h2845
-rw-r--r--src/include/taler_merchant_testing_lib.h990
-rw-r--r--src/include/taler_merchantdb_lib.h10
-rw-r--r--src/include/taler_merchantdb_plugin.h1485
-rw-r--r--src/lib/Makefile.am26
-rw-r--r--src/lib/merchant_api_common.c2
-rw-r--r--src/lib/merchant_api_curl_defaults.c15
-rw-r--r--src/lib/merchant_api_delete_account.c185
-rw-r--r--src/lib/merchant_api_delete_otp_device.c184
-rw-r--r--src/lib/merchant_api_delete_reserve.c239
-rw-r--r--src/lib/merchant_api_delete_template.c4
-rw-r--r--src/lib/merchant_api_get_account.c211
-rw-r--r--src/lib/merchant_api_get_accounts.c247
-rw-r--r--src/lib/merchant_api_get_config.c185
-rw-r--r--src/lib/merchant_api_get_instance.c176
-rw-r--r--src/lib/merchant_api_get_instances.c181
-rw-r--r--src/lib/merchant_api_get_kyc.c188
-rw-r--r--src/lib/merchant_api_get_orders.c233
-rw-r--r--src/lib/merchant_api_get_otp_device.c210
-rw-r--r--src/lib/merchant_api_get_otp_devices.c248
-rw-r--r--src/lib/merchant_api_get_product.c147
-rw-r--r--src/lib/merchant_api_get_products.c153
-rw-r--r--src/lib/merchant_api_get_reserve.c318
-rw-r--r--src/lib/merchant_api_get_reserves.c282
-rw-r--r--src/lib/merchant_api_get_template.c62
-rw-r--r--src/lib/merchant_api_get_templates.c130
-rw-r--r--src/lib/merchant_api_get_tips.c307
-rw-r--r--src/lib/merchant_api_get_transfers.c72
-rw-r--r--src/lib/merchant_api_get_webhook.c2
-rw-r--r--src/lib/merchant_api_get_webhooks.c129
-rw-r--r--src/lib/merchant_api_merchant_get_order.c330
-rw-r--r--src/lib/merchant_api_merchant_get_tip.c302
-rw-r--r--src/lib/merchant_api_patch_account.c254
-rw-r--r--src/lib/merchant_api_patch_instance.c37
-rw-r--r--src/lib/merchant_api_patch_order_forget.c21
-rw-r--r--src/lib/merchant_api_patch_otp_device.c252
-rw-r--r--src/lib/merchant_api_patch_template.c6
-rw-r--r--src/lib/merchant_api_post_account.c250
-rw-r--r--src/lib/merchant_api_post_instances.c49
-rw-r--r--src/lib/merchant_api_post_order_abort.c120
-rw-r--r--src/lib/merchant_api_post_order_claim.c64
-rw-r--r--src/lib/merchant_api_post_order_paid.c62
-rw-r--r--src/lib/merchant_api_post_order_pay.c314
-rw-r--r--src/lib/merchant_api_post_order_refund.c65
-rw-r--r--src/lib/merchant_api_post_orders.c48
-rw-r--r--src/lib/merchant_api_post_otp_devices.c237
-rw-r--r--src/lib/merchant_api_post_products.c58
-rw-r--r--src/lib/merchant_api_post_reserves.c247
-rw-r--r--src/lib/merchant_api_post_templates.c10
-rw-r--r--src/lib/merchant_api_post_transfers.c156
-rw-r--r--src/lib/merchant_api_post_using_templates.c6
-rw-r--r--src/lib/merchant_api_tip_authorize.c376
-rw-r--r--src/lib/merchant_api_tip_pickup.c446
-rw-r--r--src/lib/merchant_api_tip_pickup2.c353
-rw-r--r--src/lib/merchant_api_wallet_get_order.c47
-rw-r--r--src/lib/merchant_api_wallet_get_template.c195
-rw-r--r--src/lib/merchant_api_wallet_get_tip.c227
-rw-r--r--src/lib/merchant_api_wallet_post_order_refund.c287
-rw-r--r--src/merchant-tools/.gitignore5
-rw-r--r--src/merchant-tools/Makefile.am21
-rw-r--r--src/merchant-tools/benchmark-common.conf88
-rw-r--r--src/merchant-tools/benchmark-cs.conf16
-rw-r--r--src/merchant-tools/benchmark-rsa.conf16
-rw-r--r--src/merchant-tools/coins-cs.conf58
-rw-r--r--src/merchant-tools/coins-rsa.conf63
-rw-r--r--src/merchant-tools/exchange_benchmark_home/taler/exchange/offline-keys/master.priv1
-rw-r--r--src/merchant-tools/taler-merchant-benchmark.c696
-rw-r--r--src/merchant-tools/taler-merchant-passwd.c187
-rw-r--r--src/merchant-tools/taler-merchant-setup-reserve.c91
-rw-r--r--src/testing/.gitignore3
-rw-r--r--src/testing/Makefile.am54
-rwxr-xr-xsrc/testing/initialize_taler_system.sh283
-rwxr-xr-xsrc/testing/setup.sh67
-rwxr-xr-xsrc/testing/test-merchant-walletharness.sh17
-rw-r--r--src/testing/test.conf181
-rw-r--r--src/testing/test_key_rotation.conf12
-rwxr-xr-xsrc/testing/test_key_rotation.sh411
-rw-r--r--src/testing/test_kyc_api.c533
-rw-r--r--src/testing/test_kyc_api.conf87
-rw-r--r--src/testing/test_merchant_api-cs.conf124
-rw-r--r--src/testing/test_merchant_api-rsa.conf124
-rw-r--r--src/testing/test_merchant_api.c1358
-rw-r--r--src/testing/test_merchant_api.conf73
-rw-r--r--src/testing/test_merchant_api_home/.config/taler/exchange/account-2.json4
-rw-r--r--src/testing/test_merchant_api_home/.local/share/taler/exchange-offline/master.priv2
-rw-r--r--src/testing/test_merchant_api_home/taler/auditor/offline-keys/auditor.priv1
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/16964377041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/16970422041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1697646704bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/16982512041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/16988557041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1699460204bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17000647041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17006692041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17012737041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17018782041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17024827042
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17030872041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17036917042
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17042962041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17049007041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1705505204bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17061097041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17067142041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17073187042
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17079232042
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1708527704bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17091322041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17097367041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17103412041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1710945704bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17115502042
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17121547041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17127592041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1713363704bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17139682041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17145727041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17151772041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17157817041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17163862041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17169907041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/17175952041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/16964377041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/16970422041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1697646704bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/16982512041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/16988557041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/16994602041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1700064704bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17006692041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17012737041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17018782041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17024827042
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1703087204bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17036917041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17042962041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17049007041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17055052041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17061097043
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17067142041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17073187041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1707923204bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1708527704bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17091322041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17097367041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1710341204bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17109457041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1711550204bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17121547041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17127592041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17133637041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17139682041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17145727041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17151772041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17157817041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1716386204bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17169907041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/17175952042
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/16964377041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/16970422042
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/16976467041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/16982512041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/16988557041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1699460204bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17000647041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17006692041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17012737041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17018782041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17024827041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17030872041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17036917041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17042962041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17049007041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17055052041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17061097041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17067142041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17073187041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17079232041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17091322041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17097367041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17103412041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17109457041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17115502041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17121547041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17127592041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17133637041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1713968204bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1714572704bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17151772041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17157817042
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17163862041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/17169907041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1717595204bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/16964377041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/16970422041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1697646704bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/16982512041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/16988557041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1699460204bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17000647041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17006692041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17012737041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17018782041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17024827041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17030872041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1703691704bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17042962041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17049007041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17055052041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17061097041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17067142041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17073187042
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17079232041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1708527704bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17091322041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17097367041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17103412041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17109457041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17115502041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1712154704bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17127592041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17133637041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17139682041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17145727041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1715177204bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17157817041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1716386204bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1716990704bin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/17175952041
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-cs/secmod-private-keybin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-eddsa/secmod-private-key1
-rw-r--r--src/testing/test_merchant_api_home/taler/exchange-secmod-rsa/secmod-private-key1
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/auditor/offline-keys/auditor.priv1
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/16265613431
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/16338186431
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/16410759431
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/16483332431
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/16555905431
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1626554443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1627158943bin0 -> 767 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1627763443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1628367943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1628972443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1629576943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1630181443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1630785943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1631390443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1631994943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1632599443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1633203943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1633808443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1634412943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1635017443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1635621943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1636226443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1636830943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1637435443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1638039943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1638644443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1639248943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1639853443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1640457943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1641062443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1641666943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1642271443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1642875943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1643480443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1644084943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1644689443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1645293943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1645898443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1646502943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1647107443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1647711943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1648316443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1648920943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1649525443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1650129943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1650734443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1651338943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1651943443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1652547943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1653152443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1653756943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1654361443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1654965943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1655570443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1656174943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1656779443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1657383943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1657988443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1658592943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1626554443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1627158943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1627763443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1628367943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1628972443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1629576943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1630181443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1630785943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1631390443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1631994943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1632599443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1633203943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1633808443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1634412943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1635017443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1635621943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1636226443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1636830943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1637435443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1638039943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1638644443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1639248943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1639853443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1640457943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1641062443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1641666943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1642271443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1642875943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1643480443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1644084943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1644689443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1645293943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1645898443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1646502943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1647107443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1647711943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1648316443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1648920943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1649525443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1650129943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1650734443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1651338943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1651943443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1652547943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1653152443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1653756943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1654361443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1654965943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1655570443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1656174943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1656779443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1657383943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1657988443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1658592943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1626554443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1627158943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1627763443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1628367943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1628972443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1629576943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1630181443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1630785943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1631390443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1631994943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1632599443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1633203943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1633808443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1634412943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1635017443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1635621943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1636226443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1636830943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1637435443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1638039943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1638644443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1639248943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1639853443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1640457943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1641062443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1641666943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1642271443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1642875943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1643480443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1644084943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1644689443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1645293943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1645898443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1646502943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1647107443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1647711943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1648316443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1648920943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1649525443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1650129943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1650734443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1651338943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1651943443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1652547943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1653152443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1653756943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1654361443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1654965943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1655570443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1656174943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1656779443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1657383943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1657988443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1658592943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1626554443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1627158943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1627763443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1628367943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1628972443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1629576943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1630181443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1630785943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1631390443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1631994943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1632599443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1633203943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1633808443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1634412943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1635017443bin0 -> 767 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1635621943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1636226443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1636830943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1637435443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1638039943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1638644443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1639248943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1639853443bin0 -> 767 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1640457943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1641062443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1641666943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1642271443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1642875943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1643480443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1644084943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1644689443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1645293943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1645898443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1646502943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1647107443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1647711943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1648316443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1648920943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1649525443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1650129943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1650734443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1651338943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1651943443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1652547943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1653152443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1653756943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1654361443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1654965943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1655570443bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1656174943bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1656779443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1657383943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1657988443bin0 -> 769 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1658592943bin0 -> 768 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/exchange-offline/master.priv (renamed from src/testing/test_merchant_api_home/.local/share/taler/exchange/offline-keys/master.priv)0
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/exchange-offline/secm_tofus.pubbin0 -> 96 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/16861604421
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/16934177421
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/17006750421
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/17079323421
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/17151896421
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/secmod-private-key2
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/exchange/offline-keys/master.priv1
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/exchange/wirefees/x-taler-bank.feebin0 -> 800 bytes
-rw-r--r--src/testing/test_merchant_api_home/taler/taler/merchant/merchant.priv1
-rw-r--r--src/testing/test_merchant_api_twisted-cs.conf3
-rw-r--r--src/testing/test_merchant_api_twisted-rsa.conf3
-rw-r--r--src/testing/test_merchant_api_twisted.c195
-rwxr-xr-xsrc/testing/test_merchant_instance_auth.sh195
-rwxr-xr-xsrc/testing/test_merchant_instance_creation.sh24
-rwxr-xr-xsrc/testing/test_merchant_instance_purge.sh44
-rwxr-xr-xsrc/testing/test_merchant_instance_response.sh60
-rwxr-xr-xsrc/testing/test_merchant_kyc.sh80
-rwxr-xr-xsrc/testing/test_merchant_order_autocleanup.sh291
-rwxr-xr-xsrc/testing/test_merchant_order_creation.sh588
-rwxr-xr-xsrc/testing/test_merchant_product_creation.sh248
-rwxr-xr-xsrc/testing/test_merchant_reserve_creation.sh179
-rwxr-xr-xsrc/testing/test_merchant_transfer_tracking.sh610
-rwxr-xr-xsrc/testing/test_merchant_wirewatch.sh376
-rw-r--r--src/testing/test_template.conf60
-rw-r--r--src/testing/testing_api_cmd_abort_order.c53
-rw-r--r--src/testing/testing_api_cmd_checkserver.c270
-rw-r--r--src/testing/testing_api_cmd_claim_order.c40
-rw-r--r--src/testing/testing_api_cmd_config.c23
-rw-r--r--src/testing/testing_api_cmd_delete_account.c213
-rw-r--r--src/testing/testing_api_cmd_delete_instance.c22
-rw-r--r--src/testing/testing_api_cmd_delete_order.c13
-rw-r--r--src/testing/testing_api_cmd_delete_otp_device.c181
-rw-r--r--src/testing/testing_api_cmd_delete_product.c11
-rw-r--r--src/testing/testing_api_cmd_delete_reserve.c226
-rw-r--r--src/testing/testing_api_cmd_delete_template.c35
-rw-r--r--src/testing/testing_api_cmd_delete_transfer.c11
-rw-r--r--src/testing/testing_api_cmd_delete_webhook.c11
-rw-r--r--src/testing/testing_api_cmd_depositcheck.c162
-rw-r--r--src/testing/testing_api_cmd_forget_order.c29
-rw-r--r--src/testing/testing_api_cmd_get_instance.c377
-rw-r--r--src/testing/testing_api_cmd_get_instances.c125
-rw-r--r--src/testing/testing_api_cmd_get_orders.c60
-rw-r--r--src/testing/testing_api_cmd_get_otp_device.c206
-rw-r--r--src/testing/testing_api_cmd_get_otp_devices.c238
-rw-r--r--src/testing/testing_api_cmd_get_product.c81
-rw-r--r--src/testing/testing_api_cmd_get_products.c85
-rw-r--r--src/testing/testing_api_cmd_get_reserve.c330
-rw-r--r--src/testing/testing_api_cmd_get_reserves.c272
-rw-r--r--src/testing/testing_api_cmd_get_template.c59
-rw-r--r--src/testing/testing_api_cmd_get_templates.c52
-rw-r--r--src/testing/testing_api_cmd_get_tips.c310
-rw-r--r--src/testing/testing_api_cmd_get_transfers.c111
-rw-r--r--src/testing/testing_api_cmd_get_webhook.c35
-rw-r--r--src/testing/testing_api_cmd_get_webhooks.c37
-rw-r--r--src/testing/testing_api_cmd_instance_auth.c15
-rw-r--r--src/testing/testing_api_cmd_kyc_get.c84
-rw-r--r--src/testing/testing_api_cmd_lock_product.c20
-rw-r--r--src/testing/testing_api_cmd_merchant_get_order.c337
-rw-r--r--src/testing/testing_api_cmd_merchant_get_tip.c368
-rw-r--r--src/testing/testing_api_cmd_patch_instance.c118
-rw-r--r--src/testing/testing_api_cmd_patch_otp_device.c250
-rw-r--r--src/testing/testing_api_cmd_patch_product.c40
-rw-r--r--src/testing/testing_api_cmd_patch_template.c48
-rw-r--r--src/testing/testing_api_cmd_patch_webhook.c37
-rw-r--r--src/testing/testing_api_cmd_pay_order.c101
-rw-r--r--src/testing/testing_api_cmd_post_account.c250
-rw-r--r--src/testing/testing_api_cmd_post_instances.c147
-rw-r--r--src/testing/testing_api_cmd_post_orders.c165
-rw-r--r--src/testing/testing_api_cmd_post_orders_paid.c20
-rw-r--r--src/testing/testing_api_cmd_post_otp_devices.c256
-rw-r--r--src/testing/testing_api_cmd_post_products.c64
-rw-r--r--src/testing/testing_api_cmd_post_reserves.c280
-rw-r--r--src/testing/testing_api_cmd_post_templates.c46
-rw-r--r--src/testing/testing_api_cmd_post_transfers.c255
-rw-r--r--src/testing/testing_api_cmd_post_using_templates.c396
-rw-r--r--src/testing/testing_api_cmd_post_webhooks.c45
-rw-r--r--src/testing/testing_api_cmd_refund_order.c62
-rw-r--r--src/testing/testing_api_cmd_testserver.c374
-rw-r--r--src/testing/testing_api_cmd_tip_authorize.c489
-rw-r--r--src/testing/testing_api_cmd_tip_pickup.c413
-rw-r--r--src/testing/testing_api_cmd_tme.c161
-rw-r--r--src/testing/testing_api_cmd_wallet_get_order.c174
-rw-r--r--src/testing/testing_api_cmd_wallet_get_tip.c262
-rw-r--r--src/testing/testing_api_cmd_wallet_post_orders_refund.c49
-rw-r--r--src/testing/testing_api_cmd_webhook.c161
-rw-r--r--src/testing/testing_api_helpers.c161
955 files changed, 51662 insertions, 40100 deletions
diff --git a/.gitignore b/.gitignore
index ed40a189..c11acb71 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.vscode
+.DS_Store
*~
*Makefile.in
!src/mitm/*.in
@@ -82,3 +83,4 @@ doc/stamp-vti
doc/mdate-sh
doc/texinfo.tex
.private-key
+src/merchant-tools/taler-merchant-passwd
diff --git a/.gitmodules b/.gitmodules
index 857edd23..40eaf8da 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -5,3 +5,4 @@
[submodule "contrib/wallet-core"]
path = contrib/wallet-core
url = git://git.taler.net/wallet-core
+ branch = prebuilt
diff --git a/ChangeLog b/ChangeLog
index dbb5b4f7..8c43a1f4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,6 @@
+Wed Nov 29 09:06:49 AM JST 2023
+ Creating bugfix release taler-merchant 0.9.3a. -CG
+
Sat 28 Aug 2021 05:27:02 PM CEST
Improved language on HTML pages shown to user on
payment/refund (#6995). -CG/BP
diff --git a/bootstrap b/bootstrap
index 4663438e..c3a26432 100755
--- a/bootstrap
+++ b/bootstrap
@@ -8,7 +8,7 @@ if ! git --version >/dev/null; then
fi
echo "$0: Updating submodules"
-echo | git submodule update --init --force
+echo | git submodule update --init --force --remote
# This is more portable than `which' but comes with
@@ -29,11 +29,16 @@ else
echo "Uncrustify not detected, hook not installed. Please install uncrustify if you plan on doing development"
fi
+# Generate Makefile.am in contrib/
+cd contrib
+rm -f Makefile.am
+find wallet-core/backoffice/ -type f -printf ' %p \\\n' | sort > Makefile.am.ext
+# Remove extra '\' at the end of the file
+truncate -s -2 Makefile.am.ext
+cat Makefile.am.in Makefile.am.ext >> Makefile.am
+# Prevent accidental editing of the generated Makefile.am
+chmod -w Makefile.am
+cd ..
+
echo "$0: Running autoreconf"
autoreconf -if
-
-echo "Importing single-page app (from external Git repository)"
-cd contrib/wallet-core
-git checkout -f origin/prebuilt
-
-exit 0
diff --git a/configure.ac b/configure.ac
index f507ebc3..8fd422fc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
#
# This file is part of TALER
-# Copyright (C) 2014-2023 Taler Systems SA
+# Copyright (C) 2014-2024 Taler Systems SA
#
# TALER is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
@@ -18,7 +18,7 @@
# This configure file is in the public domain
AC_PREREQ([2.69])
-AC_INIT([taler-merchant],[0.9.1],[taler-bug@gnunet.org])
+AC_INIT([taler-merchant],[0.10.0],[taler-bug@gnunet.org])
AC_CONFIG_SRCDIR([src/backend/taler-merchant-httpd.c])
AC_CONFIG_HEADERS([taler_merchant_config.h])
# support for non-recursive builds
@@ -61,76 +61,36 @@ AS_IF([test "x$doc_only" != xyes],[
# Checks for programs.
AC_PROG_CC
-CFLAGS="-Wall -Wno-address-of-packed-member $CFLAGS"
-# Checks for header files.
-AC_CHECK_HEADERS([stdint.h stdlib.h string.h unistd.h])
-# Check for GNUnet's libgnunetutil.
-libgnunetutil=0
-AC_MSG_CHECKING([for libgnunetutil])
-AC_ARG_WITH(gnunet,
- [AS_HELP_STRING([--with-gnunet=PFX], [base of GNUnet installation])],
- [AC_MSG_RESULT([given as $with_gnunet])],
- [AC_MSG_RESULT(not given)
- with_gnunet=yes])
-AS_CASE([$with_gnunet],
- [yes], [],
- [no], [AC_MSG_ERROR([--with-gnunet is required])],
- [LDFLAGS="-L$with_gnunet/lib $LDFLAGS"
- CPPFLAGS="-I$with_gnunet/include $CPPFLAGS"])
-AC_CHECK_HEADERS([gnunet/gnunet_util_lib.h],
- [AC_CHECK_LIB([gnunetutil], [GNUNET_SCHEDULER_run], libgnunetutil=1)])
-AS_IF([test $libgnunetutil != 1],
- [AC_MSG_ERROR([[
-***
-*** You need libgnunetutil to build this program.
-*** This library is part of GNUnet, available at
-*** https://gnunet.org
-*** ]])])
+CFLAGS="-Wall -Wno-address-of-packed-member $CFLAGS"
+# Adam shostack suggests the following for Windows:
+# -D_FORTIFY_SOURCE=2 -fstack-protector-all
+AC_ARG_ENABLE(gcc-hardening,
+ AS_HELP_STRING(--enable-gcc-hardening, enable compiler security checks),
+[AS_IF([test x$enableval = xyes],[
+ CFLAGS="$CFLAGS -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -fstack-protector-all"
+ CFLAGS="$CFLAGS -fwrapv -fPIE -Wstack-protector"
+ CFLAGS="$CFLAGS --param ssp-buffer-size=1"
+ LDFLAGS="$LDFLAGS -pie"])])
-# test for postgres
-AX_LIB_POSTGRESQL([13.0])
-AS_IF([test "x$found_postgresql" = "xyes"],
- [SAVE_CPPFLAGS="$CPPFLAGS"
- CPPFLAGS="$POSTGRES_CPPFLAGS $CPPFLAGS"
- AC_CHECK_HEADERS([libpq-fe.h], [postgres=1], [postgres=0])])
-AS_IF([test "x$postgres" != "x1"],
- [AC_MSG_ERROR([[
-***
-*** You need libpq(-dev) >= 13.0 to build this program.
-*** ]])])
-AM_CONDITIONAL([HAVE_POSTGRESQL], [test "x$postgres" = "x1"])
-AC_DEFINE_UNQUOTED([HAVE_POSTGRESQL], [$postgres],
- [Define to 1 if Postgres is available])
-TALER_LIB_LDFLAGS="-export-dynamic -no-undefined"
-TALER_PLUGIN_LDFLAGS="-export-dynamic -avoid-version -module -no-undefined"
+# Linker hardening options
+# Currently these options are ELF specific - you can't use this with MacOSX
+AC_ARG_ENABLE(linker-hardening,
+ AS_HELP_STRING(--enable-linker-hardening, enable linker security fixups),
+ [AS_IF([test x$enableval = xyes],[LDFLAGS="$LDFLAGS -z relro -z now"])])
-AC_SUBST(TALER_LIB_LDFLAGS)
-AC_SUBST(TALER_PLUGIN_LDFLAGS)
+AC_ARG_ENABLE(sanitizer,
+ AS_HELP_STRING(--enable-sanitizer, enable Address Sanitizer and Undefined Behavior Sanitizer),
+[AS_IF([test x$enableval = xyes],[
+ LDFLAGS="$CFLAGS -fsanitize=address,undefined -fno-omit-frame-pointer"
+ ])])
-# Check for Taler's libtalerpq
-libtalerpq=0
-AC_MSG_CHECKING([for libtalerpq])
-AC_ARG_WITH(exchange,
- [AS_HELP_STRING([--with-exchange=PFX], [base of Taler EXCHANGE installation])],
- [AC_MSG_RESULT([given as $with_exchange])],
- [AC_MSG_RESULT(not given)
- with_exchange=yes])
-AS_CASE([$with_exchange],
- [yes], [],
- [no], [AC_MSG_ERROR([--with-exchange is required])],
- [LDFLAGS="-L$with_exchange/lib $LDFLAGS"
- CPPFLAGS="-I$with_exchange/include $CPPFLAGS"])
-
-CPPFLAGS="$CPPFLAGS $POSTGRESQL_CPPFLAGS"
-
-AC_CHECK_HEADERS([gnunet/gnunet_pq_lib.h],
- [AC_CHECK_LIB([gnunetpq], [GNUNET_PQ_connect_with_cfg], libgnunetpq=1)])
-AM_CONDITIONAL(HAVE_GNUNETPQ, test x$libgnunetpq = x1)
+# Checks for header files.
+AC_CHECK_HEADERS([stdint.h stdlib.h string.h unistd.h])
# check for libmicrohttpd
AC_MSG_CHECKING([for microhttpd])
@@ -146,6 +106,7 @@ AS_CASE([$with_microhttpd],
CPPFLAGS="-I$with_microhttpd/include $CPPFLAGS"])
MHD_VERSION_AT_LEAST([0.9.71])
+
jansson=0
PKG_CHECK_MODULES([JANSSON], [jansson >= 2.3],
[LDFLAGS="$JANSSON_LIBS $LDFLAGS"
@@ -156,6 +117,33 @@ PKG_CHECK_MODULES([JANSSON], [jansson >= 2.3],
***]])])
+# Require minimum libgcrypt version
+need_libgcrypt_version=1.6.1
+AC_DEFINE_UNQUOTED([NEED_LIBGCRYPT_VERSION], ["$need_libgcrypt_version"],
+ [minimum version of libgcrypt required])
+AM_PATH_LIBGCRYPT([$need_libgcrypt_version])
+
+
+# NOTE: If we find libcurl here we set LIBCURL to -lcurl
+# This affects the LIBCURL_CHECK_CONFIG call below as it takes LIBCURL into
+# account when checking for curl.
+AC_CHECK_LIB([curl],
+ [curl_easy_getinfo],
+ [LIBCURL="-lcurl"
+ curl_gnutls=1],
+ [curl_gnutls=0])
+
+LIBCURL_CHECK_CONFIG([], [7.34.0], [],
+ [AC_MSG_ERROR([cURL must have a version >= 7.34.0])])
+
+# Even if curl is found, we check for this constant in order to determine
+# if we can use this feature.
+AC_CHECK_HEADER([curl/curl.h],
+ [AC_CHECK_DECLS([CURLINFO_TLS_SSL_PTR],
+ [],
+ [AC_MSG_ERROR([cURL must support CURLINFO_TLS_SSL_PTR])],
+ [[#include <curl/curl.h>]])])
+
# test for libqrencode
qrencode=0
QR_LIBS="-lqrencode"
@@ -184,33 +172,65 @@ AS_IF([test "$qrencode" != 1],
*** You need libqrencode to build this program.
*** ]])])
-
AC_SUBST(QR_CFLAGS)
AC_SUBST(QR_LIBS)
-# NOTE: If we find libcurl here we set LIBCURL to -lcurl
-# This affects the LIBCURL_CHECK_CONFIG call below as it takes LIBCURL into
-# account when checking for curl.
-AC_CHECK_LIB([curl],
- [curl_easy_getinfo],
- [LIBCURL="-lcurl"
- curl_gnutls=1],
- [curl_gnutls=0])
-LIBCURL_CHECK_CONFIG([], [7.34.0], [],
- [AC_MSG_ERROR([cURL must have a version >= 7.34.0])])
+# test for postgres
+AX_LIB_POSTGRESQL([15.0])
+AS_IF([test "x$found_postgresql" = "xyes"],
+ [SAVE_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$POSTGRES_CPPFLAGS $CPPFLAGS"
+ AC_CHECK_HEADERS([libpq-fe.h], [postgres=1], [postgres=0])])
+AS_IF([test "x$postgres" != "x1"],
+ [AC_MSG_ERROR([[
+***
+*** You need libpq(-dev) >= 15.0 to build this program.
+*** ]])])
+AM_CONDITIONAL([HAVE_POSTGRESQL], [test "x$postgres" = "x1"])
+AC_DEFINE_UNQUOTED([HAVE_POSTGRESQL], [$postgres],
+ [Define to 1 if Postgres is available])
-# Even if curl is found, we check for this constant in order to determine
-# if we can use this feature.
-AC_CHECK_HEADER([curl/curl.h],
- [AC_CHECK_DECLS([CURLINFO_TLS_SSL_PTR],
- [],
- [AC_MSG_ERROR([cURL must support CURLINFO_TLS_SSL_PTR])],
- [[#include <curl/curl.h>]])])
-# Check for Taler's libtalerfakebank
-libtalerfakebank=0
-AC_MSG_CHECKING([for libtalerfakebank])
+CPPFLAGS="$CPPFLAGS $POSTGRESQL_CPPFLAGS"
+
+# Check for GNUnet's libgnunetutil.
+libgnunetutil=0
+AC_MSG_CHECKING([for libgnunetutil])
+AC_ARG_WITH(gnunet,
+ [AS_HELP_STRING([--with-gnunet=PFX], [base of GNUnet installation])],
+ [AC_MSG_RESULT([given as $with_gnunet])],
+ [AC_MSG_RESULT(not given)
+ with_gnunet=yes])
+AS_CASE([$with_gnunet],
+ [yes], [],
+ [no], [AC_MSG_ERROR([--with-gnunet is required])],
+ [LDFLAGS="-L$with_gnunet/lib $LDFLAGS"
+ CPPFLAGS="-I$with_gnunet/include $CPPFLAGS"])
+AC_CHECK_HEADERS([gnunet/gnunet_util_lib.h],
+ [AC_CHECK_LIB([gnunetutil], [GNUNET_SCHEDULER_run], libgnunetutil=1)])
+AS_IF([test $libgnunetutil != 1],
+ [AC_MSG_ERROR([[
+***
+*** You need libgnunetutil >= 0.21.0 to build this program.
+*** This library is part of GNUnet, available at
+*** https://gnunet.org
+*** ]])])
+
+AC_CHECK_HEADERS([gnunet/gnunet_pq_lib.h],
+ [AC_CHECK_LIB([gnunetpq], [GNUNET_PQ_connect_with_cfg], libgnunetpq=1)])
+AM_CONDITIONAL(HAVE_GNUNETPQ, test x$libgnunetpq = x1)
+
+TALER_LIB_LDFLAGS="-export-dynamic -no-undefined"
+TALER_PLUGIN_LDFLAGS="-export-dynamic -avoid-version -module -no-undefined"
+
+
+AC_SUBST(TALER_LIB_LDFLAGS)
+AC_SUBST(TALER_PLUGIN_LDFLAGS)
+
+
+libtalerutil=0
+AC_MSG_CHECKING([for libtalerutil])
AC_ARG_WITH(exchange,
[AS_HELP_STRING([--with-exchange=PFX], [base of Taler EXCHANGE installation])],
[AC_MSG_RESULT([given as $with_exchange])],
@@ -222,13 +242,80 @@ AS_CASE([$with_exchange],
[LDFLAGS="-L$with_exchange/lib $LDFLAGS"
CPPFLAGS="-I$with_exchange/include $CPPFLAGS $POSTGRESQL_CPPFLAGS"])
-CPPFLAGS="$CPPFLAGS $POSTGRESQL_CPPFLAGS"
+AC_CHECK_HEADERS([taler/taler_util.h],
+ [AC_CHECK_LIB([talerutil], [TALER_payto_normalize], libtalerutil=1)])
+AM_CONDITIONAL(HAVE_TALERUTIL, test x$libtalerutil = x1)
+AS_IF([test $libtalerutil != 1],
+ [AC_MSG_ERROR([[
+***
+*** You need libtalerutil >= 0.9.4 to build this program.
+*** This library is part of the GNU Taler exchange, available at
+*** https://taler.net
+*** ]])])
+
+
+libtalermhd=0
+AC_MSG_CHECKING([for libtalermhd])
+AC_ARG_WITH(exchange,
+ [AS_HELP_STRING([--with-exchange=PFX], [base of Taler EXCHANGE installation])],
+ [AC_MSG_RESULT([given as $with_exchange])],
+ [AC_MSG_RESULT(not given)
+ with_exchange=yes])
+AS_CASE([$with_exchange],
+ [yes], [],
+ [no], [AC_MSG_ERROR([--with-exchange is required])],
+ [LDFLAGS="-L$with_exchange/lib $LDFLAGS"
+ CPPFLAGS="-I$with_exchange/include $CPPFLAGS $POSTGRESQL_CPPFLAGS"])
+AC_CHECK_HEADERS([taler/taler_mhd_lib.h],
+ [AC_CHECK_LIB([talermhd], [TALER_MHD_parse_request_arg_snumber], libtalermhd=1)])
+AM_CONDITIONAL(HAVE_TALERMHD, test x$libtalermhd = x1)
+AS_IF([test $libtalermhd != 1],
+ [AC_MSG_ERROR([[
+***
+*** You need libtalermhd >= 0.10.1 (API v2) to build this program.
+*** This library is part of the GNU Taler exchange, available at
+*** https://taler.net
+*** ]])])
+
+libtalerjson=0
+AC_CHECK_HEADERS([taler/taler_json_lib.h],
+ [AC_CHECK_LIB([talerjson], [TALER_JSON_spec_otp_type], libtalerjson=1)])
+AM_CONDITIONAL(HAVE_TALERJSON, test x$libtalerjson = x1)
+AS_IF([test $libtalerjson != 1],
+ [AC_MSG_ERROR([[
+***
+*** You need libtalerjson >= 0.9.4 to build this program.
+*** This library is part of the GNU Taler exchange, available at
+*** https://taler.net
+*** ]])])
+
+
+# Check for Taler's libtalerpq
+
+libtalerpq=0
+AC_MSG_CHECKING([for libtalerpq])
+AC_CHECK_HEADERS([taler/taler_pq_lib.h],
+ [AC_CHECK_LIB([talerpq], [TALER_PQ_query_param_array_blinded_denom_sig], libtalerpq=1)])
+AM_CONDITIONAL(HAVE_TALERPQ, test x$libtalerpq = x1)
+AS_IF([test $libtalerpq != 1],
+ [AC_MSG_ERROR([[
+***
+*** You need libtalerpq >= 0.9.4 to build this program.
+*** This library is part of the GNU Taler exchange, available at
+*** https://taler.net
+*** ]])])
+
+
+# Check for Taler's libtalerfakebank
+libtalerfakebank=0
+AC_MSG_CHECKING([for libtalerfakebank])
AC_CHECK_HEADERS([taler/taler_fakebank_lib.h],
[AC_CHECK_LIB([talerfakebank], [TALER_FAKEBANK_start], libtalerfakebank=1)])
AM_CONDITIONAL(HAVE_TALERFAKEBANK, test x$libtalerfakebank = x1)
+
# check for libtalertwister
twistertesting=0
AC_MSG_CHECKING([for talerwtistertesting])
@@ -257,11 +344,6 @@ AC_ARG_ENABLE([coverage],
AC_MSG_RESULT($use_gcov)
AM_CONDITIONAL([USE_COVERAGE], [test "x$use_gcov" = "xyes"])
-# Require minimum libgcrypt version
-need_libgcrypt_version=1.6.1
-AC_DEFINE_UNQUOTED([NEED_LIBGCRYPT_VERSION], ["$need_libgcrypt_version"],
- [minimum version of libgcrypt required])
-AM_PATH_LIBGCRYPT([$need_libgcrypt_version])
# logging
extra_logging=0
@@ -280,7 +362,7 @@ AC_MSG_CHECKING(for source being under a VCS)
git_version=
AS_IF([test ! "X$gitcommand" = "X"],
[
- git_version=$(cd $srcdir ; git rev-list --full-history --all --abbrev-commit | head -n 1 2>/dev/null)
+ git_version=$(cd $srcdir ; git rev-list -n 1 --abbrev-commit HEAD 2>/dev/null)
])
AS_IF([test "X$git_version" = "X"],
[
@@ -310,13 +392,6 @@ AC_TYPE_UINTMAX_T
AC_CHECK_FUNCS([strdup])
-#
-# Check for tsc
-#
-AC_CHECK_PROG([tsc],[tsc],[yes],[no])
-AM_CONDITIONAL([HAVE_TSC], [test "x$tsc" = xyes])
-
-
AC_ARG_ENABLE([[doc]],
[AS_HELP_STRING([[--disable-doc]], [do not build any documentation])], ,
[enable_doc=yes])
@@ -329,8 +404,11 @@ AM_CONDITIONAL([ENABLE_DOC], [test "x$enable_doc" = "xyes"])
# logic if doc_only is set, make sure conditionals are still defined
AM_CONDITIONAL([HAVE_GNUNETPQ], [false])
AM_CONDITIONAL([HAVE_POSTGRESQL], [false])
+AM_CONDITIONAL([HAVE_TALERUTIL], [false])
+AM_CONDITIONAL([HAVE_TALERPQ], [false])
+AM_CONDITIONAL([HAVE_TALERMHD], [false])
+AM_CONDITIONAL([HAVE_TALERJSON], [false])
AM_CONDITIONAL([HAVE_TALERFAKEBANK], [false])
-AM_CONDITIONAL([HAVE_TSC], [false])
AM_CONDITIONAL([USE_COVERAGE], [false])
AM_CONDITIONAL([ENABLE_DOC], [true])
AM_CONDITIONAL([HAVE_TWISTER], [true])
@@ -355,6 +433,7 @@ contrib/Makefile
doc/Makefile
doc/doxygen/Makefile
src/Makefile
+src/bank/Makefile
src/backend/Makefile
src/backenddb/Makefile
src/include/Makefile
diff --git a/contrib/.gitignore b/contrib/.gitignore
index 94308bdc..ad4d5ab1 100644
--- a/contrib/.gitignore
+++ b/contrib/.gitignore
@@ -1,2 +1,4 @@
spa.html
*.must
+Makefile.am
+Makefile.am.ext
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
deleted file mode 100644
index daebbd30..00000000
--- a/contrib/Makefile.am
+++ /dev/null
@@ -1,39 +0,0 @@
-tmplpkgdatadir = $(prefix)/share/taler/merchant/templates/
-staticpkgdatadir = $(prefix)/share/taler/merchant/static/
-spapkgdatadir = $(prefix)/share/taler/merchant/spa/
-
-dist_tmplpkgdata_DATA = \
- depleted_tip.en.must \
- offer_refund.en.must \
- offer_tip.en.must \
- request_payment.en.must \
- show_order_details.en.must
-
-%.must: wallet-core/backend/%.html
- cp $< $@
-
-# Note: only works when building from Git, if you
-# run maintainer clean against the TGZ, you will
-# not be able to recover...
-MAINTAINERCLEANFILES = \
- $(dist_tmplpkgdata_DATA)
-
-# Note: historic mechanism for static resource files, currently not used.
-#dist_staticpkgdata_DATA = \
-# foo.css
-
-
-# spa.html is the single-page-app imported from the wallet-core.git
-# prebuilt branch.
-dist_spapkgdata_DATA = \
- wallet-core/backoffice/index.css \
- wallet-core/backoffice/index.css.map \
- wallet-core/backoffice/index.html \
- wallet-core/backoffice/index.js \
- wallet-core/backoffice/index.js.map \
- wallet-core/backoffice/languageicon-LWKRUH5D.svg \
- wallet-core/backoffice/materialdesignicons-webfont-4.9.95-7W2GKO6O.woff2 \
- wallet-core/backoffice/materialdesignicons-webfont-4.9.95-GELXKCZG.ttf \
- wallet-core/backoffice/materialdesignicons-webfont-4.9.95-HENMUVWG.eot \
- wallet-core/backoffice/materialdesignicons-webfont-4.9.95-WP2MXZKI.woff \
- wallet-core/backoffice/XRXV3I6Li01BKofINeaE-34D53UZZ.ttf
diff --git a/contrib/Makefile.am.in b/contrib/Makefile.am.in
new file mode 100644
index 00000000..9ae18eb3
--- /dev/null
+++ b/contrib/Makefile.am.in
@@ -0,0 +1,36 @@
+# This file is in the public domain.
+
+tmplpkgdatadir = $(prefix)/share/taler/merchant/templates/
+staticpkgdatadir = $(prefix)/share/taler/merchant/static/
+spapkgdatadir = $(prefix)/share/taler/merchant/spa/
+
+bin_SCRIPTS = \
+ taler-merchant-dbconfig
+
+EXTRA_DIST = \
+ $(bin_SCRIPTS)
+
+dist_tmplpkgdata_DATA = \
+ offer_refund.en.must \
+ request_payment.en.must \
+ show_order_details.en.must
+
+%.must: wallet-core/backend/%.html
+ cp $< $@
+
+# Note: only works when building from Git, if you
+# run maintainer clean against the TGZ, you will
+# not be able to recover...
+MAINTAINERCLEANFILES = \
+ $(dist_tmplpkgdata_DATA)
+
+# Note: historic mechanism for static resource files, currently not used.
+#dist_staticpkgdata_DATA = \
+# foo.css
+
+
+# This is for the single-page-app imported from the wallet-core.git
+# prebuilt branch. This MUST be the last line in the
+# Makefile.am.in, as it will be combined with the
+# actual SPA data by 'bootstrap'!
+dist_spapkgdata_DATA = \
diff --git a/contrib/ci/Containerfile b/contrib/ci/Containerfile
new file mode 100644
index 00000000..a1235706
--- /dev/null
+++ b/contrib/ci/Containerfile
@@ -0,0 +1,76 @@
+FROM docker.io/library/debian:bookworm
+
+ENV DEBIAN_FRONTEND=noninteractive
+
+RUN apt-get update -yqq && \
+ apt-get install -yqq \
+ git \
+ autoconf \
+ libjansson-dev \
+ libgcrypt-dev \
+ libqrencode-dev \
+ libpq-dev \
+ pkg-config \
+ libtool \
+ recutils \
+ make \
+ python3-pip \
+ python3-sphinx \
+ python3-sphinx-rtd-theme \
+ texinfo \
+ autopoint \
+ curl \
+ libcurl4-gnutls-dev \
+ libsodium-dev \
+ libidn11-dev \
+ zlib1g-dev \
+ libunistring-dev
+
+# Debian packaging tools
+RUN apt-get install -yqq \
+ po-debconf \
+ build-essential \
+ debhelper-compat \
+ devscripts \
+ git-buildpackage
+
+RUN pip3 install --break-system-packages requests click poetry uwsgi htmlark
+
+# Install docs generation utils
+RUN apt-get update -yqq && \
+ apt-get install -yqq \
+ graphviz \
+ doxygen \
+ && rm -rf /var/lib/apt/lists/*
+
+# Install Taler (and friends) packages
+RUN curl -sS https://deb.taler.net/apt-nightly/taler-bookworm-ci.sources \
+ | tee /etc/apt/sources.list.d/taler-bookworm-ci.sources
+
+RUN echo '\
+Package: * \n\
+Pin: origin "deb.taler.net" \n\
+Pin-Priority: 999' > /etc/apt/preferences.d/taler
+
+RUN cat /etc/apt/preferences.d/taler && \
+ apt-get update -y && \
+ apt-get install -y \
+ libgnunet-dev \
+ libgnunet \
+ libtalerexchange-dev \
+ libtalerexchange \
+ taler-exchange \
+ taler-exchange-database \
+ taler-exchange-offline \
+ taler-auditor \
+ taler-wallet-cli \
+ taler-harness \
+&& rm -rf /var/lib/apt/lists/*
+
+RUN apt-get update -yqq && \
+ apt-get install -yqq \
+ jq
+
+WORKDIR /workdir
+
+CMD ["bash", "/workdir/ci/ci.sh"]
diff --git a/contrib/ci/ci.sh b/contrib/ci/ci.sh
new file mode 100755
index 00000000..0719015b
--- /dev/null
+++ b/contrib/ci/ci.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+set -exvuo pipefail
+
+# Requires podman
+# Fails if not found in PATH
+OCI_RUNTIME=$(which podman)
+REPO_NAME=$(basename "${PWD}")
+JOB_NAME="${1}"
+JOB_ARCH=$((grep CONTAINER_ARCH contrib/ci/jobs/${JOB_NAME}/config.ini | cut -d' ' -f 3) || echo "${2:-amd64}")
+JOB_CONTAINER=$((grep CONTAINER_NAME contrib/ci/jobs/${JOB_NAME}/config.ini | cut -d' ' -f 3) || echo "localhost/${REPO_NAME}:${JOB_ARCH}")
+CONTAINER_BUILD=$((grep CONTAINER_BUILD contrib/ci/jobs/${JOB_NAME}/config.ini | cut -d' ' -f 3) || echo "True")
+
+echo "Image name: ${JOB_CONTAINER}"
+
+if [ "${CONTAINER_BUILD}" = "True" ] ; then
+ "${OCI_RUNTIME}" build \
+ --arch "${JOB_ARCH}" \
+ -t "${JOB_CONTAINER}" \
+ -f contrib/ci/Containerfile .
+fi
+
+"${OCI_RUNTIME}" run \
+ --rm \
+ -ti \
+ --arch "${JOB_ARCH}" \
+ --env CI_COMMIT_REF="$(git rev-parse HEAD)" \
+ --volume "${PWD}":/workdir \
+ --workdir /workdir \
+ "${JOB_CONTAINER}" \
+ contrib/ci/jobs/"${JOB_NAME}"/job.sh
+
+top_dir=$(dirname "${BASH_SOURCE[0]}")
+
+#"${top_dir}"/build.sh
diff --git a/contrib/ci/jobs/0-codespell/config.ini b/contrib/ci/jobs/0-codespell/config.ini
new file mode 100644
index 00000000..bd7d7386
--- /dev/null
+++ b/contrib/ci/jobs/0-codespell/config.ini
@@ -0,0 +1,6 @@
+[build]
+HALT_ON_FAILURE = False
+WARN_ON_FAILURE = True
+CONTAINER_BUILD = False
+CONTAINER_NAME = nixery.dev/shell/codespell
+CONTAINER_ARCH = amd64
diff --git a/contrib/ci/jobs/0-codespell/dictionary.txt b/contrib/ci/jobs/0-codespell/dictionary.txt
new file mode 100644
index 00000000..e555e8da
--- /dev/null
+++ b/contrib/ci/jobs/0-codespell/dictionary.txt
@@ -0,0 +1,46 @@
+# List of "words" that codespell should ignore in our sources.
+#
+# Note: The word sensitivity depends on how the to-be-ignored word is
+# spelled in codespell_lib/data/dictionary.txt. F.e. if there is a word
+# 'foo' and you add 'Foo' _here_, codespell will continue to complain
+# about 'Foo'.
+#
+BRE
+ND
+Nd
+TE
+TEH
+UPDATEing
+WAN
+aci
+acn
+ba
+bre
+cant
+complet
+doas
+ect
+ehr
+fo
+hel
+ifset
+ist
+keypair
+nd
+oce
+onl
+openin
+ot
+ser
+sie
+som
+sover
+te
+te
+teh
+tha
+ths
+updateing
+wan
+wih
+decose
diff --git a/contrib/ci/jobs/0-codespell/job.sh b/contrib/ci/jobs/0-codespell/job.sh
new file mode 100755
index 00000000..7b486bc7
--- /dev/null
+++ b/contrib/ci/jobs/0-codespell/job.sh
@@ -0,0 +1,101 @@
+#!/bin/bash
+set -exuo pipefail
+
+job_dir=$(dirname "${BASH_SOURCE[0]}")
+
+skip=$(cat <<EOF
+ABOUT-NLS
+*/afl-tests/*
+**/auditor/*.sql
+*.bbl
+*.bib
+*build-aux*
+*.bst
+*.cache/*
+*/cbdc-es.tex
+*/cbdc-it.tex
+ChangeLog
+*.cls
+configure*
+config.status
+config.guess
+depcomp
+*/contrib/*
+*/contrib/hellos/**
+*.dat
+*.deflate
+*.doc
+*/doc/*
+**/doc/flows/main.de.tex
+*/doc/texinfo.tex
+*.docx
+*.ecc
+*.eot
+*.epgz
+*.eps
+*.epub
+**/ExchangeSelection/example.ts
+*.fee
+*.fees
+*.file
+**/fonts/**
+*.gif
+*/.git/**
+*.gz
+*/i18n/strings.ts
+*.info
+*.jpeg
+*.jpg
+*.??.json
+*.json
+*.json-*
+*/keys/*
+*key
+*.latexmkrc
+*libtool*
+ltmain.sh
+*.log
+*/m4/*
+*.m4
+**/*.map
+*.min.js
+*.mp4
+*.odg
+*.ods
+*.odt
+*.pack.js
+*.pdf
+*.png
+*.PNG
+**/pnpm-lock.yaml
+*.po
+*.pptx
+*.priv
+**/rfc.bib
+*.rpath
+**/signing-key.asc
+*.sqlite
+*/src/anastasis-data.ts
+**/*.svg
+*.svg
+*.tag
+**/templating/mustach**
+*/templating/test?/**
+*/testcurl/test_tricky.c
+*.tgz
+*.ttf
+*.ttf
+**/valgrind.h
+*/vpn/tests/**
+*.wav
+*.woff
+*.woff2
+*.xcf
+*.xlsx
+*.zkey
+EOF
+);
+
+echo Current directory: `pwd`
+
+codespell -I "${job_dir}"/dictionary.txt -S ${skip//$'\n'/,}
diff --git a/contrib/ci/jobs/1-build/build.sh b/contrib/ci/jobs/1-build/build.sh
new file mode 100755
index 00000000..7d1b502a
--- /dev/null
+++ b/contrib/ci/jobs/1-build/build.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+set -exuo pipefail
+
+./bootstrap
+./configure CFLAGS="-ggdb -O0" \
+ --prefix=/usr \
+ --enable-logging=verbose \
+ --disable-doc
+
+make
diff --git a/contrib/ci/jobs/1-build/job.sh b/contrib/ci/jobs/1-build/job.sh
new file mode 100755
index 00000000..c1fc4e32
--- /dev/null
+++ b/contrib/ci/jobs/1-build/job.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -exuo pipefail
+
+apt-get update -yq
+apt-get upgrade -yq
+
+job_dir=$(dirname "${BASH_SOURCE[0]}")
+
+"${job_dir}"/build.sh
diff --git a/contrib/ci/jobs/2-test/config.ini b/contrib/ci/jobs/2-test/config.ini
new file mode 100644
index 00000000..ae1f46cc
--- /dev/null
+++ b/contrib/ci/jobs/2-test/config.ini
@@ -0,0 +1,6 @@
+[build]
+HALT_ON_FAILURE = False
+WARN_ON_FAILURE = True
+CONTAINER_BUILD = True
+CONTAINER_NAME = localhost/merchant
+CONTAINER_ARCH = amd64
diff --git a/contrib/ci/jobs/2-test/job.sh b/contrib/ci/jobs/2-test/job.sh
new file mode 100755
index 00000000..bfb24e33
--- /dev/null
+++ b/contrib/ci/jobs/2-test/job.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -exuo pipefail
+
+job_dir=$(dirname "${BASH_SOURCE[0]}")
+
+"${job_dir}"/test.sh
diff --git a/contrib/ci/jobs/2-test/test.sh b/contrib/ci/jobs/2-test/test.sh
new file mode 100755
index 00000000..fd2e15cd
--- /dev/null
+++ b/contrib/ci/jobs/2-test/test.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+set -evu
+
+apt-get update
+apt-get upgrade -yqq
+
+./bootstrap
+./configure CFLAGS="-ggdb -O0" \
+ --prefix=/usr \
+ --enable-logging=verbose \
+ --disable-doc
+make -j install
+
+sudo -u postgres /usr/lib/postgresql/15/bin/postgres -D /etc/postgresql/15/main -h localhost -p 5432 &
+sleep 10
+sudo -u postgres createuser -p 5432 root
+sudo -u postgres createdb -p 5432 -O root talercheck
+
+check_command()
+{
+ # Set LD_LIBRARY_PATH so tests can find the installed libs
+ LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/taler:/usr/lib:/usr/lib/taler PGPORT=5432 make check
+}
+
+print_logs()
+{
+ for i in src/*/test-suite.log
+ do
+ for FAILURE in $(grep '^FAIL:' ${i} | cut -d' ' -f2)
+ do
+ echo "Printing ${FAILURE}.log"
+ cat "$(dirname $i)/${FAILURE}.log"
+ done
+ done
+}
+
+if ! check_command ; then
+ print_logs
+ exit 1
+fi
diff --git a/contrib/ci/jobs/3-docs/config.ini b/contrib/ci/jobs/3-docs/config.ini
new file mode 100644
index 00000000..ae1f46cc
--- /dev/null
+++ b/contrib/ci/jobs/3-docs/config.ini
@@ -0,0 +1,6 @@
+[build]
+HALT_ON_FAILURE = False
+WARN_ON_FAILURE = True
+CONTAINER_BUILD = True
+CONTAINER_NAME = localhost/merchant
+CONTAINER_ARCH = amd64
diff --git a/contrib/ci/jobs/3-docs/docs.sh b/contrib/ci/jobs/3-docs/docs.sh
new file mode 100755
index 00000000..fe2b9687
--- /dev/null
+++ b/contrib/ci/jobs/3-docs/docs.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+set -exuo pipefail
+
+./bootstrap
+./configure --enable-only-doc
+
+pushd ./doc/doxygen/
+
+make full
+
+popd
diff --git a/contrib/ci/jobs/3-docs/job.sh b/contrib/ci/jobs/3-docs/job.sh
new file mode 100755
index 00000000..a72bca4b
--- /dev/null
+++ b/contrib/ci/jobs/3-docs/job.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -exuo pipefail
+
+job_dir=$(dirname "${BASH_SOURCE[0]}")
+
+"${job_dir}"/docs.sh
diff --git a/contrib/ci/jobs/4-deb-package/job.sh b/contrib/ci/jobs/4-deb-package/job.sh
new file mode 100755
index 00000000..a0801b41
--- /dev/null
+++ b/contrib/ci/jobs/4-deb-package/job.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+set -exuo pipefail
+# This file is in the public domain.
+# Helper script to build the latest DEB packages in the container.
+
+
+unset LD_LIBRARY_PATH
+
+# Install build-time dependencies.
+# Update apt cache first
+apt-get update
+apt-get upgrade -y
+mk-build-deps --install --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control
+
+export VERSION="$(./contrib/ci/jobs/4-deb-package/version.sh)"
+echo "Building package version ${VERSION}"
+EMAIL=none gbp dch --ignore-branch --debian-tag="%(version)s" --git-author --new-version="${VERSION}"
+./bootstrap
+dpkg-buildpackage -rfakeroot -b -uc -us
+
+ls -alh ../*.deb
+mkdir -p /artifacts/merchant/${CI_COMMIT_REF} # Variable comes from CI environment
+mv ../*.deb /artifacts/merchant/${CI_COMMIT_REF}/
diff --git a/contrib/ci/jobs/4-deb-package/version.sh b/contrib/ci/jobs/4-deb-package/version.sh
new file mode 100755
index 00000000..a6e740af
--- /dev/null
+++ b/contrib/ci/jobs/4-deb-package/version.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+set -ex
+
+BRANCH=$(git name-rev --name-only HEAD)
+if [ -z "${BRANCH}" ]; then
+ exit 1
+else
+ # "Unshallow" our checkout, but only our current branch, and exclude the submodules.
+ git fetch --no-recurse-submodules --tags --depth=1000 origin "${BRANCH}"
+ RECENT_VERSION_TAG=$(git describe --tags --match 'v*.*.*' --always --abbrev=0 HEAD || exit 1)
+ commits="$(git rev-list ${RECENT_VERSION_TAG}..HEAD --count)"
+ if [ "${commits}" = "0" ]; then
+ git describe --tag HEAD | sed -r 's/^v//' || exit 1
+ else
+ echo $(echo ${RECENT_VERSION_TAG} | sed -r 's/^v//')-${commits}-$(git rev-parse --short=8 HEAD)
+ fi
+fi
diff --git a/contrib/ci/jobs/5-deploy-package/config.ini b/contrib/ci/jobs/5-deploy-package/config.ini
new file mode 100644
index 00000000..08c106f9
--- /dev/null
+++ b/contrib/ci/jobs/5-deploy-package/config.ini
@@ -0,0 +1,6 @@
+[build]
+HALT_ON_FAILURE = True
+WARN_ON_FAILURE = True
+CONTAINER_BUILD = False
+CONTAINER_NAME = nixery.dev/shell/rsync
+CONTAINER_ARCH = amd64
diff --git a/contrib/ci/jobs/5-deploy-package/job.sh b/contrib/ci/jobs/5-deploy-package/job.sh
new file mode 100755
index 00000000..edd47554
--- /dev/null
+++ b/contrib/ci/jobs/5-deploy-package/job.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+set -exuo pipefail
+
+ARTIFACT_PATH="/artifacts/merchant/${CI_COMMIT_REF}/*.deb"
+
+RSYNC_HOST="taler.host.internal"
+RSYNC_PORT=424242
+RSYNC_PATH="incoming_packages/bookworm-taler-ci/"
+RSYNC_DEST="rsync://${RSYNC_HOST}/${RSYNC_PATH}"
+
+
+rsync -vP \
+ --port ${RSYNC_PORT} \
+ ${ARTIFACT_PATH} ${RSYNC_DEST}
diff --git a/contrib/taler-exchange.tag b/contrib/taler-exchange.tag
index 2eadbdf2..4ee916a8 100644
--- a/contrib/taler-exchange.tag
+++ b/contrib/taler-exchange.tag
@@ -64,7 +64,7 @@
<filename>taler_error_codes.h</filename>
<member kind="function">
<type>enum TALER_ErrorCode</type>
- <name>TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_EXPIRED</name>
+ <name>TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_RESERVE_EXPIRED</name>
<anchorfile>taler_error_codes.h</anchorfile>
</member>
</compound>
@@ -74,7 +74,7 @@
<filename>taler_error_codes.h</filename>
<member kind="function">
<type>enum TALER_ErrorCode</type>
- <name>TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_NOT_FOUND</name>
+ <name>TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_RESERVE_NOT_FOUND</name>
<anchorfile>taler_error_codes.h</anchorfile>
</member>
</compound>
@@ -84,7 +84,7 @@
<filename>taler_error_codes.h</filename>
<member kind="function">
<type>enum TALER_ErrorCode</type>
- <name>TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_INSUFFICIENT_FUNDS</name>
+ <name>TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_INSUFFICIENT_FUNDS</name>
<anchorfile>taler_error_codes.h</anchorfile>
</member>
</compound>
@@ -154,6 +154,16 @@
<filename>taler_dbevents.h</filename>
<member kind="function">
<type>#define</type>
+ <name>TALER_DBEVENT_MERCHANT_REWARD_PICKUP</name>
+ <anchorfile>taler_dbevents.h</anchorfile>
+ </member>
+ </compound>
+ <compound kind="file">
+ <name>taler_dbevents.h</name>
+ <path></path>
+ <filename>taler_dbevents.h</filename>
+ <member kind="function">
+ <type>#define</type>
<name>TALER_DBEVENT_MERCHANT_ORDER_PAID</name>
<anchorfile>taler_dbevents.h</anchorfile>
</member>
diff --git a/contrib/taler-merchant-dbconfig b/contrib/taler-merchant-dbconfig
new file mode 100755
index 00000000..db9e38c4
--- /dev/null
+++ b/contrib/taler-merchant-dbconfig
@@ -0,0 +1,148 @@
+#!/bin/bash
+# This file is part of GNU TALER.
+# Copyright (C) 2023 Taler Systems SA
+#
+# TALER is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free Software
+# Foundation; either version 2.1, or (at your option) any later version.
+#
+# TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along with
+# TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+#
+# @author Christian Grothoff
+#
+#
+# Error checking on
+set -eu
+
+RESET_DB=0
+SKIP_DBINIT=0
+DBUSER="taler-merchant-httpd"
+CFGFILE="/etc/taler/taler.conf"
+
+# Parse command-line options
+while getopts 'c:hrsu:' OPTION; do
+ case "$OPTION" in
+ c)
+ CFGFILE="$OPTARG"
+ ;;
+ h)
+ echo 'Supported options:'
+ echo " -c FILENAME -- use configuration FILENAME (default: $CFGFILE)"
+ echo " -r -- reset database (dangerous)"
+ echo " -s -- skip database initialization"
+ echo " -u USER -- taler-merchant to be run by USER (default: $DBUSER)"
+ exit 0
+ ;;
+ r)
+ RESET_DB="1"
+ ;;
+ s)
+ SKIP_DBINIT="1"
+ ;;
+ u)
+ DBUSER="$OPTARG"
+ ;;
+ ?)
+ exit_fail "Unrecognized command line option"
+ ;;
+ esac
+done
+
+if ! id postgres > /dev/null
+then
+ echo "Could not find 'postgres' user. Please install Postgresql first"
+ exit 1
+fi
+
+if [ "$(id -u)" -ne 0 ]
+then
+ echo "This script must be run as root"
+ exit 1
+fi
+
+if [ 0 = "$SKIP_DBINIT" ]
+then
+ if ! taler-merchant-dbinit -v 2> /dev/null
+ then
+ echo "Required 'taler-merchant-dbinit' not found. Please fix your installation."
+ exit 1
+ fi
+ DBINIT=$(which taler-merchant-dbinit)
+fi
+
+if ! id "$DBUSER" > /dev/null
+then
+ echo "Could not find '$DBUSER' user. Please set it up first"
+ exit 1
+fi
+
+echo "Setting up database user $DBUSER." 1>&2
+
+if ! sudo -i -u postgres createuser "$DBUSER" 2> /dev/null
+then
+ echo "Database user '$DBUSER' already existed. Continuing anyway." 1>&2
+fi
+
+DBPATH=$(taler-config \
+ -c "$CFGFILE" \
+ -s merchantdb-postgres \
+ -o CONFIG)
+
+if ! echo "$DBPATH" | grep "postgres://" > /dev/null
+then
+ echo "Invalid database configuration value '$DBPATH'." 1>&2
+ exit 1
+fi
+
+DBNAME=$(echo "$DBPATH" \
+ | sed \
+ -e "s/postgres:\/\/.*\///" \
+ -e "s/?.*//")
+
+if sudo -i -u postgres psql "$DBNAME" < /dev/null 2> /dev/null
+then
+ if [ 1 = "$RESET_DB" ]
+ then
+ echo "Deleting existing database $DBNAME." 1>&2
+ if ! sudo -i -u postgres dropdb "$DBNAME"
+ then
+ echo "Failed to delete existing database '$DBNAME'"
+ exit 1
+ fi
+ DO_CREATE=1
+ else
+ echo "Database '$DBNAME' already exists, continuing anyway."
+ DO_CREATE=0
+ fi
+else
+ DO_CREATE=1
+fi
+
+if [ 1 = "$DO_CREATE" ]
+then
+ echo "Creating database $DBNAME." 1>&2
+ if ! sudo -i -u postgres createdb -O "$DBUSER" "$DBNAME"
+ then
+ echo "Failed to create database '$DBNAME'"
+ exit 1
+ fi
+fi
+
+if [ 0 = "$SKIP_DBINIT" ]
+then
+ echo "Initializing database $DBNAME." 1>&2
+ if ! sudo -u "$DBUSER" "$DBINIT" -c "$CFGFILE"
+ then
+ echo "Failed to initialize database schema"
+ exit 1
+ fi
+fi
+
+echo "Database configuration finished." 1>&2
+
+exit 0
diff --git a/contrib/uncrustify.cfg b/contrib/uncrustify.cfg
index 00cf2281..af2d8e69 100644
--- a/contrib/uncrustify.cfg
+++ b/contrib/uncrustify.cfg
@@ -28,7 +28,7 @@ ls_code_width=true
pos_arith=lead
# Fully parenthesize boolean exprs
-mod_full_paren_if_bool=true
+mod_full_paren_if_bool=false
# Braces should be on their own line
nl_fdef_brace=add
@@ -49,8 +49,6 @@ nl_assign_brace=remove
# No extra newlines that cause noisy diffs
nl_start_of_file=remove
-nl_before_func_body_proto = 0
-nl_before_func_body_def = 0
nl_after_func_proto = 2
nl_after_func_body = 3
# If there's no new line, it's not a text file!
diff --git a/contrib/wallet-core b/contrib/wallet-core
-Subproject 13119702d90733174fc69a75de46bda6401d083
+Subproject 35212ac57bfe5625fcf574b0fd2d9baf395dc4c
diff --git a/debian/.gitignore b/debian/.gitignore
index b566fe18..f1850388 100644
--- a/debian/.gitignore
+++ b/debian/.gitignore
@@ -24,3 +24,4 @@ libtalermerchant.substvars
taler-merchant.substvars
taler-merchant.postrm.debhelper
taler-merchant.links
+taler-merchant.postinst.debhelper
diff --git a/debian/changelog b/debian/changelog
index e7831df3..36344f8b 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,101 @@
+taler-merchant (0.10.0) unstable; urgency=low
+
+ * Implement public GET API for templates (#8608).
+
+ -- Christian Grothoff <grothoff@gnu.org> Sat, 9 Mar 2024 21:50:12 +0200
+
+taler-merchant (0.9.4-3) unstable; urgency=low
+
+ * v0.9.4b bugfix release (mostly updates SPA).
+
+ -- Christian Grothoff <grothoff@gnu.org> Thu, 7 Mar 2024 21:50:12 +0200
+
+taler-merchant (0.9.4-2) unstable; urgency=low
+
+ * v0.9.4a bugfix release.
+
+ -- Christian Grothoff <grothoff@gnu.org> Mon, 3 Mar 2024 21:50:12 +0200
+
+taler-merchant (0.9.4-1) unstable; urgency=low
+
+ * Actual v0.9.4 release.
+
+ -- Christian Grothoff <grothoff@gnu.org> Sat, 10 Feb 2024 03:50:12 +0200
+
+taler-merchant (0.9.4) unstable; urgency=low
+
+ * Add support for new taler-merchant-depositcheck service.
+ * Packages the v0.9.4 release.
+
+ -- Christian Grothoff <grothoff@gnu.org> Sat, 6 Jan 2024 14:50:12 +0100
+
+taler-merchant (0.9.3-5) unstable; urgency=low
+
+ * Tolerate missing currencies.conf, but log a warning.
+ * Use taler-merchant as default database name.
+
+ -- Christian Grothoff <grothoff@gnu.org> Tue, 15 Dec 2023 18:50:12 -0700
+
+taler-merchant (0.9.3-4) unstable; urgency=low
+
+ * More fixes to the database setup automation scripts.
+
+ -- Christian Grothoff <grothoff@gnu.org> Tue, 13 Dec 2023 18:50:12 -0700
+
+taler-merchant (0.9.3-3) unstable; urgency=low
+
+ * More fixes to the database setup automation scripts.
+
+ -- Christian Grothoff <grothoff@gnu.org> Thu, 7 Dec 2023 00:50:12 -0800
+
+taler-merchant (0.9.3-2) unstable; urgency=low
+
+ * This packages the v0.9.3a bugfix release.
+
+ -- Christian Grothoff <grothoff@gnu.org> Wed, 29 Nov 2023 03:50:12 +0200
+
+taler-merchant (0.9.3-1) unstable; urgency=low
+
+ * Actual v0.9.3 release.
+
+ -- Christian Grothoff <grothoff@gnu.org> Wed, 27 Sep 2023 03:50:12 +0200
+
+taler-merchant (0.9.3) unstable; urgency=low
+
+ * First work towards packaging v0.9.3.
+
+ -- Christian Grothoff <grothoff@gnu.org> Thu, 7 Sep 2023 23:50:12 +0200
+
+taler-merchant (0.9.2-5) unstable; urgency=low
+
+ * Further improvements to Debian package.
+
+ -- Florian Dold <dold@taler.net> Wed, 15 Mar 2023 15:48:38 +0100
+
+taler-merchant (0.9.2-4) unstable; urgency=low
+
+ * Further improvements to Debian package.
+
+ -- Florian Dold <dold@taler.net> Wed, 08 Mar 2023 18:39:44 +0100
+
+taler-merchant (0.9.2-2) unstable; urgency=low
+
+ * Further improvements to Debian package.
+
+ -- Christian Grothoff <grothoff@gnu.org> Sat, 3 Mar 2023 23:50:12 +0200
+
+taler-merchant (0.9.2-1) unstable; urgency=low
+
+ * Minor improvements to Debian package.
+
+ -- Christian Grothoff <grothoff@gnu.org> Sat, 3 Mar 2023 13:50:12 +0200
+
+taler-merchant (0.9.2) unstable; urgency=low
+
+ * Packaging latest release.
+
+ -- Christian Grothoff <grothoff@gnu.org> Tue, 21 Feb 2023 13:50:12 +0200
+
taler-merchant (0.9.1) unstable; urgency=low
* Packaging latest release.
diff --git a/debian/control b/debian/control
index f0416127..9977ac39 100644
--- a/debian/control
+++ b/debian/control
@@ -8,9 +8,9 @@ Build-Depends:
autopoint,
debhelper-compat (= 12),
gettext,
- libgnunet-dev (>=0.17.1),
- libtalerexchange-dev (>=0.8.99),
- libpq-dev (>=13.0),
+ libgnunet-dev (>=0.21),
+ libtalerexchange-dev (>=0.9.4),
+ libpq-dev (>=14.0),
po-debconf,
libqrencode-dev,
zlib1g-dev,
@@ -47,17 +47,17 @@ Architecture: any
Pre-Depends:
${misc:Pre-Depends}
Depends:
- libtalerexchange (>= 0.8.99),
+ libtalermerchant (= ${binary:Version}),
+ libtalerexchange (>= 0.9.4),
adduser,
lsb-base,
netbase,
- sudo,
- apache2 | nginx | httpd,
- dbconfig-pgsql | dbconfig-no-thanks,
${misc:Depends},
${shlibs:Depends}
Recommends:
- postgresql (>=13.0)
+ postgresql (>=14.0),
+ taler-terms-generator,
+ apache2 | nginx | httpd
Description: GNU's payment system merchant backend.
.
The GNU Taler merchant backend provides e-commerce
@@ -69,8 +69,8 @@ Package: libtalermerchant-dev
Section: libdevel
Architecture: any
Depends:
- libtalerexchange-dev (>= 0.8.99),
- libgnunet-dev (>=0.17.1),
+ libtalerexchange-dev (>= 0.9.4),
+ libgnunet-dev (>=0.21),
${misc:Depends},
${shlibs:Depends}
Description: libraries to talk to a GNU Taler merchant (development).
diff --git a/debian/db/install/pgsql b/debian/db/install/pgsql
deleted file mode 100755
index 33b8cb1d..00000000
--- a/debian/db/install/pgsql
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/bash
-
-set -eu
-
-merchantdb_secretconf=/etc/taler/secrets/merchant-db.secret.conf
-merchantdb_overrideconf=/etc/taler/merchant-overrides.conf
-
-# Get database settings from dbconfig-common and write Taler configuration files.
-if [ -f /etc/dbconfig-common/taler-merchant.conf ]; then
- . /etc/dbconfig-common/taler-merchant.conf
- case "$dbc_dbtype" in
- pgsql)
- echo -e "# Config file auto-generated by Debian.\n[merchant]\nDB=postgres\n\n" > \
- $merchantdb_overrideconf
- # We assume ident auth here. We might support password auth later.
- echo -e "[merchantdb-postgres]\nCONFIG=postgres:///${dbc_dbname}\n\n" > \
- $merchantdb_secretconf
-
- # Allow the taler-merchant-httpd user to create schemas, needed by dbinit
- echo "GRANT CREATE ON DATABASE \"${dbc_dbtype}\" TO \"taler-merchant-httpd\";" | sudo -u postgres psql -f -
- # Run database initialization logic
- sudo -u taler-merchant-httpd taler-merchant-dbinit -c /etc/taler/taler.conf
- ;;
- sqlite3)
- # Later: use something like:
- # sqlite:///$DATA_DIR/merchant.db
- # But for now, sqlite is unsupported:
- echo "Unsupported database type $dbc_type."
- exit 1
- ;;
- "") ;;
-
- *)
- echo "Unsupported database type $dbc_type."
- exit 1
- ;;
- esac
-fi
diff --git a/debian/etc/nginx/sites-available/taler-merchant b/debian/etc/nginx/sites-available/taler-merchant
index 8de78a88..30ed62db 100644
--- a/debian/etc/nginx/sites-available/taler-merchant
+++ b/debian/etc/nginx/sites-available/taler-merchant
@@ -12,14 +12,16 @@ server {
# - replace with your actual server name
server_name localhost;
- location / {
+ access_log /var/log/nginx/merchant.log;
+ error_log /var/log/nginx/merchant.err;
+ location /taler-merchant/ {
proxy_pass http://unix:/var/run/taler/merchant-httpd/merchant-http.sock;
proxy_redirect off;
proxy_set_header Host $host;
# NOTE:
# - put your actual DNS name here
- proxy_set_header X-Forwarded-Host "example.com";
+ proxy_set_header X-Forwarded-Host "localhost";
# NOTE:
# - uncomment the following line if you are using HTTPS
diff --git a/debian/etc/taler/secrets/merchant-db.secret.conf b/debian/etc/taler/secrets/merchant-db.secret.conf
index 85bf6d3e..6cbbb24c 100644
--- a/debian/etc/taler/secrets/merchant-db.secret.conf
+++ b/debian/etc/taler/secrets/merchant-db.secret.conf
@@ -2,7 +2,7 @@
# Typically, there should only be a single line here, of the form:
-CONFIG=postgres:///DATABASE
+CONFIG=postgres:///taler-merchant
# The details of the URI depend on where the database lives and how
# access control was configured.
diff --git a/debian/libtalermerchant-dev.install b/debian/libtalermerchant-dev.install
index 08045798..1c316573 100644
--- a/debian/libtalermerchant-dev.install
+++ b/debian/libtalermerchant-dev.install
@@ -3,3 +3,6 @@ usr/include
usr/lib/*/*.so
usr/lib/*/libtalermerchanttesting.so.*
usr/lib/*/libtalermerchanttesting.so
+
+# Man pages
+usr/share/man/man1/taler-merchant-benchmark*
diff --git a/debian/libtalermerchant.install b/debian/libtalermerchant.install
index de3e6159..3ae1691d 100644
--- a/debian/libtalermerchant.install
+++ b/debian/libtalermerchant.install
@@ -1 +1,2 @@
usr/lib/*/libtalermerchant.so.*
+usr/lib/*/libtalermerchantbank.so.*
diff --git a/debian/rules b/debian/rules
index 9e170335..eba1c7cd 100755
--- a/debian/rules
+++ b/debian/rules
@@ -38,6 +38,11 @@ override_dh_installsystemd:
# Need to specify units manually, since we have multiple
# and dh_installsystemd by default only looks for "<package>.service".
dh_installsystemd -ptaler-merchant --name=taler-merchant-httpd --no-start --no-enable
+ dh_installsystemd -ptaler-merchant --name=taler-merchant-exchange --no-start --no-enable
+ dh_installsystemd -ptaler-merchant --name=taler-merchant-depositcheck --no-start --no-enable
+ dh_installsystemd -ptaler-merchant --name=taler-merchant-webhook --no-start --no-enable
+ dh_installsystemd -ptaler-merchant --name=taler-merchant-wirewatch --no-start --no-enable
+ dh_installsystemd -ptaler-merchant --name=taler-merchant --no-start --no-enable
# final invocation to generate daemon reload
dh_installsystemd
diff --git a/debian/taler-merchant.README.Debian b/debian/taler-merchant.README.Debian
index 4cae6f4b..32ac0f22 100644
--- a/debian/taler-merchant.README.Debian
+++ b/debian/taler-merchant.README.Debian
@@ -27,7 +27,7 @@ https://localhost:9966/ and configured via
a browser at that endpoint.
You can improve the security of the setup by enabling the use
-of uinx domain sockets, see
+of unix domain sockets, see
$ info taler-merchant "Secure setup"
diff --git a/debian/taler-merchant.config b/debian/taler-merchant.config
deleted file mode 100644
index 4a876261..00000000
--- a/debian/taler-merchant.config
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/sh
-
-set -e
-
-. /usr/share/debconf/confmodule
-
-_USERNAME=taler-merchant-httpd
-_GROUPNAME=www-data
-
-# For now, we only support postgres
-dbc_dbtypes=pgsql
-dbc_dbuser=${_USERNAME}
-
-dbc_authmethod_user=ident
-dbc_authmethod_admin=ident
-
-if [ -f /usr/share/dbconfig-common/dpkg/config.pgsql ]; then
- . /usr/share/dbconfig-common/dpkg/config.pgsql
- dbc_go taler-merchant "$@"
-fi
-
-db_stop
diff --git a/debian/taler-merchant.install b/debian/taler-merchant.install
index 3ae878a2..b1d93b17 100644
--- a/debian/taler-merchant.install
+++ b/debian/taler-merchant.install
@@ -9,5 +9,3 @@ usr/share/man/man1/*
debian/etc/* /etc/
-# Files needed by dbconf
-debian/db/install/* usr/share/dbconfig-common/scripts/taler-merchant/install/
diff --git a/debian/taler-merchant.postinst b/debian/taler-merchant.postinst
index 58c075d9..cac1355a 100644
--- a/debian/taler-merchant.postinst
+++ b/debian/taler-merchant.postinst
@@ -22,11 +22,6 @@ TALER_HOME="/var/lib/taler"
_USERNAME=taler-merchant-httpd
_GROUPNAME=www-data
-# Set permissions for sqlite3 file
-# (for when we support sqlite3 in the future)
-dbc_dbfile_owner="${_USERNAME}:${_GROUPNAME}"
-dbc_dbfile_perms="0600"
-
. /usr/share/debconf/confmodule
case "${1}" in
@@ -37,19 +32,12 @@ configure)
adduser --quiet --system --ingroup ${_GROUPNAME} --no-create-home --home ${TALER_HOME} ${_USERNAME}
fi
- if ! dpkg-statoverride --list /etc/taler/secrets/merchant-db.secret.conf >/dev/null 2>&1; then
- dpkg-statoverride --add --update \
- taler-merchant-httpd root 460 \
- /etc/taler/secrets/merchant-db.secret.conf
- fi
-
- # Set up postgres database (needs dbconfig-pgsql package)
- if [ -f /usr/share/dbconfig-common/dpkg/postinst.pgsql ]; then
- . /usr/share/dbconfig-common/dpkg/postinst.pgsql
- dbc_pgsql_createdb_encoding="UTF8"
- dbc_go taler-merchant "$@"
+ if ! dpkg-statoverride --list /etc/taler/secrets/merchant-db.secret.conf >/dev/null 2>&1
+ then
+ dpkg-statoverride --add --update \
+ taler-merchant-httpd root 460 \
+ /etc/taler/secrets/merchant-db.secret.conf
fi
-
;;
abort-upgrade | abort-remove | abort-deconfigure) ;;
diff --git a/debian/taler-merchant.postrm b/debian/taler-merchant.postrm
index 716f8982..693460ef 100644
--- a/debian/taler-merchant.postrm
+++ b/debian/taler-merchant.postrm
@@ -2,27 +2,26 @@
set -e
-if [ -f /usr/share/debconf/confmodule ]; then
- . /usr/share/debconf/confmodule
-fi
+_USERNAME=taler-merchant-httpd
+
-if [ -f /usr/share/dbconfig-common/dpkg/postrm.pgsql ]; then
- . /usr/share/dbconfig-common/dpkg/postrm.pgsql
- dbc_go taler-merchant "$@"
+if [ -f /usr/share/debconf/confmodule ]; then
+ . /usr/share/debconf/confmodule
fi
case "${1}" in
purge)
-# TODO: anything to clean up? Like:
-# rm -f /etc/taler/merchant-overrides.conf
- ;;
-remove | upgrade | failed-upgrade | abort-install | abort-upgrade | disappear)
- ;;
+ dpkg-statoverride --remove \
+ /etc/taler/secrets/merchant-db.secret.conf || true
+ deluser --quiet --system ${_USERNAME} || true
+ ;;
- *)
- echo "postrm called with unknown argument \`${1}'" >&2
- exit 1
- ;;
+remove | upgrade | failed-upgrade | abort-install | abort-upgrade | disappear)
+ ;;
+*)
+ echo "postrm called with unknown argument \`${1}'" >&2
+ exit 1
+ ;;
esac
#DEBHELPER#
diff --git a/debian/taler-merchant.preinst b/debian/taler-merchant.preinst
deleted file mode 100644
index dda68f09..00000000
--- a/debian/taler-merchant.preinst
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash
-
-# We prevent a few questions from being asked
-# upon installation by specifying defaults. Namely,
-# we want the database to be accessed via Unix domain
-# sockets and password-less.
-
-set -e
-
-# When purging this package after the selections in the preinst have been made,
-# the debconf database is left in an inconsistent state and the package cannot
-# be installed again. This happens because dbconf-common will create a
-# template for these questions with a shared owner. Purging will only delete
-# one of the two templates, leading to a DB state where debconf-set-selections
-# fails. We work around this by manually fixing up the debconf database.
-#
-# Unfortunately we can't do this in "postrm", because during "postrm"
-# the configuration database is locked (even after db_stop).
-#
-# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=487300
-if [ -x /usr/share/debconf/fix_db.pl ]; then
- /usr/share/debconf/fix_db.pl || true
-fi
-
-echo taler-merchant taler-merchant/pgsql/method select Unix socket | debconf-set-selections
-echo taler-merchant taler-merchant/pgsql/authmethod-user select ident | debconf-set-selections
-echo taler-merchant taler-merchant/pgsql/app-pass password | debconf-set-selections
-
-exit 0
diff --git a/debian/taler-merchant.prerm b/debian/taler-merchant.prerm
index eccb8259..727964b0 100644
--- a/debian/taler-merchant.prerm
+++ b/debian/taler-merchant.prerm
@@ -6,16 +6,4 @@ if [ -d /run/systemd/system ] && [ "$1" = remove ]; then
deb-systemd-invoke stop 'taler-merchant-httpd.service' >/dev/null || true
fi
-if [ -f /usr/share/debconf/confmodule ]; then
- . /usr/share/debconf/confmodule
-fi
-. /usr/share/dbconfig-common/dpkg/prerm
-
-if [ -f /usr/share/dbconfig-common/dpkg/prerm.pgsql ]; then
- . /usr/share/dbconfig-common/dpkg/prerm.pgsql
- dbc_go taler-merchant "$@"
-fi
-
-
-db_stop
exit 0
diff --git a/debian/taler-merchant.taler-merchant-depositcheck.service b/debian/taler-merchant.taler-merchant-depositcheck.service
new file mode 100644
index 00000000..bc5b84c0
--- /dev/null
+++ b/debian/taler-merchant.taler-merchant-depositcheck.service
@@ -0,0 +1,17 @@
+[Unit]
+Description=GNU Taler payment system merchant deposit check service
+After=postgres.service
+
+[Service]
+User=taler-merchant-httpd
+Type=simple
+Restart=always
+RestartMode=direct
+RestartSec=1s
+RestartPreventExitStatus=2 3 4 5 6 9
+ExecStart=/usr/bin/taler-merchant-depositcheck -c /etc/taler/taler.conf -L INFO
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+RuntimeMaxSec=3600s
+Slice=taler-merchant.slice
diff --git a/debian/taler-merchant.taler-merchant-exchange.service b/debian/taler-merchant.taler-merchant-exchange.service
new file mode 100644
index 00000000..4d368c3b
--- /dev/null
+++ b/debian/taler-merchant.taler-merchant-exchange.service
@@ -0,0 +1,17 @@
+[Unit]
+Description=GNU Taler merchant-exchange transaction reconciliation service
+After=postgres.service
+
+[Service]
+User=taler-merchant-exchange
+Type=simple
+Restart=always
+RestartMode=direct
+RestartSec=1s
+RestartPreventExitStatus=2 3 4 5 6 9
+ExecStart=/usr/bin/taler-merchant-exchange -c /etc/taler/taler.conf -L INFO
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+RuntimeMaxSec=3600s
+Slice=taler-merchant.slice
diff --git a/debian/taler-merchant.taler-merchant-httpd.service b/debian/taler-merchant.taler-merchant-httpd.service
index 6737fadf..e97bb6f6 100644
--- a/debian/taler-merchant.taler-merchant-httpd.service
+++ b/debian/taler-merchant.taler-merchant-httpd.service
@@ -1,11 +1,16 @@
[Unit]
Description=GNU Taler payment system merchant backend
+After=postgres.service
[Service]
User=taler-merchant-httpd
Type=simple
-Restart=on-failure
-ExecStart=/usr/bin/taler-merchant-httpd -c /etc/taler/taler.conf
+Restart=always
+RestartSec=1s
+RestartPreventExitStatus=9
+RuntimeMaxSec=3600s
+ExecStart=/usr/bin/taler-merchant-httpd -c /etc/taler/taler.conf -L INFO
+Slice=taler-merchant.slice
[Install]
WantedBy=multi-user.target
diff --git a/debian/taler-merchant.taler-merchant-webhook.service b/debian/taler-merchant.taler-merchant-webhook.service
new file mode 100644
index 00000000..e71bb5c8
--- /dev/null
+++ b/debian/taler-merchant.taler-merchant-webhook.service
@@ -0,0 +1,17 @@
+[Unit]
+Description=GNU Taler payment system merchant backend webhook trigger service
+After=postgres.service
+
+[Service]
+User=taler-merchant-httpd
+Type=simple
+Restart=always
+RestartMode=direct
+RestartSec=1s
+RestartPreventExitStatus=2 3 4 5 6 9
+ExecStart=/usr/bin/taler-merchant-webhook -c /etc/taler/taler.conf -L INFO
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+RuntimeMaxSec=3600s
+Slice=taler-merchant.slice
diff --git a/debian/taler-merchant.taler-merchant-wirewatch.service b/debian/taler-merchant.taler-merchant-wirewatch.service
new file mode 100644
index 00000000..8b61d68e
--- /dev/null
+++ b/debian/taler-merchant.taler-merchant-wirewatch.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler payment system merchant bank transfer import service
+After=postgres.service
+
+[Service]
+User=taler-merchant-httpd
+Type=simple
+Restart=always
+RestartMode=direct
+RestartSec=1s
+RestartPreventExitStatus=2 3 4 5 6 9
+ExecStart=/usr/bin/taler-merchant-wirewatch -c /etc/taler/taler.conf -L INFO
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+RuntimeMaxSec=3600s
+Slice=taler-merchant.slice
+
diff --git a/debian/taler-merchant.taler-merchant.slice b/debian/taler-merchant.taler-merchant.slice
new file mode 100644
index 00000000..6717bf7b
--- /dev/null
+++ b/debian/taler-merchant.taler-merchant.slice
@@ -0,0 +1,7 @@
+[Unit]
+Description=Slice for GNU taler merchant processes
+Before=slices.target
+
+[Slice]
+# Add settings that should affect all GNU Taler merchant
+# components here.
diff --git a/debian/taler-merchant.taler-merchant.target b/debian/taler-merchant.taler-merchant.target
new file mode 100644
index 00000000..bfab54f5
--- /dev/null
+++ b/debian/taler-merchant.taler-merchant.target
@@ -0,0 +1,12 @@
+[Unit]
+Description=GNU Taler merchant
+After=postgres.service network.target
+
+Wants=taler-merchant-httpd.service
+Wants=taler-merchant-wirewatch.service
+Wants=taler-merchant-exchange.service
+Wants=taler-merchant-webhook.service
+Wants=taler-merchant-depositcheck.service
+
+[Install]
+WantedBy=multi-user.target
diff --git a/doc/Makefile.am b/doc/Makefile.am
index c9c725f5..9efaf49d 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,26 +1,40 @@
SUBDIRS = . doxygen
man_MANS = \
+ prebuilt/man/taler-merchant-benchmark.1 \
+ prebuilt/man/taler-merchant-dbconfig.1\
prebuilt/man/taler-merchant-dbinit.1\
+ prebuilt/man/taler-merchant-depositcheck.1\
+ prebuilt/man/taler-merchant-exchange.1\
prebuilt/man/taler-merchant-httpd.1 \
- prebuilt/man/taler-merchant-setup-reserve.1\
- prebuilt/man/taler-merchant-benchmark.1
+ prebuilt/man/taler-merchant-passwd.1\
+ prebuilt/man/taler-merchant-webhook.1 \
+ prebuilt/man/taler-merchant-wirewatch.1
info_TEXINFOS = \
prebuilt/texinfo/taler-merchant-api-tutorial.texi \
prebuilt/texinfo/taler-merchant.texi
+install-info-local:
+ @echo " $(MKDIR_P) '$(DESTDIR)$(infodir)/taler-merchant-api-tutorial-figures'"; \
+ $(MKDIR_P) "$(DESTDIR)$(infodir)/taler-merchant-api-tutorial-figures" || exit 1; \
+ @echo " $(MKDIR_P) '$(DESTDIR)$(infodir)/taler-merchant-figures'"; \
+ $(MKDIR_P) "$(DESTDIR)$(infodir)/taler-merchant-figures" || exit 1; \
+ echo " $(INSTALL_DATA) arch-api.png '$(DESTDIR)$(infodir)/taler-merchant-api-tutorial-figures'"; \
+ $(INSTALL_DATA) '$(srcdir)/prebuilt/texinfo/taler-merchant-api-tutorial-figures/arch-api.png' "$(DESTDIR)$(infodir)/taler-merchant-api-tutorial-figures" || exit 1;
+ echo " $(INSTALL_DATA) prebuilt/texinfo/taler-merchant-figures/*.png '$(DESTDIR)$(infodir)/taler-merchant-figures'"; \
+ $(INSTALL_DATA) '$(srcdir)/prebuilt/texinfo/taler-merchant-figures/arch-api.png' '$(srcdir)/prebuilt/texinfo/taler-merchant-figures/merchant_first_login.png' '$(srcdir)/prebuilt/texinfo/taler-merchant-figures/no_default_account_yet.png' '$(srcdir)/prebuilt/texinfo/taler-merchant-figures/enter_instance_details.png' '$(srcdir)/prebuilt/texinfo/taler-merchant-figures/instance_iban_config.png' '$(srcdir)/prebuilt/texinfo/taler-merchant-figures/create_orders.png' '$(srcdir)/prebuilt/texinfo/taler-merchant-figures/payment_links.png' '$(srcdir)/prebuilt/texinfo/taler-merchant-figures/merchant-db.png' "$(DESTDIR)$(infodir)/taler-merchant-figures" || exit 1;
+
EXTRA_DIST = \
$(man_MANS) \
$(info_TEXINFOS) \
prebuilt/texinfo/taler-merchant-api-tutorial-figures/arch-api.png \
- prebuilt/texinfo/taler-merchant-api-tutorial-figures/auditor-db.png \
- prebuilt/texinfo/taler-merchant-api-tutorial-figures/exchange-db.png \
- prebuilt/texinfo/taler-merchant-api-tutorial-figures/merchant-db.png \
- prebuilt/texinfo/taler-merchant-api-tutorial-figures/replication.png \
prebuilt/texinfo/taler-merchant-figures/arch-api.png \
- prebuilt/texinfo/taler-merchant-figures/auditor-db.png \
- prebuilt/texinfo/taler-merchant-figures/exchange-db.png \
- prebuilt/texinfo/taler-merchant-figures/merchant-db.png \
- prebuilt/texinfo/taler-merchant-figures/replication.png
+ prebuilt/texinfo/taler-merchant-figures/merchant_first_login.png \
+ prebuilt/texinfo/taler-merchant-figures/no_default_account_yet.png \
+ prebuilt/texinfo/taler-merchant-figures/enter_instance_details.png \
+ prebuilt/texinfo/taler-merchant-figures/instance_iban_config.png \
+ prebuilt/texinfo/taler-merchant-figures/create_orders.png \
+ prebuilt/texinfo/taler-merchant-figures/payment_links.png \
+ prebuilt/texinfo/taler-merchant-figures/merchant-db.png
diff --git a/doc/doxygen/taler.doxy b/doc/doxygen/taler.doxy
index 7d06be3c..d9785d42 100644
--- a/doc/doxygen/taler.doxy
+++ b/doc/doxygen/taler.doxy
@@ -5,7 +5,7 @@
#---------------------------------------------------------------------------
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "GNU Taler: Merchant"
-PROJECT_NUMBER = 0.8.1
+PROJECT_NUMBER = 0.9.3
PROJECT_LOGO = logo.svg
OUTPUT_DIRECTORY = .
CREATE_SUBDIRS = YES
@@ -273,7 +273,6 @@ EXTERNAL_GROUPS = YES
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
-CLASS_DIAGRAMS = YES
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = YES
CLASS_GRAPH = NO
@@ -291,7 +290,7 @@ DOT_IMAGE_FORMAT = svg
INTERACTIVE_SVG = NO
DOT_PATH =
DOTFILE_DIRS =
-DOT_GRAPH_MAX_NODES = 100
+DOT_GRAPH_MAX_NODES = 500
MAX_DOT_GRAPH_DEPTH = 2
DOT_TRANSPARENT = YES
DOT_MULTI_TARGETS = NO
diff --git a/doc/prebuilt b/doc/prebuilt
-Subproject 4739f1447d0e8a6534c7fbdbc361d5d756d1875
+Subproject af8c69dfe397ff4bed7abca98ed8f3b2ed70541
diff --git a/m4/libgnurl.m4 b/m4/libgnurl.m4
deleted file mode 100644
index 82b0fda1..00000000
--- a/m4/libgnurl.m4
+++ /dev/null
@@ -1,250 +0,0 @@
-# LIBGNURL_CHECK_CONFIG ([DEFAULT-ACTION], [MINIMUM-VERSION],
-# [ACTION-IF-YES], [ACTION-IF-NO])
-# ----------------------------------------------------------
-# David Shaw <dshaw@jabberwocky.com> May-09-2006
-#
-# Checks for libgnurl. DEFAULT-ACTION is the string yes or no to
-# specify whether to default to --with-libgnurl or --without-libgnurl.
-# If not supplied, DEFAULT-ACTION is yes. MINIMUM-VERSION is the
-# minimum version of libgnurl to accept. Pass the version as a regular
-# version number like 7.10.1. If not supplied, any version is
-# accepted. ACTION-IF-YES is a list of shell commands to run if
-# libgnurl was successfully found and passed the various tests.
-# ACTION-IF-NO is a list of shell commands that are run otherwise.
-# Note that using --without-libgnurl does run ACTION-IF-NO.
-#
-# This macro #defines HAVE_LIBGNURL if a working libgnurl setup is
-# found, and sets @LIBGNURL@ and @LIBGNURL_CPPFLAGS@ to the necessary
-# values. Other useful defines are LIBGNURL_FEATURE_xxx where xxx are
-# the various features supported by libgnurl, and LIBGNURL_PROTOCOL_yyy
-# where yyy are the various protocols supported by libgnurl. Both xxx
-# and yyy are capitalized. See the list of AH_TEMPLATEs at the top of
-# the macro for the complete list of possible defines. Shell
-# variables $libgnurl_feature_xxx and $libgnurl_protocol_yyy are also
-# defined to 'yes' for those features and protocols that were found.
-# Note that xxx and yyy keep the same capitalization as in the
-# gnurl-config list (e.g. it's "HTTP" and not "http").
-#
-# Users may override the detected values by doing something like:
-# LIBGNURL="-lgnurl" LIBGNURL_CPPFLAGS="-I/usr/myinclude" ./configure
-#
-# For the sake of sanity, this macro assumes that any libgnurl that is
-# found is after version 7.7.2, the first version that included the
-# gnurl-config script. Note that it is very important for people
-# packaging binary versions of libgnurl to include this script!
-# Without gnurl-config, we can only guess what protocols are available,
-# or use gnurl_version_info to figure it out at runtime.
-
-AC_DEFUN([LIBGNURL_CHECK_CONFIG],
-[
- AH_TEMPLATE([LIBGNURL_FEATURE_SSL],[Defined if libgnurl supports SSL])
- AH_TEMPLATE([LIBGNURL_FEATURE_KRB4],[Defined if libgnurl supports KRB4])
- AH_TEMPLATE([LIBGNURL_FEATURE_IPV6],[Defined if libgnurl supports IPv6])
- AH_TEMPLATE([LIBGNURL_FEATURE_LIBZ],[Defined if libgnurl supports libz])
- AH_TEMPLATE([LIBGNURL_FEATURE_ASYNCHDNS],[Defined if libgnurl supports AsynchDNS])
- AH_TEMPLATE([LIBGNURL_FEATURE_IDN],[Defined if libgnurl supports IDN])
- AH_TEMPLATE([LIBGNURL_FEATURE_SSPI],[Defined if libgnurl supports SSPI])
- AH_TEMPLATE([LIBGNURL_FEATURE_NTLM],[Defined if libgnurl supports NTLM])
-
- AH_TEMPLATE([LIBGNURL_PROTOCOL_HTTP],[Defined if libgnurl supports HTTP])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_HTTPS],[Defined if libgnurl supports HTTPS])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_FTP],[Defined if libgnurl supports FTP])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_FTPS],[Defined if libgnurl supports FTPS])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_FILE],[Defined if libgnurl supports FILE])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_TELNET],[Defined if libgnurl supports TELNET])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_LDAP],[Defined if libgnurl supports LDAP])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_DICT],[Defined if libgnurl supports DICT])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_TFTP],[Defined if libgnurl supports TFTP])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_RTSP],[Defined if libgnurl supports RTSP])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_POP3],[Defined if libgnurl supports POP3])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_IMAP],[Defined if libgnurl supports IMAP])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_SMTP],[Defined if libgnurl supports SMTP])
-
- AC_ARG_WITH(libgnurl,
- AS_HELP_STRING([--with-libgnurl=PREFIX],[look for the gnurl library in PREFIX/lib and headers in PREFIX/include]),
- [_libgnurl_with=$withval],[_libgnurl_with=ifelse([$1],,[yes],[$1])])
-
- if test "$_libgnurl_with" != "no" ; then
-
- AC_PROG_AWK
-
- _libgnurl_version_parse="eval $AWK '{split(\$NF,A,\".\"); X=256*256*A[[1]]+256*A[[2]]+A[[3]]; print X;}'"
-
- _libgnurl_try_link=yes
-
- if test -d "$_libgnurl_with" ; then
- LIBGNURL_CPPFLAGS="-I$withval/include"
- _libgnurl_ldflags="-L$withval/lib"
- AC_PATH_PROG([_libgnurl_config],[gnurl-config],[],
- ["$withval/bin"])
- else
- AC_PATH_PROG([_libgnurl_config],[gnurl-config],[],[$PATH])
- fi
-
- if test x$_libgnurl_config != "x" ; then
- AC_CACHE_CHECK([for the version of libgnurl],
- [libgnurl_cv_lib_gnurl_version],
- [libgnurl_cv_lib_gnurl_version=`$_libgnurl_config --version | $AWK '{print $[]2}'`])
-
- _libgnurl_version=`echo $libgnurl_cv_lib_gnurl_version | $_libgnurl_version_parse`
- _libgnurl_wanted=`echo ifelse([$2],,[0],[$2]) | $_libgnurl_version_parse`
-
- if test $_libgnurl_wanted -gt 0 ; then
- AC_CACHE_CHECK([for libgnurl >= version $2],
- [libgnurl_cv_lib_version_ok],
- [
- if test $_libgnurl_version -ge $_libgnurl_wanted ; then
- libgnurl_cv_lib_version_ok=yes
- else
- libgnurl_cv_lib_version_ok=no
- fi
- ])
- fi
-
- if test $_libgnurl_wanted -eq 0 || test x$libgnurl_cv_lib_version_ok = xyes ; then
- if test x"$LIBGNURL_CPPFLAGS" = "x" ; then
- LIBGNURL_CPPFLAGS=`$_libgnurl_config --cflags`
- fi
- if test x"$LIBGNURL" = "x" ; then
- LIBGNURL=`$_libgnurl_config --libs`
-
- # This is so silly, but Apple actually has a bug in their
- # gnurl-config script. Fixed in Tiger, but there are still
- # lots of Panther installs around.
- case "${host}" in
- powerpc-apple-darwin7*)
- LIBGNURL=`echo $LIBGNURL | sed -e 's|-arch i386||g'`
- ;;
- esac
- fi
-
- # All gnurl-config scripts support --feature
- _libgnurl_features=`$_libgnurl_config --feature`
-
- # Is it modern enough to have --protocols? (7.12.4)
- if test $_libgnurl_version -ge 461828 ; then
- _libgnurl_protocols=`$_libgnurl_config --protocols`
- fi
- else
- _libgnurl_try_link=no
- fi
-
- unset _libgnurl_wanted
- fi
-
- if test $_libgnurl_try_link = yes ; then
-
- # we didn't find gnurl-config, so let's see if the user-supplied
- # link line (or failing that, "-lgnurl") is enough.
- LIBGNURL=${LIBGNURL-"$_libgnurl_ldflags -lgnurl"}
-
- AC_CACHE_CHECK([whether libgnurl is usable],
- [libgnurl_cv_lib_gnurl_usable],
- [
- _libgnurl_save_cppflags=$CPPFLAGS
- CPPFLAGS="$LIBGNURL_CPPFLAGS $CPPFLAGS"
- _libgnurl_save_libs=$LIBS
- LIBS="$LIBGNURL $LIBS"
-
- AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <curl/curl.h>],[
-/* Try and use a few common options to force a failure if we are
- missing symbols or can't link. */
-int x;
-curl_easy_setopt(NULL,CURLOPT_URL,NULL);
-x=CURL_ERROR_SIZE;
-x=CURLOPT_WRITEFUNCTION;
-x=CURLOPT_FILE;
-x=CURLOPT_ERRORBUFFER;
-x=CURLOPT_STDERR;
-x=CURLOPT_VERBOSE;
-])],libgnurl_cv_lib_gnurl_usable=yes,libgnurl_cv_lib_gnurl_usable=no)
-
- CPPFLAGS=$_libgnurl_save_cppflags
- LIBS=$_libgnurl_save_libs
- unset _libgnurl_save_cppflags
- unset _libgnurl_save_libs
- ])
-
- if test $libgnurl_cv_lib_gnurl_usable = yes ; then
-
- # Does gnurl_free() exist in this version of libgnurl?
- # If not, fake it with free()
-
- _libgnurl_save_cppflags=$CPPFLAGS
- CPPFLAGS="$CPPFLAGS $LIBGNURL_CPPFLAGS"
- _libgnurl_save_libs=$LIBS
- LIBS="$LIBS $LIBGNURL"
-
- AC_CHECK_FUNC(curl_free,,
- AC_DEFINE(curl_free,free,
- [Define curl_free() as free() if our version of gnurl lacks curl_free.]))
-
- CPPFLAGS=$_libgnurl_save_cppflags
- LIBS=$_libgnurl_save_libs
- unset _libgnurl_save_cppflags
- unset _libgnurl_save_libs
-
- AC_DEFINE(HAVE_LIBGNURL,1,
- [Define to 1 if you have a functional gnurl library.])
- AC_SUBST(LIBGNURL_CPPFLAGS)
- AC_SUBST(LIBGNURL)
-
- for _libgnurl_feature in $_libgnurl_features ; do
- AC_DEFINE_UNQUOTED(AS_TR_CPP(libgnurl_feature_$_libgnurl_feature),[1])
- eval AS_TR_SH(libgnurl_feature_$_libgnurl_feature)=yes
- done
-
- if test "x$_libgnurl_protocols" = "x" ; then
-
- # We don't have --protocols, so just assume that all
- # protocols are available
- _libgnurl_protocols="HTTP FTP FILE TELNET LDAP DICT TFTP"
-
- if test x$libgnurl_feature_SSL = xyes ; then
- _libgnurl_protocols="$_libgnurl_protocols HTTPS"
-
- # FTPS wasn't standards-compliant until version
- # 7.11.0 (0x070b00 == 461568)
- if test $_libgnurl_version -ge 461568; then
- _libgnurl_protocols="$_libgnurl_protocols FTPS"
- fi
- fi
-
- # RTSP, IMAP, POP3 and SMTP were added in
- # 7.20.0 (0x071400 == 463872)
- if test $_libgnurl_version -ge 463872; then
- _libgnurl_protocols="$_libgnurl_protocols RTSP IMAP POP3 SMTP"
- fi
- fi
-
- for _libgnurl_protocol in $_libgnurl_protocols ; do
- AC_DEFINE_UNQUOTED(AS_TR_CPP(libgnurl_protocol_$_libgnurl_protocol),[1])
- eval AS_TR_SH(libgnurl_protocol_$_libgnurl_protocol)=yes
- done
- else
- unset LIBGNURL
- unset LIBGNURL_CPPFLAGS
- fi
- fi
-
- unset _libgnurl_try_link
- unset _libgnurl_version_parse
- unset _libgnurl_config
- unset _libgnurl_feature
- unset _libgnurl_features
- unset _libgnurl_protocol
- unset _libgnurl_protocols
- unset _libgnurl_version
- unset _libgnurl_ldflags
- fi
-
- if test x$_libgnurl_with = xno || test x$libgnurl_cv_lib_gnurl_usable != xyes ; then
- # This is the IF-NO path
- ifelse([$4],,:,[$4])
- else
- # This is the IF-YES path
- ifelse([$3],,:,[$3])
- fi
-
- unset _libgnurl_with
-])dnl
diff --git a/src/Makefile.am b/src/Makefile.am
index 984c780f..23b34b38 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,3 +1,3 @@
# This Makefile is in the public domain
AM_CPPFLAGS = -I$(top_srcdir)/src/include
-SUBDIRS = include backenddb backend lib testing merchant-tools
+SUBDIRS = include bank backenddb backend lib testing merchant-tools
diff --git a/src/backend/.gitignore b/src/backend/.gitignore
new file mode 100644
index 00000000..69cbfcee
--- /dev/null
+++ b/src/backend/.gitignore
@@ -0,0 +1,4 @@
+taler-merchant-webhook
+taler-merchant-wirewatch
+taler-merchant-exchange
+taler-merchant-depositcheck
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index 99548ef8..d521608d 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -16,39 +16,48 @@ EXTRA_DIST = \
$(pkgcfg_DATA)
bin_PROGRAMS = \
+ taler-merchant-depositcheck \
+ taler-merchant-exchange \
taler-merchant-httpd \
- taler-merchant-webhook
+ taler-merchant-webhook \
+ taler-merchant-wirewatch
taler_merchant_httpd_SOURCES = \
taler-merchant-httpd.c taler-merchant-httpd.h \
- taler-merchant-httpd_auditors.c taler-merchant-httpd_auditors.h \
taler-merchant-httpd_config.c taler-merchant-httpd_config.h \
taler-merchant-httpd_exchanges.c taler-merchant-httpd_exchanges.h \
taler-merchant-httpd_get-orders-ID.c \
taler-merchant-httpd_get-orders-ID.h \
- taler-merchant-httpd_get-tips-ID.c \
- taler-merchant-httpd_get-tips-ID.h \
+ taler-merchant-httpd_get-templates-ID.c \
+ taler-merchant-httpd_get-templates-ID.h \
taler-merchant-httpd_helper.c \
taler-merchant-httpd_helper.h \
- taler-merchant-httpd_private-get-tips.c \
- taler-merchant-httpd_private-get-tips.h \
- taler-merchant-httpd_private-get-tips-ID.c \
- taler-merchant-httpd_private-get-tips-ID.h \
- taler-merchant-httpd_mhd.c taler-merchant-httpd_mhd.h \
- taler-merchant-httpd_private-delete-templates-ID.c \
- taler-merchant-httpd_private-delete-templates-ID.h \
- taler-merchant-httpd_private-delete-webhooks-ID.c \
- taler-merchant-httpd_private-delete-webhooks-ID.h \
+ taler-merchant-httpd_mhd.c \
+ taler-merchant-httpd_mhd.h \
+ taler-merchant-httpd_private-delete-account-ID.c \
+ taler-merchant-httpd_private-delete-account-ID.h \
taler-merchant-httpd_private-delete-instances-ID.c \
taler-merchant-httpd_private-delete-instances-ID.h \
+ taler-merchant-httpd_private-delete-instances-ID-token.c \
+ taler-merchant-httpd_private-delete-instances-ID-token.h \
taler-merchant-httpd_private-delete-products-ID.c \
taler-merchant-httpd_private-delete-products-ID.h \
taler-merchant-httpd_private-delete-orders-ID.c \
taler-merchant-httpd_private-delete-orders-ID.h \
- taler-merchant-httpd_private-delete-reserves-ID.c \
- taler-merchant-httpd_private-delete-reserves-ID.h \
+ taler-merchant-httpd_private-delete-otp-devices-ID.c \
+ taler-merchant-httpd_private-delete-otp-devices-ID.h \
+ taler-merchant-httpd_private-delete-templates-ID.c \
+ taler-merchant-httpd_private-delete-templates-ID.h \
+ taler-merchant-httpd_private-delete-token-families-SLUG.c \
+ taler-merchant-httpd_private-delete-token-families-SLUG.h \
taler-merchant-httpd_private-delete-transfers-ID.c \
taler-merchant-httpd_private-delete-transfers-ID.h \
+ taler-merchant-httpd_private-delete-webhooks-ID.c \
+ taler-merchant-httpd_private-delete-webhooks-ID.h \
+ taler-merchant-httpd_private-get-accounts.c \
+ taler-merchant-httpd_private-get-accounts.h \
+ taler-merchant-httpd_private-get-accounts-ID.c \
+ taler-merchant-httpd_private-get-accounts-ID.h \
taler-merchant-httpd_private-get-instances.c \
taler-merchant-httpd_private-get-instances.h \
taler-merchant-httpd_private-get-instances-ID.c \
@@ -63,52 +72,66 @@ taler_merchant_httpd_SOURCES = \
taler-merchant-httpd_private-get-orders.h \
taler-merchant-httpd_private-get-orders-ID.c \
taler-merchant-httpd_private-get-orders-ID.h \
- taler-merchant-httpd_private-get-reserves.c \
- taler-merchant-httpd_private-get-reserves.h \
- taler-merchant-httpd_private-get-reserves-ID.c \
- taler-merchant-httpd_private-get-reserves-ID.h \
+ taler-merchant-httpd_private-get-otp-devices.c \
+ taler-merchant-httpd_private-get-otp-devices.h \
+ taler-merchant-httpd_private-get-otp-devices-ID.c \
+ taler-merchant-httpd_private-get-otp-devices-ID.h \
taler-merchant-httpd_private-get-transfers.c \
taler-merchant-httpd_private-get-transfers.h \
taler-merchant-httpd_private-get-templates.c \
taler-merchant-httpd_private-get-templates.h \
taler-merchant-httpd_private-get-templates-ID.c \
taler-merchant-httpd_private-get-templates-ID.h \
+ taler-merchant-httpd_private-get-token-families.c \
+ taler-merchant-httpd_private-get-token-families.h \
+ taler-merchant-httpd_private-get-token-families-SLUG.c \
+ taler-merchant-httpd_private-get-token-families-SLUG.h \
taler-merchant-httpd_private-get-webhooks.c \
taler-merchant-httpd_private-get-webhooks.h \
taler-merchant-httpd_private-get-webhooks-ID.c \
taler-merchant-httpd_private-get-webhooks-ID.h \
- taler-merchant-httpd_private-patch-templates-ID.c \
- taler-merchant-httpd_private-patch-templates-ID.h \
- taler-merchant-httpd_private-patch-webhooks-ID.c \
- taler-merchant-httpd_private-patch-webhooks-ID.h \
+ taler-merchant-httpd_private-patch-accounts-ID.c \
+ taler-merchant-httpd_private-patch-accounts-ID.h \
taler-merchant-httpd_private-patch-instances-ID.c \
taler-merchant-httpd_private-patch-instances-ID.h \
taler-merchant-httpd_private-patch-orders-ID-forget.c \
taler-merchant-httpd_private-patch-orders-ID-forget.h \
+ taler-merchant-httpd_private-patch-otp-devices-ID.c \
+ taler-merchant-httpd_private-patch-otp-devices-ID.h \
taler-merchant-httpd_private-patch-products-ID.c \
taler-merchant-httpd_private-patch-products-ID.h \
- taler-merchant-httpd_private-post-templates.c \
- taler-merchant-httpd_private-post-templates.h \
- taler-merchant-httpd_private-post-webhooks.c \
- taler-merchant-httpd_private-post-webhooks.h \
+ taler-merchant-httpd_private-patch-templates-ID.c \
+ taler-merchant-httpd_private-patch-templates-ID.h \
+ taler-merchant-httpd_private-patch-token-families-SLUG.c \
+ taler-merchant-httpd_private-patch-token-families-SLUG.h \
+ taler-merchant-httpd_private-patch-webhooks-ID.c \
+ taler-merchant-httpd_private-patch-webhooks-ID.h \
+ taler-merchant-httpd_private-post-account.c \
+ taler-merchant-httpd_private-post-account.h \
taler-merchant-httpd_private-post-instances.c \
taler-merchant-httpd_private-post-instances.h \
taler-merchant-httpd_private-post-instances-ID-auth.c \
taler-merchant-httpd_private-post-instances-ID-auth.h \
- taler-merchant-httpd_private-post-products.c \
- taler-merchant-httpd_private-post-products.h \
- taler-merchant-httpd_private-post-products-ID-lock.c \
- taler-merchant-httpd_private-post-products-ID-lock.h \
- taler-merchant-httpd_private-post-reserves.c \
- taler-merchant-httpd_private-post-reserves.h \
- taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c \
- taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h \
+ taler-merchant-httpd_private-post-instances-ID-token.c \
+ taler-merchant-httpd_private-post-instances-ID-token.h \
taler-merchant-httpd_private-post-orders-ID-refund.c \
taler-merchant-httpd_private-post-orders-ID-refund.h \
taler-merchant-httpd_private-post-orders.c \
taler-merchant-httpd_private-post-orders.h \
+ taler-merchant-httpd_private-post-products.c \
+ taler-merchant-httpd_private-post-products.h \
+ taler-merchant-httpd_private-post-otp-devices.c \
+ taler-merchant-httpd_private-post-otp-devices.h \
+ taler-merchant-httpd_private-post-products-ID-lock.c \
+ taler-merchant-httpd_private-post-products-ID-lock.h \
+ taler-merchant-httpd_private-post-templates.c \
+ taler-merchant-httpd_private-post-templates.h \
+ taler-merchant-httpd_private-post-token-families.c \
+ taler-merchant-httpd_private-post-token-families.h \
taler-merchant-httpd_private-post-transfers.c \
taler-merchant-httpd_private-post-transfers.h \
+ taler-merchant-httpd_private-post-webhooks.c \
+ taler-merchant-httpd_private-post-webhooks.h \
taler-merchant-httpd_post-orders-ID-abort.c \
taler-merchant-httpd_post-orders-ID-abort.h \
taler-merchant-httpd_post-orders-ID-claim.c \
@@ -119,25 +142,23 @@ taler_merchant_httpd_SOURCES = \
taler-merchant-httpd_post-orders-ID-paid.h \
taler-merchant-httpd_post-orders-ID-refund.c \
taler-merchant-httpd_post-orders-ID-refund.h \
- taler-merchant-httpd_post-tips-ID-pickup.c \
- taler-merchant-httpd_post-tips-ID-pickup.h \
taler-merchant-httpd_post-using-templates.c \
taler-merchant-httpd_post-using-templates.h \
taler-merchant-httpd_qr.c \
taler-merchant-httpd_qr.h \
- taler-merchant-httpd_reserves.c \
- taler-merchant-httpd_reserves.h \
taler-merchant-httpd_spa.c \
taler-merchant-httpd_spa.h \
taler-merchant-httpd_statics.c \
- taler-merchant-httpd_statics.h
+ taler-merchant-httpd_statics.h
taler_merchant_httpd_LDADD = \
$(top_builddir)/src/backenddb/libtalermerchantdb.la \
+ $(top_builddir)/src/bank/libtalermerchantbank.la \
-ltalerexchange \
-ltalertemplating \
-ltalermhd \
-ltalerbank \
+ -ltalerkyclogic \
-ltalerjson \
-ltalerutil \
-ltalerpq \
@@ -153,6 +174,23 @@ taler_merchant_httpd_CFLAGS = \
$(AM_CFLAGS)
+taler_merchant_exchange_SOURCES = \
+ taler-merchant-exchange.c
+taler_merchant_exchange_LDADD = \
+ $(top_builddir)/src/backenddb/libtalermerchantdb.la \
+ -ltalerexchange \
+ -ltalerjson \
+ -ltalerutil \
+ -ltalerpq \
+ -lgnunetjson \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -lcurl \
+ $(XLIB)
+taler_merchant_exchange_CFLAGS = \
+ $(AM_CFLAGS)
+
+
taler_merchant_webhook_SOURCES = \
taler-merchant-webhook.c
taler_merchant_webhook_LDADD = \
@@ -170,3 +208,38 @@ taler_merchant_webhook_LDADD = \
$(XLIB)
taler_merchant_webhook_CFLAGS = \
$(AM_CFLAGS)
+
+taler_merchant_depositcheck_SOURCES = \
+ taler-merchant-depositcheck.c
+taler_merchant_depositcheck_LDADD = \
+ $(top_builddir)/src/backenddb/libtalermerchantdb.la \
+ -ltalerexchange \
+ -ltalerjson \
+ -ltalerutil \
+ -ltalerpq \
+ -ljansson \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lcurl \
+ $(XLIB)
+taler_merchant_depositcheck_CFLAGS = \
+ $(AM_CFLAGS)
+
+taler_merchant_wirewatch_SOURCES = \
+ taler-merchant-wirewatch.c
+taler_merchant_wirewatch_LDADD = \
+ $(top_builddir)/src/bank/libtalermerchantbank.la \
+ $(top_builddir)/src/backenddb/libtalermerchantdb.la \
+ -ltalermhd \
+ -ltalerjson \
+ -ltalerutil \
+ -ltalerpq \
+ -ljansson \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lcurl \
+ $(XLIB)
+taler_merchant_wirewatch_CFLAGS = \
+ $(AM_CFLAGS)
diff --git a/src/backend/kudos.conf b/src/backend/kudos.conf
index 377ff899..55246fb2 100644
--- a/src/backend/kudos.conf
+++ b/src/backend/kudos.conf
@@ -1,16 +1,5 @@
-
-
# Trust Taler project for "KUDOS" currency so that demos work out-of-the-box
[merchant-exchange-kudos]
EXCHANGE_BASE_URL = https://exchange.demo.taler.net/
-MASTER_KEY = FH1Y8ZMHCTPQ0YFSZECDH8C9407JR3YN0MF1706PTG24Q4NEWGV0
-# If currency does not match [TALER] section, the exchange
-# will be ignored!
-CURRENCY = KUDOS
-
-[merchant-auditor-kudos]
-AUDITOR_BASE_URL = https://auditor.demo.taler.net/
-AUDITOR_KEY = DSDASDXAMDAARMNAD53ZA4AFAHA2QADAMAHHASWDAWXN84SDAA11
-# If currency does not match [TALER] section, the auditor
-# will be ignored!
+MASTER_KEY = GNRJCH0HYKN59939JC0CJ2JDC7ZNEBSATJFF00CVS3WPG4TQEA7G
CURRENCY = KUDOS
diff --git a/src/backend/merchant.conf b/src/backend/merchant.conf
index ee492691..44ec72d7 100644
--- a/src/backend/merchant.conf
+++ b/src/backend/merchant.conf
@@ -17,6 +17,10 @@ PORT = 9966
# if left empty. Only used if "SERVE" is 'tcp'.
# BIND_TO =
+# Base URL of the merchant backend. Optional. If not given, the backend will determine
+# the Base URL based on X-Forwarded-* headers (hopefully) set by the reverse proxy.
+# BASE_URL = https://example.com/
+
# How long do we keep contract / payment information around after the
# purchase (for tax records and other legal reasons).
LEGAL_PRESERVATION = 11 years
diff --git a/src/backend/taler-merchant-depositcheck.c b/src/backend/taler-merchant-depositcheck.c
new file mode 100644
index 00000000..9245e1fb
--- /dev/null
+++ b/src/backend/taler-merchant-depositcheck.c
@@ -0,0 +1,1071 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-depositcheck.c
+ * @brief Process that inquires with the exchange for deposits that should have been wired
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <pthread.h>
+#include "taler_merchantdb_lib.h"
+#include "taler_merchantdb_plugin.h"
+#include <taler/taler_dbevents.h>
+
+/**
+ * How many requests do we make at most in parallel to the same exchange?
+ */
+#define CONCURRENCY_LIMIT 32
+
+/**
+ * How long do we not try a deposit check if the deposit
+ * was put on hold due to a KYC/AML block?
+ */
+#define KYC_RETRY_DELAY GNUNET_TIME_UNIT_HOURS
+
+/**
+ * Information we keep per exchange.
+ */
+struct Child
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct Child *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct Child *prev;
+
+ /**
+ * The child process.
+ */
+ struct GNUNET_OS_Process *process;
+
+ /**
+ * Wait handle.
+ */
+ struct GNUNET_ChildWaitHandle *cwh;
+
+ /**
+ * Which exchange is this state for?
+ */
+ char *base_url;
+
+ /**
+ * Task to restart the child.
+ */
+ struct GNUNET_SCHEDULER_Task *rt;
+
+ /**
+ * When should the child be restarted at the earliest?
+ */
+ struct GNUNET_TIME_Absolute next_start;
+
+ /**
+ * Current minimum delay between restarts, grows
+ * exponentially if child exits before this time.
+ */
+ struct GNUNET_TIME_Relative rd;
+
+};
+
+
+/**
+ * Information we keep per exchange interaction.
+ */
+struct ExchangeInteraction
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct ExchangeInteraction *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct ExchangeInteraction *prev;
+
+ /**
+ * Handle for exchange interaction.
+ */
+ struct TALER_EXCHANGE_DepositGetHandle *dgh;
+
+ /**
+ * Wire deadline for the deposit.
+ */
+ struct GNUNET_TIME_Absolute wire_deadline;
+
+ /**
+ * Current value for the retry backoff
+ */
+ struct GNUNET_TIME_Relative retry_backoff;
+
+ /**
+ * Target account hash of the deposit.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * Deposited amount.
+ */
+ struct TALER_Amount amount_with_fee;
+
+ /**
+ * Deposit fee paid.
+ */
+ struct TALER_Amount deposit_fee;
+
+ /**
+ * Public key of the deposited coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Hash over the @e contract_terms.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Merchant instance's private key.
+ */
+ struct TALER_MerchantPrivateKeyP merchant_priv;
+
+ /**
+ * Serial number of the row in the deposits table
+ * that we are processing.
+ */
+ uint64_t deposit_serial;
+
+ /**
+ * The instance the deposit belongs to.
+ */
+ char *instance_id;
+
+};
+
+
+/**
+ * Head of list of children we forked.
+ */
+static struct Child *c_head;
+
+/**
+ * Tail of list of children we forked.
+ */
+static struct Child *c_tail;
+
+/**
+ * Key material of the exchange.
+ */
+static struct TALER_EXCHANGE_Keys *keys;
+
+/**
+ * Handle for active /keys request.
+ */
+static struct TALER_EXCHANGE_GetKeysHandle *gkh;
+
+/**
+ * Head of list of active exchange interactions.
+ */
+static struct ExchangeInteraction *w_head;
+
+/**
+ * Tail of list of active exchange interactions.
+ */
+static struct ExchangeInteraction *w_tail;
+
+/**
+ * Number of active entries in the @e w_head list.
+ */
+static uint64_t w_count;
+
+/**
+ * Notification handler from database on new work.
+ */
+static struct GNUNET_DB_EventHandler *eh;
+
+/**
+ * The merchant's configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Name of the configuration file we use.
+ */
+static char *cfg_filename;
+
+/**
+ * Our database plugin.
+ */
+static struct TALER_MERCHANTDB_Plugin *db_plugin;
+
+/**
+ * Next wire deadline that @e task is scheduled for.
+ */
+static struct GNUNET_TIME_Absolute next_deadline;
+
+/**
+ * Next task to run, if any.
+ */
+static struct GNUNET_SCHEDULER_Task *task;
+
+/**
+ * Handle to the context for interacting with the exchange.
+ */
+static struct GNUNET_CURL_Context *ctx;
+
+/**
+ * Scheduler context for running the @e ctx.
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+/**
+ * Which exchange are we monitoring? NULL if we
+ * are the parent of the workers.
+ */
+static char *exchange_url;
+
+/**
+ * Value to return from main(). 0 on success, non-zero on errors.
+ */
+static int global_ret;
+
+/**
+ * #GNUNET_YES if we are in test mode and should exit when idle.
+ */
+static int test_mode;
+
+
+/**
+ * We're being aborted with CTRL-C (or SIGTERM). Shut down.
+ *
+ * @param cls closure
+ */
+static void
+shutdown_task (void *cls)
+{
+ struct Child *c;
+ struct ExchangeInteraction *w;
+
+ (void) cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running shutdown\n");
+ if (NULL != eh)
+ {
+ db_plugin->event_listen_cancel (eh);
+ eh = NULL;
+ }
+ if (NULL != task)
+ {
+ GNUNET_SCHEDULER_cancel (task);
+ task = NULL;
+ }
+ if (NULL != gkh)
+ {
+ TALER_EXCHANGE_get_keys_cancel (gkh);
+ gkh = NULL;
+ }
+ while (NULL != (w = w_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (w_head,
+ w_tail,
+ w);
+ if (NULL != w->dgh)
+ {
+ TALER_EXCHANGE_deposits_get_cancel (w->dgh);
+ w->dgh = NULL;
+ }
+ w_count--;
+ GNUNET_free (w->instance_id);
+ GNUNET_free (w);
+ }
+ while (NULL != (c = c_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (c_head,
+ c_tail,
+ c);
+ if (NULL != c->rt)
+ {
+ GNUNET_SCHEDULER_cancel (c->rt);
+ c->rt = NULL;
+ }
+ if (NULL != c->cwh)
+ {
+ GNUNET_wait_child_cancel (c->cwh);
+ c->cwh = NULL;
+ }
+ if (NULL != c->process)
+ {
+ enum GNUNET_OS_ProcessStatusType type
+ = GNUNET_OS_PROCESS_UNKNOWN;
+ unsigned long code = 0;
+
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (c->process,
+ SIGTERM));
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_OS_process_wait_status (c->process,
+ &type,
+ &code));
+ if ( (GNUNET_OS_PROCESS_EXITED != type) ||
+ (0 != code) )
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Process for exchange %s had trouble (%d/%d)\n",
+ c->base_url,
+ (int) type,
+ (int) code);
+ GNUNET_OS_process_destroy (c->process);
+ }
+ GNUNET_free (c->base_url);
+ GNUNET_free (c);
+ }
+ if (NULL != db_plugin)
+ {
+ db_plugin->rollback (db_plugin->cls); /* just in case */
+ TALER_MERCHANTDB_plugin_unload (db_plugin);
+ db_plugin = NULL;
+ }
+ cfg = NULL;
+ if (NULL != ctx)
+ {
+ GNUNET_CURL_fini (ctx);
+ ctx = NULL;
+ }
+ if (NULL != rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (rc);
+ rc = NULL;
+ }
+}
+
+
+/**
+ * Task to get more deposits to work on from the database.
+ *
+ * @param cls NULL
+ */
+static void
+select_work (void *cls);
+
+
+/**
+ * Make sure to run the select_work() task at
+ * the @a next_deadline.
+ *
+ * @param deadline time when work becomes ready
+ */
+static void
+run_at (struct GNUNET_TIME_Absolute deadline)
+{
+ if ( (NULL != task) &&
+ (GNUNET_TIME_absolute_cmp (deadline,
+ >,
+ next_deadline)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Not scheduling for %s yet, already have earlier task pending\n",
+ GNUNET_TIME_absolute2s (deadline));
+ return;
+ }
+ if (NULL == keys)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Not scheduling for %s yet, no /keys available\n",
+ GNUNET_TIME_absolute2s (deadline));
+ return; /* too early */
+ }
+ next_deadline = deadline;
+ if (NULL != task)
+ GNUNET_SCHEDULER_cancel (task);
+ task = GNUNET_SCHEDULER_add_at (deadline,
+ &select_work,
+ NULL);
+}
+
+
+/**
+ * Function called with detailed wire transfer data.
+ *
+ * @param cls closure with a `struct ExchangeInteraction *`
+ * @param dr HTTP response data
+ */
+static void
+deposit_get_cb (void *cls,
+ const struct TALER_EXCHANGE_GetDepositResponse *dr)
+{
+ struct ExchangeInteraction *w = cls;
+ struct GNUNET_TIME_Absolute future_retry;
+
+ future_retry
+ = GNUNET_TIME_relative_to_absolute (w->retry_backoff);
+ switch (dr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange returned wire transfer over %s for deposited coin %s\n",
+ TALER_amount2s (&dr->details.ok.coin_contribution),
+ TALER_B2S (&w->coin_pub));
+ qs = db_plugin->insert_deposit_to_transfer (db_plugin->cls,
+ w->deposit_serial,
+ &dr->details.ok);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ qs = db_plugin->update_deposit_confirmation_status (
+ db_plugin->cls,
+ w->deposit_serial,
+ false, /* we are done, wire_pending is now false */
+ GNUNET_TIME_absolute_to_timestamp (future_retry),
+ w->retry_backoff,
+ NULL);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ break;
+ }
+ case MHD_HTTP_ACCEPTED:
+ {
+ /* got a 'preliminary' reply from the exchange,
+ remember our target UUID */
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp now;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange returned KYC requirement (%d/%d) for deposited coin %s\n",
+ dr->details.accepted.kyc_ok,
+ dr->details.accepted.aml_decision,
+ TALER_B2S (&w->coin_pub));
+ now = GNUNET_TIME_timestamp_get ();
+ qs = db_plugin->account_kyc_set_status (
+ db_plugin->cls,
+ w->instance_id,
+ &w->h_wire,
+ exchange_url,
+ dr->details.accepted.requirement_row,
+ NULL,
+ NULL,
+ now,
+ dr->details.accepted.kyc_ok,
+ dr->details.accepted.aml_decision);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (dr->details.accepted.kyc_ok &&
+ (TALER_AML_NORMAL ==
+ dr->details.accepted.aml_decision))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
+ GNUNET_TIME_absolute2s (future_retry));
+ qs = db_plugin->update_deposit_confirmation_status (
+ db_plugin->cls,
+ w->deposit_serial,
+ true, /* wire_pending is still true! */
+ GNUNET_TIME_absolute_to_timestamp (future_retry),
+ w->retry_backoff,
+ "Exchange reported 202 Accepted but no KYC block");
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
+ else
+ {
+ future_retry
+ = GNUNET_TIME_absolute_max (
+ future_retry,
+ GNUNET_TIME_relative_to_absolute (
+ KYC_RETRY_DELAY));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
+ GNUNET_TIME_absolute2s (future_retry));
+ qs = db_plugin->update_deposit_confirmation_status (
+ db_plugin->cls,
+ w->deposit_serial,
+ true,
+ GNUNET_TIME_absolute_to_timestamp (future_retry),
+ w->retry_backoff,
+ "Exchange reported 202 Accepted due to KYC/AML block");
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
+ break;
+ }
+ default:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ char *msg;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange %s returned tracking failure for deposited coin %s\n",
+ exchange_url,
+ TALER_B2S (&w->coin_pub));
+ GNUNET_asprintf (&msg,
+ "Unexpected exchange status %u (#%d, %s)\n",
+ dr->hr.http_status,
+ (int) dr->hr.ec,
+ dr->hr.hint);
+ qs = db_plugin->update_deposit_confirmation_status (
+ db_plugin->cls,
+ w->deposit_serial,
+ true, /* this failed, wire_pending remains true */
+ GNUNET_TIME_absolute_to_timestamp (future_retry),
+ w->retry_backoff,
+ msg);
+ GNUNET_free (msg);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ return;
+ }
+ } /* end switch */
+
+ GNUNET_CONTAINER_DLL_remove (w_head,
+ w_tail,
+ w);
+ w_count--;
+ GNUNET_free (w->instance_id);
+ GNUNET_free (w);
+ GNUNET_assert (NULL != keys);
+ if ( (w_count < CONCURRENCY_LIMIT / 2) ||
+ (0 == w_count) )
+ {
+ if (NULL != task)
+ GNUNET_SCHEDULER_cancel (task);
+ task = GNUNET_SCHEDULER_add_now (&select_work,
+ NULL);
+ }
+}
+
+
+/**
+ * Typically called by `select_work`.
+ *
+ * @param cls NULL
+ * @param deposit_serial identifies the deposit operation
+ * @param wire_deadline when is the wire due
+ * @param retry_backoff current value for the retry backoff
+ * @param h_contract_terms hash of the contract terms
+ * @param merchant_priv private key of the merchant
+ * @param instance_id row ID of the instance
+ * @param h_wire hash of the merchant's wire account into * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin which the deposit was made
+ * @param coin_pub public key of the deposited coin
+ */
+static void
+pending_deposits_cb (
+ void *cls,
+ uint64_t deposit_serial,
+ struct GNUNET_TIME_Absolute wire_deadline,
+ struct GNUNET_TIME_Relative retry_backoff,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ const char *instance_id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub)
+{
+ struct ExchangeInteraction *w = GNUNET_new (struct ExchangeInteraction);
+
+ (void) cls;
+ if (GNUNET_TIME_absolute_is_future (wire_deadline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Pending deposit has deadline in the future at %s\n",
+ GNUNET_TIME_absolute2s (wire_deadline));
+ run_at (wire_deadline);
+ return;
+ }
+ w->deposit_serial = deposit_serial;
+ w->wire_deadline = wire_deadline;
+ w->retry_backoff = GNUNET_TIME_STD_BACKOFF (retry_backoff);
+ w->h_contract_terms = *h_contract_terms;
+ w->merchant_priv = *merchant_priv;
+ w->h_wire = *h_wire;
+ w->amount_with_fee = *amount_with_fee;
+ w->deposit_fee = *deposit_fee;
+ w->coin_pub = *coin_pub;
+ w->instance_id = GNUNET_strdup (instance_id);
+ GNUNET_CONTAINER_DLL_insert (w_head,
+ w_tail,
+ w);
+ w_count++;
+ GNUNET_assert (NULL != keys);
+ if (GNUNET_TIME_absolute_is_past (keys->key_data_expiration.abs_time))
+ {
+ /* Parent should re-start us, then we will re-fetch /keys */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "/keys expired, shutting down\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_assert (NULL == w->dgh);
+ w->dgh = TALER_EXCHANGE_deposits_get (
+ ctx,
+ exchange_url,
+ keys,
+ &w->merchant_priv,
+ &w->h_wire,
+ &w->h_contract_terms,
+ &w->coin_pub,
+ GNUNET_TIME_UNIT_ZERO,
+ &deposit_get_cb,
+ w);
+}
+
+
+/**
+ * Function called on events received from Postgres.
+ *
+ * @param cls closure, NULL
+ * @param extra additional event data provided, timestamp with wire deadline
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+db_notify (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ struct GNUNET_TIME_Absolute deadline;
+ struct GNUNET_TIME_AbsoluteNBO nbo_deadline;
+
+ (void) cls;
+ if (sizeof (nbo_deadline) != extra_size)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ if (0 != w_count)
+ return; /* already at work! */
+ memcpy (&nbo_deadline,
+ extra,
+ extra_size);
+ deadline = GNUNET_TIME_absolute_ntoh (nbo_deadline);
+ run_at (deadline);
+}
+
+
+static void
+select_work (void *cls)
+{
+ bool retry = false;
+ uint64_t limit = CONCURRENCY_LIMIT - w_count;
+
+ (void) cls;
+ task = NULL;
+ GNUNET_assert (w_count <= CONCURRENCY_LIMIT);
+ GNUNET_assert (NULL != keys);
+ if (0 == limit)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ if (GNUNET_TIME_absolute_is_past (keys->key_data_expiration.abs_time))
+ {
+ /* Parent should re-start us, then we will re-fetch /keys */
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ while (1)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ db_plugin->preflight (db_plugin->cls);
+ if (retry)
+ limit = 1;
+ qs = db_plugin->lookup_pending_deposits (db_plugin->cls,
+ exchange_url,
+ limit,
+ retry,
+ &pending_deposits_cb,
+ NULL);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Looking up pending deposits query status was %d\n",
+ (int) qs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Transaction failed!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ if (test_mode)
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (retry)
+ return; /* nothing left */
+ retry = true;
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ default:
+ /* wait for async completion, then select more work. */
+ return;
+ }
+ }
+}
+
+
+/**
+ * Function called with information about who is auditing
+ * a particular exchange and what keys the exchange is using.
+ * The ownership over the @a keys object is passed to
+ * the callee, thus it is given explicitly and not
+ * (only) via @a kr.
+ *
+ * @param cls closure, NULL
+ * @param kr response from /keys
+ * @param[in] in_keys keys object passed to callback with
+ * reference counter of 1. Must be freed by callee
+ * using TALER_EXCHANGE_keys_decref(). NULL on failure.
+ */
+static void
+keys_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_KeysResponse *kr,
+ struct TALER_EXCHANGE_Keys *in_keys)
+{
+ gkh = NULL;
+ if (NULL == in_keys)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to download %skeys\n",
+ exchange_url);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ keys = TALER_EXCHANGE_keys_incref (in_keys);
+ if (NULL != task)
+ GNUNET_SCHEDULER_cancel (task);
+ task = GNUNET_SCHEDULER_add_now (&select_work,
+ NULL);
+}
+
+
+/**
+ * Start a copy of this process with the exchange URL
+ * set to the given @a base_url
+ *
+ * @param base_url base URL to run with
+ */
+static struct GNUNET_OS_Process *
+start_worker (const char *base_url)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Launching worker for exchange `%s' using `%s`\n",
+ base_url,
+ cfg_filename);
+ return GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
+ NULL,
+ NULL,
+ NULL,
+ "taler-merchant-depositcheck",
+ "taler-merchant-depositcheck",
+ "-c", cfg_filename,
+ "-e", base_url,
+ "-L", "INFO",
+ test_mode ? "-t" : NULL,
+ NULL);
+}
+
+
+/**
+ * Restart worker process for the given child.
+ *
+ * @param cls a `struct Child *` that needs a worker.
+ */
+static void
+restart_child (void *cls);
+
+
+/**
+ * Function called upon death or completion of a child process.
+ *
+ * @param cls a `struct Child *`
+ * @param type type of the process
+ * @param exit_code status code of the process
+ */
+static void
+child_done_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int exit_code)
+{
+ struct Child *c = cls;
+
+ c->cwh = NULL;
+ if ( (GNUNET_OS_PROCESS_EXITED != type) ||
+ (0 != exit_code) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Process for exchange %s had trouble (%d/%d)\n",
+ c->base_url,
+ (int) type,
+ (int) exit_code);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NOTINSTALLED;
+ return;
+ }
+ GNUNET_OS_process_destroy (c->process);
+ c->process = NULL;
+ if (test_mode &&
+ (! GNUNET_TIME_relative_is_zero (c->rd)) )
+ {
+ return;
+ }
+ if (GNUNET_TIME_absolute_is_future (c->next_start))
+ c->rd = GNUNET_TIME_STD_BACKOFF (c->rd);
+ else
+ c->rd = GNUNET_TIME_UNIT_SECONDS;
+ c->rt = GNUNET_SCHEDULER_add_at (c->next_start,
+ &restart_child,
+ c);
+}
+
+
+static void
+restart_child (void *cls)
+{
+ struct Child *c = cls;
+
+ c->rt = NULL;
+ c->next_start = GNUNET_TIME_relative_to_absolute (c->rd);
+ c->process = start_worker (c->base_url);
+ if (NULL == c->process)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "exec");
+ global_ret = EXIT_NO_RESTART;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ c->cwh = GNUNET_wait_child (c->process,
+ &child_done_cb,
+ c);
+}
+
+
+/**
+ * Function to iterate over section.
+ *
+ * @param cls closure
+ * @param section name of the section
+ */
+static void
+cfg_iter_cb (void *cls,
+ const char *section)
+{
+ char *base_url;
+ struct Child *c;
+
+ if (0 !=
+ strncasecmp (section,
+ "merchant-exchange-",
+ strlen ("merchant-exchange-")))
+ return;
+ if (GNUNET_YES ==
+ GNUNET_CONFIGURATION_get_value_yesno (cfg,
+ section,
+ "DISABLED"))
+ return;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "EXCHANGE_BASE_URL",
+ &base_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+ section,
+ "EXCHANGE_BASE_URL");
+ return;
+ }
+ c = GNUNET_new (struct Child);
+ c->rd = GNUNET_TIME_UNIT_SECONDS;
+ c->base_url = base_url;
+ GNUNET_CONTAINER_DLL_insert (c_head,
+ c_tail,
+ c);
+ c->rt = GNUNET_SCHEDULER_add_now (&restart_child,
+ c);
+}
+
+
+/**
+ * First task.
+ *
+ * @param cls closure, NULL
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param c configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *c)
+{
+ (void) args;
+
+ cfg = c;
+ cfg_filename = GNUNET_strdup (cfgfile);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running with configuration %s\n",
+ cfgfile);
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ NULL);
+ if (NULL == exchange_url)
+ {
+ GNUNET_CONFIGURATION_iterate_sections (c,
+ &cfg_iter_cb,
+ NULL);
+ if (NULL == c_head)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No exchanges found in configuration\n");
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ return;
+ }
+
+ ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &rc);
+ rc = GNUNET_CURL_gnunet_rc_create (ctx);
+ if (NULL == ctx)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NO_RESTART;
+ return;
+ }
+ if (NULL ==
+ (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_OK !=
+ db_plugin->connect (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to connect to database\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NO_RESTART;
+ return;
+ }
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE)
+ };
+
+ eh = db_plugin->event_listen (db_plugin->cls,
+ &es,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &db_notify,
+ NULL);
+ }
+ gkh = TALER_EXCHANGE_get_keys (ctx,
+ exchange_url,
+ NULL,
+ &keys_cb,
+ NULL);
+}
+
+
+/**
+ * The main function of the taler-merchant-depositcheck
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_string ('e',
+ "exchange",
+ "BASE_URL",
+ "limit us to checking deposits of this exchange",
+ &exchange_url),
+ GNUNET_GETOPT_option_timetravel ('T',
+ "timetravel"),
+ GNUNET_GETOPT_option_flag ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
+ GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+ TALER_OS_init ();
+ ret = GNUNET_PROGRAM_run (
+ argc, argv,
+ "taler-merchant-depositcheck",
+ gettext_noop (
+ "background process that checks with the exchange on deposits that are past the wire deadline"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
+}
+
+
+/* end of taler-merchant-depositcheck.c */
diff --git a/src/backend/taler-merchant-exchange.c b/src/backend/taler-merchant-exchange.c
new file mode 100644
index 00000000..e3b0d6c4
--- /dev/null
+++ b/src/backend/taler-merchant-exchange.c
@@ -0,0 +1,1255 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-exchange.c
+ * @brief Process that reconciles information about incoming bank transfers with orders by asking the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <pthread.h>
+#include <taler/taler_dbevents.h>
+#include "taler_merchant_bank_lib.h"
+#include "taler_merchantdb_lib.h"
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Timeout for the exchange interaction. Rather long as we should do
+ * long-polling and do not want to wake up too often.
+ */
+#define EXCHANGE_TIMEOUT GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_MINUTES, \
+ 30)
+
+/**
+ * How many inquiries do we process concurrently at most.
+ */
+#define OPEN_INQUIRY_LIMIT 1024
+
+/**
+ * How many inquiries do we process concurrently per exchange at most.
+ */
+#define EXCHANGE_INQUIRY_LIMIT 16
+
+
+/**
+ * Information about an inquiry job.
+ */
+struct Inquiry;
+
+
+/**
+ * Information about an exchange.
+ */
+struct Exchange
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct Exchange *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct Exchange *prev;
+
+ /**
+ * Head of active inquiries.
+ */
+ struct Inquiry *w_head;
+
+ /**
+ * Tail of active inquiries.
+ */
+ struct Inquiry *w_tail;
+
+ /**
+ * Which exchange are we tracking here.
+ */
+ char *exchange_url;
+
+ /**
+ * A connection to this exchange
+ */
+ struct TALER_EXCHANGE_GetKeysHandle *conn;
+
+ /**
+ * The keys of this exchange
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * Task where we retry fetching /keys from the exchange.
+ */
+ struct GNUNET_SCHEDULER_Task *retry_task;
+
+ /**
+ * How many active inquiries do we have right now with this exchange.
+ */
+ unsigned int exchange_inquiries;
+
+ /**
+ * How soon can may we, at the earliest, re-download /keys?
+ */
+ struct GNUNET_TIME_Absolute first_retry;
+
+ /**
+ * How long should we wait between the next retry?
+ */
+ struct GNUNET_TIME_Relative retry_delay;
+
+ /**
+ * How long should we wait between requests
+ * for transfer details?
+ */
+ struct GNUNET_TIME_Relative transfer_delay;
+
+ /**
+ * False to indicate that there is an ongoing
+ * /keys transfer we are waiting for;
+ * true to indicate that /keys data is up-to-date.
+ */
+ bool ready;
+
+};
+
+
+/**
+ * Information about an inquiry job.
+ */
+struct Inquiry
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct Inquiry *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct Inquiry *prev;
+
+ /**
+ * Handle to the exchange that made the transfer.
+ */
+ struct Exchange *exchange;
+
+ /**
+ * Task where we retry fetching transfer details from the exchange.
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * For which merchant instance is this tracking request?
+ */
+ char *instance_id;
+
+ /**
+ * payto:// URI used for the transfer.
+ */
+ char *payto_uri;
+
+ /**
+ * Handle for the /wire/transfers request.
+ */
+ struct TALER_EXCHANGE_TransfersGetHandle *wdh;
+
+ /**
+ * When did the transfer happen?
+ */
+ struct GNUNET_TIME_Timestamp execution_time;
+
+ /**
+ * Argument for the /wire/transfers request.
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+ /**
+ * Amount of the wire transfer.
+ */
+ struct TALER_Amount total;
+
+ /**
+ * Row of the wire transfer in our database.
+ */
+ uint64_t rowid;
+
+};
+
+
+/**
+ * Head of known exchanges.
+ */
+static struct Exchange *e_head;
+
+/**
+ * Tail of known exchanges.
+ */
+static struct Exchange *e_tail;
+
+/**
+ * The merchant's configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Our database plugin.
+ */
+static struct TALER_MERCHANTDB_Plugin *db_plugin;
+
+/**
+ * Handle to the context for interacting with the bank.
+ */
+static struct GNUNET_CURL_Context *ctx;
+
+/**
+ * Scheduler context for running the @e ctx.
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+/**
+ * Main task for #find_work().
+ */
+static struct GNUNET_SCHEDULER_Task *task;
+
+/**
+ * Event handler to learn that there are new transfers
+ * to check.
+ */
+static struct GNUNET_DB_EventHandler *eh;
+
+/**
+ * How many active inquiries do we have right now.
+ */
+static unsigned int active_inquiries;
+
+/**
+ * Value to return from main(). 0 on success, non-zero on errors.
+ */
+static int global_ret;
+
+/**
+ * #GNUNET_YES if we are in test mode and should exit when idle.
+ */
+static int test_mode;
+
+/**
+ * True if the last DB query was limited by the
+ * #OPEN_INQUIRY_LIMIT and we thus should check again
+ * as soon as we are substantially below that limit,
+ * and not only when we get a DB notification.
+ */
+static bool at_limit;
+
+
+/**
+ * Initiate download from exchange.
+ *
+ * @param cls a `struct Inquiry *`
+ */
+static void
+exchange_request (void *cls);
+
+
+/**
+ * The exchange @a e is ready to handle more inquiries,
+ * prepare to launch them.
+ *
+ * @param[in,out] e exchange to potentially launch inquiries on
+ */
+static void
+launch_inquiries_at_exchange (struct Exchange *e)
+{
+ for (struct Inquiry *w = e->w_head;
+ NULL != w;
+ w = w->next)
+ {
+ if (e->exchange_inquiries > EXCHANGE_INQUIRY_LIMIT)
+ break;
+ if ( (NULL == w->task) &&
+ (NULL == w->wdh) )
+ {
+ e->exchange_inquiries++;
+ w->task = GNUNET_SCHEDULER_add_now (&exchange_request,
+ w);
+ }
+ }
+}
+
+
+/**
+ * Function that initiates a /keys download.
+ *
+ * @param cls a `struct Exchange *`
+ */
+static void
+download_keys (void *cls);
+
+
+/**
+ * Function called with information about who is auditing
+ * a particular exchange and what keys the exchange is using.
+ *
+ * @param cls closure with a `struct Exchange *`
+ * @param kr response data
+ * @param[in] keys the keys of the exchange
+ */
+static void
+cert_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_KeysResponse *kr,
+ struct TALER_EXCHANGE_Keys *keys)
+{
+ struct Exchange *e = cls;
+ struct GNUNET_TIME_Absolute n;
+
+ e->conn = NULL;
+ switch (kr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ e->ready = true;
+ TALER_EXCHANGE_keys_decref (e->keys);
+ e->keys = keys;
+ launch_inquiries_at_exchange (e);
+ /* Reset back-off */
+ e->retry_delay = GNUNET_TIME_UNIT_ZERO;
+ /* Success: rate limit at once per minute */
+ e->first_retry = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_UNIT_MINUTES);
+ n = GNUNET_TIME_absolute_max (e->first_retry,
+ keys->key_data_expiration.abs_time);
+ if (NULL != e->retry_task)
+ GNUNET_SCHEDULER_cancel (e->retry_task);
+ e->retry_task = GNUNET_SCHEDULER_add_at (n,
+ &download_keys,
+ e);
+ break;
+ default:
+ e->retry_delay
+ = GNUNET_TIME_STD_BACKOFF (e->retry_delay);
+ e->first_retry
+ = GNUNET_TIME_relative_to_absolute (e->retry_delay);
+ if (NULL != e->retry_task)
+ GNUNET_SCHEDULER_cancel (e->retry_task);
+ e->retry_task = GNUNET_SCHEDULER_add_delayed (e->retry_delay,
+ &download_keys,
+ e);
+ break;
+ }
+}
+
+
+static void
+download_keys (void *cls)
+{
+ struct Exchange *e = cls;
+ struct GNUNET_TIME_Relative n;
+
+ /* If we do not hear back again soon, try again automatically */
+ n = GNUNET_TIME_STD_BACKOFF (e->retry_delay);
+ n = GNUNET_TIME_relative_max (n,
+ GNUNET_TIME_UNIT_MINUTES);
+ e->retry_task = GNUNET_SCHEDULER_add_delayed (n,
+ &download_keys,
+ e);
+ if ( (NULL == e->keys) ||
+ (GNUNET_TIME_absolute_is_past (e->keys->key_data_expiration.abs_time)) )
+ e->conn = TALER_EXCHANGE_get_keys (ctx,
+ e->exchange_url,
+ e->keys,
+ &cert_cb,
+ e);
+}
+
+
+/**
+ * Updates the transaction status for inquiry @a w to the given values.
+ *
+ * @param w inquiry to update status for
+ * @param next_attempt when should we retry @a w (if ever)
+ * @param ec error code to use (if any)
+ * @param failed failure status (if ultimately failed)
+ * @param verified success status (if ultimately successful)
+ */
+static void
+update_transaction_status (const struct Inquiry *w,
+ struct GNUNET_TIME_Absolute next_attempt,
+ enum TALER_ErrorCode ec,
+ bool failed,
+ bool verified)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db_plugin->update_transfer_status (db_plugin->cls,
+ w->exchange->exchange_url,
+ &w->wtid,
+ next_attempt,
+ ec,
+ failed,
+ verified);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+}
+
+
+/**
+ * Lookup our internal data structure for the given
+ * @a exchange_url or create one if we do not yet have
+ * one.
+ *
+ * @param exchange_url base URL of the exchange
+ * @return our state for this exchange
+ */
+static struct Exchange *
+find_exchange (const char *exchange_url)
+{
+ struct Exchange *e;
+
+ for (e = e_head; NULL != e; e = e->next)
+ if (0 == strcmp (exchange_url,
+ e->exchange_url))
+ return e;
+ e = GNUNET_new (struct Exchange);
+ e->exchange_url = GNUNET_strdup (exchange_url);
+ GNUNET_CONTAINER_DLL_insert (e_head,
+ e_tail,
+ e);
+ e->retry_task = GNUNET_SCHEDULER_add_now (&download_keys,
+ e);
+ return e;
+}
+
+
+/**
+ * Finds new transfers that require work in the merchant database.
+ *
+ * @param cls NULL
+ */
+static void
+find_work (void *cls);
+
+
+/**
+ * Free resources of @a w.
+ *
+ * @param[in] w inquiry job to terminate
+ */
+static void
+end_inquiry (struct Inquiry *w)
+{
+ struct Exchange *e = w->exchange;
+
+ GNUNET_assert (active_inquiries > 0);
+ active_inquiries--;
+ if (NULL != w->wdh)
+ {
+ TALER_EXCHANGE_transfers_get_cancel (w->wdh);
+ w->wdh = NULL;
+ }
+ GNUNET_free (w->instance_id);
+ GNUNET_free (w->payto_uri);
+ GNUNET_CONTAINER_DLL_remove (e->w_head,
+ e->w_tail,
+ w);
+ GNUNET_free (w);
+ if ( (active_inquiries < OPEN_INQUIRY_LIMIT / 2) &&
+ (NULL == task) &&
+ (at_limit) )
+ {
+ at_limit = false;
+ task = GNUNET_SCHEDULER_add_now (&find_work,
+ NULL);
+ }
+ if ( (NULL == task) &&
+ (! at_limit) &&
+ (0 == active_inquiries) &&
+ (test_mode) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No more open inquiries and in test mode. Existing.\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+}
+
+
+/**
+ * We're being aborted with CTRL-C (or SIGTERM). Shut down.
+ *
+ * @param cls closure (NULL)
+ */
+static void
+shutdown_task (void *cls)
+{
+ (void) cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running shutdown\n");
+ while (NULL != e_head)
+ {
+ struct Exchange *e = e_head;
+
+ while (NULL != e->w_head)
+ {
+ struct Inquiry *w = e->w_head;
+
+ end_inquiry (w);
+ }
+ GNUNET_free (e->exchange_url);
+ if (NULL != e->conn)
+ {
+ TALER_EXCHANGE_get_keys_cancel (e->conn);
+ e->conn = NULL;
+ }
+ if (NULL != e->keys)
+ {
+ TALER_EXCHANGE_keys_decref (e->keys);
+ e->keys = NULL;
+ }
+ if (NULL != e->retry_task)
+ {
+ GNUNET_SCHEDULER_cancel (e->retry_task);
+ e->retry_task = NULL;
+ }
+ GNUNET_CONTAINER_DLL_remove (e_head,
+ e_tail,
+ e);
+ GNUNET_free (e);
+ }
+ if (NULL != eh)
+ {
+ db_plugin->event_listen_cancel (eh);
+ eh = NULL;
+ }
+ TALER_MERCHANTDB_plugin_unload (db_plugin);
+ db_plugin = NULL;
+ cfg = NULL;
+ if (NULL != ctx)
+ {
+ GNUNET_CURL_fini (ctx);
+ ctx = NULL;
+ }
+ if (NULL != rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (rc);
+ rc = NULL;
+ }
+}
+
+
+/**
+ * Check that the given @a wire_fee is what the @a e should charge
+ * at the @a execution_time. If the fee is correct (according to our
+ * database), return #GNUNET_OK. If we do not have the fee structure in our
+ * DB, we just accept it and return #GNUNET_NO; if we have proof that the fee
+ * is bogus, we respond with the proof to the client and return
+ * #GNUNET_SYSERR.
+ *
+ * @param w inquiry to check fees of
+ * @param execution_time time of the wire transfer
+ * @param wire_fee fee claimed by the exchange
+ * @return #GNUNET_SYSERR if we returned hard proof of
+ * missbehavior from the exchange to the client
+ */
+static enum GNUNET_GenericReturnValue
+check_wire_fee (struct Inquiry *w,
+ struct GNUNET_TIME_Timestamp execution_time,
+ const struct TALER_Amount *wire_fee)
+{
+ struct Exchange *e = w->exchange;
+ const struct TALER_EXCHANGE_Keys *keys = e->keys;
+ struct TALER_WireFeeSet fees;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ enum GNUNET_DB_QueryStatus qs;
+ char *wire_method;
+
+ if (NULL == keys)
+ {
+ GNUNET_break (0);
+ return GNUNET_NO;
+ }
+ wire_method = TALER_payto_get_method (w->payto_uri);
+ qs = db_plugin->lookup_wire_fee (db_plugin->cls,
+ &keys->master_pub,
+ wire_method,
+ execution_time,
+ &fees,
+ &start_date,
+ &end_date,
+ &master_sig);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ GNUNET_free (wire_method);
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_free (wire_method);
+ return GNUNET_NO;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to find wire fee for `%s' and method `%s' at %s in DB, accepting blindly that the fee is %s\n",
+ TALER_B2S (&keys->master_pub),
+ wire_method,
+ GNUNET_TIME_timestamp2s (execution_time),
+ TALER_amount2s (wire_fee));
+ GNUNET_free (wire_method);
+ return GNUNET_OK;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (&fees.wire,
+ wire_fee)) ||
+ (0 > TALER_amount_cmp (&fees.wire,
+ wire_fee)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (wire_method);
+ return GNUNET_SYSERR; /* expected_fee >= wire_fee */
+ }
+ GNUNET_free (wire_method);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Closure for #check_transfer()
+ */
+struct CheckTransferContext
+{
+
+ /**
+ * Pointer to the detail that we are currently
+ * checking in #check_transfer().
+ */
+ const struct TALER_TrackTransferDetails *current_detail;
+
+ /**
+ * Which transaction detail are we currently looking at?
+ */
+ unsigned int current_offset;
+
+ /**
+ * #GNUNET_NO if we did not find a matching coin.
+ * #GNUNET_SYSERR if we found a matching coin, but the amounts do not match.
+ * #GNUNET_OK if we did find a matching coin.
+ */
+ enum GNUNET_GenericReturnValue check_transfer_result;
+
+ /**
+ * Set to error code, if any.
+ */
+ enum TALER_ErrorCode ec;
+
+ /**
+ * Set to true if @e ec indicates a permanent failure.
+ */
+ bool failure;
+};
+
+
+/**
+ * This function checks that the information about the coin which
+ * was paid back by _this_ wire transfer matches what _we_ (the merchant)
+ * knew about this coin.
+ *
+ * @param cls closure with our `struct CheckTransferContext *`
+ * @param exchange_url URL of the exchange that issued @a coin_pub
+ * @param amount_with_fee amount the exchange will transfer for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param refund_fee fee the exchange will charge for refunding this coin
+ * @param wire_fee paid wire fee
+ * @param h_wire hash of merchant's wire details
+ * @param deposit_timestamp when did the exchange receive the deposit
+ * @param refund_deadline until when are refunds allowed
+ * @param exchange_sig signature by the exchange
+ * @param exchange_pub exchange signing key used for @a exchange_sig
+ */
+static void
+check_transfer (void *cls,
+ const char *exchange_url,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_Amount *refund_fee,
+ const struct TALER_Amount *wire_fee,
+ const struct TALER_MerchantWireHashP *h_wire,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+ struct CheckTransferContext *ctc = cls;
+ const struct TALER_TrackTransferDetails *ttd = ctc->current_detail;
+
+ if (GNUNET_SYSERR == ctc->check_transfer_result)
+ {
+ GNUNET_break (0);
+ return; /* already had a serious issue; odd that we're called more than once as well... */
+ }
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (amount_with_fee,
+ &ttd->coin_value)) ||
+ (0 != TALER_amount_cmp (amount_with_fee,
+ &ttd->coin_value)) ||
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (deposit_fee,
+ &ttd->coin_fee)) ||
+ (0 != TALER_amount_cmp (deposit_fee,
+ &ttd->coin_fee)) )
+ {
+ /* Disagreement between the exchange and us about how much this
+ coin is worth! */
+ GNUNET_break_op (0);
+ ctc->check_transfer_result = GNUNET_SYSERR;
+ /* Build the `TrackTransferConflictDetails` */
+ ctc->ec = TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_REPORTS;
+ ctc->failure = true;
+ /* FIXME: this should be reported to the auditor (once the auditor has an API for this) */
+ return;
+ }
+ ctc->check_transfer_result = GNUNET_OK;
+}
+
+
+/**
+ * Function called with detailed wire transfer data, including all
+ * of the coin transactions that were combined into the wire transfer.
+ *
+ * @param cls closure a `struct Inquiry *`
+ * @param tgr response details
+ */
+static void
+wire_transfer_cb (void *cls,
+ const struct TALER_EXCHANGE_TransfersGetResponse *tgr)
+{
+ struct Inquiry *w = cls;
+ struct Exchange *e = w->exchange;
+ enum GNUNET_DB_QueryStatus qs;
+ const struct TALER_EXCHANGE_TransferData *td = NULL;
+
+ e->exchange_inquiries--;
+ w->wdh = NULL;
+ if (EXCHANGE_INQUIRY_LIMIT - 1 == e->exchange_inquiries)
+ launch_inquiries_at_exchange (e);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got response code %u from exchange for GET /transfers/$WTID\n",
+ tgr->hr.http_status);
+ switch (tgr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ td = &tgr->details.ok.td;
+ w->execution_time = td->execution_time;
+ e->transfer_delay = GNUNET_TIME_UNIT_ZERO;
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_FORBIDDEN:
+ update_transaction_status (w,
+ GNUNET_TIME_UNIT_FOREVER_ABS,
+ TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_HARD_FAILURE,
+ true,
+ false);
+ end_inquiry (w);
+ return;
+ case MHD_HTTP_NOT_FOUND:
+ update_transaction_status (w,
+ GNUNET_TIME_UNIT_FOREVER_ABS,
+ TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_FATAL_NOT_FOUND,
+ true,
+ false);
+ end_inquiry (w);
+ return;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ case MHD_HTTP_BAD_GATEWAY:
+ case MHD_HTTP_GATEWAY_TIMEOUT:
+ e->transfer_delay = GNUNET_TIME_STD_BACKOFF (e->transfer_delay);
+ update_transaction_status (w,
+ GNUNET_TIME_relative_to_absolute (
+ e->transfer_delay),
+ TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_TRANSIENT_FAILURE,
+ false,
+ false);
+ end_inquiry (w);
+ return;
+ default:
+ e->transfer_delay = GNUNET_TIME_STD_BACKOFF (e->transfer_delay);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected HTTP status %u\n",
+ tgr->hr.http_status);
+ update_transaction_status (w,
+ GNUNET_TIME_relative_to_absolute (
+ e->transfer_delay),
+ TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_TRANSIENT_FAILURE,
+ false,
+ false);
+ end_inquiry (w);
+ return;
+ }
+ db_plugin->preflight (db_plugin->cls);
+ qs = db_plugin->insert_transfer_details (db_plugin->cls,
+ w->instance_id,
+ w->exchange->exchange_url,
+ w->payto_uri,
+ &w->wtid,
+ td);
+ if (0 > qs)
+ {
+ /* Always report on DB error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Transfer already known. Ignoring duplicate.\n");
+ return;
+ }
+
+ {
+ struct CheckTransferContext ctc = {
+ .ec = TALER_EC_NONE,
+ .failure = false
+ };
+
+ for (unsigned int i = 0; i<td->details_length; i++)
+ {
+ const struct TALER_TrackTransferDetails *ttd = &td->details[i];
+ enum GNUNET_DB_QueryStatus qs;
+
+ if (TALER_EC_NONE != ctc.ec)
+ break; /* already encountered an error */
+ ctc.current_offset = i;
+ ctc.current_detail = ttd;
+ /* Set the coin as "never seen" before. */
+ ctc.check_transfer_result = GNUNET_NO;
+ qs = db_plugin->lookup_deposits_by_contract_and_coin (
+ db_plugin->cls,
+ w->instance_id,
+ &ttd->h_contract_terms,
+ &ttd->coin_pub,
+ &check_transfer,
+ &ctc);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ ctc.ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
+ break;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ ctc.ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* The exchange says we made this deposit, but WE do not
+ recall making it (corrupted / unreliable database?)!
+ Well, let's say thanks and accept the money! */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to find payment data in DB\n");
+ ctc.check_transfer_result = GNUNET_OK;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ switch (ctc.check_transfer_result)
+ {
+ case GNUNET_NO:
+ /* Internal error: how can we have called #check_transfer()
+ but still have no result? */
+ GNUNET_break (0);
+ ctc.ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ return;
+ case GNUNET_SYSERR:
+ /* #check_transfer() failed, report conflict! */
+ GNUNET_break_op (0);
+ GNUNET_assert (TALER_EC_NONE != ctc.ec);
+ break;
+ case GNUNET_OK:
+ break;
+ }
+ }
+ if (TALER_EC_NONE != ctc.ec)
+ {
+ update_transaction_status (
+ w,
+ ctc.failure
+ ? GNUNET_TIME_UNIT_FOREVER_ABS
+ : GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_UNIT_MINUTES),
+ ctc.ec,
+ ctc.failure,
+ false);
+ end_inquiry (w);
+ return;
+ }
+ }
+
+ if (GNUNET_SYSERR ==
+ check_wire_fee (w,
+ td->execution_time,
+ &td->wire_fee))
+ {
+ GNUNET_break_op (0);
+ update_transaction_status (w,
+ GNUNET_TIME_UNIT_FOREVER_ABS,
+ TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_BAD_WIRE_FEE,
+ true,
+ false);
+ end_inquiry (w);
+ return;
+ }
+
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (&td->total_amount,
+ &w->total)) ||
+ (0 !=
+ TALER_amount_cmp (&td->total_amount,
+ &w->total)) )
+ {
+ GNUNET_break_op (0);
+ update_transaction_status (w,
+ GNUNET_TIME_UNIT_FOREVER_ABS,
+ TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_CONFLICTING_TRANSFERS,
+ true,
+ false);
+ end_inquiry (w);
+ return;
+ }
+ /* set transaction to successful */
+ update_transaction_status (w,
+ GNUNET_TIME_UNIT_FOREVER_ABS,
+ TALER_EC_NONE,
+ false,
+ true);
+ end_inquiry (w);
+}
+
+
+/**
+ * Initiate download from an exchange for a given inquiry.
+ *
+ * @param cls a `struct Inquiry *`
+ */
+static void
+exchange_request (void *cls)
+{
+ struct Inquiry *w = cls;
+ struct Exchange *e = w->exchange;
+
+ w->task = NULL;
+ GNUNET_assert (e->ready);
+ w->wdh = TALER_EXCHANGE_transfers_get (
+ ctx,
+ e->exchange_url,
+ e->keys,
+ &w->wtid,
+ &wire_transfer_cb,
+ w);
+ if (NULL == w->wdh)
+ {
+ GNUNET_break (0);
+ e->exchange_inquiries--;
+ e->transfer_delay = GNUNET_TIME_STD_BACKOFF (e->transfer_delay);
+ update_transaction_status (w,
+ GNUNET_TIME_relative_to_absolute (
+ e->transfer_delay),
+ TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_TRANSIENT_FAILURE,
+ false,
+ false);
+ end_inquiry (w);
+ return;
+ }
+ /* Wait at least 1m for the network transfer */
+ update_transaction_status (w,
+ GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_UNIT_MINUTES),
+ TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_AWAITING_LIST,
+ false,
+ false);
+}
+
+
+/**
+ * Function called with information about a transfer we
+ * should ask the exchange about.
+ *
+ * @param cls closure (NULL)
+ * @param rowid row of the transfer in the merchant database
+ * @param instance_id instance that received the transfer
+ * @param exchange_url base URL of the exchange that initiated the transfer
+ * @param payto_uri account of the merchant that received the transfer
+ * @param wtid wire transfer subject identifying the aggregation
+ * @param total total amount that was wired
+ * @param next_attempt when should we next try to interact with the exchange
+ */
+static void
+start_inquiry (
+ void *cls,
+ uint64_t rowid,
+ const char *instance_id,
+ const char *exchange_url,
+ const char *payto_uri,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *total,
+ struct GNUNET_TIME_Absolute next_attempt)
+{
+ struct Exchange *e;
+ struct Inquiry *w;
+
+ (void) cls;
+ if (GNUNET_TIME_absolute_is_future (next_attempt))
+ {
+ if (NULL == task)
+ task = GNUNET_SCHEDULER_add_at (next_attempt,
+ &find_work,
+ NULL);
+ return;
+ }
+ e = find_exchange (exchange_url);
+ for (w = e->w_head; NULL != w; w = w->next)
+ {
+ if (0 == GNUNET_memcmp (&w->wtid,
+ wtid))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Already processing inquiry. Aborting ongoing inquiry\n");
+ end_inquiry (w);
+ break;
+ }
+ }
+
+ active_inquiries++;
+ w = GNUNET_new (struct Inquiry);
+ w->payto_uri = GNUNET_strdup (payto_uri);
+ w->instance_id = GNUNET_strdup (instance_id);
+ w->rowid = rowid;
+ w->wtid = *wtid;
+ w->total = *total;
+ GNUNET_CONTAINER_DLL_insert (e->w_head,
+ e->w_tail,
+ w);
+ w->exchange = e;
+ if (w->exchange->ready)
+ w->task = GNUNET_SCHEDULER_add_now (&exchange_request,
+ w);
+ /* Wait at least 1 minute for /keys */
+ update_transaction_status (w,
+ GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_UNIT_MINUTES),
+ TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_AWAITING_KEYS,
+ false,
+ false);
+}
+
+
+static void
+find_work (void *cls)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ int limit;
+
+ (void) cls;
+ task = NULL;
+ GNUNET_assert (OPEN_INQUIRY_LIMIT >= active_inquiries);
+ limit = OPEN_INQUIRY_LIMIT - active_inquiries;
+ if (0 == limit)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Not looking for work: at limit\n");
+ at_limit = true;
+ return;
+ }
+ at_limit = false;
+ qs = db_plugin->select_open_transfers (db_plugin->cls,
+ limit,
+ &start_inquiry,
+ NULL);
+ if (qs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to obtain open transfers from database\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (qs == limit)
+ {
+ /* DB limited response, re-trigger DB interaction
+ the moment we significantly fall below the
+ limit */
+ at_limit = true;
+ }
+ if (0 == active_inquiries)
+ {
+ if (test_mode)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No more open inquiries and in test mode. Existing.\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No open inquiries found, waiting for notification to resume\n");
+ }
+}
+
+
+/**
+ * Function called when transfers are added to the merchant database. We look
+ * for more work.
+ *
+ * @param cls closure (NULL)
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+transfer_added (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ (void) cls;
+ (void) extra;
+ (void) extra_size;
+ if (active_inquiries > OPEN_INQUIRY_LIMIT / 2)
+ {
+ /* Trigger DB only once we are substantially below the limit */
+ at_limit = true;
+ return;
+ }
+ if (NULL != task)
+ return;
+ task = GNUNET_SCHEDULER_add_now (&find_work,
+ NULL);
+}
+
+
+/**
+ * First task.
+ *
+ * @param cls closure, NULL
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param c configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *c)
+{
+ (void) args;
+ (void) cfgfile;
+
+ cfg = c;
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ NULL);
+ ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &rc);
+ rc = GNUNET_CURL_gnunet_rc_create (ctx);
+ if (NULL == ctx)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NO_RESTART;
+ return;
+ }
+ if (NULL ==
+ (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_OK !=
+ db_plugin->connect (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to connect to database\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NO_RESTART;
+ return;
+ }
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_MERCHANT_WIRE_TRANSFER_CONFIRMED)
+ };
+
+ eh = db_plugin->event_listen (db_plugin->cls,
+ &es,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &transfer_added,
+ NULL);
+ }
+ task = GNUNET_SCHEDULER_add_now (&find_work,
+ NULL);
+}
+
+
+/**
+ * The main function of taler-merchant-exchange
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_timetravel ('T',
+ "timetravel"),
+ GNUNET_GETOPT_option_flag ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
+ GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+ TALER_OS_init ();
+ ret = GNUNET_PROGRAM_run (
+ argc, argv,
+ "taler-merchant-exchange",
+ gettext_noop (
+ "background process that reconciles bank transfers with orders by asking the exchange"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
+}
+
+
+/* end of taler-merchant-exchange.c */
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
index 8e1a0fc0..7384bfc9 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2022 Taler Systems SA
+ (C) 2014-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -28,23 +28,23 @@
#include <taler/taler_mhd_lib.h>
#include <taler/taler_templating_lib.h>
#include <taler/taler_exchange_service.h>
-#include "taler-merchant-httpd_auditors.h"
#include "taler-merchant-httpd_config.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_get-orders-ID.h"
-#include "taler-merchant-httpd_get-tips-ID.h"
+#include "taler-merchant-httpd_get-templates-ID.h"
#include "taler-merchant-httpd_mhd.h"
-#include "taler-merchant-httpd_private-delete-webhooks-ID.h"
-#include "taler-merchant-httpd_private-delete-templates-ID.h"
+#include "taler-merchant-httpd_private-delete-account-ID.h"
#include "taler-merchant-httpd_private-delete-instances-ID.h"
+#include "taler-merchant-httpd_private-delete-instances-ID-token.h"
#include "taler-merchant-httpd_private-delete-products-ID.h"
#include "taler-merchant-httpd_private-delete-orders-ID.h"
-#include "taler-merchant-httpd_private-delete-reserves-ID.h"
+#include "taler-merchant-httpd_private-delete-otp-devices-ID.h"
+#include "taler-merchant-httpd_private-delete-templates-ID.h"
+#include "taler-merchant-httpd_private-delete-token-families-SLUG.h"
#include "taler-merchant-httpd_private-delete-transfers-ID.h"
-#include "taler-merchant-httpd_private-get-webhooks.h"
-#include "taler-merchant-httpd_private-get-webhooks-ID.h"
-#include "taler-merchant-httpd_private-get-templates.h"
-#include "taler-merchant-httpd_private-get-templates-ID.h"
+#include "taler-merchant-httpd_private-delete-webhooks-ID.h"
+#include "taler-merchant-httpd_private-get-accounts.h"
+#include "taler-merchant-httpd_private-get-accounts-ID.h"
#include "taler-merchant-httpd_private-get-instances.h"
#include "taler-merchant-httpd_private-get-instances-ID.h"
#include "taler-merchant-httpd_private-get-instances-ID-kyc.h"
@@ -52,35 +52,42 @@
#include "taler-merchant-httpd_private-get-products-ID.h"
#include "taler-merchant-httpd_private-get-orders.h"
#include "taler-merchant-httpd_private-get-orders-ID.h"
-#include "taler-merchant-httpd_private-get-reserves.h"
-#include "taler-merchant-httpd_private-get-reserves-ID.h"
-#include "taler-merchant-httpd_private-get-tips-ID.h"
-#include "taler-merchant-httpd_private-get-tips.h"
+#include "taler-merchant-httpd_private-get-otp-devices.h"
+#include "taler-merchant-httpd_private-get-otp-devices-ID.h"
+#include "taler-merchant-httpd_private-get-templates.h"
+#include "taler-merchant-httpd_private-get-templates-ID.h"
+#include "taler-merchant-httpd_private-get-token-families.h"
+#include "taler-merchant-httpd_private-get-token-families-SLUG.h"
#include "taler-merchant-httpd_private-get-transfers.h"
-#include "taler-merchant-httpd_private-patch-webhooks-ID.h"
-#include "taler-merchant-httpd_private-patch-templates-ID.h"
+#include "taler-merchant-httpd_private-get-webhooks.h"
+#include "taler-merchant-httpd_private-get-webhooks-ID.h"
+#include "taler-merchant-httpd_private-patch-accounts-ID.h"
#include "taler-merchant-httpd_private-patch-instances-ID.h"
#include "taler-merchant-httpd_private-patch-orders-ID-forget.h"
+#include "taler-merchant-httpd_private-patch-otp-devices-ID.h"
#include "taler-merchant-httpd_private-patch-products-ID.h"
-#include "taler-merchant-httpd_private-post-webhooks.h"
-#include "taler-merchant-httpd_private-post-templates.h"
+#include "taler-merchant-httpd_private-patch-templates-ID.h"
+#include "taler-merchant-httpd_private-patch-token-families-SLUG.h"
+#include "taler-merchant-httpd_private-patch-webhooks-ID.h"
+#include "taler-merchant-httpd_private-post-account.h"
#include "taler-merchant-httpd_private-post-instances.h"
#include "taler-merchant-httpd_private-post-instances-ID-auth.h"
+#include "taler-merchant-httpd_private-post-instances-ID-token.h"
+#include "taler-merchant-httpd_private-post-otp-devices.h"
#include "taler-merchant-httpd_private-post-orders.h"
#include "taler-merchant-httpd_private-post-orders-ID-refund.h"
#include "taler-merchant-httpd_private-post-products.h"
#include "taler-merchant-httpd_private-post-products-ID-lock.h"
-#include "taler-merchant-httpd_private-post-reserves.h"
-#include "taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h"
+#include "taler-merchant-httpd_private-post-templates.h"
+#include "taler-merchant-httpd_private-post-token-families.h"
#include "taler-merchant-httpd_private-post-transfers.h"
+#include "taler-merchant-httpd_private-post-webhooks.h"
#include "taler-merchant-httpd_post-orders-ID-abort.h"
#include "taler-merchant-httpd_post-orders-ID-claim.h"
#include "taler-merchant-httpd_post-orders-ID-paid.h"
#include "taler-merchant-httpd_post-orders-ID-pay.h"
#include "taler-merchant-httpd_post-using-templates.h"
#include "taler-merchant-httpd_post-orders-ID-refund.h"
-#include "taler-merchant-httpd_post-tips-ID-pickup.h"
-#include "taler-merchant-httpd_reserves.h"
#include "taler-merchant-httpd_spa.h"
#include "taler-merchant-httpd_statics.h"
@@ -107,6 +114,13 @@
char *TMH_currency;
/**
+ * What is the base URL for this merchant backend? NULL if it is not
+ * configured and is to be determined from HTTP headers (X-Forwarded-Host and
+ * X-Forwarded-Port and X-Forwarded-Prefix) of the reverse proxy.
+ */
+char *TMH_base_url;
+
+/**
* Inform the auditor for all deposit confirmations (global option)
*/
int TMH_force_audit;
@@ -136,6 +150,16 @@ struct GNUNET_CONTAINER_MultiHashMap *TMH_by_id_map;
struct GNUNET_TIME_Relative TMH_legal_expiration;
/**
+ * Length of the TMH_cspecs array.
+ */
+unsigned int TMH_num_cspecs;
+
+/**
+ * Rendering specs for currencies.
+ */
+struct TALER_CurrencySpecification *TMH_cspecs;
+
+/**
* The port we are running on
*/
static uint16_t port;
@@ -146,9 +170,20 @@ static uint16_t port;
static int merchant_connection_close;
/**
+ * Context for all exchange operations (useful to the event loop).
+ */
+struct GNUNET_CURL_Context *TMH_curl_ctx;
+
+/**
+ * Context for integrating #TMH_curl_ctx with the
+ * GNUnet event loop.
+ */
+static struct GNUNET_CURL_RescheduleContext *merchant_curl_rc;
+
+/**
* Global return code
*/
-static int result;
+static int global_ret;
/**
* Our configuration.
@@ -161,6 +196,75 @@ static const struct GNUNET_CONFIGURATION_Handle *cfg;
char *TMH_default_auth;
+/**
+ * Check validity of login @a token for the given @a instance_id.
+ *
+ * @param token the login token given in the request
+ * @param instance_id the instance the login is to be checked against
+ * @param[out] as set to scope of the token if it is valid
+ * @return TALER_EC_NONE on success
+ */
+static enum TALER_ErrorCode
+TMH_check_token (const char *token,
+ const char *instance_id,
+ enum TMH_AuthScope *as)
+{
+ enum TMH_AuthScope scope;
+ struct GNUNET_TIME_Timestamp expiration;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_MERCHANTDB_LoginTokenP btoken;
+
+ if (NULL == token)
+ {
+ *as = TMH_AS_NONE;
+ return TALER_EC_NONE;
+ }
+ /* This was presumably checked before... */
+ GNUNET_assert (0 == strncasecmp (token,
+ RFC_8959_PREFIX,
+ strlen (RFC_8959_PREFIX)));
+ token += strlen (RFC_8959_PREFIX);
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (token,
+ strlen (token),
+ &btoken,
+ sizeof (btoken)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Given authorization token `%s' is malformed\n",
+ token);
+ GNUNET_break_op (0);
+ return TALER_EC_GENERIC_TOKEN_MALFORMED;
+ }
+ qs = TMH_db->select_login_token (TMH_db->cls,
+ instance_id,
+ &btoken,
+ &expiration,
+ &scope);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_EC_GENERIC_DB_FETCH_FAILED;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Authorization token `%s' unknown\n",
+ token);
+ return TALER_EC_GENERIC_TOKEN_UNKNOWN;
+ }
+ if (GNUNET_TIME_absolute_is_past (expiration.abs_time))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Authorization token `%s' expired\n",
+ token);
+ return TALER_EC_GENERIC_TOKEN_EXPIRED;
+ }
+ *as = scope;
+ return TALER_EC_NONE;
+}
+
+
enum GNUNET_GenericReturnValue
TMH_check_auth (const char *token,
struct TALER_MerchantAuthenticationSaltP *salt,
@@ -225,6 +329,17 @@ TMH_compute_auth (const char *token,
void
+TMH_wire_method_free (struct TMH_WireMethod *wm)
+{
+ GNUNET_free (wm->payto_uri);
+ GNUNET_free (wm->wire_method);
+ GNUNET_free (wm->credit_facade_url);
+ json_decref (wm->credit_facade_credentials);
+ GNUNET_free (wm);
+}
+
+
+void
TMH_instance_decref (struct TMH_MerchantInstance *mi)
{
struct TMH_WireMethod *wm;
@@ -238,9 +353,7 @@ TMH_instance_decref (struct TMH_MerchantInstance *mi)
GNUNET_CONTAINER_DLL_remove (mi->wm_head,
mi->wm_tail,
wm);
- GNUNET_free (wm->payto_uri);
- GNUNET_free (wm->wire_method);
- GNUNET_free (wm);
+ TMH_wire_method_free (wm);
}
GNUNET_free (mi->settings.id);
@@ -282,13 +395,11 @@ static void
do_shutdown (void *cls)
{
(void) cls;
+ TMH_force_orders_resume ();
TMH_force_ac_resume ();
TMH_force_pc_resume ();
TMH_force_kyc_resume ();
- TMH_force_rc_resume ();
TMH_force_gorc_resume ();
- TMH_force_post_transfers_resume ();
- TMH_force_tip_pickup_resume ();
TMH_force_wallet_get_order_resume ();
TMH_force_wallet_refund_order_resume ();
{
@@ -298,19 +409,17 @@ do_shutdown (void *cls)
if (NULL != mhd)
MHD_stop_daemon (mhd);
}
- TMH_RESERVES_done ();
if (NULL != instance_eh)
{
TMH_db->event_listen_cancel (instance_eh);
instance_eh = NULL;
}
+ TMH_EXCHANGES_done ();
if (NULL != TMH_db)
{
TALER_MERCHANTDB_plugin_unload (TMH_db);
TMH_db = NULL;
}
- TMH_EXCHANGES_done ();
- TMH_AUDITORS_done ();
if (NULL != TMH_by_id_map)
{
GNUNET_CONTAINER_multihashmap_iterate (TMH_by_id_map,
@@ -320,6 +429,16 @@ do_shutdown (void *cls)
TMH_by_id_map = NULL;
}
TALER_TEMPLATING_done ();
+ if (NULL != TMH_curl_ctx)
+ {
+ GNUNET_CURL_fini (TMH_curl_ctx);
+ TMH_curl_ctx = NULL;
+ }
+ if (NULL != merchant_curl_rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (merchant_curl_rc);
+ merchant_curl_rc = NULL;
+ }
}
@@ -379,6 +498,7 @@ handle_mhd_completion_callback (void *cls,
json_decref (hc->request_body);
if (NULL != hc->instance)
TMH_instance_decref (hc->instance);
+ GNUNET_free (hc->full_url);
GNUNET_free (hc);
*con_cls = NULL;
}
@@ -459,6 +579,15 @@ handle_server_options (const struct TMH_RequestHandler *rh,
}
+/**
+ * Generates the response for "/", redirecting the
+ * client to the "/webui/" from where we serve the SPA.
+ *
+ * @param rh request handler
+ * @param connection MHD connection
+ * @param hc handler context
+ * @return MHD result code
+ */
static MHD_RESULT
spa_redirect (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
@@ -466,6 +595,7 @@ spa_redirect (const struct TMH_RequestHandler *rh,
{
const char *text = "Redirecting to /webui/";
struct MHD_Response *response;
+ char *dst;
response = MHD_create_response_from_buffer (strlen (text),
(void *) text,
@@ -480,15 +610,25 @@ spa_redirect (const struct TMH_RequestHandler *rh,
MHD_add_response_header (response,
MHD_HTTP_HEADER_CONTENT_TYPE,
"text/plain"));
+ if ( (NULL == hc->instance) ||
+ (0 == strcmp ("default",
+ hc->instance->settings.id)) )
+ dst = GNUNET_strdup ("/webui/");
+ else
+ GNUNET_asprintf (&dst,
+ "/instances/%s/webui/",
+ hc->instance->settings.id);
if (MHD_NO ==
MHD_add_response_header (response,
MHD_HTTP_HEADER_LOCATION,
- "/webui/"))
+ dst))
{
GNUNET_break (0);
MHD_destroy_response (response);
+ GNUNET_free (dst);
return MHD_NO;
}
+ GNUNET_free (dst);
{
MHD_RESULT ret;
@@ -515,12 +655,14 @@ extract_token (const char **auth)
const char *bearer = "Bearer ";
const char *tok = *auth;
- if (0 != strncmp (tok, bearer, strlen (bearer)))
+ if (0 != strncmp (tok,
+ bearer,
+ strlen (bearer)))
{
*auth = NULL;
return;
}
- tok = tok + strlen (bearer);
+ tok += strlen (bearer);
while (' ' == *tok)
tok++;
if (0 != strncasecmp (tok,
@@ -596,6 +738,29 @@ prefix_match (const struct TMH_RequestHandler *rh,
/**
+ * Function called first by MHD with the full URL.
+ *
+ * @param cls NULL
+ * @param full_url the full URL
+ * @param con MHD connection object
+ * @return our handler context
+ */
+static void *
+full_url_track_callback (void *cls,
+ const char *full_url,
+ struct MHD_Connection *con)
+{
+ struct TMH_HandlerContext *hc;
+
+ hc = GNUNET_new (struct TMH_HandlerContext);
+ GNUNET_async_scope_fresh (&hc->async_scope_id);
+ GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
+ hc->full_url = GNUNET_strdup (full_url);
+ return hc;
+}
+
+
+/**
* A client has requested the given url using the given method
* (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
* #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback
@@ -630,7 +795,8 @@ prefix_match (const struct TMH_RequestHandler *rh,
* If necessary, this state can be cleaned up in the
* global #MHD_RequestCompletedCallback (which
* can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
- * Initially, `*con_cls` will be NULL.
+ * Initially, `*con_cls` will be set up by the
+ * full_url_track_callback().
* @return #MHD_YES if the connection was handled successfully,
* #MHD_NO if the socket must be closed due to a serious
* error while handling the request
@@ -711,7 +877,7 @@ url_handler (void *cls,
/* Body should be pretty small. */
.max_upload = 1024 * 1024
},
- /* POST /kyc: */
+ /* GET /kyc: */
{
.url_prefix = "/instances/",
.url_suffix = "kyc",
@@ -880,100 +1046,66 @@ url_handler (void *cls,
.allow_deleted_instance = true,
.handler = &TMH_private_delete_orders_ID
},
- /* POST /reserves: */
+ /* POST /transfers: */
{
- .url_prefix = "/reserves",
+ .url_prefix = "/transfers",
.method = MHD_HTTP_METHOD_POST,
- .handler = &TMH_private_post_reserves,
+ .allow_deleted_instance = true,
+ .handler = &TMH_private_post_transfers,
/* the body should be pretty small, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
- /* DELETE /reserves/$ID: */
+ /* DELETE /transfers/$ID: */
{
- .url_prefix = "/reserves/",
- .have_id_segment = true,
- .allow_deleted_instance = true,
+ .url_prefix = "/transfers/",
.method = MHD_HTTP_METHOD_DELETE,
- .handler = &TMH_private_delete_reserves_ID
- },
- /* POST /reserves/$ID/authorize-tip: */
- {
- .url_prefix = "/reserves/",
- .url_suffix = "authorize-tip",
+ .allow_deleted_instance = true,
+ .handler = &TMH_private_delete_transfers_ID,
.have_id_segment = true,
- .method = MHD_HTTP_METHOD_POST,
- .handler = &TMH_private_post_reserves_ID_authorize_tip,
- /* the body should be pretty small, allow 1 MB of upload
- to set a conservative bound for sane wallets */
- .max_upload = 1024 * 1024
- },
- /* POST /tips: */
- {
- .url_prefix = "/tips",
- .method = MHD_HTTP_METHOD_POST,
- .handler = &TMH_private_post_tips,
/* the body should be pretty small, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
- /* GET /tips: */
- {
- .url_prefix = "/tips",
- .allow_deleted_instance = true,
- .method = MHD_HTTP_METHOD_GET,
- .handler = &TMH_private_get_tips
- },
- /* GET /tips/$ID: */
+ /* GET /transfers: */
{
- .url_prefix = "/tips/",
+ .url_prefix = "/transfers",
.method = MHD_HTTP_METHOD_GET,
.allow_deleted_instance = true,
- .have_id_segment = true,
- .handler = &TMH_private_get_tips_ID
+ .handler = &TMH_private_get_transfers
},
- /* GET /reserves: */
+ /* POST /otp-devices: */
{
- .url_prefix = "/reserves",
- .allow_deleted_instance = true,
- .method = MHD_HTTP_METHOD_GET,
- .handler = &TMH_private_get_reserves
+ .url_prefix = "/otp-devices",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler = &TMH_private_post_otp_devices
},
- /* GET /reserves/$ID: */
+ /* GET /otp-devices: */
{
- .url_prefix = "/reserves/",
- .allow_deleted_instance = true,
- .have_id_segment = true,
+ .url_prefix = "/otp-devices",
.method = MHD_HTTP_METHOD_GET,
- .handler = &TMH_private_get_reserves_ID
+ .handler = &TMH_private_get_otp_devices
},
- /* POST /transfers: */
+ /* GET /otp-devices/$ID/: */
{
- .url_prefix = "/transfers",
- .method = MHD_HTTP_METHOD_POST,
- .allow_deleted_instance = true,
- .handler = &TMH_private_post_transfers,
- /* the body should be pretty small, allow 1 MB of upload
- to set a conservative bound for sane wallets */
- .max_upload = 1024 * 1024
+ .url_prefix = "/otp-devices/",
+ .method = MHD_HTTP_METHOD_GET,
+ .have_id_segment = true,
+ .handler = &TMH_private_get_otp_devices_ID
},
- /* DELETE /transfers/$ID: */
+ /* DELETE /otp-devices/$ID/: */
{
- .url_prefix = "/transfers/",
+ .url_prefix = "/otp-devices/",
.method = MHD_HTTP_METHOD_DELETE,
- .allow_deleted_instance = true,
- .handler = &TMH_private_delete_transfers_ID,
.have_id_segment = true,
- /* the body should be pretty small, allow 1 MB of upload
- to set a conservative bound for sane wallets */
- .max_upload = 1024 * 1024
+ .handler = &TMH_private_delete_otp_devices_ID
},
- /* GET /transfers: */
+ /* PATCH /otp-devices/$ID/: */
{
- .url_prefix = "/transfers",
- .method = MHD_HTTP_METHOD_GET,
- .allow_deleted_instance = true,
- .handler = &TMH_private_get_transfers
+ .url_prefix = "/otp-devices/",
+ .method = MHD_HTTP_METHOD_PATCH,
+ .have_id_segment = true,
+ .handler = &TMH_private_patch_otp_devices_ID
},
/* POST /templates: */
{
@@ -1067,16 +1199,114 @@ url_handler (void *cls,
in the code... */
.max_upload = 1024 * 1024 * 8
},
+ /* POST /accounts: */
+ {
+ .url_prefix = "/accounts",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler = &TMH_private_post_account,
+ /* allow account details of up to 8 kb, that should be plenty */
+ .max_upload = 1024 * 8
+ },
+ /* PATCH /accounts/$H_WIRE: */
+ {
+ .url_prefix = "/accounts/",
+ .method = MHD_HTTP_METHOD_PATCH,
+ .handler = &TMH_private_patch_accounts_ID,
+ .have_id_segment = true,
+ /* allow account details of up to 8 kb, that should be plenty */
+ .max_upload = 1024 * 8
+ },
+ /* GET /accounts: */
+ {
+ .url_prefix = "/accounts",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler = &TMH_private_get_accounts
+ },
+ /* GET /accounts/$H_WIRE: */
+ {
+ .url_prefix = "/accounts/",
+ .method = MHD_HTTP_METHOD_GET,
+ .have_id_segment = true,
+ .handler = &TMH_private_get_accounts_ID
+ },
+ /* DELETE /accounts/$H_WIRE: */
+ {
+ .url_prefix = "/accounts/",
+ .method = MHD_HTTP_METHOD_DELETE,
+ .handler = &TMH_private_delete_account_ID,
+ .have_id_segment = true
+ },
+ /* POST /token: */
+ {
+ .url_prefix = "/token",
+ .auth_scope = TMH_AS_REFRESHABLE,
+ .method = MHD_HTTP_METHOD_POST,
+ .handler = &TMH_private_post_instances_ID_token,
+ /* Body should be tiny. */
+ .max_upload = 1024
+ },
+ /* DELETE /token: */
+ {
+ .url_prefix = "/token",
+ .auth_scope = TMH_AS_READ_ONLY,
+ .method = MHD_HTTP_METHOD_DELETE,
+ .handler = &TMH_private_delete_instances_ID_token,
+ },
+ /* GET /tokenfamilies: */
+ {
+ .url_prefix = "/tokenfamilies",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler = &TMH_private_get_tokenfamilies
+ },
+ /* POST /tokenfamilies: */
+ {
+ .url_prefix = "/tokenfamilies",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler = &TMH_private_post_token_families
+ },
+ /* GET /tokenfamilies/$SLUG/: */
+ {
+ .url_prefix = "/tokenfamilies/",
+ .method = MHD_HTTP_METHOD_GET,
+ .have_id_segment = true,
+ .handler = &TMH_private_get_tokenfamilies_SLUG
+ },
+ /* DELETE /tokenfamilies/$SLUG/: */
+ {
+ .url_prefix = "/tokenfamilies/",
+ .method = MHD_HTTP_METHOD_DELETE,
+ .have_id_segment = true,
+ .handler = &TMH_private_delete_token_families_SLUG
+ },
+ /* PATCH /tokenfamilies/$SLUG/: */
+ {
+ .url_prefix = "/tokenfamilies/",
+ .method = MHD_HTTP_METHOD_PATCH,
+ .have_id_segment = true,
+ .handler = &TMH_private_patch_token_family_SLUG,
+ },
{
.url_prefix = NULL
}
};
static struct TMH_RequestHandler public_handlers[] = {
{
+ /* for "default" instance, it does not even
+ have to exist before we give the WebUI */
.url_prefix = "/",
.method = MHD_HTTP_METHOD_GET,
.mime_type = "text/html",
.skip_instance = true,
+ .default_only = true,
+ .handler = &spa_redirect,
+ .response_code = MHD_HTTP_FOUND
+ },
+ {
+ /* for "normal" instance,s they must exist
+ before we give the WebUI */
+ .url_prefix = "/",
+ .method = MHD_HTTP_METHOD_GET,
+ .mime_type = "text/html",
.handler = &spa_redirect,
.response_code = MHD_HTTP_FOUND
},
@@ -1175,39 +1405,25 @@ url_handler (void *cls,
.have_id_segment = true,
.handler = &TMH_get_orders_ID
},
- /* GET /tips/$ID: */
+ /* GET /static/ *: */
{
- .url_prefix = "/tips/",
+ .url_prefix = "/static/",
.method = MHD_HTTP_METHOD_GET,
- .allow_deleted_instance = true,
- .have_id_segment = true,
- .handler = &TMH_get_tips_ID
- },
- /* POST /tips/$ID/pickup: */
- {
- .url_prefix = "/tips/",
- .method = MHD_HTTP_METHOD_POST,
.have_id_segment = true,
- .allow_deleted_instance = true,
- .url_suffix = "pickup",
- .handler = &TMH_post_tips_ID_pickup,
- /* wallet may give us many coins to sign, allow 1 MB of upload
- to set a conservative bound for sane wallets */
- .max_upload = 1024 * 1024
+ .handler = &TMH_return_static
},
- /* GET /static/ *: */
+ /* GET /templates/$ID/: */
{
- .url_prefix = "/static/",
+ .url_prefix = "/templates/",
.method = MHD_HTTP_METHOD_GET,
.have_id_segment = true,
- .handler = &TMH_return_static
+ .handler = &TMH_get_templates_ID
},
/* POST /templates/$ID: */
{
.url_prefix = "/templates/",
.method = MHD_HTTP_METHOD_POST,
.have_id_segment = true,
- .allow_deleted_instance = true,
.handler = &TMH_post_using_templates_ID,
.max_upload = 1024 * 1024
},
@@ -1226,7 +1442,7 @@ url_handler (void *cls,
(void) cls;
(void) version;
- if (NULL != hc)
+ if (NULL != hc->url)
{
/* MHD calls us again for a request, for first call
see 'else' case below */
@@ -1274,10 +1490,6 @@ url_handler (void *cls,
connection,
hc);
}
- hc = GNUNET_new (struct TMH_HandlerContext);
- *con_cls = hc;
- GNUNET_async_scope_fresh (&hc->async_scope_id);
- GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
hc->url = url;
{
const char *correlation_id;
@@ -1331,6 +1543,37 @@ url_handler (void *cls,
else
instance_id = GNUNET_strndup (istart,
slash - istart);
+ if (0 == strcmp (instance_id,
+ "default"))
+ {
+ MHD_RESULT ret;
+ struct MHD_Response *response;
+ const char *rstart = hc->full_url + strlen (instance_prefix);
+ const char *rslash = strchr (rstart, '/');
+
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Client used deprecated '/instances/default/' path. Redirecting to modern path\n");
+
+ response
+ = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (response);
+ if (MHD_NO ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_LOCATION,
+ rslash))
+ {
+ GNUNET_break (0);
+ MHD_destroy_response (response);
+ return MHD_NO;
+ }
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_PERMANENT_REDIRECT,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+ }
hc->instance = TMH_lookup_instance (instance_id);
if ( (NULL == hc->instance) &&
(0 == strcmp ("default",
@@ -1399,7 +1642,11 @@ url_handler (void *cls,
"/private")) )
{
handlers = private_handlers;
- url += strlen (private_prefix) - 1;
+ if (0 == strcmp (url,
+ "/private"))
+ url = "/";
+ else
+ url += strlen (private_prefix) - 1;
}
else
{
@@ -1566,8 +1813,8 @@ url_handler (void *cls,
(! hc->rh->skip_instance) )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Instance `%s' not known\n",
- hc->infix);
+ "Instance for `%s' not known\n",
+ hc->url);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
@@ -1619,9 +1866,40 @@ url_handler (void *cls,
(! auth_malformed) &&
(0 == strcmp (auth,
TMH_default_auth)) );
- if (! auth_ok)
+ if (auth_ok)
{
- if (auth_malformed)
+ hc->auth_scope = TMH_AS_ALL;
+ }
+ else
+ {
+ if (NULL != hc->instance)
+ {
+ enum TALER_ErrorCode ec;
+
+ ec = TMH_check_token (auth,
+ hc->instance->settings.id,
+ &hc->auth_scope);
+ if (TALER_EC_NONE != ec)
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ }
+ else
+ hc->auth_scope = TMH_AS_NONE;
+ }
+ /* We grant access if:
+ - scope is 'all'
+ - rh has an explicit non-NONE scope that matches
+ - scope is 'read only' and we have a GET request */
+ if (! ( (TMH_AS_ALL == hc->auth_scope) ||
+ ( (TMH_AS_NONE != hc->rh->auth_scope) &&
+ (hc->rh->auth_scope == (hc->rh->auth_scope & hc->auth_scope)) ) ||
+ ( (TMH_AS_READ_ONLY == (hc->auth_scope & TMH_AS_READ_ONLY)) &&
+ (0 == strcmp (MHD_HTTP_METHOD_GET,
+ method)) ) ) )
+ {
+ if (auth_malformed &&
+ (TMH_AS_NONE == hc->auth_scope) )
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_UNAUTHORIZED,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
@@ -1667,39 +1945,10 @@ url_handler (void *cls,
MHD_HTTP_METHOD_PATCH)) );
if (hc->has_body)
{
- const char *cl;
-
- /* Maybe check for maximum upload size
- and refuse requests if they are just too big. */
- cl = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_CONTENT_LENGTH);
- if (NULL != cl)
- {
- unsigned long long cv;
- size_t mul = hc->rh->max_upload;
- char dummy;
-
- if (0 == mul)
- mul = DEFAULT_MAX_UPLOAD_SIZE;
- if (1 != sscanf (cl,
- "%llu%c",
- &cv,
- &dummy))
- {
- /* Not valid HTTP request, just close connection. */
- GNUNET_break_op (0);
- return MHD_NO;
- }
- if (cv > mul)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_PAYLOAD_TOO_LARGE,
- TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT,
- cl);
- }
- }
+ TALER_MHD_check_content_length (connection,
+ 0 == hc->rh->max_upload
+ ? DEFAULT_MAX_UPLOAD_SIZE
+ : hc->rh->max_upload);
GNUNET_break (NULL == hc->request_body); /* can't have it already */
}
return MHD_YES; /* wait for MHD to call us again */
@@ -1707,6 +1956,31 @@ url_handler (void *cls,
/**
+ * Callback invoked with information about a bank account.
+ *
+ * @param cls closure with a `struct TMH_MerchantInstance *`
+ * @param acc details about the account
+ */
+static void
+add_account_cb (void *cls,
+ const struct TALER_MERCHANTDB_AccountDetails *acc)
+{
+ struct TMH_MerchantInstance *mi = cls;
+ struct TMH_WireMethod *wm;
+
+ wm = GNUNET_new (struct TMH_WireMethod);
+ wm->h_wire = acc->h_wire;
+ wm->payto_uri = GNUNET_strdup (acc->payto_uri);
+ wm->wire_salt = acc->salt;
+ wm->wire_method = TALER_payto_get_method (acc->payto_uri);
+ wm->active = acc->active;
+ GNUNET_CONTAINER_DLL_insert (mi->wm_head,
+ mi->wm_tail,
+ wm);
+}
+
+
+/**
* Function called during startup to add all known instances to our
* hash map in memory for faster lookups when we receive requests.
*
@@ -1715,19 +1989,16 @@ url_handler (void *cls,
* @param merchant_priv private key of the instance, NULL if not available
* @param is detailed configuration settings for the instance
* @param ias authentication settings for the instance
- * @param accounts_length length of the @a accounts array
- * @param accounts list of accounts of the merchant
*/
static void
add_instance_cb (void *cls,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_MerchantPrivateKeyP *merchant_priv,
const struct TALER_MERCHANTDB_InstanceSettings *is,
- const struct TALER_MERCHANTDB_InstanceAuthSettings *ias,
- unsigned int accounts_length,
- const struct TALER_MERCHANTDB_AccountDetails accounts[])
+ const struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
{
struct TMH_MerchantInstance *mi;
+ enum GNUNET_DB_QueryStatus qs;
(void) cls;
mi = TMH_lookup_instance (is->id);
@@ -1757,20 +2028,15 @@ add_instance_cb (void *cls,
else
mi->deleted = true;
mi->merchant_pub = *merchant_pub;
- for (unsigned int i = 0; i<accounts_length; i++)
+ qs = TMH_db->select_accounts (TMH_db->cls,
+ mi->settings.id,
+ &add_account_cb,
+ mi);
+ if (0 > qs)
{
- const struct TALER_MERCHANTDB_AccountDetails *acc = &accounts[i];
- struct TMH_WireMethod *wm;
-
- wm = GNUNET_new (struct TMH_WireMethod);
- wm->h_wire = acc->h_wire;
- wm->payto_uri = GNUNET_strdup (acc->payto_uri);
- wm->wire_salt = acc->salt;
- wm->wire_method = TALER_payto_get_method (acc->payto_uri);
- wm->active = acc->active;
- GNUNET_CONTAINER_DLL_insert (mi->wm_head,
- mi->wm_tail,
- wm);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error loading accounts of `%s' from database\n",
+ mi->settings.id);
}
GNUNET_assert (GNUNET_OK ==
TMH_add_instance (mi));
@@ -1794,8 +2060,6 @@ load_instances (void *cls,
const char *id = extra;
(void) cls;
- (void) extra;
- (void) extra_len;
if ( (NULL != extra) &&
( (0 == extra_len) ||
('\0' != id[extra_len - 1]) ) )
@@ -1837,7 +2101,7 @@ load_instances (void *cls,
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed initialization. Check database setup.\n");
- result = EXIT_FAILURE;
+ global_ret = EXIT_NOPERMISSION;
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -1845,9 +2109,8 @@ load_instances (void *cls,
/**
- * A transaction modified an instance setting
- * (or created/deleted/purged one). Notify all
- * backends about the change.
+ * A transaction modified an instance setting (or created/deleted/purged
+ * one). Notify all backends about the change.
*
* @param id ID of the instance that changed
*/
@@ -1889,7 +2152,6 @@ run (void *cls,
int fh;
enum TALER_MHD_GlobalOptions go;
int elen;
- int alen;
const char *tok;
(void) cls;
@@ -1904,10 +2166,10 @@ run (void *cls,
RFC_8959_PREFIX,
strlen (RFC_8959_PREFIX))) )
{
- fprintf (stderr,
- "Authentication token does not start with `%s' prefix\n",
- RFC_8959_PREFIX);
- result = GNUNET_SYSERR;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Authentication token does not start with `%s' prefix\n",
+ RFC_8959_PREFIX);
+ global_ret = EXIT_NOTCONFIGURED;
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -1919,16 +2181,46 @@ run (void *cls,
go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
TALER_MHD_setup (go);
- result = GNUNET_SYSERR;
+ global_ret = EXIT_SUCCESS;
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
NULL);
- if (GNUNET_OK !=
+
+ TMH_curl_ctx
+ = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &merchant_curl_rc);
+ if (NULL == TMH_curl_ctx)
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_NO_RESTART;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ merchant_curl_rc = GNUNET_CURL_gnunet_rc_create (TMH_curl_ctx);
+ /* Disable 100 continue processing */
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_CURL_append_header (TMH_curl_ctx,
+ MHD_HTTP_HEADER_EXPECT ":"));
+ GNUNET_CURL_enable_async_scope_header (TMH_curl_ctx,
+ "Taler-Correlation-Id");
+
+ if (GNUNET_SYSERR ==
TALER_config_get_currency (cfg,
&TMH_currency))
{
+
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ if (GNUNET_OK !=
+ TALER_CONFIG_parse_currencies (cfg,
+ &TMH_num_cspecs,
+ &TMH_cspecs))
+ {
GNUNET_SCHEDULER_shutdown ();
return;
}
+
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (cfg,
"merchant",
@@ -1941,6 +2233,22 @@ run (void *cls,
GNUNET_SCHEDULER_shutdown ();
return;
}
+ if (GNUNET_OK ==
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "merchant",
+ "BASE_URL",
+ &TMH_base_url))
+ {
+ if (! TALER_is_web_url (TMH_base_url))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "merchant",
+ "BASE_URL",
+ "Needs to start with 'http://' or 'https://'");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
if (GNUNET_YES ==
GNUNET_CONFIGURATION_get_value_yesno (cfg,
"merchant",
@@ -1957,46 +2265,41 @@ run (void *cls,
}
/* /static/ is currently not used */
/* (void) TMH_statics_init (); */
- elen = TMH_EXCHANGES_init (config);
- if (GNUNET_SYSERR == elen)
+ if (NULL ==
+ (TMH_by_id_map = GNUNET_CONTAINER_multihashmap_create (4,
+ GNUNET_YES)))
{
GNUNET_SCHEDULER_shutdown ();
return;
}
- alen = TMH_AUDITORS_init (config);
- if (GNUNET_SYSERR == alen)
+ if (NULL ==
+ (TMH_db = TALER_MERCHANTDB_plugin_load (cfg)))
{
GNUNET_SCHEDULER_shutdown ();
return;
}
- if (0 == elen + alen)
+ if (GNUNET_OK !=
+ TMH_db->connect (TMH_db->cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Fatal: no trusted exchanges and no trusted auditors configured. Exiting.\n");
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- if (NULL ==
- (TMH_by_id_map = GNUNET_CONTAINER_multihashmap_create (4,
- GNUNET_YES)))
- {
+ "Failed to initialize database connection\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
- if (NULL ==
- (TMH_db = TALER_MERCHANTDB_plugin_load (cfg)))
+ elen = TMH_EXCHANGES_init (config);
+ if (GNUNET_SYSERR == elen)
{
GNUNET_SCHEDULER_shutdown ();
return;
}
- if (GNUNET_OK !=
- TMH_db->connect (TMH_db->cls))
+ if (0 == elen)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to initialize database connection\n");
+ "Fatal: no trusted exchanges configured. Exiting.\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
+
{
struct GNUNET_DB_EventHeaderP es = {
.size = ntohs (sizeof (es)),
@@ -2012,8 +2315,6 @@ run (void *cls,
load_instances (NULL,
NULL,
0);
- /* start watching reserves */
- TMH_RESERVES_init ();
fh = TALER_MHD_bind (cfg,
"merchant",
&port);
@@ -2033,6 +2334,8 @@ run (void *cls,
NULL, NULL,
&url_handler, NULL,
MHD_OPTION_LISTEN_SOCKET, fh,
+ MHD_OPTION_URI_LOG_CALLBACK,
+ &full_url_track_callback, NULL,
MHD_OPTION_NOTIFY_COMPLETED,
&handle_mhd_completion_callback, NULL,
MHD_OPTION_CONNECTION_TIMEOUT,
@@ -2045,7 +2348,7 @@ run (void *cls,
GNUNET_SCHEDULER_shutdown ();
return;
}
- result = GNUNET_OK;
+ global_ret = EXIT_SUCCESS;
TALER_MHD_daemon_start (mhd);
}
}
@@ -2089,5 +2392,5 @@ main (int argc,
return EXIT_INVALIDARGUMENT;
if (GNUNET_NO == res)
return EXIT_SUCCESS;
- return (GNUNET_OK == result) ? EXIT_SUCCESS : 1;
+ return global_ret;
}
diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h
index e8911b32..1e5e955d 100644
--- a/src/backend/taler-merchant-httpd.h
+++ b/src/backend/taler-merchant-httpd.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -25,6 +25,7 @@
#include "taler_merchantdb_lib.h"
#include <taler/taler_mhd_lib.h>
#include <gnunet/gnunet_mhd_compat.h>
+#include "taler_merchant_bank_lib.h"
/**
* Shorthand for exit jumps.
@@ -71,6 +72,17 @@ struct TMH_WireMethod
struct TALER_MerchantWireHashP h_wire;
/**
+ * Base URL of the credit facade.
+ */
+ char *credit_facade_url;
+
+ /**
+ * Authentication data to access the credit facade.
+ * May be uninitialized if not provided by the client.
+ */
+ json_t *credit_facade_credentials;
+
+ /**
* Is this wire method active (should it be included in new contracts)?
*/
bool active;
@@ -268,6 +280,33 @@ struct TMH_OrderRefundEventP
/**
+ * Event generated when a client picks up a reward.
+ */
+struct TMH_RewardPickupEventP
+{
+ /**
+ * Type is #TALER_DBEVENT_MERCHANT_REWARD_PICKUP.
+ */
+ struct GNUNET_DB_EventHeaderP header;
+
+ /**
+ * Always zero (for alignment).
+ */
+ uint32_t reserved GNUNET_PACKED;
+
+ /**
+ * Reward ID.
+ */
+ struct TALER_RewardIdentifierP reward_id;
+
+ /**
+ * Hash of the instance ID.
+ */
+ struct GNUNET_HashCode h_instance;
+
+};
+
+/**
* Possible flags indicating the state of an order.
*/
enum TMH_OrderStateFlags
@@ -369,6 +408,34 @@ struct TMH_HandlerContext;
/**
+ * Possible authorization scopes. This is a bit mask.
+ */
+enum TMH_AuthScope
+{
+ /**
+ * Nothing is authorized.
+ */
+ TMH_AS_NONE = 0,
+
+ /**
+ * Read-only access is OK. Any GET request is
+ * automatically OK.
+ */
+ TMH_AS_READ_ONLY = 1,
+
+ /**
+ * /login access to renew the token is OK.
+ */
+ TMH_AS_REFRESHABLE = 2,
+
+ /**
+ * Full access is granted to everything.
+ */
+ TMH_AS_ALL = 7
+};
+
+
+/**
* @brief Struct describing an URL and the handler for it.
*
* The overall URL is always @e url_prefix, optionally followed by the
@@ -389,8 +456,15 @@ struct TMH_RequestHandler
const char *url_prefix;
/**
+ * Required authentication scope for this request. NONE implies that
+ * #TMH_AS_ALL is required unless this is a #MHD_HTTP_METHOD_GET method, in which
+ * case #TMH_AS_READ_ONLY is sufficient.
+ */
+ enum TMH_AuthScope auth_scope;
+
+ /**
* Does this request include an identifier segment
- * (product_id, reserve_pub, order_id, tip_id, template_id, webhook_id) in the
+ * (product_id, reserve_pub, order_id, reward_id, template_id, webhook_id) in the
* second segment?
*/
bool have_id_segment;
@@ -515,6 +589,11 @@ struct TMH_HandlerContext
const char *url;
/**
+ * Copy of our original full URL with query parameters.
+ */
+ char *full_url;
+
+ /**
* Client-provided authentication token for this
* request, can be NULL.
*
@@ -545,6 +624,12 @@ struct TMH_HandlerContext
uint64_t total_upload;
/**
+ * Actual authentication scope of this request.
+ * Only set for ``/private/`` requests.
+ */
+ enum TMH_AuthScope auth_scope;
+
+ /**
* Set to true if this is an #MHD_HTTP_METHOD_POST or #MHD_HTTP_METHOD_PATCH request.
* (In principle #MHD_HTTP_METHOD_PUT may also belong, but we do not have PUTs
* in the API today, so we do not test for PUT.)
@@ -593,11 +678,33 @@ struct TMH_SuspendedConnection
extern char *TMH_currency;
/**
+ * What is the base URL for this merchant backend? NULL if it is not
+ * configured and is to be determined from HTTP headers (X-Forwarded-Host and
+ * X-Forwarded-Port and X-Forwarded-Prefix) of the reverse proxy.
+ */
+extern char *TMH_base_url;
+
+/**
+ * Length of the TMH_cspecs array.
+ */
+extern unsigned int TMH_num_cspecs;
+
+/**
+ * Rendering specs for currencies.
+ */
+extern struct TALER_CurrencySpecification *TMH_cspecs;
+
+/**
* Inform the auditor for all deposit confirmations (global option)
*/
extern int TMH_force_audit;
/**
+ * Context for all CURL operations (useful to the event loop)
+ */
+extern struct GNUNET_CURL_Context *TMH_curl_ctx;
+
+/**
* Handle to the database backend.
*/
extern struct TALER_MERCHANTDB_Plugin *TMH_db;
@@ -657,6 +764,15 @@ TMH_instance_decref (struct TMH_MerchantInstance *mi);
/**
+ * Free memory allocated by @a wm.
+ *
+ * @param[in] wm wire method to free
+ */
+void
+TMH_wire_method_free (struct TMH_WireMethod *wm);
+
+
+/**
* Lookup a merchant instance by its instance ID.
*
* @param instance_id identifier of the instance to resolve
diff --git a/src/backend/taler-merchant-httpd_auditors.c b/src/backend/taler-merchant-httpd_auditors.c
deleted file mode 100644
index a1d70053..00000000
--- a/src/backend/taler-merchant-httpd_auditors.c
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- This file is part of TALER
- (C) 2014-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-merchant-httpd_auditors.c
- * @brief logic this HTTPD keeps for each exchange we interact with
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <taler/taler_json_lib.h>
-#include "taler-merchant-httpd_auditors.h"
-
-/**
- * Our representation of an auditor.
- */
-struct Auditor
-{
- /**
- * Auditor's legal name.
- */
- char *name;
-
- /**
- * Auditor's URL.
- */
- char *url;
-
- /**
- * Public key of the auditor.
- */
- struct TALER_AuditorPublicKeyP public_key;
-
-};
-
-
-/**
- * Array of the auditors this merchant is willing to accept.
- */
-static struct Auditor *auditors;
-
-/**
- * The length of the #auditors array.
- */
-static unsigned int nauditors;
-
-/**
- * JSON representation of the auditors accepted by this exchange.
- */
-json_t *j_auditors;
-
-
-enum GNUNET_GenericReturnValue
-TMH_AUDITORS_check_dk (struct TALER_EXCHANGE_Handle *mh,
- const struct TALER_EXCHANGE_DenomPublicKey *dk,
- bool exchange_trusted,
- unsigned int *hc,
- enum TALER_ErrorCode *ec)
-{
- const struct TALER_EXCHANGE_Keys *keys;
- const struct TALER_EXCHANGE_AuditorInformation *ai;
-
- if (GNUNET_TIME_absolute_is_past (dk->expire_deposit.abs_time))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Denomination key offered by client has expired for deposits\n");
- *hc = MHD_HTTP_GONE;
- *ec = TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_DEPOSIT_EXPIRED;
- return GNUNET_SYSERR; /* expired */
- }
- if (exchange_trusted)
- {
- *ec = TALER_EC_NONE;
- *hc = MHD_HTTP_OK;
- return GNUNET_OK;
- }
- keys = TALER_EXCHANGE_get_keys (mh);
- if (NULL == keys)
- {
- /* this should never happen, keys should have been successfully
- obtained before we even got into this function */
- GNUNET_break (0);
- *ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- *hc = MHD_HTTP_INTERNAL_SERVER_ERROR;
- return GNUNET_SYSERR;
- }
- for (unsigned int i = 0; i<keys->num_auditors; i++)
- {
- ai = &keys->auditors[i];
- for (unsigned int j = 0; j<nauditors; j++)
- {
- if (0 == GNUNET_memcmp (&ai->auditor_pub,
- &auditors[j].public_key))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Found supported auditor `%s' (%s)\n",
- auditors[j].name,
- TALER_B2S (&auditors[j].public_key));
- }
- for (unsigned int k = 0; k<ai->num_denom_keys; k++)
- if (&keys->denom_keys[k] == dk)
- {
- *ec = TALER_EC_NONE;
- *hc = MHD_HTTP_OK;
- return GNUNET_OK;
- }
- }
- }
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Denomination key %s offered by client not audited by any accepted auditor\n",
- GNUNET_h2s (&dk->h_key.hash));
- *hc = MHD_HTTP_BAD_REQUEST;
- *ec = TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_AUDITOR_FAILURE;
- return GNUNET_NO;
-}
-
-
-/**
- * Function called on each configuration section. Finds sections
- * about auditors and parses the entries.
- *
- * @param cls closure, with a `const struct GNUNET_CONFIGURATION_Handle *`
- * @param section name of the section
- */
-static void
-parse_auditors (void *cls,
- const char *section)
-{
- const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
- char *pks;
- struct Auditor auditor;
- char *currency;
-
- if (0 != strncasecmp (section,
- "merchant-auditor-",
- strlen ("merchant-auditor-")))
- return;
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- section,
- "CURRENCY",
- &currency))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- section,
- "CURRENCY");
- return;
- }
- if (0 != strcasecmp (currency,
- TMH_currency))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Auditor given in section `%s' is for another currency. Skipping.\n",
- section);
- GNUNET_free (currency);
- return;
- }
- GNUNET_free (currency);
- auditor.name = GNUNET_strdup (&section[strlen ("merchant-auditor-")]);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- section,
- "AUDITOR_BASE_URL",
- &auditor.url))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- section,
- "URL");
- GNUNET_free (auditor.name);
- return;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- section,
- "AUDITOR_KEY",
- &pks))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- section,
- "AUDITOR_KEY");
- GNUNET_free (auditor.name);
- GNUNET_free (auditor.url);
- return;
- }
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_public_key_from_string (pks,
- strlen (pks),
- &auditor.public_key.eddsa_pub))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- section,
- "AUDITOR_KEY",
- "need a valid EdDSA public key");
- GNUNET_free (auditor.name);
- GNUNET_free (auditor.url);
- GNUNET_free (pks);
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Loaded key data of auditor `%s' (%s)\n",
- auditor.name,
- TALER_B2S (&auditor.public_key));
- GNUNET_free (pks);
- GNUNET_array_append (auditors,
- nauditors,
- auditor);
-}
-
-
-int
-TMH_AUDITORS_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- GNUNET_CONFIGURATION_iterate_sections (cfg,
- &parse_auditors,
- (void *) cfg);
-
- /* Generate preferred exchange(s) array. */
- j_auditors = json_array ();
- for (unsigned int cnt = 0; cnt < nauditors; cnt++)
- GNUNET_assert (0 ==
- json_array_append_new (
- j_auditors,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("name",
- auditors[cnt].name),
- GNUNET_JSON_pack_data_auto ("auditor_pub",
- &auditors[cnt].public_key),
- GNUNET_JSON_pack_string ("url",
- auditors[cnt].url))));
- return nauditors;
-}
-
-
-/**
- * Release auditor information state.
- */
-void
-TMH_AUDITORS_done ()
-{
- json_decref (j_auditors);
- j_auditors = NULL;
- for (unsigned int i = 0; i<nauditors; i++)
- {
- GNUNET_free (auditors[i].name);
- GNUNET_free (auditors[i].url);
- }
- GNUNET_free (auditors);
- auditors = NULL;
- nauditors = 0;
-}
-
-
-/* end of taler-merchant-httpd_auditors.c */
diff --git a/src/backend/taler-merchant-httpd_auditors.h b/src/backend/taler-merchant-httpd_auditors.h
deleted file mode 100644
index 1d66c801..00000000
--- a/src/backend/taler-merchant-httpd_auditors.h
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- This file is part of TALER
- (C) 2014, 2015 GNUnet e.V. and INRIA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-merchant-httpd_auditors.h
- * @brief logic this HTTPD keeps for each exchange we interact with
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#ifndef TALER_MERCHANT_HTTPD_AUDITORS_H
-#define TALER_MERCHANT_HTTPD_AUDITORS_H
-
-#include <jansson.h>
-#include <gnunet/gnunet_util_lib.h>
-#include <taler/taler_util.h>
-#include <taler/taler_exchange_service.h>
-#include "taler-merchant-httpd.h"
-
-
-/**
- * JSON representation of the auditors accepted by this exchange.
- */
-extern json_t *j_auditors;
-
-
-/**
- * Parses auditor information from the configuration.
- *
- * @param cfg the configuration
- * @return the number of auditors found; #GNUNET_SYSERR upon error in
- * parsing.
- */
-int
-TMH_AUDITORS_init (const struct GNUNET_CONFIGURATION_Handle *cfg);
-
-
-/**
- * Check if the given @a dk issued by exchange @a mh is audited by
- * an auditor that is acceptable for this merchant. (And if the
- * denomination is not yet expired or something silly like that.)
- *
- * @param mh exchange issuing @a dk
- * @param dk a denomination issued by @a mh
- * @param exchange_trusted true if the exchange of @a dk is trusted by config
- * @param[out] hc set to the HTTP status code to return
- * @param[out] ec set to the Taler error code to return
- * @return #GNUNET_OK on success
- */
-int
-TMH_AUDITORS_check_dk (struct TALER_EXCHANGE_Handle *mh,
- const struct TALER_EXCHANGE_DenomPublicKey *dk,
- bool exchange_trusted,
- unsigned int *hc,
- enum TALER_ErrorCode *ec);
-
-
-/**
- * Release auditor information state.
- */
-void
-TMH_AUDITORS_done (void);
-
-
-#endif
diff --git a/src/backend/taler-merchant-httpd_config.c b/src/backend/taler-merchant-httpd_config.c
index 1833f78f..c7dec0f9 100644
--- a/src/backend/taler-merchant-httpd_config.c
+++ b/src/backend/taler-merchant-httpd_config.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2019, 2020, 2021 Taler Systems SA
+ (C) 2019, 2020, 2021, 2023, 2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -42,7 +42,38 @@
* #MERCHANT_PROTOCOL_CURRENT and #MERCHANT_PROTOCOL_AGE in
* merchant_api_config.c!
*/
-#define MERCHANT_PROTOCOL_VERSION "3:0:1"
+#define MERCHANT_PROTOCOL_VERSION "13:0:9"
+
+
+/**
+ * Callback on an exchange known to us. Does not warrant
+ * that the "keys" information is actually available for
+ * @a exchange.
+ *
+ * @param cls closure with `json_t *` array to expand
+ * @param url base URL of the exchange
+ * @param exchange internal handle for the exchange
+ */
+static void
+add_exchange (void *cls,
+ const char *url,
+ const struct TMH_Exchange *exchange)
+{
+ json_t *xa = cls;
+ json_t *xi;
+
+ xi = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("master_pub",
+ TMH_EXCHANGES_get_master_pub (exchange)),
+ GNUNET_JSON_pack_string ("currency",
+ TMH_EXCHANGES_get_currency (exchange)),
+ GNUNET_JSON_pack_string ("base_url",
+ url));
+ GNUNET_assert (NULL != xi);
+ GNUNET_assert (0 ==
+ json_array_append_new (xa,
+ xi));
+}
MHD_RESULT
@@ -56,9 +87,33 @@ MH_handler_config (struct TMH_RequestHandler *rh,
(void) hc;
if (NULL == response)
{
+ json_t *specs = json_object ();
+ json_t *exchanges = json_array ();
+
+ GNUNET_assert (NULL != specs);
+ GNUNET_assert (NULL != exchanges);
+ TMH_exchange_get_trusted (&add_exchange,
+ exchanges);
+ for (unsigned int i = 0; i<TMH_num_cspecs; i++)
+ {
+ const struct TALER_CurrencySpecification *cspec = &TMH_cspecs[i];
+
+ if (TMH_test_exchange_configured_for_currency (cspec->currency))
+ GNUNET_assert (0 ==
+ json_object_set_new (specs,
+ cspec->currency,
+ TALER_CONFIG_currency_specs_to_json (
+ cspec)));
+ }
response = TALER_MHD_MAKE_JSON_PACK (
GNUNET_JSON_pack_string ("currency",
TMH_currency),
+ GNUNET_JSON_pack_object_steal ("currencies",
+ specs),
+ GNUNET_JSON_pack_array_steal ("exchanges",
+ exchanges),
+ GNUNET_JSON_pack_string ("implementation",
+ "urn:net:taler:specs:merchant:c-reference"),
GNUNET_JSON_pack_string ("name",
"taler-merchant"),
GNUNET_JSON_pack_string ("version",
diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c
index f0324c47..260a725a 100644
--- a/src/backend/taler-merchant-httpd_exchanges.c
+++ b/src/backend/taler-merchant-httpd_exchanges.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2021 Taler Systems SA
+ (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -21,21 +21,15 @@
*/
#include "platform.h"
#include <taler/taler_json_lib.h>
+#include <taler/taler_dbevents.h>
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd.h"
-
-
-/**
- * Delay after which we'll re-fetch key information from the exchange.
- */
-#define RELOAD_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 2)
+#include <regex.h>
/**
- * Delay after which we'll allow clients to force us to re-fetch key
- * information from the exchange if we don't know the denomination key.
+ * How often do we retry DB transactions with soft errors?
*/
-#define FORCED_RELOAD_DELAY GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_MINUTES, 15)
+#define MAX_RETRIES 3
/**
* Threshold after which exponential backoff should not increase.
@@ -43,46 +37,35 @@
#define RETRY_BACKOFF_THRESHOLD GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_SECONDS, 60)
-
-/**
- * Perform our exponential back-off calculation, starting at 1 ms
- * and then going by a factor of 2 up unto a maximum of RETRY_BACKOFF_THRESHOLD.
- *
- * @param r current backoff time, initially zero
- */
-#define RETRY_BACKOFF(r) GNUNET_TIME_relative_min (RETRY_BACKOFF_THRESHOLD, \
- GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_relative_max ( \
- GNUNET_TIME_UNIT_MILLISECONDS, \
- (r)), 2));
-
-
/**
- * Exchange
+ * This is how long /keys long-polls for, so we should
+ * allow this time between requests if there is no
+ * answer. See exchange_api_handle.c.
*/
-struct Exchange;
+#define LONG_POLL_THRESHOLD GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_SECONDS, 120)
/**
- * Information we keep for a pending #MMH_EXCHANGES_find_exchange() operation.
+ * Information we keep for a pending #MMH_EXCHANGES_keys4exchange() operation.
*/
-struct TMH_EXCHANGES_FindOperation
+struct TMH_EXCHANGES_KeysOperation
{
/**
* Kept in a DLL.
*/
- struct TMH_EXCHANGES_FindOperation *next;
+ struct TMH_EXCHANGES_KeysOperation *next;
/**
* Kept in a DLL.
*/
- struct TMH_EXCHANGES_FindOperation *prev;
+ struct TMH_EXCHANGES_KeysOperation *prev;
/**
* Function to call with the result.
*/
- TMH_EXCHANGES_FindContinuation fc;
+ TMH_EXCHANGES_Find2Continuation fc;
/**
* Closure for @e fc.
@@ -92,12 +75,7 @@ struct TMH_EXCHANGES_FindOperation
/**
* Exchange we wait for the /keys for.
*/
- struct Exchange *my_exchange;
-
- /**
- * Wire method we care about for fees, NULL if we do not care about wire fees.
- */
- char *wire_method;
+ struct TMH_Exchange *my_exchange;
/**
* Task scheduled to asynchronously return the result to
@@ -130,43 +108,129 @@ struct FeesByWireMethod
char *wire_method;
/**
- * Full payto URI of the exchange.
+ * Applicable fees, NULL if unknown/error.
*/
- char *payto_uri;
+ struct TALER_EXCHANGE_WireAggregateFees *af;
+
+};
+
+/**
+ * Restriction that applies to an exchange account.
+ */
+struct Restriction
+{
/**
- * Applicable fees, NULL if unknown/error.
+ * Kept in a DLL.
*/
- struct TALER_EXCHANGE_WireAggregateFees *af;
+ struct Restriction *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct Restriction *prev;
+ /**
+ * Type of restriction imposed on the account.
+ */
+ enum TALER_EXCHANGE_AccountRestrictionType type;
+
+ /**
+ * Details depending on @e type.
+ */
+ union
+ {
+
+ /**
+ * Accounts must match the given regular expression.
+ */
+ struct
+ {
+
+ /**
+ * Pre-compiled regex.
+ */
+ regex_t ex;
+
+ } regex;
+
+ } details;
};
/**
- * Exchange
+ * Information about a bank account of the exchange.
*/
-struct Exchange
+struct ExchangeAccount
{
+ /**
+ * Kept in a DLL.
+ */
+ struct ExchangeAccount *next;
/**
* Kept in a DLL.
*/
- struct Exchange *next;
+ struct ExchangeAccount *prev;
+
+ /**
+ * Wire method of this exchange account.
+ */
+ char *wire_method;
+
+ /**
+ * Currency conversion that applies to this account,
+ * NULL if none.
+ */
+ char *conversion_url;
+
+ /**
+ * Head of DLL of debit restrictions of this account.
+ */
+ struct Restriction *d_head;
+
+ /**
+ * Tail of DLL of debit restrictions of this account.
+ */
+ struct Restriction *d_tail;
+};
+
+
+/**
+ * Internal representation for an exchange
+ */
+struct TMH_Exchange
+{
/**
* Kept in a DLL.
*/
- struct Exchange *prev;
+ struct TMH_Exchange *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct TMH_Exchange *prev;
/**
* Head of FOs pending for this exchange.
*/
- struct TMH_EXCHANGES_FindOperation *fo_head;
+ struct TMH_EXCHANGES_KeysOperation *keys_head;
/**
* Tail of FOs pending for this exchange.
*/
- struct TMH_EXCHANGES_FindOperation *fo_tail;
+ struct TMH_EXCHANGES_KeysOperation *keys_tail;
+
+ /**
+ * Head of accounts of this exchange.
+ */
+ struct ExchangeAccount *acc_head;
+
+ /**
+ * Tail of accounts of this exchange.
+ */
+ struct ExchangeAccount *acc_tail;
/**
* (base) URL of the exchange.
@@ -174,19 +238,19 @@ struct Exchange
char *url;
/**
- * A connection to this exchange
+ * Currency offered by the exchange according to OUR configuration.
*/
- struct TALER_EXCHANGE_Handle *conn;
+ char *currency;
/**
- * Active /wire request to the exchange, or NULL.
+ * A connection to this exchange
*/
- struct TALER_EXCHANGE_WireHandle *wire_request;
+ struct TALER_EXCHANGE_GetKeysHandle *conn;
/**
- * Task to re-run /wire after some delay.
+ * The keys of this exchange
*/
- struct GNUNET_SCHEDULER_Task *wire_task;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* Head of wire fees from /wire request.
@@ -199,8 +263,7 @@ struct Exchange
struct FeesByWireMethod *wire_fees_tail;
/**
- * Master public key, guaranteed to be set ONLY for
- * trusted exchanges.
+ * Master public key of the exchange.
*/
struct TALER_MasterPublicKeyP master_pub;
@@ -225,11 +288,45 @@ struct Exchange
struct GNUNET_SCHEDULER_Task *retry_task;
/**
- * true to indicate that there is an ongoing
- * transfer we are waiting for,
- * false to indicate that key data is up-to-date.
+ * What state is this exchange in?
*/
- bool pending;
+ enum
+ {
+
+ /**
+ * Downloading /keys failed.
+ */
+ ESTATE_FAILED = -1,
+
+ /**
+ * Nothing was ever done.
+ */
+ ESTATE_INIT = 0,
+
+ /**
+ * We are actively downloading /keys for the first time.
+ */
+ ESTATE_DOWNLOADING_FIRST = 1,
+
+ /**
+ * We finished downloading /keys and the exchange is
+ * ready.
+ */
+ ESTATE_DOWNLOADED = 2,
+
+ /**
+ * We are downloading /keys again after a previous
+ * success.
+ */
+ ESTATE_REDOWNLOADING_SUCCESS = 3,
+
+ /**
+ * We are downloading /keys again after a previous
+ * failure.
+ */
+ ESTATE_REDOWNLOADING_FAILURE = 4
+
+ } state;
/**
* true if this exchange is from our configuration and
@@ -238,557 +335,633 @@ struct Exchange
*/
bool trusted;
- /**
- * true if this exchange did return to use the
- * response from /wire.
- */
- bool have_wire;
-
};
/**
- * Context for all exchange operations (useful to the event loop)
+ * Head of exchanges we know about.
*/
-static struct GNUNET_CURL_Context *merchant_curl_ctx;
+static struct TMH_Exchange *exchange_head;
/**
- * Context for integrating #merchant_curl_ctx with the
- * GNUnet event loop.
+ * Tail of exchanges we know about.
*/
-static struct GNUNET_CURL_RescheduleContext *merchant_curl_rc;
+static struct TMH_Exchange *exchange_tail;
/**
- * Head of exchanges we know about.
+ * Our event handler listening for /keys downloads
+ * being put into the database.
*/
-static struct Exchange *exchange_head;
+static struct GNUNET_DB_EventHandler *keys_eh;
/**
- * Tail of exchanges we know about.
+ * How many exchanges do we trust (for our configured
+ * currency) as per our configuration? Used for a
+ * sanity-check on startup.
*/
-static struct Exchange *exchange_tail;
+static int trusted_exchange_count;
-/**
- * List of our trusted exchanges for inclusion in contracts.
- */
-json_t *TMH_trusted_exchanges;
+const struct TALER_MasterPublicKeyP *
+TMH_EXCHANGES_get_master_pub (
+ const struct TMH_Exchange *exchange)
+{
+ GNUNET_break ( (exchange->trusted) ||
+ (NULL != exchange->keys) );
+ return &exchange->master_pub;
+}
-/**
- * Function called with information about who is auditing
- * a particular exchange and what key the exchange is using.
- *
- * @param cls closure, will be `struct Exchange` so that
- * when this function gets called, it will change the flag 'pending'
- * to 'false'. Note: 'keys' is automatically saved inside the exchange's
- * handle, which is contained inside 'struct Exchange', when
- * this callback is called. Thus, once 'pending' turns 'false',
- * it is safe to call 'TALER_EXCHANGE_get_keys()' on the exchange's handle,
- * in order to get the "good" keys.
- * @param hr http response details
- * @param keys information about the various keys used
- * by the exchange
- * @param compat version compatibility data
- */
-static void
-keys_mgmt_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_EXCHANGE_Keys *keys,
- enum TALER_EXCHANGE_VersionCompatibility compat);
+
+const char *
+TMH_EXCHANGES_get_currency (
+ const struct TMH_Exchange *exchange)
+{
+ return exchange->currency;
+}
/**
- * Retry getting information from the given exchange in
- * the closure.
+ * Free data structures within @a ea, but not @a ea
+ * pointer itself.
*
- * @param cls the exchange
+ * @param[in] ea data structure to free
*/
static void
-retry_exchange (void *cls)
+free_exchange_account (struct ExchangeAccount *ea)
{
- struct Exchange *exchange = cls;
+ struct Restriction *r;
- /* might be a scheduled reload and not our first attempt */
- exchange->retry_task = NULL;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Connecting to exchange %s in retry_exchange()\n",
- exchange->url);
- if (NULL == exchange->conn)
+ while (NULL != (r = ea->d_head))
{
- exchange->conn = TALER_EXCHANGE_connect (merchant_curl_ctx,
- exchange->url,
- &keys_mgmt_cb,
- exchange,
- TALER_EXCHANGE_OPTION_END);
- }
- else
- {
- struct GNUNET_TIME_Timestamp next;
-
- next = TALER_EXCHANGE_check_keys_current (exchange->conn,
- TALER_EXCHANGE_CKF_NONE);
- if (! GNUNET_TIME_absolute_is_zero (next.abs_time))
+ GNUNET_CONTAINER_DLL_remove (ea->d_head,
+ ea->d_tail,
+ r);
+ switch (r->type)
{
- exchange->retry_task = GNUNET_SCHEDULER_add_at (next.abs_time,
- &retry_exchange,
- exchange);
+ case TALER_EXCHANGE_AR_INVALID:
+ GNUNET_assert (0);
+ break;
+ case TALER_EXCHANGE_AR_DENY:
+ break;
+ case TALER_EXCHANGE_AR_REGEX:
+ regfree (&r->details.regex.ex);
+ break;
}
+ GNUNET_free (r);
}
- /* Note: while the API spec says 'returns NULL on error', the implementation
- actually never returns NULL. */
- GNUNET_break (NULL != exchange->conn);
+ GNUNET_free (ea->wire_method);
+ GNUNET_free (ea->conversion_url);
}
/**
- * Function called with information about the wire fees
- * for each wire method. Stores the wire fees with the
- * exchange for later use.
+ * Free list of all accounts in @a exchange.
*
- * @param exchange connection to the exchange
- * @param master_pub public key of the exchange
- * @param wire_method name of the wire method (i.e. "iban")
- * @param payto_uri full payto URI of the exchange
- * @param fees fee structure for this method
- * @return #TALER_EC_NONE on success
+ * @param[in,out] exchange entry to free accounts for
*/
-static enum TALER_ErrorCode
-process_wire_fees (struct Exchange *exchange,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *wire_method,
- const char *payto_uri,
- const struct TALER_EXCHANGE_WireAggregateFees *fees)
+static void
+purge_exchange_accounts (struct TMH_Exchange *exchange)
{
- struct FeesByWireMethod *f;
- struct TALER_EXCHANGE_WireAggregateFees *endp;
- struct TALER_EXCHANGE_WireAggregateFees *af;
+ struct ExchangeAccount *acc;
- for (f = exchange->wire_fees_head; NULL != f; f = f->next)
- if (0 == strcasecmp (wire_method,
- f->wire_method))
- break;
- if (NULL == f)
- {
- f = GNUNET_new (struct FeesByWireMethod);
- f->wire_method = GNUNET_strdup (wire_method);
- f->payto_uri = GNUNET_strdup (payto_uri);
- GNUNET_CONTAINER_DLL_insert (exchange->wire_fees_head,
- exchange->wire_fees_tail,
- f);
- }
- endp = f->af;
- while ( (NULL != endp) &&
- (NULL != endp->next) )
- endp = endp->next;
- while ( (NULL != endp) &&
- (NULL != fees) &&
- (GNUNET_TIME_timestamp_cmp (fees->start_date,
- <,
- endp->end_date)) )
- fees = fees->next;
- if ( (NULL != endp) &&
- (NULL != fees) &&
- (GNUNET_TIME_timestamp_cmp (fees->start_date,
- !=,
- endp->end_date)) )
- {
- /* Hole in the fee structure, not allowed! */
- GNUNET_break_op (0);
- return TALER_EC_MERCHANT_GENERIC_HOLE_IN_WIRE_FEE_STRUCTURE;
- }
- while (NULL != fees)
+ while (NULL != (acc = exchange->acc_head))
{
- struct GNUNET_HashCode h_wire_method;
- enum GNUNET_DB_QueryStatus qs;
-
- af = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees);
- *af = *fees;
- GNUNET_CRYPTO_hash (wire_method,
- strlen (wire_method) + 1,
- &h_wire_method);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Storing wire fee for `%s' and method `%s' at %s in DB; the fee is %s\n",
- TALER_B2S (master_pub),
- wire_method,
- GNUNET_TIME_timestamp2s (af->start_date),
- TALER_amount2s (&af->fees.wire));
- TMH_db->preflight (TMH_db->cls);
- if (GNUNET_OK !=
- TMH_db->start (TMH_db->cls,
- "store wire fee"))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to start database transaction to store exchange wire fees (will try to continue anyway)!\n");
- GNUNET_free (af);
- fees = fees->next;
- continue;
- }
- qs = TMH_db->store_wire_fee_by_exchange (TMH_db->cls,
- master_pub,
- &h_wire_method,
- &af->fees,
- af->start_date,
- af->end_date,
- &af->master_sig);
- if (0 > qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to persist exchange wire fees in merchant DB (will try to continue anyway)!\n");
- GNUNET_free (af);
- fees = fees->next;
- TMH_db->rollback (TMH_db->cls);
- continue;
- }
- if (0 == qs)
- {
- /* Entry was already in DB, fine, continue as if we had succeeded */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Fees already in DB, rolling back transaction attempt!\n");
- TMH_db->rollback (TMH_db->cls);
- }
- if (0 < qs)
- {
- /* Inserted into DB, make sure transaction completes */
- qs = TMH_db->commit (TMH_db->cls);
- if (0 > qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to persist exchange wire fees in merchant DB (will try to continue anyway)!\n");
- GNUNET_free (af);
- fees = fees->next;
- continue;
- }
- }
- af->next = NULL;
- if (NULL == endp)
- f->af = af;
- else
- endp->next = af;
- endp = af;
- fees = fees->next;
+ GNUNET_CONTAINER_DLL_remove (exchange->acc_head,
+ exchange->acc_tail,
+ acc);
+ free_exchange_account (acc);
+ GNUNET_free (acc);
}
- return TALER_EC_NONE;
}
/**
- * Function called with information about the wire accounts
- * of the exchange. Stores the wire fees with the
- * exchange for laster use.
+ * Lookup exchange by @a exchange_url. Create one
+ * if it does not exist.
*
- * @param exchange the exchange
- * @param master_pub public key of the exchange
- * @param accounts_len length of the @a accounts array
- * @param accounts list of wire accounts of the exchange
- * @return #TALER_EC_NONE on success
+ * @param exchange_url base URL to match against
+ * @return fresh entry if exchange was not yet known
*/
-static enum TALER_ErrorCode
-process_wire_accounts (struct Exchange *exchange,
- const struct TALER_MasterPublicKeyP *master_pub,
- unsigned int accounts_len,
- const struct TALER_EXCHANGE_WireAccount *accounts)
+static struct TMH_Exchange *
+lookup_exchange (const char *exchange_url)
{
- for (unsigned int i = 0; i<accounts_len; i++)
- {
- enum TALER_ErrorCode ec;
- char *method;
+ struct TMH_Exchange *exchange;
+ enum GNUNET_DB_QueryStatus qs;
- method = TALER_payto_get_method (accounts[i].payto_uri);
- if (NULL == method)
- {
- /* malformed payto:// URI returned by exchange */
- GNUNET_break_op (0);
- return TALER_EC_GENERIC_PAYTO_URI_MALFORMED;
- }
- ec = process_wire_fees (exchange,
- master_pub,
- method,
- accounts[i].payto_uri,
- accounts[i].fees);
- GNUNET_free (method);
- if (TALER_EC_NONE != ec)
- return ec;
- }
- return TALER_EC_NONE;
+ for (exchange = exchange_head;
+ NULL != exchange;
+ exchange = exchange->next)
+ if (0 == strcmp (exchange->url,
+ exchange_url))
+ return exchange;
+ exchange = GNUNET_new (struct TMH_Exchange);
+ exchange->url = GNUNET_strdup (exchange_url);
+ GNUNET_CONTAINER_DLL_insert (exchange_head,
+ exchange_tail,
+ exchange);
+ qs = TMH_db->select_exchange_keys (TMH_db->cls,
+ exchange->url,
+ &exchange->keys);
+ GNUNET_break (qs >= 0);
+ if (qs > 0)
+ exchange->state = ESTATE_DOWNLOADED;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "The exchange `%s' is new (%d)\n",
+ exchange_url,
+ exchange->state);
+ return exchange;
}
/**
- * Obtain applicable fees for @a exchange and @a wire_method.
+ * Set the list of accounts of @a exchange.
*
- * @param exchange the exchange to query
- * @param now current time
- * @param wire_method the wire method we want the fees for
- * @return NULL if we do not have fees for this method yet
+ * @param[in,out] exchange exchange to initialize or update
+ * @param accounts_len length of @a accounts array
+ * @param accounts array of accounts to convert
+ * @return #GNUNET_OK on success
*/
-static const struct FeesByWireMethod *
-get_wire_fees (struct Exchange *exchange,
- struct GNUNET_TIME_Timestamp now,
- const char *wire_method)
+static enum GNUNET_GenericReturnValue
+set_exchange_accounts (
+ struct TMH_Exchange *exchange,
+ unsigned int accounts_len,
+ const struct TALER_EXCHANGE_WireAccount accounts[static accounts_len])
{
- for (struct FeesByWireMethod *fbw = exchange->wire_fees_head;
- NULL != fbw;
- fbw = fbw->next)
+ enum GNUNET_GenericReturnValue ret = GNUNET_OK;
+
+ purge_exchange_accounts (exchange);
+ for (unsigned int i = 0; i<accounts_len; i++)
{
- if (0 == strcasecmp (fbw->wire_method,
- wire_method) )
+ const struct TALER_EXCHANGE_WireAccount *account = &accounts[i];
+ struct ExchangeAccount *acc;
+
+ acc = GNUNET_new (struct ExchangeAccount);
+ acc->wire_method = TALER_payto_get_method (account->payto_uri);
+ if (NULL != account->conversion_url)
+ acc->conversion_url = GNUNET_strdup (account->conversion_url);
+ GNUNET_CONTAINER_DLL_insert (exchange->acc_head,
+ exchange->acc_tail,
+ acc);
+ for (unsigned int j = 0; j<account->debit_restrictions_length; j++)
{
- struct TALER_EXCHANGE_WireAggregateFees *af;
+ const struct TALER_EXCHANGE_AccountRestriction *ar =
+ &account->debit_restrictions[j];
+ struct Restriction *r;
- /* Advance through list up to current time */
- while ( (NULL != (af = fbw->af)) &&
- (GNUNET_TIME_timestamp_cmp (now,
- >=,
- af->end_date)) )
+ r = GNUNET_new (struct Restriction);
+ r->type = ar->type;
+ switch (ar->type)
{
- fbw->af = af->next;
- GNUNET_free (af);
+ case TALER_EXCHANGE_AR_INVALID:
+ GNUNET_assert (0);
+ break;
+ case TALER_EXCHANGE_AR_DENY:
+ break;
+ case TALER_EXCHANGE_AR_REGEX:
+ if (0 != regcomp (&r->details.regex.ex,
+ ar->details.regex.posix_egrep,
+ REG_NOSUB | REG_EXTENDED))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (r);
+ ret = GNUNET_SYSERR;
+ continue;
+ }
+ break;
}
- return fbw;
+ GNUNET_CONTAINER_DLL_insert (acc->d_head,
+ acc->d_tail,
+ r);
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Exchange supports `%s' as a wire method (but we do not use that one)\n",
- fbw->wire_method);
}
- return NULL;
+ return ret;
}
/**
+ * Function called with information about who is auditing
+ * a particular exchange and what key the exchange is using.
+ *
+ * @param cls closure, will be `struct TMH_Exchange`
+ * @param kr response details
+ * @param[in] keys keys object returned
+ */
+static void
+keys_mgmt_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_KeysResponse *kr,
+ struct TALER_EXCHANGE_Keys *keys);
+
+
+/**
* Check if we have any remaining pending requests for the
* given @a exchange, and if we have the required data, call
* the callback.
*
* @param exchange the exchange to check for pending find operations
- * @return true if we need /wire data from @a exchange
*/
-static bool
-process_find_operations (struct Exchange *exchange)
+static void
+process_find_operations (struct TMH_Exchange *exchange)
{
- struct TMH_EXCHANGES_FindOperation *fn;
struct GNUNET_TIME_Timestamp now;
- bool need_wire;
now = GNUNET_TIME_timestamp_get ();
- need_wire = false;
- for (struct TMH_EXCHANGES_FindOperation *fo = exchange->fo_head;
- NULL != fo;
- fo = fn)
+ for (struct FeesByWireMethod *fbw = exchange->wire_fees_head;
+ NULL != fbw;
+ fbw = fbw->next)
{
- const struct FeesByWireMethod *fbw;
-
- fn = fo->next;
- if (NULL != fo->wire_method)
+ while ( (NULL != fbw->af) &&
+ (GNUNET_TIME_timestamp_cmp (fbw->af->end_date,
+ <,
+ now)) )
{
- /* Find fee structure for our wire method */
- fbw = get_wire_fees (exchange,
- now,
- fo->wire_method);
- if (NULL == fbw)
- {
- need_wire = true;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Missing wire fees for exchange %s and method %s\n",
- exchange->url,
- fo->wire_method);
- /* Do not warn if this is before our first attempt */
- if (! GNUNET_TIME_relative_is_zero (exchange->wire_retry_delay))
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Exchange does not support `%s' wire method (will retry later)\n",
- fo->wire_method);
- fbw = NULL;
- continue;
- }
- if (NULL == fbw->af)
- {
- /* Disagreement on the current time */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Exchange has no wire fees configured for `%s' wire method (will retry later)\n",
- fo->wire_method);
- fbw = NULL;
- continue;
- }
- if (GNUNET_TIME_timestamp_cmp (fbw->af->start_date,
- >,
- now))
- {
- /* Disagreement on the current time */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Exchange's earliest fee is %s ahead of our time. Clock skew issue?\n",
- GNUNET_TIME_relative2s (
- GNUNET_TIME_absolute_get_remaining (
- fbw->af->start_date.abs_time),
- true));
- fbw = NULL;
- continue;
- }
+ struct TALER_EXCHANGE_WireAggregateFees *af = fbw->af;
+
+ fbw->af = af->next;
+ GNUNET_free (af);
}
- else
+ if (NULL == fbw->af)
{
- /* no wire transfer method given, so we yield no fee */
- fbw = NULL;
+ /* Disagreement on the current time */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Exchange has no wire fees configured for `%s' wire method\n",
+ fbw->wire_method);
}
+ else if (GNUNET_TIME_timestamp_cmp (fbw->af->start_date,
+ >,
+ now))
{
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = MHD_HTTP_OK,
- };
+ /* Disagreement on the current time */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Exchange's earliest fee is %s ahead of our time. Clock skew issue?\n",
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_remaining (
+ fbw->af->start_date.abs_time),
+ true));
+ }
+ } /* for all wire methods */
- if ( (NULL != fo->wire_method) &&
- (! exchange->have_wire) )
- {
- /* We needed /wire, but didn't get it. That's not "200 OK". */
- hr.http_status = MHD_HTTP_BAD_GATEWAY;
- hr.ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_WIRE_REQUEST_FAILED;
- }
- fo->fc (fo->fc_cls,
- &hr,
- exchange->conn,
- (NULL != fbw) ? fbw->payto_uri : NULL,
- (NULL != fbw) ? &fbw->af->fees.wire : NULL,
- exchange->trusted);
+ {
+ struct TMH_EXCHANGES_KeysOperation *kon;
+
+ kon = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Processing find operations for `%s' (%d)\n",
+ exchange->url,
+ exchange->state);
+ for (struct TMH_EXCHANGES_KeysOperation *ko = exchange->keys_head;
+ NULL != ko;
+ ko = kon)
+ {
+ kon = ko->next;
+ ko->fc (ko->fc_cls,
+ exchange->keys,
+ exchange);
+ TMH_EXCHANGES_keys4exchange_cancel (ko);
}
- TMH_EXCHANGES_find_exchange_cancel (fo);
}
- return need_wire;
}
-static void
-wire_task_cb (void *cls);
+/**
+ * Function called with information about the wire fees for each wire method.
+ * Stores the wire fees within our internal data structures for later use.
+ *
+ * @param exchange connection to the exchange
+ * @param master_pub public key of the exchange
+ * @param num_methods number of wire methods supported
+ * @param fbm wire fees by method
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+process_wire_fees (
+ struct TMH_Exchange *exchange,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ unsigned int num_methods,
+ const struct TALER_EXCHANGE_WireFeesByMethod fbm[static num_methods])
+{
+ for (unsigned int i = 0; i<num_methods; i++)
+ {
+ const char *wire_method = fbm[i].method;
+ const struct TALER_EXCHANGE_WireAggregateFees *fees = fbm[i].fees_head;
+ struct FeesByWireMethod *f;
+ struct TALER_EXCHANGE_WireAggregateFees *endp;
+
+ for (f = exchange->wire_fees_head; NULL != f; f = f->next)
+ if (0 == strcasecmp (wire_method,
+ f->wire_method))
+ break;
+ if (NULL == f)
+ {
+ f = GNUNET_new (struct FeesByWireMethod);
+ f->wire_method = GNUNET_strdup (wire_method);
+ GNUNET_CONTAINER_DLL_insert (exchange->wire_fees_head,
+ exchange->wire_fees_tail,
+ f);
+ }
+ endp = f->af;
+ while ( (NULL != endp) &&
+ (NULL != endp->next) )
+ endp = endp->next;
+ while ( (NULL != endp) &&
+ (NULL != fees) &&
+ (GNUNET_TIME_timestamp_cmp (fees->start_date,
+ <,
+ endp->end_date)) )
+ fees = fees->next;
+ if ( (NULL != endp) &&
+ (NULL != fees) &&
+ (GNUNET_TIME_timestamp_cmp (fees->start_date,
+ !=,
+ endp->end_date)) )
+ {
+ /* Hole or overlap in the fee structure, not allowed! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ while (NULL != fees)
+ {
+ struct TALER_EXCHANGE_WireAggregateFees *af;
+
+ af = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees);
+ *af = *fees;
+ af->next = NULL;
+ if (NULL == endp)
+ f->af = af;
+ else
+ endp->next = af;
+ endp = af;
+ fees = fees->next;
+ } /* all fees for this method */
+ } /* for all methods (i) */
+ return GNUNET_OK;
+}
/**
- * Callbacks of this type are used to serve the result of submitting a
- * wire format inquiry request to a exchange.
- *
- * If the request fails to generate a valid response from the
- * exchange, @a http_status will also be zero.
+ * Add account restriction @a a to array of @a restrictions.
*
- * Must only be called if 'exchange->pending' is #GNUNET_NO,
- * that is #TALER_EXCHANGE_get_keys() will succeed.
+ * @param[in,out] restrictions JSON array to build
+ * @param r restriction to add to @a restrictions
+ * @return #GNUNET_SYSERR if @a r is malformed
+ */
+static enum GNUNET_GenericReturnValue
+add_restriction (json_t *restrictions,
+ const struct TALER_EXCHANGE_AccountRestriction *r)
+{
+ json_t *jr;
+
+ jr = NULL;
+ switch (r->type)
+ {
+ case TALER_EXCHANGE_AR_INVALID:
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ case TALER_EXCHANGE_AR_DENY:
+ jr = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "deny")
+ );
+ break;
+ case TALER_EXCHANGE_AR_REGEX:
+ jr = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string (
+ "type",
+ "regex"),
+ GNUNET_JSON_pack_string (
+ "regex",
+ r->details.regex.posix_egrep),
+ GNUNET_JSON_pack_string (
+ "human_hint",
+ r->details.regex.human_hint),
+ GNUNET_JSON_pack_object_incref (
+ "human_hint_i18n",
+ (json_t *) r->details.regex.human_hint_i18n)
+ );
+ break;
+ }
+ if (NULL == jr)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (restrictions,
+ jr));
+ return GNUNET_OK;
+
+}
+
+
+/**
+ * Retry getting keys from the given exchange in the closure.
*
- * @param cls closure, a `struct Exchange`
- * @param hr HTTP response details
- * @param accounts_len length of the @a accounts array
- * @param accounts list of wire accounts of the exchange, NULL on error
+ * @param cls the `struct TMH_Exchange *`
*/
static void
-handle_wire_data (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- unsigned int accounts_len,
- const struct TALER_EXCHANGE_WireAccount *accounts)
+retry_exchange (void *cls)
{
- struct Exchange *exchange = cls;
- const struct TALER_EXCHANGE_Keys *keys;
- enum TALER_ErrorCode ecx;
+ struct TMH_Exchange *exchange = cls;
- exchange->wire_request = NULL;
+ exchange->retry_task = NULL;
+ GNUNET_assert (NULL == exchange->conn);
+ exchange->retry_delay
+ = GNUNET_TIME_randomized_backoff (exchange->retry_delay,
+ RETRY_BACKOFF_THRESHOLD);
+ /* Block for the duration of the long-poller */
+ exchange->first_retry
+ = GNUNET_TIME_relative_to_absolute (LONG_POLL_THRESHOLD);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received /wire response\n");
- if (MHD_HTTP_OK != hr->http_status)
+ "Fetching /keys from exchange %s in retry_exchange()\n",
+ exchange->url);
+ switch (exchange->state)
{
- struct TMH_EXCHANGES_FindOperation *fo;
-
- exchange->have_wire = false;
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to obtain /wire details from `%s': %u/%d\n",
- exchange->url,
- hr->http_status,
- hr->ec);
- while (NULL != (fo = exchange->fo_head))
- {
- fo->fc (fo->fc_cls,
- hr,
- exchange->conn,
- NULL,
- NULL,
- GNUNET_NO);
- TMH_EXCHANGES_find_exchange_cancel (fo);
- }
+ case ESTATE_FAILED:
+ exchange->state = ESTATE_REDOWNLOADING_FAILURE;
+ break;
+ case ESTATE_INIT:
+ exchange->state = ESTATE_DOWNLOADING_FIRST;
+ break;
+ case ESTATE_DOWNLOADING_FIRST:
+ GNUNET_break (0);
+ return;
+ case ESTATE_DOWNLOADED:
+ exchange->state = ESTATE_REDOWNLOADING_SUCCESS;
+ break;
+ case ESTATE_REDOWNLOADING_SUCCESS:
+ GNUNET_break (0);
+ return;
+ case ESTATE_REDOWNLOADING_FAILURE:
+ GNUNET_break (0);
return;
}
- keys = TALER_EXCHANGE_get_keys (exchange->conn);
- GNUNET_assert (NULL != keys);
- ecx = process_wire_accounts (exchange,
- &keys->master_pub,
- accounts_len,
- accounts);
- if (TALER_EC_NONE != ecx)
- {
- /* Report hard failure to all callbacks! */
- struct TMH_EXCHANGES_FindOperation *fo;
- struct TALER_EXCHANGE_HttpResponse hrx = {
- .ec = ecx,
- .http_status = 0,
- .reply = hr->reply
- };
+ exchange->conn
+ = TALER_EXCHANGE_get_keys (
+ TMH_curl_ctx,
+ exchange->url,
+ exchange->keys,
+ &keys_mgmt_cb,
+ exchange);
+ /* Note: while the API spec says 'returns NULL on error', the implementation
+ actually never returns NULL. */
+ GNUNET_break (NULL != exchange->conn);
+}
- GNUNET_break_op (0);
- exchange->have_wire = false;
- while (NULL != (fo = exchange->fo_head))
- {
- fo->fc (fo->fc_cls,
- &hrx,
- NULL,
- NULL,
- NULL,
- GNUNET_NO);
- TMH_EXCHANGES_find_exchange_cancel (fo);
- }
- return;
+
+/**
+ * Task to asynchronously return keys operation result to caller.
+ *
+ * @param cls a `struct TMH_EXCHANGES_KeysOperation`
+ */
+static void
+return_keys (void *cls)
+{
+ struct TMH_EXCHANGES_KeysOperation *fo = cls;
+ struct TMH_Exchange *exchange = fo->my_exchange;
+
+ fo->at = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning key data for %s instantly\n",
+ exchange->url);
+ process_find_operations (exchange);
+}
+
+
+struct TMH_EXCHANGES_KeysOperation *
+TMH_EXCHANGES_keys4exchange (
+ const char *chosen_exchange,
+ bool force_download,
+ TMH_EXCHANGES_Find2Continuation fc,
+ void *fc_cls)
+{
+ struct TMH_Exchange *exchange;
+ struct TMH_EXCHANGES_KeysOperation *fo;
+
+ if (NULL == TMH_curl_ctx)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Trying to find chosen exchange `%s'\n",
+ chosen_exchange);
+ exchange = lookup_exchange (chosen_exchange);
+ fo = GNUNET_new (struct TMH_EXCHANGES_KeysOperation);
+ fo->fc = fc;
+ fo->fc_cls = fc_cls;
+ fo->my_exchange = exchange;
+ GNUNET_CONTAINER_DLL_insert (exchange->keys_head,
+ exchange->keys_tail,
+ fo);
+ if ( (NULL != exchange->keys) &&
+ (! force_download) &&
+ (GNUNET_TIME_absolute_is_future (
+ exchange->keys->key_data_expiration.abs_time)) )
+ {
+ /* We have a valid reply, immediately return result */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "The exchange `%s' is ready\n",
+ exchange->url);
+ GNUNET_assert (NULL == fo->at);
+ fo->at = GNUNET_SCHEDULER_add_now (&return_keys,
+ fo);
+ return fo;
}
- exchange->have_wire = true;
- if ( (process_find_operations (exchange)) &&
- (NULL == exchange->wire_task) &&
- (NULL == exchange->wire_request) )
+ if ( (NULL == exchange->conn) &&
+ ( (ESTATE_FAILED == exchange->state) ||
+ (ESTATE_REDOWNLOADING_FAILURE == exchange->state) ) )
{
- /* need to run /wire again. But as we DID get a successful reply,
- and as the exchange is unlikely to offer new wire methods very
- frequently, start with some significant delay */
- exchange->wire_retry_delay
- = GNUNET_TIME_relative_max (GNUNET_TIME_UNIT_MINUTES,
- exchange->wire_retry_delay);
- exchange->wire_retry_delay = RETRY_BACKOFF (exchange->wire_retry_delay);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Exchange does not support our wire method. Retrying in %s\n",
-
- GNUNET_STRINGS_relative_time_to_string (
- exchange->wire_retry_delay,
- GNUNET_YES));
- exchange->wire_task
- = GNUNET_SCHEDULER_add_delayed (exchange->wire_retry_delay,
- &wire_task_cb,
- exchange);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Already waiting for `%skeys' for a while, failing query instantly\n",
+ exchange->url);
+ GNUNET_assert (NULL == fo->at);
+ fo->at = GNUNET_SCHEDULER_add_now (&return_keys,
+ fo);
+ return fo;
+ }
+ if ( (force_download) &&
+ (GNUNET_TIME_absolute_is_future (exchange->first_retry)) &&
+ (ESTATE_DOWNLOADED == exchange->state) )
+ {
+ /* Return results immediately. */
+ fo->at = GNUNET_SCHEDULER_add_now (&return_keys,
+ fo);
+ /* *no* return here, we MAY schedule a 'retry_task' in the
+ next block if there isn't one yet */
}
+ if ( (NULL == exchange->retry_task) &&
+ (NULL == exchange->conn) )
+ exchange->retry_task
+ = GNUNET_SCHEDULER_add_at (exchange->first_retry,
+ &retry_exchange,
+ exchange);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Next %skeys (%d) request scheduled for %s\n",
+ exchange->url,
+ exchange->state,
+ GNUNET_TIME_absolute2s (
+ exchange->first_retry));
+ /* No activity to launch, we are already doing so. */
+ return fo;
+}
+
+
+void
+TMH_EXCHANGES_keys4exchange_cancel (
+ struct TMH_EXCHANGES_KeysOperation *fo)
+{
+ struct TMH_Exchange *exchange = fo->my_exchange;
+
+ if (NULL != fo->at)
+ {
+ GNUNET_SCHEDULER_cancel (fo->at);
+ fo->at = NULL;
+ }
+ GNUNET_CONTAINER_DLL_remove (exchange->keys_head,
+ exchange->keys_tail,
+ fo);
+ GNUNET_free (fo);
}
/**
- * Check if we have any remaining pending requests for the
- * given @a exchange, and if we have the required data, call
- * the callback. If requests without /wire data remain,
- * retry the /wire request after some delay.
- *
- * Must only be called if 'exchange->pending' is #GNUNET_NO,
- * that is #TALER_EXCHANGE_get_keys() will succeed.
+ * Obtain applicable fees for @a exchange and @a wire_method.
*
- * @param cls a `struct Exchange` to check
+ * @param exchange the exchange to query
+ * @param now current time
+ * @param wire_method the wire method we want the fees for
+ * @return NULL if we do not have fees for this method yet
*/
-static void
-wire_task_cb (void *cls)
+static const struct FeesByWireMethod *
+get_wire_fees (const struct TMH_Exchange *exchange,
+ struct GNUNET_TIME_Timestamp now,
+ const char *wire_method)
{
- struct Exchange *exchange = cls;
+ for (struct FeesByWireMethod *fbw = exchange->wire_fees_head;
+ NULL != fbw;
+ fbw = fbw->next)
+ {
+ if (0 == strcasecmp (fbw->wire_method,
+ wire_method) )
+ {
+ struct TALER_EXCHANGE_WireAggregateFees *af;
- exchange->wire_task = NULL;
- GNUNET_assert (! exchange->pending);
- if (! process_find_operations (exchange))
- return; /* no more need */
- GNUNET_assert (NULL == exchange->wire_request);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Initiating /wire download\n");
- exchange->wire_request = TALER_EXCHANGE_wire (exchange->conn,
- &handle_wire_data,
- exchange);
+ /* Advance through list up to current time */
+ while ( (NULL != (af = fbw->af)) &&
+ (GNUNET_TIME_timestamp_cmp (now,
+ >=,
+ af->end_date)) )
+ {
+ fbw->af = af->next;
+ GNUNET_free (af);
+ }
+ return fbw;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange supports `%s' as a wire method (but we do not use that one)\n",
+ fbw->wire_method);
+ }
+ return NULL;
}
@@ -798,13 +971,19 @@ wire_task_cb (void *cls)
* @param[in] exchange entry to free
*/
static void
-free_exchange_entry (struct Exchange *exchange)
+free_exchange_entry (struct TMH_Exchange *exchange)
{
struct FeesByWireMethod *f;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Releasing %s exchange %s (%d)\n",
+ exchange->trusted ? "trusted" : "untrusted",
+ exchange->url,
+ exchange->state);
GNUNET_CONTAINER_DLL_remove (exchange_head,
exchange_tail,
exchange);
+ purge_exchange_accounts (exchange);
while (NULL != (f = exchange->wire_fees_head))
{
struct TALER_EXCHANGE_WireAggregateFees *af;
@@ -818,31 +997,23 @@ free_exchange_entry (struct Exchange *exchange)
GNUNET_free (af);
}
GNUNET_free (f->wire_method);
- GNUNET_free (f->payto_uri);
GNUNET_free (f);
}
- if (NULL != exchange->wire_request)
- {
- TALER_EXCHANGE_wire_cancel (exchange->wire_request);
- exchange->wire_request = NULL;
- }
- if (NULL != exchange->wire_task)
- {
- GNUNET_SCHEDULER_cancel (exchange->wire_task);
- exchange->wire_task = NULL;
- }
if (NULL != exchange->conn)
{
- TALER_EXCHANGE_disconnect (exchange->conn);
+ TALER_EXCHANGE_get_keys_cancel (exchange->conn);
exchange->conn = NULL;
}
+ TALER_EXCHANGE_keys_decref (exchange->keys);
+ exchange->keys = NULL;
if (NULL != exchange->retry_task)
{
GNUNET_SCHEDULER_cancel (exchange->retry_task);
exchange->retry_task = NULL;
}
- GNUNET_assert (NULL == exchange->fo_head);
- GNUNET_assert (NULL == exchange->fo_tail);
+ GNUNET_assert (NULL == exchange->keys_head);
+ GNUNET_assert (NULL == exchange->keys_tail);
+ GNUNET_free (exchange->currency);
GNUNET_free (exchange->url);
GNUNET_free (exchange);
}
@@ -853,148 +1024,53 @@ free_exchange_entry (struct Exchange *exchange)
* about our failure, abort pending operations and retry later.
*
* @param exchange exchange that failed
- * @param hr details about the HTTP reply
- * @param compat version compatibility data
*/
static void
-fail_and_retry (struct Exchange *exchange,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- enum TALER_EXCHANGE_VersionCompatibility compat)
+fail_and_retry (struct TMH_Exchange *exchange)
{
- struct TMH_EXCHANGES_FindOperation *fo;
+ struct TMH_EXCHANGES_KeysOperation *keys;
- exchange->pending = true;
- if (NULL != exchange->wire_request)
- {
- TALER_EXCHANGE_wire_cancel (exchange->wire_request);
- exchange->wire_request = NULL;
- }
- if (NULL != exchange->wire_task)
- {
- GNUNET_SCHEDULER_cancel (exchange->wire_task);
- exchange->wire_task = NULL;
- }
- while (NULL != (fo = exchange->fo_head))
- {
- fo->fc (fo->fc_cls,
- hr,
- NULL,
- NULL,
- NULL,
- GNUNET_NO);
- TMH_EXCHANGES_find_exchange_cancel (fo);
- }
- if (TALER_EXCHANGE_VC_INCOMPATIBLE_NEWER == compat)
- {
- /* Log hard error: we likely need admin help! */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Exchange `%s' runs an incompatible more recent version of the Taler protocol. Will not retry. This client may need to be updated.\n",
- exchange->url);
- /* Theoretically, the exchange could downgrade,
- but let's not be too aggressive about retries
- on this one. */
- exchange->retry_delay = GNUNET_TIME_relative_max (GNUNET_TIME_UNIT_HOURS,
- exchange->retry_delay);
- }
- if ( (NULL == exchange->fo_head) &&
- (TALER_EC_GENERIC_CONFIGURATION_INVALID == hr->ec) )
+ exchange->state = ESTATE_FAILED;
+ while (NULL != (keys = exchange->keys_head))
{
- /* This can NEVER work, so don't retry */
- free_exchange_entry (exchange);
- return;
+ keys->fc (keys->fc_cls,
+ NULL,
+ exchange);
+ TMH_EXCHANGES_keys4exchange_cancel (keys);
}
- exchange->retry_delay = RETRY_BACKOFF (exchange->retry_delay);
+ exchange->retry_delay
+ = GNUNET_TIME_randomized_backoff (exchange->retry_delay,
+ RETRY_BACKOFF_THRESHOLD);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to fetch /keys from `%s': %d/%u, retrying in %s\n",
+ "Failed to fetch /keys from `%s'; retrying in %s\n",
exchange->url,
- (int) hr->ec,
- hr->http_status,
GNUNET_STRINGS_relative_time_to_string (exchange->retry_delay,
- GNUNET_YES));
+ true));
if (NULL != exchange->retry_task)
GNUNET_SCHEDULER_cancel (exchange->retry_task);
- exchange->first_retry = GNUNET_TIME_relative_to_absolute (
- exchange->retry_delay);
- exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay,
- &retry_exchange,
- exchange);
+ exchange->first_retry
+ = GNUNET_TIME_relative_to_absolute (
+ exchange->retry_delay);
+ exchange->retry_task
+ = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay,
+ &retry_exchange,
+ exchange);
}
/**
- * Function called with information about who is auditing
- * a particular exchange and what key the exchange is using.
+ * Update our information in the database about the
+ * /keys of an exchange. Run inside of a database
+ * transaction scope that will re-try and/or commit
+ * depending on the return value.
*
- * @param cls closure, will be `struct Exchange` so that
- * when this function gets called, it will change the flag 'pending'
- * to 'false'. Note: 'keys' is automatically saved inside the exchange's
- * handle, which is contained inside 'struct Exchange', when
- * this callback is called. Thus, once 'pending' turns 'false',
- * it is safe to call 'TALER_EXCHANGE_get_keys()' on the exchange's handle,
- * in order to get the "good" keys.
- * @param hr http response details
- * @param keys information about the various keys used
- * by the exchange
- * @param compat version compatibility data
+ * @param keys information to persist
+ * @return transaction status
*/
-static void
-keys_mgmt_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_EXCHANGE_Keys *keys,
- enum TALER_EXCHANGE_VersionCompatibility compat)
+static enum GNUNET_DB_QueryStatus
+insert_keys_data (const struct TALER_EXCHANGE_Keys *keys)
{
- struct Exchange *exchange = cls;
- struct GNUNET_TIME_Timestamp expire;
- struct GNUNET_TIME_Relative delay;
-
- if ( (MHD_HTTP_OK != hr->http_status) ||
- (NULL == keys) )
- {
- fail_and_retry (exchange,
- hr,
- compat);
- return;
- }
- if ( (exchange->trusted) &&
- (0 != GNUNET_memcmp (&exchange->master_pub,
- &keys->master_pub)) )
- {
- /* master pub differs => do not trust the exchange (without auditor) */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Master public key of exchange `%s' differs from our configuration. Not trusting exchange.\n",
- exchange->url);
- exchange->trusted = false;
- }
- if (! exchange->trusted)
- {
- exchange->master_pub = keys->master_pub;
- for (struct Exchange *e = exchange_head;
- NULL != e;
- e = e->next)
- {
- if (e == exchange)
- continue;
- if (! e->trusted)
- continue;
- if (0 ==
- GNUNET_memcmp (&e->master_pub,
- &exchange->master_pub))
- exchange->trusted = true; /* same exchange, different URL => trust applies */
- }
- }
- if (0 != (TALER_EXCHANGE_VC_NEWER & compat))
- {
- /* Warn user exactly once about need to upgrade */
- static int once;
-
- if (0 == once)
- {
- once = 1;
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Exchange `%s' runs a more recent version of the Taler protocol. You may want to update this client.\n",
- exchange->url);
- }
- }
+ enum GNUNET_DB_QueryStatus qs;
/* store exchange online signing keys in our DB */
for (unsigned int i = 0; i<keys->num_sign_keys; i++)
@@ -1002,237 +1078,363 @@ keys_mgmt_cb (void *cls,
struct TALER_EXCHANGE_SigningPublicKey *sign_key = &keys->sign_keys[i];
enum GNUNET_DB_QueryStatus qs;
- TMH_db->preflight (TMH_db->cls);
- qs = TMH_db->insert_exchange_signkey (TMH_db->cls,
- &keys->master_pub,
- &sign_key->key,
- sign_key->valid_from,
- sign_key->valid_until,
- sign_key->valid_legal,
- &sign_key->master_sig);
+ qs = TMH_db->insert_exchange_signkey (
+ TMH_db->cls,
+ &keys->master_pub,
+ &sign_key->key,
+ sign_key->valid_from,
+ sign_key->valid_until,
+ sign_key->valid_legal,
+ &sign_key->master_sig);
/* 0 is OK, we may already have the key in the DB! */
if (0 > qs)
{
- GNUNET_break (0);
- fail_and_retry (exchange,
- hr,
- compat);
- return;
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
}
}
- exchange->first_retry = GNUNET_TIME_relative_to_absolute (RELOAD_DELAY);
- expire = TALER_EXCHANGE_check_keys_current (exchange->conn,
- TALER_EXCHANGE_CKF_NONE);
- if (0 == GNUNET_TIME_absolute_is_zero (expire.abs_time))
- {
- delay = RELOAD_DELAY;
- }
- else
+ qs = TMH_db->insert_exchange_keys (TMH_db->cls,
+ keys);
+ if (0 > qs)
{
- delay = GNUNET_TIME_absolute_get_remaining (expire.abs_time);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "/keys response from expires at %s! Retrying at that time!\n",
- GNUNET_TIME_absolute2s (expire.abs_time));
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
}
- if (GNUNET_TIME_relative_is_zero (delay))
+
+ qs = TMH_db->delete_exchange_accounts (TMH_db->cls,
+ &keys->master_pub);
+ if (0 > qs)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "/keys response from exchange expired immediately! Retrying in 1 minute.\n");
- delay = GNUNET_TIME_UNIT_MINUTES;
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
}
- exchange->retry_delay = GNUNET_TIME_UNIT_ZERO;
- if (NULL != exchange->retry_task)
- GNUNET_SCHEDULER_cancel (exchange->retry_task);
- exchange->retry_task
- = GNUNET_SCHEDULER_add_delayed (delay,
- &retry_exchange,
- exchange);
- exchange->pending = false;
- if ( (process_find_operations (exchange)) &&
- (NULL == exchange->wire_request) &&
- (NULL == exchange->wire_task) )
+
+ for (unsigned int i = 0; i<keys->accounts_len; i++)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Got key data, but also need wire data. Will request /wire now\n");
- exchange->wire_request = TALER_EXCHANGE_wire (exchange->conn,
- &handle_wire_data,
- exchange);
- }
-}
+ const struct TALER_EXCHANGE_WireAccount *account = &keys->accounts[i];
+ json_t *debit_restrictions;
+ json_t *credit_restrictions;
+
+ debit_restrictions = json_array ();
+ GNUNET_assert (NULL != debit_restrictions);
+ credit_restrictions = json_array ();
+ GNUNET_assert (NULL != credit_restrictions);
+ for (unsigned int j = 0; j<account->debit_restrictions_length; j++)
+ {
+ if (GNUNET_OK !=
+ add_restriction (debit_restrictions,
+ &account->debit_restrictions[j]))
+ {
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_break (0);
+ json_decref (debit_restrictions);
+ json_decref (credit_restrictions);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ for (unsigned int j = 0; j<account->credit_restrictions_length; j++)
+ {
+ if (GNUNET_OK !=
+ add_restriction (credit_restrictions,
+ &account->credit_restrictions[j]))
+ {
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_break (0);
+ json_decref (debit_restrictions);
+ json_decref (credit_restrictions);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ qs = TMH_db->insert_exchange_account (
+ TMH_db->cls,
+ &keys->master_pub,
+ account->payto_uri,
+ account->conversion_url,
+ debit_restrictions,
+ credit_restrictions,
+ &account->master_sig);
+ json_decref (debit_restrictions);
+ json_decref (credit_restrictions);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ } /* end 'for all accounts' */
+ for (unsigned int i = 0; i<keys->fees_len; i++)
+ {
+ const struct TALER_EXCHANGE_WireFeesByMethod *fbm = &keys->fees[i];
+ const char *wire_method = fbm->method;
+ const struct TALER_EXCHANGE_WireAggregateFees *fees = fbm->fees_head;
-/**
- * Task to return find operation result asynchronously to caller.
- *
- * @param cls a `struct TMH_EXCHANGES_FindOperation`
- */
-static void
-return_result (void *cls)
-{
- struct TMH_EXCHANGES_FindOperation *fo = cls;
- struct Exchange *exchange = fo->my_exchange;
+ while (NULL != fees)
+ {
+ struct GNUNET_HashCode h_wire_method;
+
+ GNUNET_CRYPTO_hash (wire_method,
+ strlen (wire_method) + 1,
+ &h_wire_method);
+ qs = TMH_db->store_wire_fee_by_exchange (TMH_db->cls,
+ &keys->master_pub,
+ &h_wire_method,
+ &fees->fees,
+ fees->start_date,
+ fees->end_date,
+ &fees->master_sig);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ fees = fees->next;
+ } /* all fees for this method */
+ } /* for all methods (i) */
- fo->at = NULL;
- if ( (process_find_operations (exchange)) &&
- (NULL == exchange->wire_request) &&
- (! exchange->pending) &&
- (NULL != exchange->wire_task) )
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Do not have current wire data. Will re-request /wire in 1 minute\n");
- exchange->wire_task
- = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES,
- &wire_task_cb,
- exchange);
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = ntohs (sizeof (es)),
+ .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
+ };
+
+ TMH_db->event_notify (TMH_db->cls,
+ &es,
+ keys->exchange_url,
+ strlen (keys->exchange_url) + 1);
}
+ return qs;
}
-struct TMH_EXCHANGES_FindOperation *
-TMH_EXCHANGES_find_exchange (const char *chosen_exchange,
- const char *wire_method,
- int force_reload,
- TMH_EXCHANGES_FindContinuation fc,
- void *fc_cls)
+static void
+keys_mgmt_cb (void *cls,
+ const struct TALER_EXCHANGE_KeysResponse *kr,
+ struct TALER_EXCHANGE_Keys *keys)
{
- struct Exchange *exchange;
- struct TMH_EXCHANGES_FindOperation *fo;
- struct GNUNET_TIME_Timestamp now;
+ struct TMH_Exchange *exchange = cls;
+ enum GNUNET_DB_QueryStatus qs;
- if (NULL == merchant_curl_ctx)
- {
- GNUNET_break (0);
- return NULL;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Trying to find chosen exchange `%s'\n",
- chosen_exchange);
- /* Check if the exchange is known */
- for (exchange = exchange_head; NULL != exchange; exchange = exchange->next)
+ exchange->conn = NULL;
+ if (MHD_HTTP_OK != kr->hr.http_status)
{
- /* test it by checking URL */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Comparing chosen exchange url '%s' with known url '%s'.\n",
- chosen_exchange,
- exchange->url);
- if (0 == strcmp (exchange->url,
- chosen_exchange))
+ if (GNUNET_TIME_absolute_is_future (exchange->first_retry))
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "The exchange `%s' is already known (good)\n",
- chosen_exchange);
- break;
+ /* /keys failed *before* the long polling threshold.
+ We apply the exponential back-off from now. */
+ exchange->first_retry
+ = GNUNET_TIME_relative_to_absolute (
+ exchange->retry_delay);
}
+ fail_and_retry (exchange);
+ TALER_EXCHANGE_keys_decref (keys);
+ return;
}
- if (NULL == exchange)
+ if (NULL == exchange->currency)
+ exchange->currency = GNUNET_strdup (keys->currency);
+ if (0 != strcmp (exchange->currency,
+ keys->currency))
{
- /* This is a new exchange */
- exchange = GNUNET_new (struct Exchange);
- exchange->url = GNUNET_strdup (chosen_exchange);
- exchange->pending = true;
- GNUNET_CONTAINER_DLL_insert (exchange_head,
- exchange_tail,
- exchange);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "The exchange `%s' is new\n",
- chosen_exchange);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "/keys response from `%s' is for currency `%s', but we expected `%s'\n",
+ exchange->url,
+ keys->currency,
+ exchange->currency);
+ fail_and_retry (exchange);
+ TALER_EXCHANGE_keys_decref (keys);
+ return;
}
-
- fo = GNUNET_new (struct TMH_EXCHANGES_FindOperation);
- fo->fc = fc;
- fo->fc_cls = fc_cls;
- fo->my_exchange = exchange;
- if (NULL != wire_method)
- fo->wire_method = GNUNET_strdup (wire_method);
- GNUNET_CONTAINER_DLL_insert (exchange->fo_head,
- exchange->fo_tail,
- fo);
- if ( (force_reload) &&
- (GNUNET_TIME_absolute_is_past (exchange->first_retry)) )
+ exchange->state = ESTATE_DOWNLOADED;
+ TMH_db->preflight (TMH_db->cls);
+ for (unsigned int r = 0; r<MAX_RETRIES; r++)
{
- /* increment exponential-backoff */
- exchange->retry_delay = RETRY_BACKOFF (exchange->retry_delay);
- /* do not allow forced check until both backoff and #FORCED_RELOAD_DELAY
- are satisfied again */
- exchange->first_retry
- = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_max (
- exchange->retry_delay,
- FORCED_RELOAD_DELAY));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "/keys retry forced, waiting until %s\n",
- GNUNET_TIME_absolute2s (exchange->first_retry));
- /* NOTE: return value tells us how long /keys should still
- be valid. */
- (void) TALER_EXCHANGE_check_keys_current (exchange->conn,
- TALER_EXCHANGE_CKF_FORCE_DOWNLOAD);
- return fo;
- }
+ if (GNUNET_OK !=
+ TMH_db->start (TMH_db->cls,
+ "update exchange key data"))
+ {
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_break (0);
+ fail_and_retry (exchange);
+ TALER_EXCHANGE_keys_decref (keys);
+ return;
+ }
- now = GNUNET_TIME_timestamp_get ();
- if ( (! exchange->pending) &&
- ( (NULL == fo->wire_method) ||
- (NULL != get_wire_fees (exchange,
- now,
- fo->wire_method)) ) )
+ qs = insert_keys_data (keys);
+ if (qs < 0)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ continue;
+ GNUNET_break (0);
+ fail_and_retry (exchange);
+ TALER_EXCHANGE_keys_decref (keys);
+ return;
+ }
+
+ qs = TMH_db->commit (TMH_db->cls);
+ if (qs < 0)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ continue;
+ GNUNET_break (0);
+ fail_and_retry (exchange);
+ TALER_EXCHANGE_keys_decref (keys);
+ return;
+ }
+ } /* end of retry loop */
+ if (qs < 0)
{
- /* We are not currently waiting for a reply, immediately
- return result */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "The exchange `%s' is ready\n",
- chosen_exchange);
- GNUNET_assert (NULL == fo->at);
- fo->at = GNUNET_SCHEDULER_add_now (&return_result,
- fo);
- return fo;
+ GNUNET_break (0);
+ fail_and_retry (exchange);
+ TALER_EXCHANGE_keys_decref (keys);
+ return;
}
+ TALER_EXCHANGE_keys_decref (keys);
+ exchange->retry_delay = GNUNET_TIME_UNIT_ZERO;
+}
- /* If new or resumed, (re)try fetching /keys */
- if ( (NULL == exchange->conn) &&
- (NULL == exchange->retry_task) &&
- (exchange->pending) )
+
+enum GNUNET_GenericReturnValue
+TMH_EXCHANGES_lookup_wire_fee (
+ const struct TMH_Exchange *exchange,
+ const char *wire_method,
+ struct TALER_Amount *wire_fee)
+{
+ const struct FeesByWireMethod *fbm;
+ const struct TALER_EXCHANGE_WireAggregateFees *af;
+
+ fbm = get_wire_fees (exchange,
+ GNUNET_TIME_timestamp_get (),
+ wire_method);
+ if (NULL == fbm)
+ return GNUNET_NO;
+ af = fbm->af;
+ *wire_fee = af->fees.wire;
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TMH_exchange_check_debit (
+ const struct TMH_Exchange *exchange,
+ const struct TMH_WireMethod *wm)
+{
+ if (NULL == exchange->acc_head)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Do not have current /keys data for `%s'. Will request /keys now\n",
- chosen_exchange);
- exchange->retry_task = GNUNET_SCHEDULER_add_now (&retry_exchange,
- exchange);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No accounts available for %s\n",
+ exchange->url);
+ return GNUNET_SYSERR;
}
- else if ( (! exchange->pending) &&
- (NULL == exchange->wire_task) &&
- (NULL == exchange->wire_request) )
+ for (struct ExchangeAccount *acc = exchange->acc_head;
+ NULL != acc;
+ acc = acc->next)
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Do not have required wire data. Will re-request /wire now\n");
- exchange->wire_task = GNUNET_SCHEDULER_add_now (&wire_task_cb,
- exchange);
+ bool ok = true;
+
+ if (0 != strcmp (acc->wire_method,
+ wm->wire_method))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange %s wire method %s != %s\n",
+ exchange->url,
+ acc->wire_method,
+ wm->wire_method);
+ continue;
+ }
+ if (NULL != acc->conversion_url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange %s account requires currency conversion (not supported)\n",
+ exchange->url);
+ continue; /* never use accounts with conversion */
+ }
+ for (struct Restriction *r = acc->d_head;
+ NULL != r;
+ r = r->next)
+ {
+ switch (r->type)
+ {
+ case TALER_EXCHANGE_AR_INVALID:
+ GNUNET_break (0);
+ ok = false;
+ break;
+ case TALER_EXCHANGE_AR_DENY:
+ ok = false;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange %s account is disabled\n",
+ exchange->url);
+ break;
+ case TALER_EXCHANGE_AR_REGEX:
+ if (0 != regexec (&r->details.regex.ex,
+ wm->payto_uri,
+ 0, NULL, 0))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange %s account regex does not match %s\n",
+ exchange->url,
+ wm->payto_uri);
+ ok = false;
+ }
+ break;
+ }
+ if (! ok)
+ break;
+ }
+
+ if (ok)
+ return GNUNET_OK;
}
- return fo;
+ return GNUNET_NO;
}
void
-TMH_EXCHANGES_find_exchange_cancel (struct TMH_EXCHANGES_FindOperation *fo)
+TMH_exchange_get_trusted (TMH_ExchangeCallback cb,
+ void *cb_cls)
{
- struct Exchange *exchange = fo->my_exchange;
+ for (const struct TMH_Exchange *exchange = exchange_head;
+ NULL != exchange;
+ exchange = exchange->next)
+ {
+ if (! exchange->trusted)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange %s not trusted, skipping!\n",
+ exchange->url);
+ continue;
+ }
+ cb (cb_cls,
+ exchange->url,
+ exchange);
+ }
+}
- if (NULL != fo->at)
+
+bool
+TMH_test_exchange_configured_for_currency (
+ const char *currency)
+{
+ for (const struct TMH_Exchange *exchange = exchange_head;
+ NULL != exchange;
+ exchange = exchange->next)
{
- GNUNET_SCHEDULER_cancel (fo->at);
- fo->at = NULL;
+ if (! exchange->trusted)
+ continue;
+ if (NULL == exchange->currency)
+ continue;
+ if (0 == strcmp (currency,
+ exchange->currency))
+ return true;
}
- GNUNET_CONTAINER_DLL_remove (exchange->fo_head,
- exchange->fo_tail,
- fo);
- GNUNET_free (fo->wire_method);
- GNUNET_free (fo);
+ return false;
}
/**
* Function called on each configuration section. Finds sections
- * about exchanges, parses the entries and tries to connect to
- * it in order to fetch /keys.
+ * about exchanges, parses the entries.
*
* @param cls closure, with a `const struct GNUNET_CONFIGURATION_Handle *`
* @param section name of the section
@@ -1244,47 +1446,49 @@ accept_exchanges (void *cls,
const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
char *url;
char *mks;
- struct Exchange *exchange;
+ struct TMH_Exchange *exchange;
char *currency;
+ if (GNUNET_SYSERR == trusted_exchange_count)
+ return;
if (0 != strncasecmp (section,
"merchant-exchange-",
strlen ("merchant-exchange-")))
return;
+ if (GNUNET_YES ==
+ GNUNET_CONFIGURATION_get_value_yesno (cfg,
+ section,
+ "DISABLED"))
+ return;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
section,
- "CURRENCY",
- &currency))
+ "EXCHANGE_BASE_URL",
+ &url))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
- "CURRENCY");
- return;
- }
- if (0 != strcasecmp (currency,
- TMH_currency))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Exchange given in section `%s' is for another currency. Skipping.\n",
- section);
- GNUNET_free (currency);
+ "EXCHANGE_BASE_URL");
return;
}
- GNUNET_free (currency);
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
section,
- "EXCHANGE_BASE_URL",
- &url))
+ "CURRENCY",
+ &currency))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
- "EXCHANGE_BASE_URL");
+ "CURRENCY");
+ GNUNET_free (url);
return;
}
- exchange = GNUNET_new (struct Exchange);
- exchange->url = url;
+ exchange = lookup_exchange (url);
+ GNUNET_free (url);
+ if (NULL == exchange->currency)
+ exchange->currency = currency;
+ else
+ GNUNET_free (currency);
if (GNUNET_OK ==
GNUNET_CONFIGURATION_get_value_string (cfg,
section,
@@ -1292,19 +1496,20 @@ accept_exchanges (void *cls,
&mks))
{
if (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_public_key_from_string (mks,
- strlen (mks),
- &exchange->master_pub.
- eddsa_pub))
+ GNUNET_CRYPTO_eddsa_public_key_from_string (
+ mks,
+ strlen (mks),
+ &exchange->master_pub.eddsa_pub))
{
exchange->trusted = true;
+ trusted_exchange_count++;
}
else
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
section,
"MASTER_KEY",
- _ ("ill-formed EdDSA key"));
+ "malformed EdDSA key");
}
GNUNET_free (mks);
}
@@ -1313,79 +1518,174 @@ accept_exchanges (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"MASTER_KEY missing in section '%s', not trusting exchange\n",
section);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Setup exchange %s as %s\n",
+ exchange->url,
+ exchange->trusted ? "trusted" : "untrusted");
+ if (NULL != exchange->retry_task)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Exchange at `%s' configured in multiple configuration sections (see `%s')!\n",
+ exchange->url,
+ section);
+ trusted_exchange_count = GNUNET_SYSERR;
+ return;
+ }
+ exchange->retry_task
+ = GNUNET_SCHEDULER_add_now (&retry_exchange,
+ exchange);
+}
+
+
+/**
+ * Trigger (re)loading of keys from DB.
+ *
+ * @param cls NULL
+ * @param extra base URL of the exchange that changed
+ * @param extra_len number of bytes in @a extra
+ */
+static void
+update_exchange_keys (void *cls,
+ const void *extra,
+ size_t extra_len)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ const char *url = extra;
+ struct TMH_Exchange *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
+ if ( (NULL == extra) ||
+ (0 == extra_len) )
+ {
+ GNUNET_break (0);
+ return;
}
- GNUNET_CONTAINER_DLL_insert (exchange_head,
- exchange_tail,
- exchange);
- exchange->pending = true;
- GNUNET_assert (NULL == exchange->retry_task);
- exchange->retry_task = GNUNET_SCHEDULER_add_now (&retry_exchange,
- exchange);
+ if ('\0' != url[extra_len - 1])
+ {
+ GNUNET_break (0);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received keys change notification: reload `%s'\n",
+ url);
+ exchange = lookup_exchange (url);
+ qs = TMH_db->select_exchange_keys (TMH_db->cls,
+ url,
+ &keys);
+ if (qs <= 0)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ if (NULL == exchange->currency)
+ exchange->currency = GNUNET_strdup (keys->currency);
+ if (0 != strcmp (keys->currency,
+ exchange->currency))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "/keys cached in our database are for currency `%s', but we expected `%s'\n",
+ keys->currency,
+ exchange->currency);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Loaded /keys from database with %u accounts\n",
+ keys->accounts_len);
+ if (GNUNET_OK !=
+ set_exchange_accounts (exchange,
+ keys->accounts_len,
+ keys->accounts))
+ {
+ /* invalid account specification given */
+ GNUNET_break_op (0);
+ /* but: we can continue anyway, things may just not
+ work, but probably better than to not keep going. */
+ }
+ if (GNUNET_OK !=
+ process_wire_fees (exchange,
+ &keys->master_pub,
+ keys->fees_len,
+ keys->fees))
+ {
+ /* invalid wire fee specification given */
+ GNUNET_break_op (0);
+ /* but: we can continue anyway, things may just not
+ work, but probably better than to not keep going. */
+ return;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Reloaded /keys of %s from database\n",
+ url);
+ TALER_EXCHANGE_keys_decref (exchange->keys);
+ exchange->keys = keys;
+ if ( (exchange->trusted) &&
+ (0 != GNUNET_memcmp (&exchange->master_pub,
+ &keys->master_pub)) )
+ {
+ /* master pub differs => do not trust the exchange (without auditor) */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Master public key of exchange `%s' differs from our configuration. Not trusting exchange.\n",
+ exchange->url);
+ exchange->trusted = false;
+ }
+ if (! exchange->trusted)
+ {
+ exchange->master_pub = keys->master_pub;
+ for (struct TMH_Exchange *e = exchange_head;
+ NULL != e;
+ e = e->next)
+ {
+ if (e == exchange)
+ continue;
+ if (! e->trusted)
+ continue;
+ if (0 ==
+ GNUNET_memcmp (&e->master_pub,
+ &exchange->master_pub))
+ exchange->trusted = true; /* same exchange, different URL => trust applies */
+ }
+ }
+
+ process_find_operations (exchange);
}
enum GNUNET_GenericReturnValue
TMH_EXCHANGES_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
- merchant_curl_ctx
- = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
- &merchant_curl_rc);
- if (NULL == merchant_curl_ctx)
+ /* get exchanges from the merchant configuration and try to connect to them */
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = ntohs (sizeof (es)),
+ .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
+ };
+
+ keys_eh = TMH_db->event_listen (TMH_db->cls,
+ &es,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &update_exchange_keys,
+ NULL);
}
- merchant_curl_rc = GNUNET_CURL_gnunet_rc_create (merchant_curl_ctx);
- GNUNET_CURL_enable_async_scope_header (merchant_curl_ctx,
- "Taler-Correlation-Id");
- /* get exchanges from the merchant configuration and try to connect to them */
GNUNET_CONFIGURATION_iterate_sections (cfg,
&accept_exchanges,
(void *) cfg);
/* build JSON with list of trusted exchanges (will be included in contracts) */
- TMH_trusted_exchanges = json_array ();
- for (struct Exchange *exchange = exchange_head;
- NULL != exchange;
- exchange = exchange->next)
- {
- json_t *j_exchange;
-
- if (! exchange->trusted)
- continue;
- j_exchange = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("url",
- exchange->url),
- GNUNET_JSON_pack_data_auto ("master_pub",
- &exchange->master_pub));
- GNUNET_assert (0 ==
- json_array_append_new (TMH_trusted_exchanges,
- j_exchange));
- }
- return json_array_size (TMH_trusted_exchanges);
+ return trusted_exchange_count;
}
void
TMH_EXCHANGES_done ()
{
- while (NULL != exchange_head)
- free_exchange_entry (exchange_head);
- if (NULL != merchant_curl_ctx)
+ if (NULL != keys_eh)
{
- GNUNET_CURL_fini (merchant_curl_ctx);
- merchant_curl_ctx = NULL;
- }
- if (NULL != merchant_curl_rc)
- {
- GNUNET_CURL_gnunet_rc_destroy (merchant_curl_rc);
- merchant_curl_rc = NULL;
- }
- if (NULL != TMH_trusted_exchanges)
- {
- json_decref (TMH_trusted_exchanges);
- TMH_trusted_exchanges = NULL;
+ TMH_db->event_listen_cancel (keys_eh);
+ keys_eh = NULL;
}
+ while (NULL != exchange_head)
+ free_exchange_entry (exchange_head);
}
diff --git a/src/backend/taler-merchant-httpd_exchanges.h b/src/backend/taler-merchant-httpd_exchanges.h
index df68b9a5..892843f6 100644
--- a/src/backend/taler-merchant-httpd_exchanges.h
+++ b/src/backend/taler-merchant-httpd_exchanges.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2020 Taler Systems SA
+ (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -31,9 +31,9 @@
/**
- * List of our trusted exchanges in JSON format for inclusion in contracts.
+ * Exchange
*/
-extern json_t *TMH_trusted_exchanges;
+struct TMH_Exchange;
/**
@@ -55,61 +55,145 @@ TMH_EXCHANGES_done (void);
/**
- * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * Function called with the result of a #TMH_EXCHANGES_keys4exchange()
* operation.
*
* @param cls closure
- * @param hr HTTP response details
- * @param eh handle to the exchange context
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
- * @param exchange_trusted true if this exchange is trusted by config
+ * @param keys the keys of the exchange
+ * @param exchange representation of the exchange
*/
typedef void
-(*TMH_EXCHANGES_FindContinuation)(void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *eh,
- const char *payto_uri,
- const struct TALER_Amount *wire_fee,
- bool exchange_trusted);
+(*TMH_EXCHANGES_Find2Continuation)(
+ void *cls,
+ struct TALER_EXCHANGE_Keys *keys,
+ struct TMH_Exchange *exchange);
/**
- * Information we keep for a pending #MMH_EXCHANGES_find_exchange() operation.
+ * Information we keep for a pending #MMH_EXCHANGES_keys4exchange() operation.
*/
-struct TMH_EXCHANGES_FindOperation;
+struct TMH_EXCHANGES_KeysOperation;
/**
- * Find a exchange that matches @a chosen_exchange. If we cannot connect
- * to the exchange, or if it is not acceptable, @a fc is called with
- * NULL for the exchange.
+ * Get /keys of the given @a exchange.
*
- * @param chosen_exchange URL of the exchange we would like to talk to
- * @param wire_method the wire method we will use with @a chosen_exchange, NULL for none
- * @param force_reload try to force reloading /keys from the exchange ASAP; note
- * that IF the forced reload fails, it is possible @a fc won't be called at all
- * until a /keys download succeeds; only use #GNUNET_YES if a new /keys request
- * is mandatory. If the force reload request is not allowed due to our rate limiting,
- * then @a fc will be called immediately with the existing /keys data
+ * @param exchange URL of the exchange we would like to talk to
+ * @param force_download force /keys download now
* @param fc function to call with the handles for the exchange
* @param fc_cls closure for @a fc
*/
-struct TMH_EXCHANGES_FindOperation *
-TMH_EXCHANGES_find_exchange (const char *chosen_exchange,
- const char *wire_method,
- int force_reload,
- TMH_EXCHANGES_FindContinuation fc,
- void *fc_cls);
+struct TMH_EXCHANGES_KeysOperation *
+TMH_EXCHANGES_keys4exchange (
+ const char *exchange,
+ bool force_download,
+ TMH_EXCHANGES_Find2Continuation fc,
+ void *fc_cls);
/**
- * Abort pending find operation.
+ * Abort pending keys details lookup operation.
*
* @param fo handle to operation to abort
*/
void
-TMH_EXCHANGES_find_exchange_cancel (struct TMH_EXCHANGES_FindOperation *fo);
+TMH_EXCHANGES_keys4exchange_cancel (struct TMH_EXCHANGES_KeysOperation *fo);
+
+
+/**
+ * Callback on an exchange known to us. Does not warrant
+ * that the "keys" information is actually available for
+ * @a exchange.
+ *
+ * @param cls closure
+ * @param url base URL of the exchange
+ * @param exchange internal handle for the exchange
+ */
+typedef void
+(*TMH_ExchangeCallback)(void *cls,
+ const char *url,
+ const struct TMH_Exchange *exchange);
+
+
+/**
+ * Return all trusted exchanges to @a cb.
+ *
+ * @param cb function to call
+ * @param cb_cls closure for @a cb
+ */
+void
+TMH_exchange_get_trusted (TMH_ExchangeCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Return the master public key of the given @a exchange.
+ * Will be returned from configuration for trusted
+ * exchanges.
+ *
+ * @param exchange exchange to get master public key for
+ * @return the master public key of @a exchange
+ */
+const struct TALER_MasterPublicKeyP *
+TMH_EXCHANGES_get_master_pub (
+ const struct TMH_Exchange *exchange);
+
+
+/**
+ * Return the currency of the given @a exchange.
+ * Will be returned from configuration for trusted
+ * exchanges.
+ *
+ * @param exchange exchange to get master public key for
+ * @return the currency of @a exchange
+ */
+const char *
+TMH_EXCHANGES_get_currency (
+ const struct TMH_Exchange *exchange);
+
+
+/**
+ * Lookup current wire fee by @a exchange_url and @a wire_method.
+ *
+ * @param exchange the exchange to check
+ * @param wire_method wire method to lookup fee by
+ * @param[out] wire_fee set to the wire fee
+ * @return #GNUNET_OK on success
+ * #GNUNET_NO if @a wire_method is not supported
+ * #GNUNET_SYSERR if @a exchange_url did not yet respond properly to our /wire request
+ */
+enum GNUNET_GenericReturnValue
+TMH_EXCHANGES_lookup_wire_fee (
+ const struct TMH_Exchange *exchange,
+ const char *wire_method,
+ struct TALER_Amount *wire_fee);
+
+
+/**
+ * Check if we would trust @a ex to deposit funds into our account @a
+ * wm. Checks that both @a ex is trusted and that @a ex allows wire transfers
+ * into the account given in @a wm.
+ *
+ * @param exchange the exchange to check
+ * @param wm the wire method to check with
+ * @return #GNUNET_OK if such a debit can happen
+ */
+enum GNUNET_GenericReturnValue
+TMH_exchange_check_debit (
+ const struct TMH_Exchange *exchange,
+ const struct TMH_WireMethod *wm);
+
+
+/**
+ * Check if we support the given currency (by having an
+ * exchange configured with it).
+ *
+ * @param currency currency to check
+ * @return true if the currency is supported
+ */
+bool
+TMH_test_exchange_configured_for_currency (
+ const char *currency);
#endif
diff --git a/src/backend/taler-merchant-httpd_get-orders-ID.c b/src/backend/taler-merchant-httpd_get-orders-ID.c
index c1db5ea9..53136628 100644
--- a/src/backend/taler-merchant-httpd_get-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_get-orders-ID.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2022 Taler Systems SA
+ (C) 2014-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -28,6 +28,7 @@
#include <taler/taler_templating_lib.h>
#include <taler/taler_exchange_service.h>
#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_get-orders-ID.h"
#include "taler-merchant-httpd_mhd.h"
#include "taler-merchant-httpd_qr.h"
@@ -39,6 +40,25 @@
/**
+ * The different phases in which we handle the request.
+ */
+enum Phase
+{
+ GOP_INIT = 0,
+ GOP_LOOKUP_TERMS = 1,
+ GOP_PARSE_CONTRACT = 2,
+ GOP_CHECK_CLIENT_ACCESS = 3,
+ GOP_CHECK_PAID = 4,
+ GOP_REDIRECT_TO_PAID_ORDER = 5,
+ GOP_HANDLE_UNPAID = 6,
+ GOP_CHECK_REFUNDED = 7,
+ GOP_RETURN_STATUS = 8,
+ GOP_RETURN_MHD_YES = 9,
+ GOP_RETURN_MHD_NO = 10
+};
+
+
+/**
* Context for the operation.
*/
struct GetOrderData
@@ -119,6 +139,22 @@ struct GetOrderData
json_t *contract_terms;
/**
+ * Merchant base URL from @e contract_terms.
+ */
+ const char *merchant_base_url;
+
+ /**
+ * Public reorder URL from @e contract_terms.
+ * Could be NULL if contract does not have one.
+ */
+ const char *public_reorder_url;
+
+ /**
+ * Total amount in contract.
+ */
+ struct TALER_Amount contract_total;
+
+ /**
* Total refunds granted for this payment. Only initialized
* if @e refunded is set to true.
*/
@@ -131,6 +167,12 @@ struct GetOrderData
struct TALER_Amount refund_taken;
/**
+ * Phase in which we currently are handling this
+ * request.
+ */
+ enum Phase phase;
+
+ /**
* Return code: #TALER_EC_NONE if successful.
*/
enum TALER_ErrorCode ec;
@@ -145,6 +187,22 @@ struct GetOrderData
enum GNUNET_GenericReturnValue suspended;
/**
+ * Set to YES if refunded orders should be included when
+ * doing repurchase detection.
+ */
+ enum TALER_EXCHANGE_YesNoAll allow_refunded_for_repurchase;
+
+ /**
+ * Set to true if the client passed 'h_contract'.
+ */
+ bool h_contract_provided;
+
+ /**
+ * Set to true if the client passed a 'claim' token.
+ */
+ bool claim_token_provided;
+
+ /**
* Set to true if we are dealing with a claimed order
* (and thus @e h_contract_terms is set, otherwise certain
* DB queries will not work).
@@ -152,7 +210,12 @@ struct GetOrderData
bool claimed;
/**
- * Set to true if this payment has been refunded and
+ * Set to true if this order was paid.
+ */
+ bool paid;
+
+ /**
+ * Set to true if this order has been refunded and
* @e refund_amount is initialized.
*/
bool refunded;
@@ -169,6 +232,35 @@ struct GetOrderData
*/
bool generate_html;
+ /**
+ * Did we parse the contract terms?
+ */
+ bool contract_parsed;
+
+ /**
+ * Set to true if the refunds found in the DB have
+ * a different currency then the main contract.
+ */
+ bool bad_refund_currency_in_db;
+
+ /**
+ * Did the hash of the contract match the contract
+ * hash supplied by the client?
+ */
+ bool contract_match;
+
+ /**
+ * True if we had a claim token and the claim token
+ * provided by the client matched our claim token.
+ */
+ bool token_match;
+
+ /**
+ * True if we found a (claimed) contract for the order,
+ * false if we had an unclaimed order.
+ */
+ bool contract_available;
+
};
@@ -202,6 +294,112 @@ TMH_force_wallet_get_order_resume (void)
/**
+ * Suspend this @a god until the trigger is satisfied.
+ *
+ * @param god request to suspend
+ */
+static void
+suspend_god (struct GetOrderData *god)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending GET /orders/%s\n",
+ god->order_id);
+ /* We reset the contract terms and start by looking them up
+ again, as while we are suspended fundamental things could
+ change (such as the contract being claimed) */
+ if (NULL != god->contract_terms)
+ {
+ json_decref (god->contract_terms);
+ god->fulfillment_url = NULL;
+ god->contract_terms = NULL;
+ god->contract_parsed = false;
+ god->merchant_base_url = NULL;
+ god->public_reorder_url = NULL;
+ }
+ GNUNET_assert (! god->suspended);
+ god->contract_parsed = false;
+ god->contract_match = false;
+ god->token_match = false;
+ god->contract_available = false;
+ god->phase = GOP_LOOKUP_TERMS;
+ god->suspended = GNUNET_YES;
+ GNUNET_CONTAINER_DLL_insert (god_head,
+ god_tail,
+ god);
+ MHD_suspend_connection (god->sc.con);
+}
+
+
+/**
+ * Clean up the session state for a GET /orders/$ID request.
+ *
+ * @param cls must be a `struct GetOrderData *`
+ */
+static void
+god_cleanup (void *cls)
+{
+ struct GetOrderData *god = cls;
+
+ if (NULL != god->contract_terms)
+ {
+ json_decref (god->contract_terms);
+ god->contract_terms = NULL;
+ }
+ if (NULL != god->refund_eh)
+ {
+ TMH_db->event_listen_cancel (god->refund_eh);
+ god->refund_eh = NULL;
+ }
+ if (NULL != god->pay_eh)
+ {
+ TMH_db->event_listen_cancel (god->pay_eh);
+ god->pay_eh = NULL;
+ }
+ GNUNET_free (god);
+}
+
+
+/**
+ * Finish the request by returning @a mret as the
+ * final result.
+ *
+ * @param[in,out] god request we are processing
+ * @param mret MHD result to return
+ */
+static void
+phase_end (struct GetOrderData *god,
+ MHD_RESULT mret)
+{
+ god->phase = (MHD_YES == mret)
+ ? GOP_RETURN_MHD_YES
+ : GOP_RETURN_MHD_NO;
+}
+
+
+/**
+ * Finish the request by returning an error @a ec
+ * with HTTP status @a http_status and @a message.
+ *
+ * @param[in,out] god request we are processing
+ * @param http_status HTTP status code to return
+ * @param ec error code to return
+ * @param message human readable hint to return, can be NULL
+ */
+static void
+phase_fail (struct GetOrderData *god,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const char *message)
+{
+ phase_end (god,
+ TALER_MHD_reply_with_error (god->sc.con,
+ http_status,
+ ec,
+ message));
+}
+
+
+/**
* We have received a trigger from the database
* that we should (possibly) resume the request.
*
@@ -252,8 +450,10 @@ resume_by_event (void *cls,
{
GNUNET_break (0);
GNUNET_async_scope_restore (&old);
+ GNUNET_free (as);
return;
}
+ GNUNET_free (as);
if (GNUNET_OK !=
TALER_amount_cmp_currency (&god->sc.refund_expected,
&a))
@@ -288,261 +488,385 @@ resume_by_event (void *cls,
/**
- * Suspend this @a god until the trigger is satisfied.
+ * First phase (after request parsing).
+ * Set up long-polling.
*
- * @param god request to suspend
+ * @param[in,out] god request context
*/
static void
-suspend_god (struct GetOrderData *god)
+phase_init (struct GetOrderData *god)
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Suspending GET /orders/%s\n",
- god->order_id);
- if (NULL != god->contract_terms)
+ god->phase++;
+ if (god->generate_html)
+ return; /* If HTML is requested, we never actually long poll. */
+ if (! GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout))
+ return; /* long polling not requested */
+
+ if (god->sc.awaiting_refund ||
+ god->sc.awaiting_refund_obtained)
{
- json_decref (god->contract_terms);
- god->fulfillment_url = NULL;
- god->contract_terms = NULL;
+ struct TMH_OrderPayEventP refund_eh = {
+ .header.size = htons (sizeof (refund_eh)),
+ .header.type = htons (god->sc.awaiting_refund_obtained
+ ? TALER_DBEVENT_MERCHANT_REFUND_OBTAINED
+ : TALER_DBEVENT_MERCHANT_ORDER_REFUND),
+ .merchant_pub = god->hc->instance->merchant_pub
+ };
+
+ GNUNET_CRYPTO_hash (god->order_id,
+ strlen (god->order_id),
+ &refund_eh.h_order_id);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Subscribing %p to refunds on %s\n",
+ god,
+ god->order_id);
+ god->refund_eh
+ = TMH_db->event_listen (
+ TMH_db->cls,
+ &refund_eh.header,
+ GNUNET_TIME_absolute_get_remaining (
+ god->sc.long_poll_timeout),
+ &resume_by_event,
+ god);
+ }
+ {
+ struct TMH_OrderPayEventP pay_eh = {
+ .header.size = htons (sizeof (pay_eh)),
+ .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
+ .merchant_pub = god->hc->instance->merchant_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Subscribing to payments on %s\n",
+ god->order_id);
+ GNUNET_CRYPTO_hash (god->order_id,
+ strlen (god->order_id),
+ &pay_eh.h_order_id);
+ god->pay_eh
+ = TMH_db->event_listen (
+ TMH_db->cls,
+ &pay_eh.header,
+ GNUNET_TIME_absolute_get_remaining (
+ god->sc.long_poll_timeout),
+ &resume_by_event,
+ god);
}
- GNUNET_assert (! god->suspended);
- god->suspended = GNUNET_YES;
- GNUNET_CONTAINER_DLL_insert (god_head,
- god_tail,
- god);
- MHD_suspend_connection (god->sc.con);
}
/**
- * Create a taler://refund/ URI for the given @a con and @a order_id
- * and @a instance_id.
+ * Lookup contract terms and check client has the
+ * right to access this order (by claim token or
+ * contract hash).
*
- * @param merchant_base_url URL to take host and path from;
- * we cannot take it from the MHD connection as a browser
- * may have changed 'http' to 'https' and we MUST be consistent
- * with what the merchant's frontend used initially
- * @param order_id the order id
- * @return corresponding taler://refund/ URI, or NULL on missing "host"
+ * @param[in,out] god request context
*/
-static char *
-make_taler_refund_uri (const char *merchant_base_url,
- const char *order_id)
+static void
+phase_lookup_terms (struct GetOrderData *god)
{
- struct GNUNET_Buffer buf = { 0 };
- char *url;
- struct GNUNET_Uri uri;
+ uint64_t order_serial;
+ struct TALER_ClaimTokenP db_claim_token;
+ enum GNUNET_DB_QueryStatus qs;
- url = GNUNET_strdup (merchant_base_url);
- if (-1 == GNUNET_uri_parse (&uri,
- url))
+ /* Convert order_id to h_contract_terms */
+ TMH_db->preflight (TMH_db->cls);
+ GNUNET_assert (NULL == god->contract_terms);
+ qs = TMH_db->lookup_contract_terms (
+ TMH_db->cls,
+ god->hc->instance->settings.id,
+ god->order_id,
+ &god->contract_terms,
+ &order_serial,
+ &db_claim_token);
+ if (0 > qs)
{
+ /* single, read-only SQL statements should never cause
+ serialization problems */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ /* Always report on hard error as well to enable diagnostics */
GNUNET_break (0);
- GNUNET_free (url);
- return NULL;
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_contract_terms");
+ return;
}
- GNUNET_assert (NULL != order_id);
- GNUNET_buffer_write_str (&buf,
- "taler");
- if (0 == strcasecmp ("http",
- uri.scheme))
- GNUNET_buffer_write_str (&buf,
- "+http");
- GNUNET_buffer_write_str (&buf,
- "://refund/");
- GNUNET_buffer_write_str (&buf,
- uri.host);
- if (0 != uri.port)
- GNUNET_buffer_write_fstr (&buf,
- ":%u",
- (unsigned int) uri.port);
- if (NULL != uri.path)
- GNUNET_buffer_write_path (&buf,
- uri.path);
- GNUNET_buffer_write_path (&buf,
- order_id);
- GNUNET_buffer_write_path (&buf,
- ""); // Trailing slash
- GNUNET_free (url);
- return GNUNET_buffer_reap_str (&buf);
-}
-
+ /* Note: when "!ord.requireClaimToken" and the client does not provide
+ a claim token (all zeros!), then token_match==TRUE below: */
+ god->token_match
+ = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ && (0 == GNUNET_memcmp (&db_claim_token,
+ &god->claim_token));
-char *
-TMH_make_order_status_url (struct MHD_Connection *con,
- const char *order_id,
- const char *session_id,
- const char *instance_id,
- struct TALER_ClaimTokenP *claim_token,
- struct TALER_PrivateContractHashP *h_contract)
-{
- const char *host;
- const char *forwarded_host;
- const char *uri_path;
- struct GNUNET_Buffer buf = { 0 };
- /* Number of query parameters written so far */
- unsigned int num_qp = 0;
-
- host = MHD_lookup_connection_value (con,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_HOST);
- forwarded_host = MHD_lookup_connection_value (con,
- MHD_HEADER_KIND,
- "X-Forwarded-Host");
- uri_path = MHD_lookup_connection_value (con,
- MHD_HEADER_KIND,
- "X-Forwarded-Prefix");
- if (NULL != forwarded_host)
- host = forwarded_host;
- if (NULL == host)
- {
- GNUNET_break (0);
- return NULL;
- }
- if (NULL != strchr (host, '/'))
+ /* Check if client provided the right hash code of the contract terms */
+ if (NULL != god->contract_terms)
{
- GNUNET_break_op (0);
- return NULL;
- }
- GNUNET_assert (NULL != instance_id);
- GNUNET_assert (NULL != order_id);
+ god->contract_available = true;
+ if (GNUNET_YES ==
+ GNUNET_is_zero (&god->h_contract_terms))
+ {
+ if (GNUNET_OK !=
+ TALER_JSON_contract_hash (god->contract_terms,
+ &god->h_contract_terms))
+ {
+ GNUNET_break (0);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+ "contract terms");
+ return;
+ }
+ }
+ else
+ {
+ struct TALER_PrivateContractHashP h;
- if (GNUNET_NO == TALER_mhd_is_https (con))
- GNUNET_buffer_write_str (&buf,
- "http://");
- else
- GNUNET_buffer_write_str (&buf,
- "https://");
- GNUNET_buffer_write_str (&buf,
- host);
- if (NULL != uri_path)
- GNUNET_buffer_write_path (&buf,
- uri_path);
- if (0 != strcmp ("default",
- instance_id))
- {
- GNUNET_buffer_write_path (&buf,
- "instances");
- GNUNET_buffer_write_path (&buf,
- instance_id);
- }
- GNUNET_buffer_write_path (&buf,
- "/orders");
- GNUNET_buffer_write_path (&buf,
- order_id);
- if ((NULL != claim_token) &&
- (GNUNET_NO == GNUNET_is_zero (claim_token)))
- {
- /* 'token=' for human readability */
- GNUNET_buffer_write_str (&buf,
- "?token=");
- GNUNET_buffer_write_data_encoded (&buf,
- (char *) claim_token,
- sizeof (*claim_token));
- num_qp++;
+ if (GNUNET_OK !=
+ TALER_JSON_contract_hash (god->contract_terms,
+ &h))
+ {
+ GNUNET_break (0);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+ "contract terms");
+ return;
+ }
+ god->contract_match = (0 ==
+ GNUNET_memcmp (&h,
+ &god->h_contract_terms));
+ if (! god->contract_match)
+ {
+ GNUNET_break_op (0);
+ phase_fail (god,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
+ NULL);
+ return;
+ }
+ }
}
- if (NULL != session_id)
+ if (god->contract_available)
{
- if (num_qp > 0)
- GNUNET_buffer_write_str (&buf,
- "&session_id=");
- else
- GNUNET_buffer_write_str (&buf,
- "?session_id=");
- GNUNET_buffer_write_str (&buf,
- session_id);
- num_qp++;
+ god->claimed = true;
}
-
- if (NULL != h_contract)
+ else
{
- if (num_qp > 0)
- GNUNET_buffer_write_str (&buf,
- "&h_contract=");
- else
- GNUNET_buffer_write_str (&buf,
- "?h_contract=");
- GNUNET_buffer_write_data_encoded (&buf,
- (char *) h_contract,
- sizeof (*h_contract));
- }
-
- return GNUNET_buffer_reap_str (&buf);
+ struct TALER_ClaimTokenP db_claim_token;
+ struct TALER_MerchantPostDataHashP unused;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->lookup_order (
+ TMH_db->cls,
+ god->hc->instance->settings.id,
+ god->order_id,
+ &db_claim_token,
+ &unused,
+ (NULL == god->contract_terms)
+ ? &god->contract_terms
+ : NULL);
+ if (0 > qs)
+ {
+ /* single, read-only SQL statements should never cause
+ serialization problems */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (0);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_order");
+ return;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Unknown order id given: `%s'\n",
+ god->order_id);
+ phase_fail (god,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
+ god->order_id);
+ return;
+ }
+ /* Note: when "!ord.requireClaimToken" and the client does not provide
+ a claim token (all zeros!), then token_match==TRUE below: */
+ god->token_match
+ = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
+ (0 == GNUNET_memcmp (&db_claim_token,
+ &god->claim_token));
+ } /* end unclaimed order logic */
+ god->phase++;
}
-char *
-TMH_make_taler_pay_uri (struct MHD_Connection *con,
- const char *order_id,
- const char *session_id,
- const char *instance_id,
- struct TALER_ClaimTokenP *claim_token)
+/**
+ * Parse contract terms.
+ *
+ * @param[in,out] god request context
+ */
+static void
+phase_parse_contract (struct GetOrderData *god)
{
- const char *host;
- const char *forwarded_host;
- const char *uri_path;
- struct GNUNET_Buffer buf = { 0 };
+ struct GNUNET_JSON_Specification espec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &god->contract_total),
+ TALER_JSON_spec_web_url ("merchant_base_url",
+ &god->merchant_base_url),
+ GNUNET_JSON_spec_mark_optional (
+ /* this one does NOT have to be a Web URL! */
+ GNUNET_JSON_spec_string ("fulfillment_url",
+ &god->fulfillment_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("public_reorder_url",
+ &god->public_reorder_url),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+ const char *ename;
+ unsigned int eline;
- host = MHD_lookup_connection_value (con,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_HOST);
- forwarded_host = MHD_lookup_connection_value (con,
- MHD_HEADER_KIND,
- "X-Forwarded-Host");
- uri_path = MHD_lookup_connection_value (con,
- MHD_HEADER_KIND,
- "X-Forwarded-Prefix");
- if (NULL != forwarded_host)
- host = forwarded_host;
- if (NULL == host)
+ GNUNET_assert (NULL != god->contract_terms);
+ if (god->contract_parsed)
+ return; /* not sure this is possible... */
+
+ res = GNUNET_JSON_parse (god->contract_terms,
+ espec,
+ &ename,
+ &eline);
+ if (GNUNET_OK != res)
{
GNUNET_break (0);
- return NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse contract %s in DB at field %s\n",
+ god->order_id,
+ ename);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ god->order_id);
+ return;
}
- if (NULL != strchr (host, '/'))
+ god->contract_parsed = true;
+ god->phase++;
+}
+
+
+/**
+ * Check that this order is unclaimed or claimed by
+ * this client.
+ *
+ * @param[in,out] god request context
+ */
+static void
+phase_check_client_access (struct GetOrderData *god)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Token match: %d, contract_available: %d, contract match: %d, claimed: %d\n",
+ god->token_match,
+ god->contract_available,
+ god->contract_match,
+ god->claimed);
+
+ if (god->claim_token_provided && ! god->token_match)
{
+ /* Authentication provided but wrong. */
GNUNET_break_op (0);
- return NULL;
+ phase_fail (god,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
+ "authentication with claim token provided but wrong");
+ return;
}
- GNUNET_assert (NULL != instance_id);
- GNUNET_assert (NULL != order_id);
- GNUNET_buffer_write_str (&buf,
- "taler");
- if (GNUNET_NO == TALER_mhd_is_https (con))
- GNUNET_buffer_write_str (&buf,
- "+http");
- GNUNET_buffer_write_str (&buf,
- "://pay/");
- GNUNET_buffer_write_str (&buf,
- host);
- if (NULL != uri_path)
- GNUNET_buffer_write_path (&buf,
- uri_path);
- if (0 != strcmp ("default",
- instance_id))
+
+ if (god->h_contract_provided && ! god->contract_match)
{
- GNUNET_buffer_write_path (&buf,
- "instances");
- GNUNET_buffer_write_path (&buf,
- instance_id);
+ /* Authentication provided but wrong. */
+ GNUNET_break_op (0);
+ phase_fail (god,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_HASH,
+ NULL);
+ return;
}
- GNUNET_buffer_write_path (&buf,
- order_id);
- GNUNET_buffer_write_path (&buf,
- (session_id == NULL) ? "" : session_id);
- if ((NULL != claim_token) &&
- (GNUNET_NO == GNUNET_is_zero (claim_token)))
+
+ if (! (god->token_match ||
+ god->contract_match) )
{
- /* Just 'c=' because this goes into QR
- codes, so this is more compact. */
- GNUNET_buffer_write_str (&buf,
- "?c=");
- GNUNET_buffer_write_data_encoded (&buf,
- (char *) claim_token,
- sizeof (struct TALER_ClaimTokenP));
- }
- return GNUNET_buffer_reap_str (&buf);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Neither claim token nor contract matched\n");
+ /* Client has no rights to this order */
+ if (NULL == god->public_reorder_url)
+ {
+ /* We cannot give the client a new order, just fail */
+ if (! GNUNET_is_zero (&god->h_contract_terms))
+ {
+ GNUNET_break_op (0);
+ phase_fail (god,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
+ NULL);
+ return;
+ }
+ GNUNET_break_op (0);
+ phase_fail (god,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
+ "no 'public_reorder_url'");
+ return;
+ }
+ /* We have a fulfillment URL, redirect the client there, maybe
+ the frontend can generate a fresh order for this new customer */
+ if (god->generate_html)
+ {
+ /* Contract was claimed (maybe by another device), so this client
+ cannot get the status information. Redirect to fulfillment page,
+ where the client may be able to pickup a fresh order -- or might
+ be able authenticate via session ID */
+ struct MHD_Response *reply;
+ MHD_RESULT ret;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Contract claimed, redirecting to fulfillment page for order %s\n",
+ god->order_id);
+ reply = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ if (NULL == reply)
+ {
+ GNUNET_break (0);
+ phase_end (god,
+ MHD_NO);
+ return;
+ }
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (reply,
+ MHD_HTTP_HEADER_LOCATION,
+ god->public_reorder_url));
+ ret = MHD_queue_response (god->sc.con,
+ MHD_HTTP_FOUND,
+ reply);
+ MHD_destroy_response (reply);
+ phase_end (god,
+ ret);
+ return;
+ }
+ /* Need to generate JSON reply */
+ phase_end (god,
+ TALER_MHD_REPLY_JSON_PACK (
+ god->sc.con,
+ MHD_HTTP_ACCEPTED,
+ GNUNET_JSON_pack_string ("public_reorder_url",
+ god->public_reorder_url)));
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Claim token or contract matched\n");
+ god->phase++;
}
@@ -586,9 +910,9 @@ get_order_summary (const struct GetOrderData *god)
* @param already_paid_order_id if for the fulfillment URI there is
* already a paid order, this is the order ID to redirect
* the wallet to; NULL if not applicable
- * @return #MHD_YES on success
+ * @return true to exit due to suspension
*/
-static MHD_RESULT
+static bool
send_pay_request (struct GetOrderData *god,
const char *already_paid_order_id)
{
@@ -605,7 +929,7 @@ send_pay_request (struct GetOrderData *god,
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Suspending request: long polling for payment\n");
suspend_god (god);
- return MHD_YES;
+ return true;
}
/* Check if resource_id has been paid for in the same session
@@ -613,27 +937,30 @@ send_pay_request (struct GetOrderData *god,
*/
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Sending payment request\n");
- taler_pay_uri = TMH_make_taler_pay_uri (god->sc.con,
- god->order_id,
- god->session_id,
- god->hc->instance->settings.id,
- &god->claim_token);
- order_status_url = TMH_make_order_status_url (god->sc.con,
- god->order_id,
- god->session_id,
- god->hc->instance->settings.id,
- &god->claim_token,
- NULL);
+ taler_pay_uri = TMH_make_taler_pay_uri (
+ god->sc.con,
+ god->order_id,
+ god->session_id,
+ god->hc->instance->settings.id,
+ &god->claim_token);
+ order_status_url = TMH_make_order_status_url (
+ god->sc.con,
+ god->order_id,
+ god->session_id,
+ god->hc->instance->settings.id,
+ &god->claim_token,
+ NULL);
if ( (NULL == taler_pay_uri) ||
(NULL == order_status_url) )
{
GNUNET_break_op (0);
GNUNET_free (taler_pay_uri);
GNUNET_free (order_status_url);
- return TALER_MHD_reply_with_error (god->sc.con,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
- "host");
+ phase_fail (god,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
+ "host");
+ return false;
}
if (god->generate_html)
{
@@ -652,7 +979,9 @@ send_pay_request (struct GetOrderData *god,
if (NULL == reply)
{
GNUNET_break (0);
- return MHD_NO;
+ phase_end (god,
+ MHD_NO);
+ return false;
}
GNUNET_break (MHD_YES ==
MHD_add_response_header (reply,
@@ -665,7 +994,9 @@ send_pay_request (struct GetOrderData *god,
MHD_HTTP_FOUND,
reply);
MHD_destroy_response (reply);
- return ret;
+ phase_end (god,
+ ret);
+ return false;
}
}
@@ -676,7 +1007,9 @@ send_pay_request (struct GetOrderData *god,
if (NULL == qr)
{
GNUNET_break (0);
- return MHD_NO;
+ phase_end (god,
+ MHD_NO);
+ return false;
}
{
enum GNUNET_GenericReturnValue res;
@@ -691,12 +1024,13 @@ send_pay_request (struct GetOrderData *god,
qr),
GNUNET_JSON_pack_string ("order_summary",
get_order_summary (god)));
- res = TALER_TEMPLATING_reply (god->sc.con,
- MHD_HTTP_PAYMENT_REQUIRED,
- "request_payment",
- god->hc->instance->settings.id,
- taler_pay_uri,
- context);
+ res = TALER_TEMPLATING_reply (
+ god->sc.con,
+ MHD_HTTP_PAYMENT_REQUIRED,
+ "request_payment",
+ god->hc->instance->settings.id,
+ taler_pay_uri,
+ context);
if (GNUNET_SYSERR == res)
{
GNUNET_break (0);
@@ -727,7 +1061,146 @@ send_pay_request (struct GetOrderData *god,
}
GNUNET_free (taler_pay_uri);
GNUNET_free (order_status_url);
- return ret;
+ phase_end (god,
+ ret);
+ return false;
+}
+
+
+/**
+ * Check if the order has been paid.
+ *
+ * @param[in,out] god request context
+ */
+static void
+phase_check_paid (struct GetOrderData *god)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_PrivateContractHashP h_contract;
+
+ god->paid = false;
+ qs = TMH_db->lookup_order_status (
+ TMH_db->cls,
+ god->hc->instance->settings.id,
+ god->order_id,
+ &h_contract,
+ &god->paid);
+ if (0 > qs)
+ {
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_order_status");
+ return;
+ }
+ god->phase++;
+}
+
+
+/**
+ * Check if the client already paid for an equivalent
+ * order under this session, and if so redirect to
+ * that order.
+ *
+ * @param[in,out] god request context
+ * @return true to exit due to suspension
+ */
+static bool
+phase_redirect_to_paid_order (struct GetOrderData *god)
+{
+ if ( (NULL != god->session_id) &&
+ (NULL != god->fulfillment_url) )
+ {
+ /* Check if client paid for this fulfillment article
+ already within this session, but using a different
+ order ID. If so, redirect the client to the order
+ it already paid. Allows, for example, the case
+ where a mobile phone pays for a browser's session,
+ where the mobile phone has a different order
+ ID (because it purchased the article earlier)
+ than the one that the browser is waiting for. */
+ char *already_paid_order_id = NULL;
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running re-purchase detection for %s/%s\n",
+ god->session_id,
+ god->fulfillment_url);
+ qs = TMH_db->lookup_order_by_fulfillment (
+ TMH_db->cls,
+ god->hc->instance->settings.id,
+ god->fulfillment_url,
+ god->session_id,
+ TALER_EXCHANGE_YNA_NO != god->allow_refunded_for_repurchase,
+ &already_paid_order_id);
+ if (qs < 0)
+ {
+ /* single, read-only SQL statements should never cause
+ serialization problems */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "order by fulfillment");
+ return false;
+ }
+ if ( (! god->paid) &&
+ ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
+ (0 != strcmp (god->order_id,
+ already_paid_order_id)) ) )
+ {
+ bool ret;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sending pay request for order %s (already paid: %s)\n",
+ god->order_id,
+ already_paid_order_id);
+ ret = send_pay_request (god,
+ already_paid_order_id);
+ GNUNET_free (already_paid_order_id);
+ return ret;
+ }
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ GNUNET_free (already_paid_order_id);
+ }
+ god->phase++;
+ return false;
+}
+
+
+/**
+ * Check if the order has been paid, and if not
+ * request payment.
+ *
+ * @param[in,out] god request context
+ * @return true to exit due to suspension
+ */
+static bool
+phase_handle_unpaid (struct GetOrderData *god)
+{
+ if (god->paid)
+ {
+ god->phase++;
+ return false;
+ }
+ if (god->claimed)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order claimed but unpaid, sending pay request for order %s\n",
+ god->order_id);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order unclaimed, sending pay request for order %s\n",
+ god->order_id);
+ }
+ return send_pay_request (god,
+ NULL);
}
@@ -768,6 +1241,16 @@ process_refunds_cb (void *cls,
TALER_B2S (coin_pub),
reason);
god->refund_pending |= pending;
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (&god->refund_taken,
+ refund_amount)) ||
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&god->refund_amount,
+ refund_amount)) )
+ {
+ god->bad_refund_currency_in_db = true;
+ return;
+ }
if (! pending)
{
GNUNET_assert (0 <=
@@ -784,31 +1267,266 @@ process_refunds_cb (void *cls,
/**
- * Clean up the session state for a GET /orders/$ID request.
+ * Check if the order has been refunded.
*
- * @param cls must be a `struct GetOrderData *`
+ * @param[in,out] god request context
+ * @return true to exit due to suspension
*/
-static void
-god_cleanup (void *cls)
+static bool
+phase_check_refunded (struct GetOrderData *god)
{
- struct GetOrderData *god = cls;
+ enum GNUNET_DB_QueryStatus qs;
- if (NULL != god->contract_terms)
+ if ( (god->sc.awaiting_refund) &&
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&god->contract_total,
+ &god->sc.refund_expected)) )
{
- json_decref (god->contract_terms);
- god->contract_terms = NULL;
+ GNUNET_break (0);
+ phase_fail (god,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ god->contract_total.currency);
+ return false;
}
- if (NULL != god->refund_eh)
+
+ /* At this point, we know the contract was paid. Let's check for
+ refunds. First, clear away refunds found from previous invocations. */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (god->contract_total.currency,
+ &god->refund_amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (god->contract_total.currency,
+ &god->refund_taken));
+ qs = TMH_db->lookup_refunds_detailed (
+ TMH_db->cls,
+ god->hc->instance->settings.id,
+ &god->h_contract_terms,
+ &process_refunds_cb,
+ god);
+ if (0 > qs)
{
- TMH_db->event_listen_cancel (god->refund_eh);
- god->refund_eh = NULL;
+ GNUNET_break (0);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_refunds_detailed");
+ return false;
}
- if (NULL != god->pay_eh)
+ if (god->bad_refund_currency_in_db)
{
- TMH_db->event_listen_cancel (god->pay_eh);
- god->pay_eh = NULL;
+ GNUNET_break (0);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "currency mix-up between contract price and refunds in database");
+ return false;
+ }
+ if ( ((god->sc.awaiting_refund) &&
+ ( (! god->refunded) ||
+ (1 != TALER_amount_cmp (&god->refund_amount,
+ &god->sc.refund_expected)) )) ||
+ ( (god->sc.awaiting_refund_obtained) &&
+ (god->refund_pending) ) )
+ {
+ /* Client is waiting for a refund larger than what we have, suspend
+ until timeout */
+ struct GNUNET_TIME_Relative remaining;
+
+ remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
+ if ( (! GNUNET_TIME_relative_is_zero (remaining)) &&
+ (! god->generate_html) )
+ {
+ /* yes, indeed suspend */
+ if (god->sc.awaiting_refund)
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Awaiting refund exceeding %s\n",
+ TALER_amount2s (&god->sc.refund_expected));
+ if (god->sc.awaiting_refund_obtained)
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Awaiting pending refunds\n");
+ suspend_god (god);
+ return true;
+ }
+ }
+ god->phase++;
+ return false;
+}
+
+
+/**
+ * Create a taler://refund/ URI for the given @a con and @a order_id
+ * and @a instance_id.
+ *
+ * @param merchant_base_url URL to take host and path from;
+ * we cannot take it from the MHD connection as a browser
+ * may have changed 'http' to 'https' and we MUST be consistent
+ * with what the merchant's frontend used initially
+ * @param order_id the order id
+ * @return corresponding taler://refund/ URI, or NULL on missing "host"
+ */
+static char *
+make_taler_refund_uri (const char *merchant_base_url,
+ const char *order_id)
+{
+ struct GNUNET_Buffer buf = { 0 };
+ char *url;
+ struct GNUNET_Uri uri;
+
+ url = GNUNET_strdup (merchant_base_url);
+ if (-1 == GNUNET_uri_parse (&uri,
+ url))
+ {
+ GNUNET_break (0);
+ GNUNET_free (url);
+ return NULL;
+ }
+ GNUNET_assert (NULL != order_id);
+ GNUNET_buffer_write_str (&buf,
+ "taler");
+ if (0 == strcasecmp ("http",
+ uri.scheme))
+ GNUNET_buffer_write_str (&buf,
+ "+http");
+ GNUNET_buffer_write_str (&buf,
+ "://refund/");
+ GNUNET_buffer_write_str (&buf,
+ uri.host);
+ if (0 != uri.port)
+ GNUNET_buffer_write_fstr (&buf,
+ ":%u",
+ (unsigned int) uri.port);
+ if (NULL != uri.path)
+ GNUNET_buffer_write_path (&buf,
+ uri.path);
+ GNUNET_buffer_write_path (&buf,
+ order_id);
+ GNUNET_buffer_write_path (&buf,
+ ""); // Trailing slash
+ GNUNET_free (url);
+ return GNUNET_buffer_reap_str (&buf);
+}
+
+
+/**
+ * Generate the order status response.
+ *
+ * @param[in,out] god request context
+ */
+static void
+phase_return_status (struct GetOrderData *god)
+{
+ /* All operations done, build final response */
+ if (! god->generate_html)
+ {
+ phase_end (god,
+ TALER_MHD_REPLY_JSON_PACK (
+ god->sc.con,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("fulfillment_url",
+ god->fulfillment_url)),
+ GNUNET_JSON_pack_bool ("refunded",
+ god->refunded),
+ GNUNET_JSON_pack_bool ("refund_pending",
+ god->refund_pending),
+ TALER_JSON_pack_amount ("refund_taken",
+ &god->refund_taken),
+ TALER_JSON_pack_amount ("refund_amount",
+ &god->refund_amount)));
+ return;
+ }
+
+ if (god->refund_pending)
+ {
+ char *qr;
+ char *uri;
+
+ GNUNET_assert (NULL != god->contract_terms);
+ uri = make_taler_refund_uri (god->merchant_base_url,
+ god->order_id);
+ if (NULL == uri)
+ {
+ GNUNET_break (0);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ "refund URI");
+ return;
+ }
+ qr = TMH_create_qrcode (uri);
+ if (NULL == qr)
+ {
+ GNUNET_break (0);
+ GNUNET_free (uri);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ "qr code");
+ return;
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+ json_t *context;
+
+ context = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("order_summary",
+ get_order_summary (god)),
+ TALER_JSON_pack_amount ("refund_amount",
+ &god->refund_amount),
+ TALER_JSON_pack_amount ("refund_taken",
+ &god->refund_taken),
+ GNUNET_JSON_pack_string ("taler_refund_uri",
+ uri),
+ GNUNET_JSON_pack_string ("taler_refund_qrcode_svg",
+ qr));
+ res = TALER_TEMPLATING_reply (
+ god->sc.con,
+ MHD_HTTP_OK,
+ "offer_refund",
+ god->hc->instance->settings.id,
+ uri,
+ context);
+ GNUNET_break (GNUNET_OK == res);
+ json_decref (context);
+ phase_end (god,
+ (GNUNET_SYSERR == res)
+ ? MHD_NO
+ : MHD_YES);
+ }
+ GNUNET_free (uri);
+ GNUNET_free (qr);
+ return;
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+ json_t *context;
+
+ context = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_incref ("contract_terms",
+ god->contract_terms),
+ GNUNET_JSON_pack_string ("order_summary",
+ get_order_summary (god)),
+ TALER_JSON_pack_amount ("refund_amount",
+ &god->refund_amount),
+ TALER_JSON_pack_amount ("refund_taken",
+ &god->refund_taken));
+ res = TALER_TEMPLATING_reply (
+ god->sc.con,
+ MHD_HTTP_OK,
+ "show_order_details",
+ god->hc->instance->settings.id,
+ NULL,
+ context);
+ GNUNET_break (GNUNET_OK == res);
+ json_decref (context);
+ phase_end (god,
+ (GNUNET_SYSERR == res)
+ ? MHD_NO
+ : MHD_YES);
}
- GNUNET_free (god);
}
@@ -818,14 +1536,6 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
struct TMH_HandlerContext *hc)
{
struct GetOrderData *god = hc->ctx;
- const char *order_id = hc->infix;
- enum GNUNET_DB_QueryStatus qs;
- bool contract_match = false;
- bool token_match = false;
- bool h_contract_provided = false;
- bool claim_token_provided = false;
- bool contract_available = false;
- const char *merchant_base_url;
(void) rh;
if (NULL == god)
@@ -835,56 +1545,27 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
hc->cc = &god_cleanup;
god->sc.con = connection;
god->hc = hc;
- god->order_id = order_id;
- god->generate_html = TMH_MHD_test_html_desired (connection);
-
+ god->order_id = hc->infix;
+ god->generate_html
+ = TMH_MHD_test_html_desired (connection);
/* first-time initialization / sanity checks */
- {
- const char *cts;
-
- cts = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "h_contract");
- if ( (NULL != cts) &&
- (GNUNET_OK !=
- GNUNET_CRYPTO_hash_from_string (cts,
- &god->h_contract_terms.hash)) )
- {
- /* cts has wrong encoding */
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "h_contract");
- }
- if (NULL != cts)
- h_contract_provided = true;
- }
-
- {
- const char *ct;
-
- ct = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "token");
- if ( (NULL != ct) &&
- (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (ct,
- strlen (ct),
- &god->claim_token,
- sizeof (god->claim_token))) )
- {
- /* ct has wrong encoding */
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "token");
- }
- if (NULL != ct)
- claim_token_provided = true;
- }
+ TALER_MHD_parse_request_arg_auto (connection,
+ "h_contract",
+ &god->h_contract_terms,
+ god->h_contract_provided);
+ TALER_MHD_parse_request_arg_auto (connection,
+ "token",
+ &god->claim_token,
+ god->claim_token_provided);
+ if (! (TALER_arg_to_yna (connection,
+ "allow_refunded_for_repurchase",
+ TALER_EXCHANGE_YNA_NO,
+ &god->allow_refunded_for_repurchase)) )
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "allow_refunded_for_repurchase");
god->session_id = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"session_id");
@@ -907,118 +1588,19 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
"Awaiting refund obtained\n");
}
+ TALER_MHD_parse_request_amount (connection,
+ "refund",
+ &god->sc.refund_expected);
+ if (TALER_amount_is_valid (&god->sc.refund_expected))
{
- const char *min_refund;
-
- min_refund = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "refund");
- if (NULL != min_refund)
- {
- if ( (GNUNET_OK !=
- TALER_string_to_amount (min_refund,
- &god->sc.refund_expected)) ||
- (0 != strcasecmp (god->sc.refund_expected.currency,
- TMH_currency) ) )
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "refund");
- }
- god->sc.awaiting_refund = true;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Awaiting minimum refund of %s\n",
- min_refund);
- }
+ god->sc.awaiting_refund = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Awaiting minimum refund of %s\n",
+ TALER_amount2s (&god->sc.refund_expected));
}
-
-
- /* process timeout_ms argument */
- {
- const char *long_poll_timeout_ms;
-
- long_poll_timeout_ms = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "timeout_ms");
- if (NULL != long_poll_timeout_ms)
- {
- unsigned int timeout_ms;
- char dummy;
-
- if (1 != sscanf (long_poll_timeout_ms,
- "%u%c",
- &timeout_ms,
- &dummy))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "timeout_ms (must be non-negative number)");
- }
- /* If HTML is requested, we never long poll. Makes no sense */
- if (! god->generate_html)
- {
- struct GNUNET_TIME_Relative timeout;
-
- timeout = GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_MILLISECONDS,
- timeout_ms);
- god->sc.long_poll_timeout
- = GNUNET_TIME_relative_to_absolute (timeout);
- if (! GNUNET_TIME_relative_is_zero (timeout))
- {
- if (god->sc.awaiting_refund ||
- god->sc.awaiting_refund_obtained)
- {
- struct TMH_OrderPayEventP refund_eh = {
- .header.size = htons (sizeof (refund_eh)),
- .header.type = htons (god->sc.awaiting_refund_obtained
- ? TALER_DBEVENT_MERCHANT_REFUND_OBTAINED
- : TALER_DBEVENT_MERCHANT_ORDER_REFUND),
- .merchant_pub = hc->instance->merchant_pub
- };
-
- GNUNET_CRYPTO_hash (god->order_id,
- strlen (god->order_id),
- &refund_eh.h_order_id);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Subscribing %p to refunds on %s\n",
- god,
- god->order_id);
- god->refund_eh = TMH_db->event_listen (TMH_db->cls,
- &refund_eh.header,
- timeout,
- &resume_by_event,
- god);
- }
- {
- struct TMH_OrderPayEventP pay_eh = {
- .header.size = htons (sizeof (pay_eh)),
- .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
- .merchant_pub = hc->instance->merchant_pub
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Subscribing to payments on %s\n",
- god->order_id);
- GNUNET_CRYPTO_hash (god->order_id,
- strlen (god->order_id),
- &pay_eh.h_order_id);
- god->pay_eh = TMH_db->event_listen (TMH_db->cls,
- &pay_eh.header,
- timeout,
- &resume_by_event,
- god);
- }
- } /* end of timeout non-zero */
- } /* end of HTML generation NOT requested */
- } /* end of timeout_ms argument provided */
- } /* end of timeout_ms argument handling */
-
- } /* end of first-time initialization / sanity checks */
+ TALER_MHD_parse_request_timeout (connection,
+ &god->sc.long_poll_timeout);
+ }
if (GNUNET_SYSERR == god->suspended)
return MHD_NO; /* we are in shutdown */
@@ -1030,494 +1612,49 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
god);
}
- /* Convert order_id to h_contract_terms */
- TMH_db->preflight (TMH_db->cls);
- if (NULL == god->contract_terms)
- {
- uint64_t order_serial;
- bool paid = false;
- struct TALER_ClaimTokenP db_claim_token;
-
- qs = TMH_db->lookup_contract_terms (TMH_db->cls,
- hc->instance->settings.id,
- order_id,
- &god->contract_terms,
- &order_serial,
- &paid,
- &db_claim_token);
- if (0 > qs)
- {
- /* single, read-only SQL statements should never cause
- serialization problems */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- /* Always report on hard error as well to enable diagnostics */
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_contract_terms");
- }
-
- /* Note: when "!ord.requireClaimToken" and the client does not provide
- a claim token (all zeros!), then token_match==TRUE below: */
- token_match = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- && (0 == GNUNET_memcmp (&db_claim_token,
- &god->claim_token));
- }
-
- /* Check if client provided the right hash code of the contract terms */
- if (NULL != god->contract_terms)
- {
- contract_available = true;
-
- if (GNUNET_YES == GNUNET_is_zero (&god->h_contract_terms))
- {
-
- if (GNUNET_OK !=
- TALER_JSON_contract_hash (god->contract_terms,
- &god->h_contract_terms))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
- "contract terms");
- }
-
- }
- else
- {
-
- struct TALER_PrivateContractHashP h;
-
- if (GNUNET_OK !=
- TALER_JSON_contract_hash (god->contract_terms,
- &h))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
- "contract terms");
- }
- contract_match = (0 ==
- GNUNET_memcmp (&h,
- &god->h_contract_terms));
- if (! contract_match)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
- NULL);
- }
-
- }
-
- }
-
- if (contract_available)
- {
- god->claimed = true;
- }
- else
- {
- struct TALER_ClaimTokenP db_claim_token;
- struct TALER_MerchantPostDataHashP unused;
-
- qs = TMH_db->lookup_order (TMH_db->cls,
- hc->instance->settings.id,
- order_id,
- &db_claim_token,
- &unused,
- (NULL == god->contract_terms)
- ? &god->contract_terms
- : NULL);
- if (0 > qs)
- {
- /* single, read-only SQL statements should never cause
- serialization problems */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- /* Always report on hard error as well to enable diagnostics */
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_order");
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Unknown order id given: `%s'\n",
- order_id);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
- order_id);
- }
- /* Note: when "!ord.requireClaimToken" and the client does not provide
- a claim token (all zeros!), then token_match==TRUE below: */
- token_match = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
- (0 == GNUNET_memcmp (&db_claim_token,
- &god->claim_token));
- } /* end unclaimed order logic */
-
- GNUNET_assert (NULL != god->contract_terms);
- merchant_base_url = json_string_value (json_object_get (god->contract_terms,
- "merchant_base_url"));
- if (NULL == merchant_base_url)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
- order_id);
- }
-
- if (NULL == god->fulfillment_url)
- god->fulfillment_url = json_string_value (json_object_get (
- god->contract_terms,
- "fulfillment_url"));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Token match: %d, contract_available: %d, contract match: %d, claimed: %d\n",
- token_match,
- contract_available,
- contract_match,
- god->claimed);
-
- if (claim_token_provided && ! token_match)
- {
- /* Authentication provided but wrong. */
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
- "authentication with claim token provided but wrong");
- }
-
- if (h_contract_provided && ! contract_match)
- {
- /* Authentication provided but wrong. */
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_HASH,
- NULL);
- }
-
- if (! (token_match ||
- contract_match) )
- {
- const char *public_reorder_url;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Neither claim token nor contract matched\n");
- public_reorder_url = json_string_value (json_object_get (
- god->contract_terms,
- "public_reorder_url"));
- /* Client has no rights to this order */
- if (NULL == public_reorder_url)
- {
- /* We cannot give the client a new order, just fail */
- if (! GNUNET_is_zero (&god->h_contract_terms))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
- NULL);
- }
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
- "no 'public_reorder_url'");
- }
- /* We have a fulfillment URL, redirect the client there, maybe
- the frontend can generate a fresh order for this new customer */
- if (god->generate_html)
- {
- /* Contract was claimed (maybe by another device), so this client
- cannot get the status information. Redirect to fulfillment page,
- where the client may be able to pickup a fresh order -- or might
- be able authenticate via session ID */
- struct MHD_Response *reply;
- MHD_RESULT ret;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Contract claimed, redirecting to fulfillment page for order %s\n",
- order_id);
- reply = MHD_create_response_from_buffer (0,
- NULL,
- MHD_RESPMEM_PERSISTENT);
- if (NULL == reply)
- {
- GNUNET_break (0);
- return MHD_NO;
- }
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (reply,
- MHD_HTTP_HEADER_LOCATION,
- public_reorder_url));
- ret = MHD_queue_response (connection,
- MHD_HTTP_FOUND,
- reply);
- MHD_destroy_response (reply);
- return ret;
- }
- /* Need to generate JSON reply */
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_ACCEPTED,
- GNUNET_JSON_pack_string ("public_reorder_url",
- public_reorder_url));
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Claim token or contract matched\n");
-
- if ( (NULL != god->session_id) &&
- (NULL != god->fulfillment_url) )
- {
- /* Check if client paid for this fulfillment article
- already within this session, but using a different
- order ID. If so, redirect the client to the order
- it already paid. Allows, for example, the case
- where a mobile phone pays for a browser's session,
- where the mobile phone has a different order
- ID (because it purchased the article earlier)
- than the one that the browser is waiting for. */
- char *already_paid_order_id = NULL;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Running re-purchase detection for %s/%s\n",
- god->session_id,
- god->fulfillment_url);
- qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls,
- hc->instance->settings.id,
- god->fulfillment_url,
- god->session_id,
- &already_paid_order_id);
- if (qs < 0)
- {
- /* single, read-only SQL statements should never cause
- serialization problems */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- /* Always report on hard error as well to enable diagnostics */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "order by fulfillment");
- }
- if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
- (0 != strcmp (order_id,
- already_paid_order_id)) )
- {
- MHD_RESULT ret;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Sending pay request for order %s (already paid: %s)\n",
- order_id,
- already_paid_order_id);
- ret = send_pay_request (god,
- already_paid_order_id);
- GNUNET_free (already_paid_order_id);
- return ret;
- }
- GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
- GNUNET_free (already_paid_order_id);
- }
-
- if (! god->claimed)
+ while (1)
{
- /* Order is unclaimed, no need to check for payments or even
- refunds, simply always generate payment request */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Order unclaimed, sending pay request for order %s\n",
- order_id);
- return send_pay_request (god,
- NULL);
- }
-
- {
- /* Check if paid. */
- struct TALER_PrivateContractHashP h_contract;
- bool paid;
-
- qs = TMH_db->lookup_order_status (TMH_db->cls,
- hc->instance->settings.id,
- order_id,
- &h_contract,
- &paid);
- if (0 >= qs)
- {
- /* Always report on hard error as well to enable diagnostics */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_order_status");
- }
- GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
- if (! paid)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Order claimed but unpaid, sending pay request for order %s\n",
- order_id);
- return send_pay_request (god,
- NULL);
- }
- }
-
- /* At this point, we know the contract was paid. Let's check for
- refunds. First, clear away refunds found from previous invocations. */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TMH_currency,
- &god->refund_amount));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TMH_currency,
- &god->refund_taken));
- qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
- hc->instance->settings.id,
- &god->h_contract_terms,
- &process_refunds_cb,
- god);
- if (0 > qs)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_refunds_detailed");
- }
-
- if ( ((god->sc.awaiting_refund) &&
- ( (! god->refunded) ||
- (1 != TALER_amount_cmp (&god->refund_amount,
- &god->sc.refund_expected)) )) ||
- ( (god->sc.awaiting_refund_obtained) &&
- (god->refund_pending) ) )
- {
- /* Client is waiting for a refund larger than what we have, suspend
- until timeout */
- struct GNUNET_TIME_Relative remaining;
-
- remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
- if (! GNUNET_TIME_relative_is_zero (remaining))
+ "Handling request in phase %d\n",
+ (int) god->phase);
+ switch (god->phase)
{
- /* yes, indeed suspend */
- if (god->sc.awaiting_refund)
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Awaiting refund exceeding %s\n",
- TALER_amount2s (&god->sc.refund_expected));
- if (god->sc.awaiting_refund_obtained)
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Awaiting pending refunds\n");
- suspend_god (god);
+ case GOP_INIT:
+ phase_init (god);
+ break;
+ case GOP_LOOKUP_TERMS:
+ phase_lookup_terms (god);
+ break;
+ case GOP_PARSE_CONTRACT:
+ phase_parse_contract (god);
+ break;
+ case GOP_CHECK_CLIENT_ACCESS:
+ phase_check_client_access (god);
+ break;
+ case GOP_CHECK_PAID:
+ phase_check_paid (god);
+ break;
+ case GOP_REDIRECT_TO_PAID_ORDER:
+ if (phase_redirect_to_paid_order (god))
+ return MHD_YES;
+ break;
+ case GOP_HANDLE_UNPAID:
+ if (phase_handle_unpaid (god))
+ return MHD_YES;
+ break;
+ case GOP_CHECK_REFUNDED:
+ if (phase_check_refunded (god))
+ return MHD_YES;
+ break;
+ case GOP_RETURN_STATUS:
+ phase_return_status (god);
+ break;
+ case GOP_RETURN_MHD_YES:
return MHD_YES;
- }
- }
-
- /* All operations done, build final response */
- if (god->generate_html)
- {
- enum GNUNET_GenericReturnValue res;
-
- if (god->refund_pending)
- {
- char *qr;
- char *uri;
-
- GNUNET_assert (NULL != god->contract_terms);
- uri = make_taler_refund_uri (merchant_base_url,
- order_id);
- if (NULL == uri)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (god->sc.con,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_ALLOCATION_FAILURE,
- "refund URI");
- }
- qr = TMH_create_qrcode (uri);
- if (NULL == qr)
- {
- GNUNET_break (0);
- GNUNET_free (uri);
- return TALER_MHD_reply_with_error (god->sc.con,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_ALLOCATION_FAILURE,
- "qr code");
- }
- {
- json_t *context;
-
- context = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("order_summary",
- get_order_summary (god)),
- TALER_JSON_pack_amount ("refund_amount",
- &god->refund_amount),
- TALER_JSON_pack_amount ("refund_taken",
- &god->refund_taken),
- GNUNET_JSON_pack_string ("taler_refund_uri",
- uri),
- GNUNET_JSON_pack_string ("taler_refund_qrcode_svg",
- qr));
- res = TALER_TEMPLATING_reply (god->sc.con,
- MHD_HTTP_OK,
- "offer_refund",
- hc->instance->settings.id,
- uri,
- context);
- json_decref (context);
- }
- GNUNET_free (uri);
- GNUNET_free (qr);
- }
- else
- {
- json_t *context;
-
- context = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_object_incref ("contract_terms",
- god->contract_terms),
- GNUNET_JSON_pack_string ("order_summary",
- get_order_summary (god)),
- TALER_JSON_pack_amount ("refund_amount",
- &god->refund_amount),
- TALER_JSON_pack_amount ("refund_taken",
- &god->refund_taken));
- res = TALER_TEMPLATING_reply (god->sc.con,
- MHD_HTTP_OK,
- "show_order_details",
- hc->instance->settings.id,
- NULL,
- context);
- json_decref (context);
- }
- if (GNUNET_SYSERR == res)
- {
- GNUNET_break (0);
+ case GOP_RETURN_MHD_NO:
return MHD_NO;
}
- return MHD_YES;
}
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("fulfillment_url",
- god->fulfillment_url)),
- GNUNET_JSON_pack_bool ("refunded",
- god->refunded),
- GNUNET_JSON_pack_bool ("refund_pending",
- god->refund_pending),
- TALER_JSON_pack_amount ("refund_taken",
- &god->refund_taken),
- TALER_JSON_pack_amount ("refund_amount",
- &god->refund_amount));
}
diff --git a/src/backend/taler-merchant-httpd_get-orders-ID.h b/src/backend/taler-merchant-httpd_get-orders-ID.h
index 97b8525b..49ef5a73 100644
--- a/src/backend/taler-merchant-httpd_get-orders-ID.h
+++ b/src/backend/taler-merchant-httpd_get-orders-ID.h
@@ -32,45 +32,6 @@ TMH_force_wallet_get_order_resume (void);
/**
- * Create a taler://pay/ URI for the given @a con and @a order_id
- * and @a session_id and @a instance_id.
- *
- * @param con HTTP connection
- * @param order_id the order id
- * @param session_id session, may be NULL
- * @param instance_id instance, may be "default"
- * @param claim_token claim token for the order, may be NULL
- * @return corresponding taler://pay/ URI, or NULL on missing "host"
- */
-char *
-TMH_make_taler_pay_uri (struct MHD_Connection *con,
- const char *order_id,
- const char *session_id,
- const char *instance_id,
- struct TALER_ClaimTokenP *claim_token);
-
-/**
- * Create a http(s) URL for the given @a con and @a order_id
- * and @a instance_id to display the /orders/{order_id} page.
- *
- * @param con HTTP connection
- * @param order_id the order id
- * @param session_id session, may be NULL
- * @param instance_id instance, may be "default"
- * @param claim_token claim token for the order, may be NULL
- * @param h_contract contract hash for authentication, may be NULL
- * @return corresponding http(s):// URL, or NULL on missing "host"
- */
-char *
-TMH_make_order_status_url (struct MHD_Connection *con,
- const char *order_id,
- const char *session_id,
- const char *instance_id,
- struct TALER_ClaimTokenP *claim_token,
- struct TALER_PrivateContractHashP *h_contract);
-
-
-/**
* Handle a GET "/orders/$ID" request.
*
* @param rh context of the handler
diff --git a/src/backend/taler-merchant-httpd_get-templates-ID.c b/src/backend/taler-merchant-httpd_get-templates-ID.c
new file mode 100644
index 00000000..add67b4d
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_get-templates-ID.c
@@ -0,0 +1,78 @@
+/*
+ This file is part of TALER
+ (C) 2022-2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_private-get-templates-ID.c
+ * @brief implement GET /templates/$ID
+ * @author Priscilla HUANG
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-templates-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+MHD_RESULT
+TMH_get_templates_ID (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ struct TALER_MERCHANTDB_TemplateDetails tp = { 0 };
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_assert (NULL != mi);
+ qs = TMH_db->lookup_template (TMH_db->cls,
+ mi->settings.id,
+ hc->infix,
+ &tp);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_template");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
+ hc->infix);
+ }
+ {
+ MHD_RESULT ret;
+
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("required_currency",
+ tp.required_currency)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("editable_defaults",
+ tp.editable_defaults)),
+ GNUNET_JSON_pack_object_incref ("template_contract",
+ tp.template_contract));
+ TALER_MERCHANTDB_template_details_free (&tp);
+ return ret;
+ }
+}
+
+
+/* end of taler-merchant-httpd_get-templates-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-tips.h b/src/backend/taler-merchant-httpd_get-templates-ID.h
index d1ce18f0..2fc4c0d8 100644
--- a/src/backend/taler-merchant-httpd_private-get-tips.h
+++ b/src/backend/taler-merchant-httpd_get-templates-ID.h
@@ -1,9 +1,9 @@
/*
This file is part of TALER
- (C) 2020 Taler Systems SA
+ (C) 2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
+ terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
@@ -14,18 +14,18 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file taler-merchant-httpd_private-get-tips.h
- * @brief headers for GET /private/tips handler
- * @author Jonathan Buchanan
+ * @file taler-merchant-httpd_private-get-templates-ID.h
+ * @brief implement GET /templates/$ID/
+ * @author Priscilla HUANG
*/
-#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_TIPS_H
-#define TALER_MERCHANT_HTTPD_PRIVATE_GET_TIPS_H
-#include <microhttpd.h>
+#ifndef TALER_MERCHANT_HTTPD_GET_TEMPLATES_ID_H
+#define TALER_MERCHANT_HTTPD_GET_TEMPLATES_ID_H
+
#include "taler-merchant-httpd.h"
/**
- * Manages a GET /private/tips call.
+ * Handle a GET "/templates/$ID" request.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
@@ -33,9 +33,10 @@
* @return MHD result code
*/
MHD_RESULT
-TMH_private_get_tips (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc);
-
+TMH_get_templates_ID (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+/* end of taler-merchant-httpd_get-templates-ID.h */
#endif
diff --git a/src/backend/taler-merchant-httpd_get-tips-ID.c b/src/backend/taler-merchant-httpd_get-tips-ID.c
deleted file mode 100644
index b20de7e1..00000000
--- a/src/backend/taler-merchant-httpd_get-tips-ID.c
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- This file is part of TALER
- (C) 2014-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-merchant-httpd_get-tips-ID.c
- * @brief implementation of GET /tips/$ID
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <taler/taler_signatures.h>
-#include <taler/taler_json_lib.h>
-#include <taler/taler_templating_lib.h>
-#include "taler-merchant-httpd_get-tips-ID.h"
-#include "taler-merchant-httpd_mhd.h"
-#include "taler-merchant-httpd_qr.h"
-
-
-char *
-TMH_make_taler_tip_uri (struct MHD_Connection *con,
- const struct TALER_TipIdentifierP *tip_id,
- const char *instance_id)
-{
- const char *host;
- const char *forwarded_host;
- const char *uri_path;
- struct GNUNET_Buffer buf = { 0 };
-
- host = MHD_lookup_connection_value (con,
- MHD_HEADER_KIND,
- "Host");
- forwarded_host = MHD_lookup_connection_value (con,
- MHD_HEADER_KIND,
- "X-Forwarded-Host");
-
- uri_path = MHD_lookup_connection_value (con,
- MHD_HEADER_KIND,
- "X-Forwarded-Prefix");
- if (NULL != forwarded_host)
- host = forwarded_host;
-
- if (NULL == host)
- {
- GNUNET_break (0);
- return NULL;
- }
-
- GNUNET_assert (NULL != instance_id);
- GNUNET_assert (NULL != tip_id);
-
- GNUNET_buffer_write_str (&buf,
- "taler");
- if (GNUNET_NO == TALER_mhd_is_https (con))
- GNUNET_buffer_write_str (&buf,
- "+http");
- GNUNET_buffer_write_str (&buf,
- "://tip/");
- GNUNET_buffer_write_str (&buf,
- host);
- if (NULL != uri_path)
- GNUNET_buffer_write_path (&buf,
- uri_path);
- if (0 != strcmp ("default",
- instance_id))
- {
- GNUNET_buffer_write_path (&buf,
- "instances");
- GNUNET_buffer_write_path (&buf,
- instance_id);
- }
- /* Ensure previous part is slash-terminated */
- GNUNET_buffer_write_path (&buf,
- "");
- GNUNET_buffer_write_data_encoded (&buf,
- tip_id,
- sizeof (*tip_id));
- return GNUNET_buffer_reap_str (&buf);
-}
-
-
-char *
-TMH_make_tip_status_url (struct MHD_Connection *con,
- const struct TALER_TipIdentifierP *tip_id,
- const char *instance_id)
-{
- const char *host;
- const char *forwarded_host;
- const char *uri_path;
- struct GNUNET_Buffer buf = { 0 };
-
- host = MHD_lookup_connection_value (con,
- MHD_HEADER_KIND,
- "Host");
- forwarded_host = MHD_lookup_connection_value (con,
- MHD_HEADER_KIND,
- "X-Forwarded-Host");
-
- uri_path = MHD_lookup_connection_value (con,
- MHD_HEADER_KIND,
- "X-Forwarded-Prefix");
- if (NULL != forwarded_host)
- host = forwarded_host;
-
- if (NULL == host)
- {
- GNUNET_break (0);
- return NULL;
- }
-
- GNUNET_assert (NULL != instance_id);
- GNUNET_assert (NULL != tip_id);
-
- if (GNUNET_NO == TALER_mhd_is_https (con))
- GNUNET_buffer_write_str (&buf,
- "http://");
- else
- GNUNET_buffer_write_str (&buf,
- "https://");
- GNUNET_buffer_write_str (&buf,
- host);
- if (NULL != uri_path)
- GNUNET_buffer_write_path (&buf,
- uri_path);
- if (0 != strcmp ("default",
- instance_id))
- {
- GNUNET_buffer_write_path (&buf,
- "instances");
- GNUNET_buffer_write_path (&buf,
- instance_id);
- }
- GNUNET_buffer_write_path (&buf,
- "tips/");
- GNUNET_buffer_write_data_encoded (&buf,
- tip_id,
- sizeof (*tip_id));
- return GNUNET_buffer_reap_str (&buf);
-}
-
-
-MHD_RESULT
-TMH_get_tips_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
-{
- struct TALER_TipIdentifierP tip_id;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_Amount total_authorized;
- struct TALER_Amount total_picked_up;
- struct GNUNET_TIME_Timestamp expiration;
- char *exchange_url;
- struct TALER_ReservePrivateKeyP reserve_priv;
-
- (void) rh;
- if (GNUNET_OK !=
- GNUNET_CRYPTO_hash_from_string (hc->infix,
- &tip_id.hash))
- {
- /* tip_id has wrong encoding */
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- hc->infix);
- }
-
- TMH_db->preflight (TMH_db->cls);
- qs = TMH_db->lookup_tip (TMH_db->cls,
- hc->instance->settings.id,
- &tip_id,
- &total_authorized,
- &total_picked_up,
- &expiration,
- &exchange_url,
- &reserve_priv);
- if (0 > qs)
- {
- /* single, read-only SQL statements should never cause
- serialization problems */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- /* Always report on hard error as well to enable diagnostics */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- NULL);
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Unknown tip id given: `%s'\n",
- hc->infix);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_TIP_ID_UNKNOWN,
- hc->infix);
- }
-
- /* Build response */
- {
- struct TALER_Amount remaining;
- MHD_RESULT ret;
-
- GNUNET_break (0 <=
- TALER_amount_subtract (&remaining,
- &total_authorized,
- &total_picked_up));
- if (TMH_MHD_test_html_desired (connection))
- {
- char *qr;
- char *uri;
- char *tip_status_url;
-
- uri = TMH_make_taler_tip_uri (connection,
- &tip_id,
- hc->instance->settings.id);
- tip_status_url = TMH_make_tip_status_url (connection,
- &tip_id,
- hc->instance->settings.id);
- qr = TMH_create_qrcode (uri);
- if (NULL == qr)
- {
- GNUNET_break (0);
- GNUNET_free (uri);
- ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_ALLOCATION_FAILURE,
- "during QR code generation");
- }
- else
- {
- json_t *context;
-
- context = GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("remaining_tip",
- &remaining),
- GNUNET_JSON_pack_string ("taler_tip_uri",
- uri),
- GNUNET_JSON_pack_string ("taler_tip_qrcode_svg",
- qr));
- ret = TALER_TEMPLATING_reply (connection,
- ( (0 == remaining.value) &&
- (0 == remaining.fraction) )
- ? MHD_HTTP_GONE
- : MHD_HTTP_OK,
- ( (0 == remaining.value) &&
- (0 == remaining.fraction) )
- ? "depleted_tip"
- : "offer_tip",
- hc->instance->settings.id,
- uri,
- context);
- json_decref (context);
- }
- GNUNET_free (tip_status_url);
- GNUNET_free (uri);
- GNUNET_free (qr);
- }
- else
- {
- ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
- TALER_amount_is_zero (&remaining)
- ? MHD_HTTP_GONE
- : MHD_HTTP_OK,
- GNUNET_JSON_pack_string ("exchange_url",
- exchange_url),
- TALER_JSON_pack_amount ("tip_amount",
- &remaining),
- GNUNET_JSON_pack_timestamp ("expiration",
- expiration));
- }
- GNUNET_free (exchange_url);
- return ret;
- }
-}
-
-
-/* end of taler-merchant-httpd_get-tips-ID.c */
diff --git a/src/backend/taler-merchant-httpd_get-tips-ID.h b/src/backend/taler-merchant-httpd_get-tips-ID.h
deleted file mode 100644
index bd50da6f..00000000
--- a/src/backend/taler-merchant-httpd_get-tips-ID.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- This file is part of TALER
- (C) 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-merchant-httpd_get-tips-ID.h
- * @brief implementation of GET /tips/$ID
- * @author Marcello Stanisci
- */
-#ifndef TALER_MERCHANT_HTTPD_GET_TIPS_ID_H
-#define TALER_MERCHANT_HTTPD_GET_TIPS_ID_H
-#include <microhttpd.h>
-#include "taler-merchant-httpd.h"
-
-/**
- * Create a taler://tip/ URI for the given @a con and @a tip_id
- * and @a instance_id.
- *
- * @param con HTTP connection
- * @param tip_id the tip id
- * @param instance_id instance, may be "default"
- * @return corresponding taler://tip/ URI, or NULL on missing "host"
- */
-char *
-TMH_make_taler_tip_uri (struct MHD_Connection *con,
- const struct TALER_TipIdentifierP *tip_id,
- const char *instance_id);
-
-/**
- * Create a http(s):// URL for the given @a con and @a tip_id
- * and @a instance_id.
- *
- * @param con HTTP connection
- * @param tip_id the tip id
- * @param instance_id instance, may be "default"
- * @return corresponding taler://tip/ URI, or NULL on missing "host"
- */
-char *
-TMH_make_tip_status_url (struct MHD_Connection *con,
- const struct TALER_TipIdentifierP *tip_id,
- const char *instance_id);
-
-/**
- * Handle a GET "/tips/$ID" request.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] hc context with further information about the request
- * @return MHD result code
- */
-MHD_RESULT
-TMH_get_tips_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc);
-
-#endif
diff --git a/src/backend/taler-merchant-httpd_helper.c b/src/backend/taler-merchant-httpd_helper.c
index 2cffa20c..8fb5823e 100644
--- a/src/backend/taler-merchant-httpd_helper.c
+++ b/src/backend/taler-merchant-httpd_helper.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014--2021 Taler Systems SA
+ (C) 2014--2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -20,74 +20,183 @@
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_db_lib.h>
#include <taler/taler_util.h>
#include <taler/taler_json_lib.h>
#include "taler-merchant-httpd_helper.h"
#include <taler/taler_templating_lib.h>
+#include <taler/taler_dbevents.h>
+
+
+enum GNUNET_GenericReturnValue
+TMH_cmp_wire_account (
+ const json_t *account,
+ const struct TMH_WireMethod *wm)
+{
+ const char *credit_facade_url = NULL;
+ const json_t *credit_facade_credentials = NULL;
+ const char *uri;
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &uri),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("credit_facade_url",
+ &credit_facade_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("credit_facade_credentials",
+ &credit_facade_credentials),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+ const char *ename;
+ unsigned int eline;
+
+ res = GNUNET_JSON_parse (account,
+ ispec,
+ &ename,
+ &eline);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse account spec: %s (%u)\n",
+ ename,
+ eline);
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ strcmp (wm->payto_uri,
+ uri))
+ {
+ return GNUNET_SYSERR;
+ }
+ if ( (NULL == credit_facade_url) !=
+ (NULL == wm->credit_facade_url) ||
+ (NULL == credit_facade_credentials) !=
+ (NULL == wm->credit_facade_credentials) )
+ {
+ return GNUNET_NO;
+ }
+ if ( (NULL != credit_facade_url) &&
+ (0 != strcmp (credit_facade_url,
+ wm->credit_facade_url)) )
+ {
+ return GNUNET_NO;
+ }
+ if ( (NULL != credit_facade_credentials) &&
+ (0 != json_equal (credit_facade_credentials,
+ wm->credit_facade_credentials)) )
+ {
+ return GNUNET_NO;
+ }
+ return GNUNET_YES;
+}
-/**
- * check @a payto_uris for well-formedness
- *
- * @param payto_uris JSON array of payto URIs (presumably)
- * @return true if they are all valid URIs (and this is an array of strings)
- */
bool
-TMH_payto_uri_array_valid (const json_t *payto_uris)
+TMH_accounts_array_valid (const json_t *accounts)
{
- bool payto_ok = true;
+ size_t len;
- if (! json_is_array (payto_uris))
+ if (! json_is_array (accounts))
{
GNUNET_break_op (0);
- payto_ok = false;
+ return false;
}
- else
+ len = json_array_size (accounts);
+ for (size_t i = 0; i<len; i++)
{
- unsigned int len = json_array_size (payto_uris);
+ json_t *payto_uri = json_array_get (accounts,
+ i);
+ const char *credit_facade_url = NULL;
+ const json_t *credit_facade_credentials = NULL;
+ const char *uri;
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &uri),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("credit_facade_url",
+ &credit_facade_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("credit_facade_credentials",
+ &credit_facade_credentials),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+ const char *ename;
+ unsigned int eline;
+
+ res = GNUNET_JSON_parse (payto_uri,
+ ispec,
+ &ename,
+ &eline);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse account spec: %s (%u)\n",
+ ename,
+ eline);
+ return false;
+ }
- for (unsigned int i = 0; i<len; i++)
+ /* Test for the same payto:// URI being given twice */
+ for (unsigned int j = 0; j<i; j++)
{
- json_t *payto_uri = json_array_get (payto_uris,
- i);
- const char *uri;
-
- if (! json_is_string (payto_uri))
- payto_ok = false;
- uri = json_string_value (payto_uri);
- /* Test for the same payto:// URI being given twice */
- for (unsigned int j = 0; j<i; j++)
+ json_t *old_uri = json_array_get (accounts,
+ j);
+ if (0 == strcmp (uri,
+ json_string_value (
+ json_object_get (old_uri,
+ "payto_uri"))))
{
- json_t *old_uri = json_array_get (payto_uris,
- j);
- if (json_equal (payto_uri,
- old_uri))
- {
- GNUNET_break_op (0);
- payto_ok = false;
- break;
- }
+ GNUNET_break_op (0);
+ return false;
}
- if (! payto_ok)
- break;
+ }
+ {
+ char *err;
+
+ if (NULL !=
+ (err = TALER_payto_validate (uri)))
{
- char *err;
-
- if (NULL !=
- (err = TALER_payto_validate (uri)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Encountered invalid payto://-URI `%s': %s\n",
- uri,
- err);
- GNUNET_free (err);
- payto_ok = false;
- break;
- }
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Encountered invalid payto://-URI `%s': %s\n",
+ uri,
+ err);
+ GNUNET_free (err);
+ return false;
}
}
- }
- return payto_ok;
+ if ( (NULL == credit_facade_url) !=
+ (NULL == credit_facade_credentials) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "If credit_facade_url is given, credit_facade_credentials must also be specified (violated for %s)\n",
+ uri);
+ return false;
+ }
+ if ( (NULL != credit_facade_url) ||
+ (NULL != credit_facade_credentials) )
+ {
+ struct TALER_MERCHANT_BANK_AuthenticationData auth;
+
+ if (GNUNET_OK !=
+ TALER_MERCHANT_BANK_auth_parse_json (credit_facade_credentials,
+ credit_facade_url,
+ &auth))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid credit facade URL or credentials `%s'\n",
+ credit_facade_url);
+ return false;
+ }
+ TALER_MERCHANT_BANK_auth_free (&auth);
+ }
+ } /* end for all accounts */
+ return true;
}
@@ -103,7 +212,7 @@ TMH_location_object_valid (const json_t *location)
const char *street = NULL;
const char *building = NULL;
const char *building_no = NULL;
- json_t *lines = NULL;
+ const json_t *lines = NULL;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("country",
@@ -142,8 +251,8 @@ TMH_location_object_valid (const json_t *location)
&building_no),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("address_lines",
- &lines),
+ GNUNET_JSON_spec_array_const ("address_lines",
+ &lines),
NULL),
GNUNET_JSON_spec_end ()
};
@@ -166,13 +275,6 @@ TMH_location_object_valid (const json_t *location)
size_t idx;
json_t *line;
- if (! json_is_array (lines))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Invalid location for field %s\n",
- "lines");
- return false;
- }
json_array_foreach (lines, idx, line)
{
if (! json_is_string (line))
@@ -204,24 +306,19 @@ TMH_products_array_valid (const json_t *products)
{
const char *product_id = NULL;
const char *description;
- json_t *description_i18n = NULL;
uint64_t quantity = 0;
const char *unit = NULL;
struct TALER_Amount price = { .value = 0 };
const char *image_data_url = NULL;
- json_t *taxes = NULL;
+ const json_t *taxes = NULL;
struct GNUNET_TIME_Timestamp delivery_date = { 0 };
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("product_id",
&product_id),
NULL),
- GNUNET_JSON_spec_string ("description",
- &description),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("description_i18n",
- &description_i18n),
- NULL),
+ TALER_JSON_spec_i18n_str ("description",
+ &description),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_uint64 ("quantity",
&quantity),
@@ -231,17 +328,16 @@ TMH_products_array_valid (const json_t *products)
&unit),
NULL),
GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount ("price",
- TMH_currency,
- &price),
+ TALER_JSON_spec_amount_any ("price",
+ &price),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("image",
&image_data_url),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("taxes",
- &taxes),
+ GNUNET_JSON_spec_array_const ("taxes",
+ &taxes),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("delivery_date",
@@ -276,12 +372,6 @@ TMH_products_array_valid (const json_t *products)
GNUNET_break_op (0);
valid = false;
}
- if ( (NULL != description_i18n) &&
- (! TALER_JSON_check_i18n (description_i18n)) )
- {
- GNUNET_break_op (0);
- valid = false;
- }
GNUNET_JSON_parse_free (spec);
if (! valid)
break;
@@ -323,6 +413,7 @@ bool
TMH_template_contract_valid (const json_t *template_contract)
{
const char *summary;
+ const char *currency;
struct TALER_Amount amount = { .value = 0};
uint32_t minimum_age = 0;
struct GNUNET_TIME_Relative pay_duration = { 0 };
@@ -332,9 +423,12 @@ TMH_template_contract_valid (const json_t *template_contract)
&summary),
NULL),
GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount ("amount",
- TMH_currency,
- &amount),
+ GNUNET_JSON_spec_string ("currency",
+ &currency),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("amount",
+ &amount),
NULL),
GNUNET_JSON_spec_uint32 ("minimum_age",
&minimum_age),
@@ -395,14 +489,17 @@ TMH_taxes_array_valid (const json_t *taxes)
struct TMH_WireMethod *
-TMH_setup_wire_account (const char *payto_uri)
+TMH_setup_wire_account (
+ const char *payto_uri,
+ const char *credit_facade_url,
+ const json_t *credit_facade_credentials)
{
struct TMH_WireMethod *wm;
char *emsg;
if (NULL != (emsg = TALER_payto_validate (payto_uri)))
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Invalid URI `%s': %s\n",
payto_uri,
emsg);
@@ -411,6 +508,12 @@ TMH_setup_wire_account (const char *payto_uri)
}
wm = GNUNET_new (struct TMH_WireMethod);
+ if (NULL != credit_facade_url)
+ wm->credit_facade_url
+ = GNUNET_strdup (credit_facade_url);
+ if (NULL != credit_facade_credentials)
+ wm->credit_facade_credentials
+ = json_incref ((json_t*) credit_facade_credentials);
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
&wm->wire_salt,
sizeof (wm->wire_salt));
@@ -471,19 +574,14 @@ TMH_check_auth_config (struct MHD_Connection *connection,
TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_BAD_AUTH,
- "bad authentication config")) ?
- GNUNET_NO : GNUNET_SYSERR;
+ "bad authentication config"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
return GNUNET_OK;
}
-/**
- * Generate binary UUID from client-provided UUID-string.
- *
- * @param uuids string intpu
- * @param[out] uuid set to binary UUID
- */
void
TMH_uuid_from_string (const char *uuids,
struct GNUNET_Uuid *uuid)
@@ -494,9 +592,9 @@ TMH_uuid_from_string (const char *uuids,
strlen (uuids),
&hc);
GNUNET_static_assert (sizeof (hc) > sizeof (*uuid));
- memcpy (uuid,
- &hc,
- sizeof (*uuid));
+ GNUNET_memcpy (uuid,
+ &hc,
+ sizeof (*uuid));
}
@@ -560,7 +658,8 @@ trigger_webhook_cb (void *cls,
t->rv = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
- GNUNET_assert ('\0' == ((const char *) header)[header_size-1]);
+ /* Note: header is actually header_size+1 bytes long here, see mustach.c::memfile_close() */
+ GNUNET_assert ('\0' == ((const char *) header)[header_size]);
}
if (NULL != body_template)
{
@@ -572,13 +671,14 @@ trigger_webhook_cb (void *cls,
if (0 != ret)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to expand webhook header template for webhook %llu (%d)\n",
+ "Failed to expand webhook body template for webhook %llu (%d)\n",
(unsigned long long) webhook_serial,
ret);
t->rv = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
- GNUNET_assert ('\0' == ((const char *) body)[body_size-1]);
+ /* Note: body is actually body_size+1 bytes long here, see mustach.c::memfile_close() */
+ GNUNET_assert ('\0' == ((const char *) body)[body_size]);
}
t->rv = TMH_db->insert_pending_webhook (TMH_db->cls,
t->instance,
@@ -587,6 +687,19 @@ trigger_webhook_cb (void *cls,
http_method,
header,
body);
+ if (t->rv > 0)
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof(es)),
+ .type = htons (TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING)
+ };
+ const void *extra = NULL;
+ size_t extra_size = 0;
+ TMH_db->event_notify (TMH_db->cls,
+ &es,
+ &extra,
+ extra_size);
+ }
free (header);
free (body);
}
@@ -620,3 +733,360 @@ TMH_trigger_webhook (const char *instance,
return qs;
return t.rv;
}
+
+
+enum GNUNET_GenericReturnValue
+TMH_base_url_by_connection (struct MHD_Connection *connection,
+ const char *instance,
+ struct GNUNET_Buffer *buf)
+{
+ const char *host;
+ const char *forwarded_host;
+ const char *forwarded_port;
+ const char *uri_path;
+
+ memset (buf,
+ 0,
+ sizeof (*buf));
+ if (NULL != TMH_base_url)
+ {
+ GNUNET_buffer_write_str (buf,
+ TMH_base_url);
+ }
+ else
+ {
+ if (GNUNET_YES ==
+ TALER_mhd_is_https (connection))
+ GNUNET_buffer_write_str (buf,
+ "https://");
+ else
+ GNUNET_buffer_write_str (buf,
+ "http://");
+ host = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_HOST);
+ forwarded_host = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Host");
+ if (NULL != forwarded_host)
+ {
+ GNUNET_buffer_write_str (buf,
+ forwarded_host);
+ }
+ else
+ {
+ if (NULL == host)
+ {
+ GNUNET_buffer_clear (buf);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_buffer_write_str (buf,
+ host);
+ }
+ forwarded_port = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Port");
+ if (NULL != forwarded_port)
+ {
+ GNUNET_buffer_write_str (buf,
+ ":");
+ GNUNET_buffer_write_str (buf,
+ forwarded_port);
+ }
+ uri_path = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Prefix");
+ if (NULL != uri_path)
+ GNUNET_buffer_write_path (buf,
+ uri_path);
+ }
+ if (0 != strcmp (instance,
+ "default"))
+ {
+ GNUNET_buffer_write_path (buf,
+ "/instances/");
+ GNUNET_buffer_write_str (buf,
+ instance);
+ }
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TMH_taler_uri_by_connection (struct MHD_Connection *connection,
+ const char *method,
+ const char *instance,
+ struct GNUNET_Buffer *buf)
+{
+ const char *host;
+ const char *forwarded_host;
+ const char *forwarded_port;
+ const char *uri_path;
+
+ memset (buf,
+ 0,
+ sizeof (*buf));
+ host = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ "Host");
+ forwarded_host = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Host");
+ forwarded_port = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Port");
+ uri_path = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Prefix");
+ if (NULL != forwarded_host)
+ host = forwarded_host;
+ if (NULL == host)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_buffer_write_str (buf,
+ "taler");
+ if (GNUNET_NO == TALER_mhd_is_https (connection))
+ GNUNET_buffer_write_str (buf,
+ "+http");
+ GNUNET_buffer_write_str (buf,
+ "://");
+ GNUNET_buffer_write_str (buf,
+ method);
+ GNUNET_buffer_write_str (buf,
+ "/");
+ GNUNET_buffer_write_str (buf,
+ host);
+ if (NULL != forwarded_port)
+ {
+ GNUNET_buffer_write_str (buf,
+ ":");
+ GNUNET_buffer_write_str (buf,
+ forwarded_port);
+ }
+ if (NULL != uri_path)
+ GNUNET_buffer_write_path (buf,
+ uri_path);
+ if (0 != strcmp ("default",
+ instance))
+ {
+ GNUNET_buffer_write_path (buf,
+ "instances");
+ GNUNET_buffer_write_path (buf,
+ instance);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Closure for #add_matching_account().
+ */
+struct ExchangeMatchContext
+{
+ /**
+ * Wire method to match, NULL for all.
+ */
+ const char *wire_method;
+
+ /**
+ * Array of accounts to return.
+ */
+ json_t *accounts;
+};
+
+
+/**
+ * If the given account is feasible, add it to the array
+ * of accounts we return.
+ *
+ * @param cls a `struct PostReserveContext`
+ * @param payto_uri URI of the account
+ * @param conversion_url URL of a conversion service
+ * @param debit_restrictions restrictions for debits from account
+ * @param credit_restrictions restrictions for credits to account
+ * @param master_sig signature affirming the account
+ */
+static void
+add_matching_account (
+ void *cls,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct ExchangeMatchContext *rc = cls;
+ char *method;
+
+ method = TALER_payto_get_method (payto_uri);
+ if ( (NULL == rc->wire_method) ||
+ (0 == strcmp (method,
+ rc->wire_method)) )
+ {
+ json_t *acc;
+
+ acc = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto_uri",
+ payto_uri),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("conversion_url",
+ conversion_url)),
+ GNUNET_JSON_pack_array_incref ("credit_restrictions",
+ (json_t *) credit_restrictions),
+ GNUNET_JSON_pack_array_incref ("debit_restrictions",
+ (json_t *) debit_restrictions)
+ );
+ GNUNET_assert (0 ==
+ json_array_append_new (rc->accounts,
+ acc));
+ }
+ GNUNET_free (method);
+}
+
+
+/**
+ * Return JSON array with all of the exchange accounts
+ * that support the given @a wire_method.
+ *
+ * @param master_pub master public key to match exchange by
+ * @param wire_method NULL for any
+ * @return JSON array with information about all matching accounts
+ */
+json_t *
+TMH_exchange_accounts_by_method (
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const char *wire_method)
+{
+ struct ExchangeMatchContext emc = {
+ .wire_method = wire_method,
+ .accounts = json_array ()
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_assert (NULL != emc.accounts);
+ qs = TMH_db->select_accounts_by_exchange (TMH_db->cls,
+ master_pub,
+ &add_matching_account,
+ &emc);
+ if (qs < 0)
+ {
+ json_decref (emc.accounts);
+ return NULL;
+ }
+ return emc.accounts;
+}
+
+
+char *
+TMH_make_order_status_url (struct MHD_Connection *con,
+ const char *order_id,
+ const char *session_id,
+ const char *instance_id,
+ struct TALER_ClaimTokenP *claim_token,
+ struct TALER_PrivateContractHashP *h_contract)
+{
+ struct GNUNET_Buffer buf;
+ /* Number of query parameters written so far */
+ unsigned int num_qp = 0;
+
+ GNUNET_assert (NULL != instance_id);
+ GNUNET_assert (NULL != order_id);
+ if (GNUNET_OK !=
+ TMH_base_url_by_connection (con,
+ instance_id,
+ &buf))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ GNUNET_buffer_write_path (&buf,
+ "/orders");
+ GNUNET_buffer_write_path (&buf,
+ order_id);
+ if ( (NULL != claim_token) &&
+ (! GNUNET_is_zero (claim_token)) )
+ {
+ /* 'token=' for human readability */
+ GNUNET_buffer_write_str (&buf,
+ "?token=");
+ GNUNET_buffer_write_data_encoded (&buf,
+ (char *) claim_token,
+ sizeof (*claim_token));
+ num_qp++;
+ }
+
+ if (NULL != session_id)
+ {
+ if (num_qp > 0)
+ GNUNET_buffer_write_str (&buf,
+ "&session_id=");
+ else
+ GNUNET_buffer_write_str (&buf,
+ "?session_id=");
+ GNUNET_buffer_write_str (&buf,
+ session_id);
+ num_qp++;
+ }
+
+ if (NULL != h_contract)
+ {
+ if (num_qp > 0)
+ GNUNET_buffer_write_str (&buf,
+ "&h_contract=");
+ else
+ GNUNET_buffer_write_str (&buf,
+ "?h_contract=");
+ GNUNET_buffer_write_data_encoded (&buf,
+ (char *) h_contract,
+ sizeof (*h_contract));
+ }
+
+ return GNUNET_buffer_reap_str (&buf);
+}
+
+
+char *
+TMH_make_taler_pay_uri (struct MHD_Connection *con,
+ const char *order_id,
+ const char *session_id,
+ const char *instance_id,
+ struct TALER_ClaimTokenP *claim_token)
+{
+ struct GNUNET_Buffer buf;
+
+ GNUNET_assert (NULL != instance_id);
+ GNUNET_assert (NULL != order_id);
+ if (GNUNET_OK !=
+ TMH_taler_uri_by_connection (con,
+ "pay",
+ instance_id,
+ &buf))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ GNUNET_buffer_write_path (&buf,
+ order_id);
+ GNUNET_buffer_write_path (&buf,
+ (NULL == session_id)
+ ? ""
+ : session_id);
+ if ( (NULL != claim_token) &&
+ (! GNUNET_is_zero (claim_token)))
+ {
+ /* Just 'c=' because this goes into QR
+ codes, so this is more compact. */
+ GNUNET_buffer_write_str (&buf,
+ "?c=");
+ GNUNET_buffer_write_data_encoded (&buf,
+ (char *) claim_token,
+ sizeof (struct TALER_ClaimTokenP));
+ }
+
+ return GNUNET_buffer_reap_str (&buf);
+}
diff --git a/src/backend/taler-merchant-httpd_helper.h b/src/backend/taler-merchant-httpd_helper.h
index 5cee97cc..6783b9d4 100644
--- a/src/backend/taler-merchant-httpd_helper.h
+++ b/src/backend/taler-merchant-httpd_helper.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2021-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -13,7 +13,6 @@
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-
/**
* @file taler-merchant-httpd_helper.h
* @brief helpers for shared logic
@@ -28,13 +27,13 @@
#include "taler-merchant-httpd.h"
/**
- * check @a payto_uris for well-formedness
+ * check @a accounts for well-formedness
*
- * @param payto_uris JSON array of payto URIs (presumably)
- * @return true if they are all valid URIs (and this is an array of strings)
+ * @param accounts JSON array of merchant accounts (presumably)
+ * @return true if they are all valid accounts
*/
bool
-TMH_payto_uri_array_valid (const json_t *payto_uris);
+TMH_accounts_array_valid (const json_t *accounts);
/**
@@ -103,15 +102,45 @@ TMH_template_contract_valid (const json_t *template_contract);
* Setup new wire method for the given @ payto_uri.
*
* @param payto_uri already validated payto URI
+ * @param credit_facade_url where to download credit information for this account (can be NULL)
+ * @param credit_facade_credentials credentials for the @a credit_facade_url
* @return new wire method object, never fails
*/
struct TMH_WireMethod *
-TMH_setup_wire_account (const char *payto_uri);
+TMH_setup_wire_account (
+ const char *payto_uri,
+ const char *credit_facade_url,
+ const json_t *credit_facade_credentials);
+
/**
- * FIXME: document
+ * Test if JSON spec @a account for a wire method is equal to the given @a wm.
+ *
+ * @param account JSON spec for a merchant account
+ * @param wm known wire method
+ * @return #GNUNET_YES if both specifications are equal
+ * #GNUNET_NO if the specifications are for
+ * the same account but differ in the credit facade
+ * #GNUNET_SYSERR if the specs are for different accounts
+ * or if @a account is malformed
*/
+enum GNUNET_GenericReturnValue
+TMH_cmp_wire_account (
+ const json_t *account,
+ const struct TMH_WireMethod *wm);
+
+/**
+ * Check that the provided authentication configuration
+ * is valid.
+ *
+ * @param connection connection to use for returning errors
+ * @param jauth JSON with authentication data
+ * @param[out] auth_token set to the authentication token
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if an error was returned on @a connection
+ * #GNUNET_SYSERR if we failed to return an error on @a connection
+ */
enum GNUNET_GenericReturnValue
TMH_check_auth_config (struct MHD_Connection *connection,
const json_t *jauth,
@@ -130,6 +159,79 @@ TMH_uuid_from_string (const char *uuids,
/**
+ * Initializes a buffer with
+ * the ``http[s]://$HOST/[$PATH/][instances/$INSTANCE/]``
+ * string using $HOST and $PATH from @a connection.
+ *
+ * @param[in] connection connection to base the construction on
+ * @param instance instance to set, NULL for none
+ * @param[out] buf buffer to initialize
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TMH_base_url_by_connection (struct MHD_Connection *connection,
+ const char *instance,
+ struct GNUNET_Buffer *buf);
+
+
+/**
+ * Initializes a buffer with
+ * the ``taler[+http]://$METHOD/$HOST/[instances/$INSTANCE/]``
+ * string using $HOST from @a connection.
+ *
+ * @param[in] connection connection to base the construction on
+ * @param method taler-URI method to inject
+ * @param instance instance to set, NULL for none
+ * @param[out] buf buffer to initialize
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TMH_taler_uri_by_connection (struct MHD_Connection *connection,
+ const char *method,
+ const char *instance,
+ struct GNUNET_Buffer *buf);
+
+
+/**
+ * Create a taler://pay/ URI for the given @a con and @a order_id
+ * and @a session_id and @a instance_id.
+ *
+ * @param con HTTP connection
+ * @param order_id the order id
+ * @param session_id session, may be NULL
+ * @param instance_id instance, may be "default"
+ * @param claim_token claim token for the order, may be NULL
+ * @return corresponding taler://pay/ URI, or NULL on missing "host"
+ */
+char *
+TMH_make_taler_pay_uri (struct MHD_Connection *con,
+ const char *order_id,
+ const char *session_id,
+ const char *instance_id,
+ struct TALER_ClaimTokenP *claim_token);
+
+/**
+ * Create a http(s) URL for the given @a con and @a order_id
+ * and @a instance_id to display the /orders/{order_id} page.
+ *
+ * @param con HTTP connection
+ * @param order_id the order id
+ * @param session_id session, may be NULL
+ * @param instance_id instance, may be "default"
+ * @param claim_token claim token for the order, may be NULL
+ * @param h_contract contract hash for authentication, may be NULL
+ * @return corresponding http(s):// URL, or NULL on missing "host"
+ */
+char *
+TMH_make_order_status_url (struct MHD_Connection *con,
+ const char *order_id,
+ const char *session_id,
+ const char *instance_id,
+ struct TALER_ClaimTokenP *claim_token,
+ struct TALER_PrivateContractHashP *h_contract);
+
+
+/**
* Put data from an exchange's HTTP response into
* a JSON reply
*
@@ -144,7 +246,6 @@ TMH_uuid_from_string (const char *uuids,
GNUNET_JSON_pack_object_incref ("exchange_reply", (json_t *) (hr)->reply))
-
/**
* TMH_trigger_webhook is a function that need to be use when someone
* pay. Merchant need to have a notification.
@@ -159,5 +260,18 @@ TMH_trigger_webhook (const char *instance,
const json_t *args);
+/**
+ * Return JSON array with all of the exchange accounts
+ * that support the given @a wire_method.
+ *
+ * @param master_pub master public key to match exchange by
+ * @param wire_method NULL for any
+ * @return JSON array with information about all matching accounts
+ */
+json_t *
+TMH_exchange_accounts_by_method (
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const char *wire_method);
+
#endif
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c
index d0fcfbc0..50a793a3 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c
@@ -176,7 +176,7 @@ struct AbortContext
* the exchange used for this transaction; NULL if no operation is
* pending.
*/
- struct TMH_EXCHANGES_FindOperation *fo;
+ struct TMH_EXCHANGES_KeysOperation *fo;
/**
* URL of the exchange used for the last @e fo.
@@ -186,7 +186,7 @@ struct AbortContext
/**
* Number of coins this abort is for. Length of the @e rd array.
*/
- unsigned int coins_cnt;
+ size_t coins_cnt;
/**
* How often have we retried the 'main' transaction?
@@ -198,7 +198,7 @@ struct AbortContext
* @e coins_cnt, decremented on each transaction that
* successfully finished.
*/
- unsigned int pending;
+ size_t pending;
/**
* Number of transactions still pending for the currently selected
@@ -206,7 +206,7 @@ struct AbortContext
* exchange, decremented on each transaction that successfully
* finished. Once it hits zero, we pick the next exchange.
*/
- unsigned int pending_at_ce;
+ size_t pending_at_ce;
/**
* HTTP status code to use for the reply, i.e 200 for "OK".
@@ -247,7 +247,7 @@ abort_refunds (struct AbortContext *ac)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Aborting pending /deposit operations\n");
- for (unsigned int i = 0; i<ac->coins_cnt; i++)
+ for (size_t i = 0; i<ac->coins_cnt; i++)
{
struct RefundDetails *rdi = &ac->rd[i];
@@ -356,7 +356,7 @@ generate_success_response (struct AbortContext *ac)
"could not create JSON array");
return;
}
- for (unsigned int i = 0; i<ac->coins_cnt; i++)
+ for (size_t i = 0; i<ac->coins_cnt; i++)
{
struct RefundDetails *rdi = &ac->rd[i];
json_t *detail;
@@ -424,7 +424,7 @@ abort_context_cleanup (void *cls)
ac->timeout_task = NULL;
}
abort_refunds (ac);
- for (unsigned int i = 0; i<ac->coins_cnt; i++)
+ for (size_t i = 0; i<ac->coins_cnt; i++)
{
struct RefundDetails *rdi = &ac->rd[i];
@@ -438,7 +438,7 @@ abort_context_cleanup (void *cls)
GNUNET_free (ac->rd);
if (NULL != ac->fo)
{
- TMH_EXCHANGES_find_exchange_cancel (ac->fo);
+ TMH_EXCHANGES_keys4exchange_cancel (ac->fo);
ac->fo = NULL;
}
if (NULL != ac->response)
@@ -468,30 +468,23 @@ find_next_exchange (struct AbortContext *ac);
* passed back to the wallet).
*
* @param cls closure
- * @param hr HTTP response data
- * @param sign_key exchange key used to sign @a obj, or NULL
- * @param signature the actual signature, or NULL on error
+ * @param rr response data
*/
static void
refund_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_ExchangePublicKeyP *sign_key,
- const struct TALER_ExchangeSignatureP *signature)
+ const struct TALER_EXCHANGE_RefundResponse *rr)
{
struct RefundDetails *rd = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
struct AbortContext *ac = rd->ac;
- (void) sign_key;
- (void) signature;
rd->rh = NULL;
rd->http_status = hr->http_status;
rd->exchange_reply = json_incref ((json_t*) hr->reply);
if (MHD_HTTP_OK == hr->http_status)
{
- GNUNET_assert (NULL != sign_key);
- GNUNET_assert (NULL != signature);
- rd->exchange_pub = *sign_key;
- rd->exchange_sig = *signature;
+ rd->exchange_pub = rr->details.ok.exchange_pub;
+ rd->exchange_sig = rr->details.ok.exchange_sig;
}
ac->pending_at_ce--;
if (0 == ac->pending_at_ce)
@@ -503,29 +496,20 @@ refund_cb (void *cls,
* Function called with the result of our exchange lookup.
*
* @param cls the `struct AbortContext`
- * @param hr HTTP response details
- * @param payto_uri payto://-URI of the exchange
- * @param exchange_handle NULL if exchange was not found to be acceptable
- * @param wire_fee current applicable fee for dealing with @a exchange_handle,
- * NULL if not available
- * @param exchange_trusted true if this exchange is
- * trusted by config
+ * @param keys keys of the exchange
+ * @param exchange representation of the exchange
*/
static void
process_abort_with_exchange (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *exchange_handle,
- const char *payto_uri,
- const struct TALER_Amount *wire_fee,
- bool exchange_trusted)
+ struct TALER_EXCHANGE_Keys *keys,
+ struct TMH_Exchange *exchange)
{
struct AbortContext *ac = cls;
- (void) payto_uri;
- (void) exchange_trusted;
+ (void) exchange;
ac->fo = NULL;
GNUNET_assert (GNUNET_YES == ac->suspended);
- if (NULL == hr)
+ if (NULL == keys)
{
resume_abort_with_response (
ac,
@@ -535,23 +519,10 @@ process_abort_with_exchange (void *cls,
NULL));
return;
}
- if (NULL == exchange_handle)
- {
- /* The request failed somehow */
- GNUNET_break_op (0);
- resume_abort_with_response (
- ac,
- MHD_HTTP_BAD_GATEWAY,
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
- TMH_pack_exchange_reply (hr)));
- return;
- }
/* Initiate refund operation for all coins of
the current exchange (!) */
GNUNET_assert (0 == ac->pending_at_ce);
- for (unsigned int i = 0; i<ac->coins_cnt; i++)
+ for (size_t i = 0; i<ac->coins_cnt; i++)
{
struct RefundDetails *rdi = &ac->rd[i];
@@ -563,14 +534,17 @@ process_abort_with_exchange (void *cls,
continue;
rdi->processed = true;
ac->pending--;
- rdi->rh = TALER_EXCHANGE_refund (exchange_handle,
- &rdi->amount_with_fee,
- &ac->h_contract_terms,
- &rdi->coin_pub,
- 0, /* rtransaction_id */
- &ac->hc->instance->merchant_priv,
- &refund_cb,
- rdi);
+ rdi->rh = TALER_EXCHANGE_refund (
+ TMH_curl_ctx,
+ ac->current_exchange,
+ keys,
+ &rdi->amount_with_fee,
+ &ac->h_contract_terms,
+ &rdi->coin_pub,
+ 0, /* rtransaction_id */
+ &ac->hc->instance->merchant_priv,
+ &refund_cb,
+ rdi);
if (NULL == rdi->rh)
{
GNUNET_break_op (0);
@@ -605,16 +579,15 @@ begin_transaction (struct AbortContext *ac);
static void
find_next_exchange (struct AbortContext *ac)
{
- for (unsigned int i = 0; i<ac->coins_cnt; i++)
+ for (size_t i = 0; i<ac->coins_cnt; i++)
{
struct RefundDetails *rdi = &ac->rd[i];
if (! rdi->processed)
{
ac->current_exchange = rdi->exchange_url;
- ac->fo = TMH_EXCHANGES_find_exchange (ac->current_exchange,
- NULL,
- GNUNET_NO,
+ ac->fo = TMH_EXCHANGES_keys4exchange (ac->current_exchange,
+ false,
&process_abort_with_exchange,
ac);
if (NULL == ac->fo)
@@ -646,7 +619,6 @@ find_next_exchange (struct AbortContext *ac)
* @param amount_with_fee amount the exchange will deposit for this coin
* @param deposit_fee fee the exchange will charge for this coin
* @param refund_fee fee the exchange will charge for refunding this coin
- * @param wire_fee wire fee the exchange of this coin charges
*/
static void
refund_coins (void *cls,
@@ -654,8 +626,7 @@ refund_coins (void *cls,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
- const struct TALER_Amount *refund_fee,
- const struct TALER_Amount *wire_fee)
+ const struct TALER_Amount *refund_fee)
{
struct AbortContext *ac = cls;
struct GNUNET_TIME_Timestamp now;
@@ -663,9 +634,8 @@ refund_coins (void *cls,
(void) amount_with_fee;
(void) deposit_fee;
(void) refund_fee;
- (void) wire_fee;
now = GNUNET_TIME_timestamp_get ();
- for (unsigned int i = 0; i<ac->coins_cnt; i++)
+ for (size_t i = 0; i<ac->coins_cnt; i++)
{
struct RefundDetails *rdi = &ac->rd[i];
enum GNUNET_DB_QueryStatus qs;
@@ -871,10 +841,10 @@ parse_abort (struct MHD_Connection *connection,
struct TMH_HandlerContext *hc,
struct AbortContext *ac)
{
- json_t *coins;
+ const json_t *coins;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("coins",
- &coins),
+ GNUNET_JSON_spec_array_const ("coins",
+ &coins),
GNUNET_JSON_spec_fixed_auto ("h_contract",
&ac->h_contract_terms),
@@ -893,7 +863,7 @@ parse_abort (struct MHD_Connection *connection,
ac->coins_cnt = json_array_size (coins);
if (0 == ac->coins_cnt)
{
- GNUNET_JSON_parse_free (spec);
+ GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_COINS_ARRAY_EMPTY,
@@ -915,7 +885,7 @@ parse_abort (struct MHD_Connection *connection,
TALER_JSON_spec_amount ("contribution",
TMH_currency,
&rd->amount_with_fee),
- GNUNET_JSON_spec_string ("exchange_url",
+ TALER_JSON_spec_web_url ("exchange_url",
&exchange_url),
GNUNET_JSON_spec_fixed_auto ("coin_pub",
&rd->coin_pub),
@@ -927,7 +897,6 @@ parse_abort (struct MHD_Connection *connection,
ispec);
if (GNUNET_YES != res)
{
- GNUNET_JSON_parse_free (spec);
GNUNET_break_op (0);
return res;
}
@@ -936,7 +905,6 @@ parse_abort (struct MHD_Connection *connection,
rd->ac = ac;
}
}
- GNUNET_JSON_parse_free (spec);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Handling /abort for order `%s' with contract hash `%s'\n",
ac->hc->infix,
@@ -961,7 +929,7 @@ handle_abort_timeout (void *cls)
"Resuming abort with error after timeout\n");
if (NULL != ac->fo)
{
- TMH_EXCHANGES_find_exchange_cancel (ac->fo);
+ TMH_EXCHANGES_keys4exchange_cancel (ac->fo);
ac->fo = NULL;
}
resume_abort_with_error (ac,
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-claim.c b/src/backend/taler-merchant-httpd_post-orders-ID-claim.c
index 91a16814..16b69c53 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-claim.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-claim.c
@@ -62,7 +62,6 @@ claim_order (const char *instance_id,
struct TALER_ClaimTokenP order_ct;
enum GNUNET_DB_QueryStatus qs;
uint64_t order_serial;
- bool paid = false;
if (GNUNET_OK !=
TMH_db->start (TMH_db->cls,
@@ -76,7 +75,6 @@ claim_order (const char *instance_id,
order_id,
contract_terms,
&order_serial,
- &paid,
NULL);
if (0 > qs)
{
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-paid.c b/src/backend/taler-merchant-httpd_post-orders-ID-paid.c
index ee00b973..4eb7280f 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-paid.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-paid.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2021 Taler Systems SA
+ (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -31,6 +31,26 @@
/**
+ * Function called with information about a refund.
+ * Sets the boolean in @a cls to true when called.
+ *
+ * @param cls closure, a `bool *`
+ * @param coin_pub public coin from which the refund comes from
+ * @param refund_amount refund amount which is being taken from @a coin_pub
+ */
+static void
+refund_cb (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *refund_amount)
+{
+ bool *refunded = cls;
+
+ *refunded = true;
+}
+
+
+/**
* Use database to notify other clients about the
* session being captured.
*
@@ -99,7 +119,6 @@ TMH_post_orders_ID_paid (const struct TMH_RequestHandler *rh,
}
}
-
if (GNUNET_OK !=
TALER_merchant_pay_verify (&hct,
&hc->instance->merchant_pub,
@@ -115,14 +134,12 @@ TMH_post_orders_ID_paid (const struct TMH_RequestHandler *rh,
TMH_db->preflight (TMH_db->cls);
{
uint64_t order_serial;
- bool paid;
qs = TMH_db->lookup_contract_terms (TMH_db->cls,
hc->instance->settings.id,
order_id,
&contract_terms,
&order_serial,
- &paid,
NULL);
}
if (0 > qs)
@@ -173,7 +190,6 @@ TMH_post_orders_ID_paid (const struct TMH_RequestHandler *rh,
}
}
-
fulfillment_url
= json_string_value (json_object_get (contract_terms,
"fulfillment_url"));
@@ -204,12 +220,21 @@ TMH_post_orders_ID_paid (const struct TMH_RequestHandler *rh,
session_id,
fulfillment_url);
/* fulfillment_url is part of the contract_terms */
- json_decref (contract_terms);
- return TALER_MHD_reply_static (connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
+ {
+ bool refunded = false;
+
+ qs = TMH_db->lookup_refunds (TMH_db->cls,
+ hc->instance->settings.id,
+ &hct,
+ &refund_cb,
+ &refunded);
+ json_decref (contract_terms);
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_bool ("refunded",
+ refunded));
+ }
}
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
index 957bfdfa..07a6233a 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2022 Taler Systems SA
+ (C) 2014-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -29,7 +29,6 @@
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_exchange_service.h>
-#include "taler-merchant-httpd_auditors.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_post-orders-ID-pay.h"
@@ -60,6 +59,63 @@ struct PayContext;
/**
+ * Different phases of processing the /pay request.
+ */
+enum PayPhase
+{
+ /**
+ * Initial phase where the request is parsed.
+ */
+ PP_INIT = 0,
+
+ /**
+ * Check database state for the given order.
+ */
+ PP_CHECK_CONTRACT,
+
+ /**
+ * Contract has been paid.
+ */
+ PP_CONTRACT_PAID,
+
+ /**
+ * Execute payment transaction.
+ */
+ PP_PAY_TRANSACTION,
+
+ /**
+ * Notify other processes about successful payment.
+ */
+ PP_PAYMENT_NOTIFICATION,
+
+ /**
+ * Create final success response.
+ */
+ PP_SUCCESS_RESPONSE,
+
+ /**
+ * Perform batch deposits with exchange(s).
+ */
+ PP_BATCH_DEPOSITS,
+
+ /**
+ * Return response in payment context.
+ */
+ PP_RETURN_RESPONSE,
+
+ /**
+ * Return #MHD_YES to end processing.
+ */
+ PP_END_YES,
+
+ /**
+ * Return #MHD_NO to end processing.
+ */
+ PP_END_NO
+};
+
+
+/**
* Information kept during a pay request for each coin.
*/
struct DepositConfirmation
@@ -71,12 +127,6 @@ struct DepositConfirmation
struct PayContext *pc;
/**
- * Handle to the deposit operation we are performing for
- * this coin, NULL after the operation is done.
- */
- struct TALER_EXCHANGE_DepositHandle *dh;
-
- /**
* URL of the exchange that issued this coin.
*/
char *exchange_url;
@@ -97,11 +147,6 @@ struct DepositConfirmation
struct TALER_Amount refund_fee;
/**
- * Wire fee charged by the exchange of this coin.
- */
- struct TALER_Amount wire_fee;
-
- /**
* If a minimum age was required (i. e. pc->minimum_age is large enough),
* this is the signature of the minimum age (as a single uint8_t), using the
* private key to the corresponding age group. Might be all zeroes for no
@@ -180,7 +225,7 @@ struct ExchangeGroup
* the exchange used for this transaction; NULL if no operation is
* pending.
*/
- struct TMH_EXCHANGES_FindOperation *fo;
+ struct TMH_EXCHANGES_KeysOperation *fo;
/**
* URL of the exchange that issued this coin. Aliases
@@ -189,6 +234,12 @@ struct ExchangeGroup
const char *exchange_url;
/**
+ * Wire fee that applies to this exchange for the
+ * given payment context's wire method.
+ */
+ struct TALER_Amount wire_fee;
+
+ /**
* true if we already tried a forced /keys download.
*/
bool tried_force_keys;
@@ -234,7 +285,7 @@ struct PayContext
/**
* What wire method (of the @e mi) was selected by the wallet?
- * Set in #parse_pay().
+ * Set in #phase_parse_pay().
*/
struct TMH_WireMethod *wm;
@@ -297,30 +348,11 @@ struct PayContext
* Note that IF the total fee of the exchange is higher, that is
* acceptable to the merchant if the customer is willing to
* pay the difference
- * (i.e. amount - max_fee <= actual-amount - actual-fee).
+ * (i.e. amount - max_fee <= actual_amount - actual_fee).
*/
struct TALER_Amount max_fee;
/**
- * Maximum wire fee the merchant is willing to pay, from @e root.
- * Note that IF the total fee of the exchange is higher, that is
- * acceptable to the merchant if the customer is willing to
- * pay the amorized difference. Wire fees are charged over an
- * aggregate of several translations, hence unlike the deposit
- * fees, they are amortized over several customer's transactions.
- * The contract specifies under @e wire_fee_amortization how many
- * customer's transactions he expects the wire fees to be amortized
- * over on average. Thus, if the wire fees are larger than
- * @e max_wire_fee, each customer is expected to contribute
- * $\frac{actual-wire-fee - max_wire_fee}{wire_fee_amortization}$.
- * The customer's contribution may be further reduced by the
- * difference between @e max_fee and the sum of the deposit fees.
- *
- * Default is that the merchant is unwilling to pay any wire fees.
- */
- struct TALER_Amount max_wire_fee;
-
- /**
* Amount from @e root. This is the amount the merchant expects
* to make, minus @e max_fee.
*/
@@ -368,13 +400,14 @@ struct PayContext
struct GNUNET_TIME_Timestamp pay_deadline;
/**
- * Number of transactions that the wire fees are expected to be
- * amortized over. Never zero, defaults (conservateively) to 1.
- * May be higher if merchants expect many small transactions to
- * be aggregated and thus wire fees to be reasonably amortized
- * due to aggregation.
+ * Set to the POS key, if applicable for this order.
*/
- uint32_t wire_fee_amortization;
+ char *pos_key;
+
+ /**
+ * Algorithm chosen for generating the confirmation code.
+ */
+ enum TALER_MerchantConfirmationAlgorithm pos_alg;
/**
* Minimum age required for this purchase.
@@ -385,7 +418,7 @@ struct PayContext
* Number of coins this payment is made of. Length
* of the @e dc array.
*/
- unsigned int coins_cnt;
+ size_t coins_cnt;
/**
* Number of exchanges involved in the payment. Length
@@ -416,6 +449,11 @@ struct PayContext
unsigned int response_code;
/**
+ * Payment processing phase we are in.
+ */
+ enum PayPhase phase;
+
+ /**
* #GNUNET_NO if the @e connection was not suspended,
* #GNUNET_YES if the @e connection was suspended,
* #GNUNET_SYSERR if @e connection was resumed to as
@@ -423,68 +461,17 @@ struct PayContext
*/
enum GNUNET_GenericReturnValue suspended;
-};
-
-
-/**
- * Active KYC operation with an exchange.
- */
-struct KycContext
-{
- /**
- * Kept in a DLL.
- */
- struct KycContext *next;
-
- /**
- * Kept in a DLL.
- */
- struct KycContext *prev;
-
/**
- * Looking for the exchange.
+ * Set to true if the deposit currency of a coin
+ * does not match the contract currency.
*/
- struct TMH_EXCHANGES_FindOperation *fo;
-
- /**
- * Exchange this is about.
- */
- char *exchange_url;
+ bool deposit_currency_mismatch;
/**
- * Merchant instance this is for.
+ * Set to true if the database contains a (bogus)
+ * refund for a different currency.
*/
- struct TMH_MerchantInstance *mi;
-
- /**
- * Wire method we are checking the status of.
- */
- struct TMH_WireMethod *wm;
-
- /**
- * Handle for the GET /deposits operation.
- */
- struct TALER_EXCHANGE_DepositGetHandle *dg;
-
- /**
- * Contract we are looking up.
- */
- struct TALER_PrivateContractHashP h_contract_terms;
-
- /**
- * Coin we are looking up.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Initial DB timestamp.
- */
- struct GNUNET_TIME_Timestamp kyc_timestamp;
-
- /**
- * Initial KYC status.
- */
- bool kyc_ok;
+ bool refund_currency_mismatch;
};
@@ -499,108 +486,14 @@ static struct PayContext *pc_head;
*/
static struct PayContext *pc_tail;
-/**
- * Head of active KYC context DLL.
- */
-static struct KycContext *kc_head;
-
-/**
- * Tail of active KYC context DLL.
- */
-static struct KycContext *kc_tail;
-
-
-/**
- * Free resources used by @a kc.
- *
- * @param[in] kc object to free
- */
-static void
-destroy_kc (struct KycContext *kc)
-{
- if (NULL != kc->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (kc->fo);
- kc->fo = NULL;
- }
- if (NULL != kc->dg)
- {
- TALER_EXCHANGE_deposits_get_cancel (kc->dg);
- kc->dg = NULL;
- }
- TMH_instance_decref (kc->mi);
- kc->mi = NULL;
- GNUNET_free (kc->exchange_url);
- GNUNET_CONTAINER_DLL_remove (kc_head,
- kc_tail,
- kc);
- GNUNET_free (kc);
-}
-
-
-/**
- * Compute the timeout for a /pay request based on the number of coins
- * involved.
- *
- * @param num_coins number of coins
- * @returns timeout for the /pay request
- */
-static struct GNUNET_TIME_Relative
-get_pay_timeout (unsigned int num_coins)
-{
- struct GNUNET_TIME_Relative t;
-
- /* FIXME: Do some benchmarking to come up with a better timeout.
- * We've increased this value so the wallet integration test passes again
- * on my (Florian) machine.
- */
- t = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
- 15 * (1 + (num_coins / 5)));
-
- return t;
-}
-
-
-/**
- * Abort all pending /deposit operations.
- *
- * @param pc pay context to abort
- */
-static void
-abort_active_deposits (struct PayContext *pc)
-{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Aborting pending /deposit operations\n");
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
- {
- struct DepositConfirmation *dci = &pc->dc[i];
-
- if (NULL != dci->dh)
- {
- TALER_EXCHANGE_deposit_cancel (dci->dh);
- dci->dh = NULL;
- }
- }
-}
-
void
TMH_force_pc_resume ()
{
- struct KycContext *kc;
-
- while (NULL != (kc = kc_head))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Aborting KYC check at %s\n",
- kc->exchange_url);
- destroy_kc (kc);
- }
for (struct PayContext *pc = pc_head;
NULL != pc;
pc = pc->next)
{
- abort_active_deposits (pc);
if (NULL != pc->timeout_task)
{
GNUNET_SCHEDULER_cancel (pc->timeout_task);
@@ -616,6 +509,21 @@ TMH_force_pc_resume ()
/**
+ * Resume payment processing.
+ *
+ * @param[in,out] pc payment process to resume
+ */
+static void
+pay_resume (struct PayContext *pc)
+{
+ GNUNET_assert (GNUNET_YES == pc->suspended);
+ pc->suspended = GNUNET_NO;
+ MHD_resume_connection (pc->connection);
+ TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
+}
+
+
+/**
* Resume the given pay context and send the given response.
* Stores the response in the @a pc and signals MHD to resume
* the connection. Also ensures MHD runs immediately.
@@ -629,21 +537,36 @@ resume_pay_with_response (struct PayContext *pc,
unsigned int response_code,
struct MHD_Response *response)
{
- abort_active_deposits (pc);
pc->response_code = response_code;
pc->response = response;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Resuming /pay handling. HTTP status for our reply is %u.\n",
response_code);
+ for (unsigned int i = 0; i<pc->num_exchanges; i++)
+ {
+ struct ExchangeGroup *eg = pc->egs[i];
+
+ if (NULL != eg->fo)
+ {
+ TMH_EXCHANGES_keys4exchange_cancel (eg->fo);
+ eg->fo = NULL;
+ pc->pending_at_eg--;
+ }
+ if (NULL != eg->bdh)
+ {
+ TALER_EXCHANGE_batch_deposit_cancel (eg->bdh);
+ eg->bdh = NULL;
+ pc->pending_at_eg--;
+ }
+ }
+ GNUNET_assert (0 == pc->pending_at_eg);
if (NULL != pc->timeout_task)
{
GNUNET_SCHEDULER_cancel (pc->timeout_task);
pc->timeout_task = NULL;
}
- GNUNET_assert (GNUNET_YES == pc->suspended);
- pc->suspended = GNUNET_NO;
- MHD_resume_connection (pc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
+ pc->phase = PP_RETURN_RESPONSE;
+ pay_resume (pc);
}
@@ -651,292 +574,148 @@ resume_pay_with_response (struct PayContext *pc,
* Resume payment processing with an error.
*
* @param pc operation to resume
- * @param http_status http status code to return
* @param ec taler error code to return
* @param msg human readable error message
*/
static void
resume_pay_with_error (struct PayContext *pc,
- unsigned int http_status,
enum TALER_ErrorCode ec,
const char *msg)
{
- resume_pay_with_response (pc,
- http_status,
- TALER_MHD_make_error (ec,
- msg));
-}
-
-
-/**
- * Custom cleanup routine for a `struct PayContext`.
- *
- * @param cls the `struct PayContext` to clean up.
- */
-static void
-pay_context_cleanup (void *cls)
-{
- struct PayContext *pc = cls;
-
- if (NULL != pc->timeout_task)
- {
- GNUNET_SCHEDULER_cancel (pc->timeout_task);
- pc->timeout_task = NULL;
- }
- if (NULL != pc->contract_terms)
- {
- json_decref (pc->contract_terms);
- pc->contract_terms = NULL;
- }
- abort_active_deposits (pc);
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
- {
- struct DepositConfirmation *dc = &pc->dc[i];
-
- TALER_denom_sig_free (&dc->cdd.denom_sig);
- GNUNET_free (dc->exchange_url);
- }
- GNUNET_free (pc->dc);
- for (unsigned int i = 0; i<pc->num_exchanges; i++)
- {
- struct ExchangeGroup *eg = pc->egs[i];
-
- if (NULL != eg->fo)
- TMH_EXCHANGES_find_exchange_cancel (eg->fo);
- GNUNET_free (eg);
- }
- GNUNET_free (pc->egs);
- if (NULL != pc->response)
- {
- MHD_destroy_response (pc->response);
- pc->response = NULL;
- }
- GNUNET_free (pc->fulfillment_url);
- GNUNET_free (pc->session_id);
- GNUNET_CONTAINER_DLL_remove (pc_head,
- pc_tail,
- pc);
- GNUNET_free (pc);
+ resume_pay_with_response (
+ pc,
+ TALER_ErrorCode_get_http_status_safe (ec),
+ TALER_MHD_make_error (ec,
+ msg));
}
/**
- * Execute the DB transaction. If required (from
- * soft/serialization errors), the transaction can be
- * restarted here.
- *
- * @param pc payment context to transact
- */
-static void
-execute_pay_transaction (struct PayContext *pc);
-
-
-/**
- * Function called with detailed wire transfer data.
+ * Conclude payment processing for @a pc with the
+ * given @a res MHD status code.
*
- * @param cls a `struct KycContext *`
- * @param dr HTTP response data
+ * @param[in,out] pc payment context for final state transition
+ * @param res MHD return code to end with
*/
static void
-deposit_get_callback (
- void *cls,
- const struct TALER_EXCHANGE_GetDepositResponse *dr)
+pay_end (struct PayContext *pc,
+ MHD_RESULT res)
{
- struct KycContext *kc = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Timestamp now;
-
- kc->dg = NULL;
- now = GNUNET_TIME_timestamp_get ();
- switch (dr->hr.http_status)
- {
- case MHD_HTTP_OK:
- qs = TMH_db->account_kyc_set_status (
- TMH_db->cls,
- kc->mi->settings.id,
- &kc->wm->h_wire,
- kc->exchange_url,
- 0LL,
- NULL, /* no signature */
- NULL, /* no signature */
- now,
- true);
- GNUNET_break (qs > 0);
- break;
- case MHD_HTTP_ACCEPTED:
- qs = TMH_db->account_kyc_set_status (
- TMH_db->cls,
- kc->mi->settings.id,
- &kc->wm->h_wire,
- kc->exchange_url,
- dr->details.accepted.requirement_row,
- NULL, /* no signature */
- NULL, /* no signature */
- now,
- dr->details.accepted.kyc_ok);
- GNUNET_break (qs > 0);
- break;
- default:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "KYC check failed at %s with unexpected status %u\n",
- kc->exchange_url,
- dr->hr.http_status);
- }
- destroy_kc (kc);
+ pc->phase = (MHD_YES == res)
+ ? PP_END_YES
+ : PP_END_NO;
}
/**
- * Function called with the result of our exchange lookup.
+ * Return response stored in @a pc.
*
- * @param cls the `struct KycContext`
- * @param hr HTTP response details
- * @param exchange_handle NULL if exchange was not found to be acceptable
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable fee for dealing with @a exchange_handle,
- * NULL if not available
- * @param exchange_trusted true if this exchange is
- * trusted by config
+ * @param[in,out] pc payment context we are processing
*/
static void
-process_kyc_with_exchange (
- void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *exchange_handle,
- const char *payto_uri,
- const struct TALER_Amount *wire_fee,
- bool exchange_trusted)
+phase_return_response (struct PayContext *pc)
{
- struct KycContext *kc = cls;
-
- kc->fo = NULL;
- if (NULL == exchange_handle)
- {
- destroy_kc (kc);
- return;
- }
- kc->dg = TALER_EXCHANGE_deposits_get (exchange_handle,
- &kc->mi->merchant_priv,
- &kc->wm->h_wire,
- &kc->h_contract_terms,
- &kc->coin_pub,
- &deposit_get_callback,
- kc);
- if (NULL == kc->dg)
+ GNUNET_assert (0 != pc->response_code);
+ /* We are *done* processing the request, just queue the response (!) */
+ if (UINT_MAX == pc->response_code)
{
GNUNET_break (0);
- destroy_kc (kc);
+ pay_end (pc,
+ MHD_NO); /* hard error */
+ return;
}
+ pay_end (pc,
+ MHD_queue_response (pc->connection,
+ pc->response_code,
+ pc->response));
}
/**
- * Function called from ``account_kyc_get_status``
- * with KYC status information for this merchant.
- *
- * @param cls a `struct KycContext *`
- * @param h_wire hash of the wire account
- * @param exchange_kyc_serial serial number for the KYC process at the exchange, 0 if unknown
- * @param payto_uri payto:// URI of the merchant's bank account
- * @param exchange_url base URL of the exchange for which this is a status
- * @param last_check when did we last get an update on our KYC status from the exchange
- * @param kyc_ok true if we satisfied the KYC requirements
- */
-static void
-kyc_cb (
- void *cls,
- const struct TALER_MerchantWireHashP *h_wire,
- uint64_t exchange_kyc_serial,
- const char *payto_uri,
- const char *exchange_url,
- struct GNUNET_TIME_Timestamp last_check,
- bool kyc_ok)
-{
- struct KycContext *kc = cls;
-
- kc->kyc_timestamp = last_check;
- kc->kyc_ok = kyc_ok;
-}
-
-
-/**
- * Check for our KYC status at @a exchange_url for the
- * payment of @a pc. First checks if we already have a
- * positive result from the exchange, and if not checks
- * with the exchange.
+ * Do database transaction for a completed batch deposit.
*
- * @param pc payment context to use as starting point
- * @param eg exchange group of the exchange we are triggering on
+ * @param eg group that completed
+ * @param dr response from the server
+ * @return transaction status
*/
-static void
-check_kyc (struct PayContext *pc,
- const struct ExchangeGroup *eg)
+static enum GNUNET_DB_QueryStatus
+batch_deposit_transaction (const struct ExchangeGroup *eg,
+ const struct TALER_EXCHANGE_BatchDepositResult *dr)
{
+ const struct PayContext *pc = eg->pc;
enum GNUNET_DB_QueryStatus qs;
- struct KycContext *kc;
+ struct TALER_Amount total_without_fees;
+ uint64_t b_dep_serial;
+ uint32_t off = 0;
- kc = GNUNET_new (struct KycContext);
- qs = TMH_db->account_kyc_get_status (TMH_db->cls,
- pc->hc->instance->settings.id,
- &pc->wm->h_wire,
- eg->exchange_url,
- &kyc_cb,
- kc);
- if (qs < 0)
- {
- GNUNET_break (0);
- GNUNET_free (kc);
- return;
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- if (kc->kyc_ok)
- {
- GNUNET_free (kc);
- return; /* we are done */
- }
- if (GNUNET_TIME_relative_cmp (
- GNUNET_TIME_absolute_get_duration (
- kc->kyc_timestamp.abs_time),
- <,
- KYC_RETRY_FREQUENCY))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Not re-checking KYC status at `%s', as we already recently asked\n",
- eg->exchange_url);
- GNUNET_free (kc);
- return;
- }
- }
- kc->mi = pc->hc->instance;
- kc->mi->rc++;
- kc->wm = pc->wm;
- kc->exchange_url = GNUNET_strdup (eg->exchange_url);
- kc->h_contract_terms = pc->h_contract_terms;
- /* find one of the coins of the batch */
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pc->amount.currency,
+ &total_without_fees));
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
+ struct TALER_Amount amount_without_fees;
+ /* might want to group deposits by batch more explicitly ... */
if (0 != strcmp (eg->exchange_url,
- pc->dc[i].exchange_url))
+ dc->exchange_url))
continue;
- kc->coin_pub = dc->cdd.coin_pub;
- break;
- }
- GNUNET_CONTAINER_DLL_insert (kc_head,
- kc_tail,
- kc);
- kc->fo = TMH_EXCHANGES_find_exchange (kc->exchange_url,
- NULL,
- GNUNET_NO,
- &process_kyc_with_exchange,
- kc);
- if (NULL == kc->fo)
+ if (dc->found_in_db)
+ continue;
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&amount_without_fees,
+ &dc->cdd.amount,
+ &dc->deposit_fee));
+ GNUNET_assert (0 <=
+ TALER_amount_add (&total_without_fees,
+ &total_without_fees,
+ &amount_without_fees));
+ }
+ qs = TMH_db->insert_deposit_confirmation (
+ TMH_db->cls,
+ pc->hc->instance->settings.id,
+ dr->details.ok.deposit_timestamp,
+ &pc->h_contract_terms,
+ eg->exchange_url,
+ pc->wire_transfer_deadline,
+ &total_without_fees,
+ &eg->wire_fee,
+ &pc->wm->h_wire,
+ dr->details.ok.exchange_sig,
+ dr->details.ok.exchange_pub,
+ &b_dep_serial);
+ if (qs <= 0)
+ return qs; /* Entire batch already known or failure, we're done */
+
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
- GNUNET_break (0);
- destroy_kc (kc);
+ struct DepositConfirmation *dc = &pc->dc[i];
+
+ /* might want to group deposits by batch more explicitly ... */
+ if (0 != strcmp (eg->exchange_url,
+ dc->exchange_url))
+ continue;
+ if (dc->found_in_db)
+ continue;
+ /* NOTE: We might want to check if the order was fully paid concurrently
+ by some other wallet here, and if so, issue an auto-refund. Right now,
+ it is possible to over-pay if two wallets literally make a concurrent
+ payment, as the earlier check for 'paid' is not in the same transaction
+ scope as this 'insert' operation. */
+ qs = TMH_db->insert_deposit (
+ TMH_db->cls,
+ off++, /* might want to group deposits by batch more explicitly ... */
+ b_dep_serial,
+ &dc->cdd.coin_pub,
+ &dc->cdd.coin_sig,
+ &dc->cdd.amount,
+ &dc->deposit_fee,
+ &dc->refund_fee);
+ if (qs < 0)
+ return qs;
+ GNUNET_break (qs > 0);
}
+ return qs;
}
@@ -963,8 +742,6 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg,
pc->hc->instance->settings.id);
for (unsigned int r = 0; r<MAX_RETRIES; r++)
{
- unsigned int j = 0;
-
TMH_db->preflight (TMH_db->cls);
if (GNUNET_OK !=
TMH_db->start (TMH_db->cls,
@@ -979,51 +756,19 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg,
TMH_pack_exchange_reply (&dr->hr)));
return;
}
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ qs = batch_deposit_transaction (eg,
+ dr);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
- struct DepositConfirmation *dc = &pc->dc[i];
-
- if (0 != strcmp (eg->exchange_url,
- pc->dc[i].exchange_url))
- continue;
- if (dc->found_in_db)
- continue;
- /* NOTE: We might want to check if the order was fully paid concurrently
- by some other wallet here, and if so, issue an auto-refund. Right now,
- it is possible to over-pay if two wallets literally make a concurrent
- payment, as the earlier check for 'paid' is not in the same transaction
- scope as this 'insert' operation. */
- GNUNET_assert (j < dr->details.success.num_signatures);
- qs = TMH_db->insert_deposit (
- TMH_db->cls,
- pc->hc->instance->settings.id,
- dr->details.success.deposit_timestamp,
- &pc->h_contract_terms,
- &dc->cdd.coin_pub,
- dc->exchange_url,
- &dc->cdd.amount,
- &dc->deposit_fee,
- &dc->refund_fee,
- &dc->wire_fee,
- &pc->wm->h_wire,
- &dr->details.success.exchange_sigs[j++],
- dr->details.success.exchange_pub);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- TMH_db->rollback (TMH_db->cls);
- break;
- }
- if (0 > qs)
- {
- /* Always report on hard error as well to enable diagnostics */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- /* Forward error including 'proof' for the body */
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert_deposit");
- return;
- }
+ TMH_db->rollback (TMH_db->cls);
+ continue;
+ }
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ GNUNET_break (0);
+ resume_pay_with_error (pc,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "batch_deposit_transaction");
}
qs = TMH_db->commit (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@@ -1035,23 +780,21 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg,
{
GNUNET_break (0);
resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_COMMIT_FAILED,
"insert_deposit");
}
break; /* DB transaction succeeded */
- } /* FOR DB retries */
+ }
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_SOFT_FAILURE,
"insert_deposit");
return;
}
/* Transaction is done, mark affected coins as complete as well. */
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
@@ -1063,8 +806,6 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg,
dc->found_in_db = true; /* well, at least NOW it'd be true ;-) */
pc->pending--;
}
- check_kyc (pc,
- eg);
}
@@ -1083,18 +824,21 @@ batch_deposit_cb (
struct PayContext *pc = eg->pc;
eg->bdh = NULL;
+ pc->pending_at_eg--;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Batch deposit completed with status %u\n",
dr->hr.http_status);
GNUNET_assert (GNUNET_YES == pc->suspended);
- pc->pending_at_eg--;
switch (dr->hr.http_status)
{
case MHD_HTTP_OK:
handle_batch_deposit_ok (eg,
dr);
if (0 == pc->pending_at_eg)
- execute_pay_transaction (eg->pc);
+ {
+ pc->phase = PP_PAY_TRANSACTION;
+ pay_resume (pc);
+ }
return;
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@@ -1137,8 +881,8 @@ batch_deposit_cb (
TALER_JSON_pack_ec (
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS),
TMH_pack_exchange_reply (&dr->hr),
- GNUNET_JSON_pack_data_auto ("exchange_url",
- &eg->exchange_url)));
+ GNUNET_JSON_pack_string ("exchange_url",
+ eg->exchange_url)));
return;
}
resume_pay_with_response (
@@ -1148,92 +892,102 @@ batch_deposit_cb (
TALER_JSON_pack_ec (
TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUS),
TMH_pack_exchange_reply (&dr->hr),
- GNUNET_JSON_pack_data_auto ("exchange_url",
- &eg->exchange_url)));
+ GNUNET_JSON_pack_string ("exchange_url",
+ eg->exchange_url)));
return;
} /* end switch */
}
/**
- * Function called with the result of our exchange lookup.
+ * Force re-downloading keys for @a eg.
+ *
+ * @param[in,out] eg group to re-download keys for
+ */
+static void
+force_keys (struct ExchangeGroup *eg);
+
+
+/**
+ * Function called with the result of our exchange keys lookup.
*
* @param cls the `struct ExchangeGroup`
- * @param hr HTTP response details
- * @param exchange_handle NULL if exchange was not found to be acceptable
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable fee for dealing with @a exchange_handle,
- * NULL if not available
- * @param exchange_trusted true if this exchange is
- * trusted by config
+ * @param keys the keys of the exchange
+ * @param exchange representation of the exchange
*/
static void
-process_pay_with_exchange (
+process_pay_with_keys (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *exchange_handle,
- const char *payto_uri,
- const struct TALER_Amount *wire_fee,
- bool exchange_trusted)
+ struct TALER_EXCHANGE_Keys *keys,
+ struct TMH_Exchange *exchange)
{
struct ExchangeGroup *eg = cls;
struct PayContext *pc = eg->pc;
struct TMH_HandlerContext *hc = pc->hc;
- const struct TALER_EXCHANGE_Keys *keys;
unsigned int group_size;
- (void) payto_uri;
eg->fo = NULL;
+ pc->pending_at_eg--;
+ GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Processing payment with exchange %s\n",
- (NULL == exchange_handle)
- ? "<null>"
- : TALER_EXCHANGE_get_base_url (exchange_handle));
+ eg->exchange_url);
GNUNET_assert (GNUNET_YES == pc->suspended);
- if (NULL == hr)
+ if (NULL == keys)
{
- pc->pending_at_eg--;
- resume_pay_with_response (
+ GNUNET_break_op (0);
+ resume_pay_with_error (
pc,
- MHD_HTTP_GATEWAY_TIMEOUT,
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT)));
+ TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
+ NULL);
return;
}
- if ( (MHD_HTTP_OK != hr->http_status) ||
- (NULL == exchange_handle) )
+
+ if (GNUNET_OK !=
+ TMH_exchange_check_debit (exchange,
+ pc->wm))
{
- pc->pending_at_eg--;
- resume_pay_with_response (
- pc,
- MHD_HTTP_BAD_GATEWAY,
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
- TMH_pack_exchange_reply (hr)));
+ if (eg->tried_force_keys)
+ {
+ GNUNET_break_op (0);
+ resume_pay_with_error (
+ pc,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_METHOD_UNSUPPORTED,
+ NULL);
+ return;
+ }
+ force_keys (eg);
return;
}
- keys = TALER_EXCHANGE_get_keys (exchange_handle);
- if (NULL == keys)
+
+ if (GNUNET_OK !=
+ TMH_EXCHANGES_lookup_wire_fee (exchange,
+ pc->wm->wire_method,
+ &eg->wire_fee))
{
- pc->pending_at_eg--;
- GNUNET_break (0); /* should not be possible if HTTP status is #MHD_HTTP_OK */
- resume_pay_with_error (pc,
- MHD_HTTP_BAD_GATEWAY,
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE,
- NULL);
+ if (eg->tried_force_keys)
+ {
+ GNUNET_break_op (0);
+ resume_pay_with_error (
+ pc,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_METHOD_UNSUPPORTED,
+ pc->wm->wire_method);
+ return;
+ }
+ force_keys (eg);
return;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got wire data for %s\n",
+ eg->exchange_url);
/* Initiate /batch-deposit operation for all coins of
the current exchange (!) */
group_size = 0;
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
const struct TALER_EXCHANGE_DenomPublicKey *denom_details;
- unsigned int http_status;
- enum TALER_ErrorCode ec;
bool is_age_restricted_denom = false;
if (0 != strcmp (eg->exchange_url,
@@ -1247,74 +1001,45 @@ process_pay_with_exchange (
&dc->cdd.h_denom_pub);
if (NULL == denom_details)
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Denomination not found, re-fetching /keys\n");
- if (! eg->tried_force_keys)
+ if (eg->tried_force_keys)
{
- /* let's try *forcing* a re-download of /keys from the exchange.
- Maybe the wallet has seen /keys that we missed. */
- eg->tried_force_keys = true;
- eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url,
- pc->wm->wire_method,
- GNUNET_YES,
- &process_pay_with_exchange,
- eg);
- if (NULL != eg->fo)
- return;
+ GNUNET_break_op (0);
+ resume_pay_with_response (
+ pc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_MHD_MAKE_JSON_PACK (
+ TALER_JSON_pack_ec (
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &dc->cdd.h_denom_pub),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_steal (
+ "exchange_keys",
+ TALER_EXCHANGE_keys_to_json (keys)))));
+ return;
}
- /* Forcing failed or we already did it, give up */
- pc->pending_at_eg--;
- resume_pay_with_response (
- pc,
- MHD_HTTP_BAD_REQUEST,
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND),
- GNUNET_JSON_pack_data_auto ("h_denom_pub",
- &dc->cdd.h_denom_pub),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref (
- "exchange_keys",
- (json_t *) TALER_EXCHANGE_get_keys_raw (exchange_handle)))));
+ force_keys (eg);
return;
}
dc->deposit_fee = denom_details->fees.deposit;
dc->refund_fee = denom_details->fees.refund;
- if (GNUNET_OK !=
- TMH_AUDITORS_check_dk (exchange_handle,
- denom_details,
- exchange_trusted,
- &http_status,
- &ec))
+ if (GNUNET_TIME_absolute_is_past (
+ denom_details->expire_deposit.abs_time))
{
- if (! eg->tried_force_keys)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Denomination not audited by trusted auditor, re-fetching /keys\n");
- /* let's try *forcing* a re-download of /keys from the exchange.
- Maybe the wallet has seen auditors that we missed. */
- eg->tried_force_keys = true;
- eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url,
- pc->wm->wire_method,
- GNUNET_YES,
- &process_pay_with_exchange,
- eg);
- if (NULL != eg->fo)
- return;
- }
- pc->pending_at_eg--;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Denomination key offered by client has expired for deposits\n");
resume_pay_with_response (
pc,
- http_status,
+ MHD_HTTP_GONE,
TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (ec),
+ TALER_JSON_pack_ec (
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_DEPOSIT_EXPIRED),
GNUNET_JSON_pack_data_auto ("h_denom_pub",
&denom_details->h_key)));
return;
}
-
/* Now that we have the details about the denomination, we can verify age
* restriction requirements, if applicable. Note that denominations with an
* age_mask equal to zero always pass the age verification. */
@@ -1351,7 +1076,7 @@ process_pay_with_exchange (
AGE_FAIL:
if (0 < code)
{
- pc->pending_at_eg--;
+ GNUNET_break_op (0);
GNUNET_free (dc->age_commitment.keys);
resume_pay_with_response (
pc,
@@ -1369,13 +1094,13 @@ AGE_FAIL:
&dc->cdd.h_age_commitment);
GNUNET_free (dc->age_commitment.keys);
}
- else if (is_age_restricted_denom && dc->no_h_age_commitment)
+ else if (is_age_restricted_denom &&
+ dc->no_h_age_commitment)
{
/* The contract did not ask for a minimum_age but the client paid
* with a coin that has age restriction enabled. We lack the hash
* of the age commitment in this case in order to verify the coin
* and to deposit it with the exchange. */
- pc->pending_at_eg--;
GNUNET_break_op (0);
resume_pay_with_response (
pc,
@@ -1393,12 +1118,15 @@ AGE_FAIL:
if (0 == group_size)
{
GNUNET_break (0);
- pc->pending_at_eg--;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Group size zero, %u batch transactions remain pending\n",
pc->pending_at_eg);
if (0 == pc->pending_at_eg)
- execute_pay_transaction (pc);
+ {
+ pc->phase = PP_PAY_TRANSACTION;
+ pay_resume (pc);
+ return;
+ }
return;
}
@@ -1409,14 +1137,14 @@ AGE_FAIL:
.merchant_payto_uri = pc->wm->payto_uri,
.wire_salt = pc->wm->wire_salt,
.h_contract_terms = pc->h_contract_terms,
- .policy_details = NULL, /* FIXME-oec #7270 */
- .timestamp = pc->timestamp,
+ .wallet_timestamp = pc->timestamp,
.merchant_pub = hc->instance->merchant_pub,
.refund_deadline = pc->refund_deadline
};
enum TALER_ErrorCode ec;
+ size_t off = 0;
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
@@ -1425,25 +1153,27 @@ AGE_FAIL:
if (0 != strcmp (dc->exchange_url,
eg->exchange_url))
continue;
- cdds[i] = dc->cdd;
- dc->wire_fee = *wire_fee;
+ GNUNET_assert (off < group_size);
+ cdds[off++] = dc->cdd;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Initiating batch deposit with %u coins\n",
group_size);
- eg->bdh = TALER_EXCHANGE_batch_deposit (exchange_handle,
- &dcd,
- group_size,
- cdds,
- &batch_deposit_cb,
- eg,
- &ec);
+ eg->bdh = TALER_EXCHANGE_batch_deposit (
+ TMH_curl_ctx,
+ eg->exchange_url,
+ keys,
+ &dcd,
+ group_size,
+ cdds,
+ &batch_deposit_cb,
+ eg,
+ &ec);
if (NULL == eg->bdh)
{
/* Signature was invalid or some other constraint was not satisfied. If
the exchange was unavailable, we'd get that information in the
callback. */
- pc->pending_at_eg--;
GNUNET_break_op (0);
resume_pay_with_response (
pc,
@@ -1454,27 +1184,96 @@ AGE_FAIL:
eg->exchange_url)));
return;
}
+ pc->pending_at_eg++;
if (TMH_force_audit)
TALER_EXCHANGE_batch_deposit_force_dc (eg->bdh);
}
}
+static void
+force_keys (struct ExchangeGroup *eg)
+{
+ struct PayContext *pc = eg->pc;
+
+ eg->tried_force_keys = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Forcing /keys download (once) as wire fees are unknown\n");
+ eg->fo = TMH_EXCHANGES_keys4exchange (
+ eg->exchange_url,
+ true,
+ &process_pay_with_keys,
+ eg);
+ if (NULL == eg->fo)
+ {
+ GNUNET_break (0);
+ resume_pay_with_error (pc,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED,
+ "Failed to lookup exchange by URL");
+ return;
+ }
+ pc->pending_at_eg++;
+}
+
+
+/**
+ * Handle a timeout for the processing of the pay request.
+ *
+ * @param cls our `struct PayContext`
+ */
+static void
+handle_pay_timeout (void *cls)
+{
+ struct PayContext *pc = cls;
+
+ pc->timeout_task = NULL;
+ GNUNET_assert (GNUNET_YES == pc->suspended);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming pay with error after timeout\n");
+ resume_pay_with_error (pc,
+ TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
+ NULL);
+}
+
+
+/**
+ * Compute the timeout for a /pay request based on the number of coins
+ * involved.
+ *
+ * @param num_coins number of coins
+ * @returns timeout for the /pay request
+ */
+static struct GNUNET_TIME_Relative
+get_pay_timeout (unsigned int num_coins)
+{
+ struct GNUNET_TIME_Relative t;
+
+ /* FIXME-Performance-Optimization: Do some benchmarking to come up with a
+ * better timeout. We've increased this value so the wallet integration
+ * test passes again on my (Florian) machine.
+ */
+ t = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
+ 15 * (1 + (num_coins / 5)));
+
+ return t;
+}
+
+
/**
* Start batch deposits for all exchanges involved
* in this payment.
*
- * @param pc payment context we are processing
+ * @param[in,out] pc payment context we are processing
*/
static void
-start_batch_deposits (struct PayContext *pc)
+phase_batch_deposits (struct PayContext *pc)
{
for (unsigned int i = 0; i<pc->num_exchanges; i++)
{
struct ExchangeGroup *eg = pc->egs[i];
bool have_coins = false;
- for (unsigned int j = 0; j<pc->coins_cnt; j++)
+ for (size_t j = 0; j<pc->coins_cnt; j++)
{
struct DepositConfirmation *dc = &pc->dc[j];
@@ -1488,24 +1287,131 @@ start_batch_deposits (struct PayContext *pc)
}
if (! have_coins)
continue; /* no coins left to deposit at this exchange */
- eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url,
- pc->wm->wire_method,
- GNUNET_NO,
- &process_pay_with_exchange,
- eg);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Getting /keys for %s\n",
+ eg->exchange_url);
+ eg->fo = TMH_EXCHANGES_keys4exchange (
+ eg->exchange_url,
+ false,
+ &process_pay_with_keys,
+ eg);
if (NULL == eg->fo)
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED,
- "Failed to lookup exchange by URL");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED,
+ "Failed to lookup exchange by URL"));
return;
}
pc->pending_at_eg++;
}
if (0 == pc->pending_at_eg)
- execute_pay_transaction (pc);
+ {
+ pc->phase = PP_PAY_TRANSACTION;
+ pay_resume (pc);
+ return;
+ }
+ /* Suspend while we interact with the exchange */
+ MHD_suspend_connection (pc->connection);
+ pc->suspended = GNUNET_YES;
+ GNUNET_assert (NULL == pc->timeout_task);
+ pc->timeout_task
+ = GNUNET_SCHEDULER_add_delayed (get_pay_timeout (pc->coins_cnt),
+ &handle_pay_timeout,
+ pc);
+}
+
+
+/**
+ * Generate response (payment successful)
+ *
+ * @param[in,out] pc payment context where the payment was successful
+ */
+static void
+phase_success_response (struct PayContext *pc)
+{
+ struct TALER_MerchantSignatureP sig;
+ char *pos_confirmation;
+
+ /* Sign on our end (as the payment did go through, even if it may
+ have been refunded already) */
+ TALER_merchant_pay_sign (&pc->h_contract_terms,
+ &pc->hc->instance->merchant_priv,
+ &sig);
+ /* Build the response */
+ pos_confirmation = (NULL == pc->pos_key)
+ ? NULL
+ : TALER_build_pos_confirmation (pc->pos_key,
+ pc->pos_alg,
+ &pc->amount,
+ pc->timestamp);
+ pay_end (pc,
+ TALER_MHD_REPLY_JSON_PACK (
+ pc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("pos_confirmation",
+ pos_confirmation)),
+ GNUNET_JSON_pack_data_auto ("sig",
+ &sig)));
+ GNUNET_free (pos_confirmation);
+}
+
+
+/**
+ * Use database to notify other clients about the
+ * payment being completed.
+ *
+ * @param[in,out] pc context to trigger notification for
+ */
+static void
+phase_payment_notification (struct PayContext *pc)
+{
+ {
+ struct TMH_OrderPayEventP pay_eh = {
+ .header.size = htons (sizeof (pay_eh)),
+ .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
+ .merchant_pub = pc->hc->instance->merchant_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Notifying clients about payment of order %s\n",
+ pc->order_id);
+ GNUNET_CRYPTO_hash (pc->order_id,
+ strlen (pc->order_id),
+ &pay_eh.h_order_id);
+ TMH_db->event_notify (TMH_db->cls,
+ &pay_eh.header,
+ NULL,
+ 0);
+ }
+ if ( (NULL != pc->session_id) &&
+ (NULL != pc->fulfillment_url) )
+ {
+ struct TMH_SessionEventP session_eh = {
+ .header.size = htons (sizeof (session_eh)),
+ .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
+ .merchant_pub = pc->hc->instance->merchant_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Notifying clients about session change to %s for %s\n",
+ pc->session_id,
+ pc->fulfillment_url);
+ GNUNET_CRYPTO_hash (pc->session_id,
+ strlen (pc->session_id),
+ &session_eh.h_session_id);
+ GNUNET_CRYPTO_hash (pc->fulfillment_url,
+ strlen (pc->fulfillment_url),
+ &session_eh.h_fulfillment_url);
+ TMH_db->event_notify (TMH_db->cls,
+ &session_eh.header,
+ NULL,
+ 0);
+ }
+ pc->phase = PP_SUCCESS_RESPONSE;
}
@@ -1518,7 +1424,6 @@ start_batch_deposits (struct PayContext *pc)
* @param amount_with_fee amount the exchange will deposit for this coin
* @param deposit_fee fee the exchange will charge for this coin
* @param refund_fee fee the exchange will charge for refunding this coin
- * @param wire_fee wire fee the exchange of this coin charges
*/
static void
check_coin_paid (void *cls,
@@ -1526,12 +1431,11 @@ check_coin_paid (void *cls,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
- const struct TALER_Amount *refund_fee,
- const struct TALER_Amount *wire_fee)
+ const struct TALER_Amount *refund_fee)
{
struct PayContext *pc = cls;
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
@@ -1543,13 +1447,26 @@ check_coin_paid (void *cls,
(0 !=
strcmp (exchange_url,
dc->exchange_url)) ||
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (amount_with_fee,
+ &dc->cdd.amount)) ||
(0 != TALER_amount_cmp (amount_with_fee,
&dc->cdd.amount)) )
continue; /* does not match, skip */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Deposit of coin `%s' already in our DB.\n",
TALER_B2S (coin_pub));
-
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (&pc->total_paid,
+ amount_with_fee)) ||
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&pc->total_fees_paid,
+ deposit_fee)) )
+ {
+ GNUNET_break_op (0);
+ pc->deposit_currency_mismatch = true;
+ break;
+ }
GNUNET_assert (0 <=
TALER_amount_add (&pc->total_paid,
&pc->total_paid,
@@ -1560,7 +1477,6 @@ check_coin_paid (void *cls,
deposit_fee));
dc->deposit_fee = *deposit_fee;
dc->refund_fee = *refund_fee;
- dc->wire_fee = *wire_fee;
dc->cdd.amount = *amount_with_fee;
dc->found_in_db = true;
pc->pending--;
@@ -1593,7 +1509,7 @@ check_coin_refunded (void *cls,
an abort-pay refund (an unusual but possible case), we need
to make sure that existing refunds are accounted for. */
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
@@ -1601,6 +1517,14 @@ check_coin_refunded (void *cls,
if (0 != GNUNET_memcmp (coin_pub,
&dc->cdd.coin_pub))
continue;
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&pc->total_refunded,
+ refund_amount))
+ {
+ GNUNET_break (0);
+ pc->refund_currency_mismatch = true;
+ break;
+ }
GNUNET_assert (0 <=
TALER_amount_add (&pc->total_refunded,
&pc->total_refunded,
@@ -1623,30 +1547,75 @@ check_payment_sufficient (struct PayContext *pc)
struct TALER_Amount acc_fee;
struct TALER_Amount acc_amount;
struct TALER_Amount final_amount;
- struct TALER_Amount wire_fee_delta;
- struct TALER_Amount wire_fee_customer_contribution;
struct TALER_Amount total_wire_fee;
struct TALER_Amount total_needed;
if (0 == pc->coins_cnt)
+ return TALER_amount_is_zero (&pc->amount);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pc->amount.currency,
+ &total_wire_fee));
+ for (unsigned int i = 0; i < pc->num_exchanges; i++)
{
- return ((0 == pc->amount.value) &&
- (0 == pc->amount.fraction));
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&total_wire_fee,
+ &pc->egs[i]->wire_fee))
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ total_wire_fee.currency));
+ return false;
+ }
+ if (0 >
+ TALER_amount_add (&total_wire_fee,
+ &total_wire_fee,
+ &pc->egs[i]->wire_fee))
+ {
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED,
+ "could not add exchange wire fee to total"));
+ return false;
+ }
}
- acc_fee = pc->dc[0].deposit_fee;
- total_wire_fee = pc->dc[0].wire_fee;
- acc_amount = pc->dc[0].cdd.amount;
-
/**
* This loops calculates what are the deposit fee / total
* amount with fee / and wire fee, for all the coins.
*/
- for (unsigned int i = 1; i<pc->coins_cnt; i++)
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pc->amount.currency,
+ &acc_fee));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pc->amount.currency,
+ &acc_amount));
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
GNUNET_assert (dc->found_in_db);
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (&acc_fee,
+ &dc->deposit_fee)) ||
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&acc_amount,
+ &dc->cdd.amount)) )
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ dc->deposit_fee.currency));
+ return false;
+ }
if ( (0 >
TALER_amount_add (&acc_fee,
&dc->deposit_fee,
@@ -1658,10 +1627,12 @@ check_payment_sufficient (struct PayContext *pc)
{
GNUNET_break (0);
/* Overflow in these amounts? Very strange. */
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
- "Overflow adding up amounts");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
+ "Overflow adding up amounts"));
return false;
}
if (1 ==
@@ -1669,53 +1640,15 @@ check_payment_sufficient (struct PayContext *pc)
&dc->cdd.amount))
{
GNUNET_break_op (0);
- resume_pay_with_error (pc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_FEES_EXCEED_PAYMENT,
- "Deposit fees exceed coin's contribution");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_FEES_EXCEED_PAYMENT,
+ "Deposit fees exceed coin's contribution"));
return false;
}
-
- /* If exchange differs, add wire fee */
- {
- bool new_exchange = true;
-
- for (unsigned int j = 0; j<i; j++)
- if (0 == strcasecmp (dc->exchange_url,
- pc->dc[j].exchange_url))
- {
- new_exchange = false;
- break;
- }
-
- if (! new_exchange)
- continue;
-
- if (GNUNET_OK !=
- TALER_amount_cmp_currency (&total_wire_fee,
- &dc->wire_fee))
- {
- GNUNET_break_op (0);
- resume_pay_with_error (pc,
- MHD_HTTP_CONFLICT,
- TALER_EC_GENERIC_CURRENCY_MISMATCH,
- total_wire_fee.currency);
- return false;
- }
- if (0 >
- TALER_amount_add (&total_wire_fee,
- &total_wire_fee,
- &dc->wire_fee))
- {
- GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED,
- "could not add exchange wire fee to total");
- return false;
- }
- }
- } /* deposit loop */
+ } /* end deposit loop */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Amount received from wallet: %s\n",
@@ -1727,9 +1660,6 @@ check_payment_sufficient (struct PayContext *pc)
"Total wire fee: %s\n",
TALER_amount2s (&total_wire_fee));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Max wire fee: %s\n",
- TALER_amount2s (&pc->max_wire_fee));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Deposit fee limit for merchant: %s\n",
TALER_amount2s (&pc->max_fee));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -1737,53 +1667,34 @@ check_payment_sufficient (struct PayContext *pc)
TALER_amount2s (&pc->total_refunded));
/* Now compare exchange wire fee compared to
- * what we are willing to pay */
+ * what we are willing to pay */
if (GNUNET_YES !=
TALER_amount_cmp_currency (&total_wire_fee,
- &pc->max_wire_fee))
+ &acc_fee))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_CONFLICT,
- TALER_EC_GENERIC_CURRENCY_MISMATCH,
- total_wire_fee.currency);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ total_wire_fee.currency));
return false;
}
- switch (TALER_amount_subtract (&wire_fee_delta,
- &total_wire_fee,
- &pc->max_wire_fee))
- {
- case TALER_AAR_RESULT_POSITIVE:
- /* Actual wire fee is indeed higher than our maximum,
- compute how much the customer is expected to cover! */
- TALER_amount_divide (&wire_fee_customer_contribution,
- &wire_fee_delta,
- pc->wire_fee_amortization);
- break;
- case TALER_AAR_RESULT_ZERO:
- case TALER_AAR_INVALID_NEGATIVE_RESULT:
- /* Wire fee threshold is still above the wire fee amount.
- Customer is not going to contribute on this. */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (total_wire_fee.currency,
- &wire_fee_customer_contribution));
- break;
- default:
- GNUNET_assert (0);
- }
-
- /* add wire fee contribution to the total fees */
+ /* add wire fee to the total fees */
if (0 >
TALER_amount_add (&acc_fee,
&acc_fee,
- &wire_fee_customer_contribution))
+ &total_wire_fee))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
- "Overflow adding up amounts");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
+ "Overflow adding up amounts"));
return false;
}
if (-1 == TALER_amount_cmp (&pc->max_fee,
@@ -1808,10 +1719,12 @@ check_payment_sufficient (struct PayContext *pc)
&pc->amount))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
- "Overflow adding up amounts");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
+ "Overflow adding up amounts"));
return false;
}
}
@@ -1832,10 +1745,12 @@ check_payment_sufficient (struct PayContext *pc)
&pc->total_refunded))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDS_EXCEED_PAYMENTS,
- "refunded amount exceeds total payments");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDS_EXCEED_PAYMENTS,
+ "refunded amount exceeds total payments"));
return false;
}
@@ -1847,28 +1762,33 @@ check_payment_sufficient (struct PayContext *pc)
&total_needed))
{
GNUNET_break_op (0);
- resume_pay_with_error (pc,
- MHD_HTTP_PAYMENT_REQUIRED,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDED,
- "contract not paid up due to refunds");
- }
- else if (-1 < TALER_amount_cmp (&acc_amount,
- &pc->amount))
- {
- GNUNET_break_op (0);
- resume_pay_with_error (pc,
- MHD_HTTP_NOT_ACCEPTABLE,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_DUE_TO_FEES,
- "contract not paid up due to fees (client may have calculated them badly)");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_PAYMENT_REQUIRED,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDED,
+ "contract not paid up due to refunds"));
+ return false;
}
- else
+ if (-1 < TALER_amount_cmp (&acc_amount,
+ &pc->amount))
{
GNUNET_break_op (0);
- resume_pay_with_error (pc,
- MHD_HTTP_NOT_ACCEPTABLE,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_PAYMENT_INSUFFICIENT,
- "payment insufficient");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_DUE_TO_FEES,
+ "contract not paid up due to fees (client may have calculated them badly)"));
+ return false;
}
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_PAYMENT_INSUFFICIENT,
+ "payment insufficient"));
return false;
}
return true;
@@ -1876,86 +1796,14 @@ check_payment_sufficient (struct PayContext *pc)
/**
- * Use database to notify other clients about the
- * payment being completed.
- *
- * @param pc context to trigger notification for
- */
-static void
-trigger_payment_notification (struct PayContext *pc)
-{
- {
- struct TMH_OrderPayEventP pay_eh = {
- .header.size = htons (sizeof (pay_eh)),
- .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
- .merchant_pub = pc->hc->instance->merchant_pub
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Notifying clients about payment of order %s\n",
- pc->order_id);
- GNUNET_CRYPTO_hash (pc->order_id,
- strlen (pc->order_id),
- &pay_eh.h_order_id);
- TMH_db->event_notify (TMH_db->cls,
- &pay_eh.header,
- NULL,
- 0);
- }
- if ( (NULL != pc->session_id) &&
- (NULL != pc->fulfillment_url) )
- {
- struct TMH_SessionEventP session_eh = {
- .header.size = htons (sizeof (session_eh)),
- .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
- .merchant_pub = pc->hc->instance->merchant_pub
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Notifying clients about session change to %s for %s\n",
- pc->session_id,
- pc->fulfillment_url);
- GNUNET_CRYPTO_hash (pc->session_id,
- strlen (pc->session_id),
- &session_eh.h_session_id);
- GNUNET_CRYPTO_hash (pc->fulfillment_url,
- strlen (pc->fulfillment_url),
- &session_eh.h_fulfillment_url);
- TMH_db->event_notify (TMH_db->cls,
- &session_eh.header,
- NULL,
- 0);
- }
-}
-
-
-/**
- * Generate response (payment successful)
+ * Execute the DB transaction. If required (from
+ * soft/serialization errors), the transaction can be
+ * restarted here.
*
- * @param[in,out] pc payment context where the payment was successful
+ * @param[in,out] pc payment context to transact
*/
static void
-generate_success_response (struct PayContext *pc)
-{
- struct GNUNET_CRYPTO_EddsaSignature sig;
-
- /* Sign on our end (as the payment did go through, even if it may
- have been refunded already) */
- TALER_merchant_pay_sign (&pc->h_contract_terms,
- &pc->hc->instance->merchant_priv,
- &sig);
- /* Build the response */
- resume_pay_with_response (
- pc,
- MHD_HTTP_OK,
- TALER_MHD_MAKE_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("sig",
- &sig)));
-}
-
-
-static void
-execute_pay_transaction (struct PayContext *pc)
+phase_execute_pay_transaction (struct PayContext *pc)
{
struct TMH_HandlerContext *hc = pc->hc;
const char *instance_id = hc->instance->settings.id;
@@ -1964,13 +1812,13 @@ execute_pay_transaction (struct PayContext *pc)
if (pc->retry_counter++ > MAX_RETRIES)
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_SOFT_FAILURE,
- NULL);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ NULL));
return;
}
- GNUNET_assert (GNUNET_YES == pc->suspended);
/* Initialize some amount accumulators
(used in check_coin_paid(), check_coin_refunded()
@@ -1984,7 +1832,7 @@ execute_pay_transaction (struct PayContext *pc)
GNUNET_break (GNUNET_OK ==
TALER_amount_set_zero (pc->amount.currency,
&pc->total_refunded));
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
pc->dc[i].found_in_db = false;
pc->pending = pc->coins_cnt;
@@ -1995,10 +1843,11 @@ execute_pay_transaction (struct PayContext *pc)
"run pay"))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- NULL);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ NULL));
return;
}
@@ -2015,21 +1864,28 @@ execute_pay_transaction (struct PayContext *pc)
{
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- execute_pay_transaction (pc);
- return;
- }
+ return; /* do it again */
/* Always report on hard error as well to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup deposits");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup deposits"));
+ return;
+ }
+ if (pc->deposit_currency_mismatch)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ pc->amount.currency));
return;
}
}
-
{
enum GNUNET_DB_QueryStatus qs;
@@ -2043,16 +1899,24 @@ execute_pay_transaction (struct PayContext *pc)
{
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- execute_pay_transaction (pc);
- return;
- }
+ return; /* do it again */
/* Always report on hard error as well to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup refunds");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup refunds"));
+ return;
+ }
+ if (pc->refund_currency_mismatch)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "refund currency in database does not match order currency"));
return;
}
}
@@ -2062,11 +1926,10 @@ execute_pay_transaction (struct PayContext *pc)
{
/* we made no DB changes, so we can just rollback */
TMH_db->rollback (TMH_db->cls);
-
/* Ok, we need to first go to the network to process more coins.
We that interaction in *tiny* transactions (hence the rollback
above). */
- start_batch_deposits (pc);
+ pc->phase = PP_BATCH_DEPOSITS;
return;
}
@@ -2094,15 +1957,13 @@ execute_pay_transaction (struct PayContext *pc)
{
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- execute_pay_transaction (pc);
- return;
- }
+ return; /* do it again */
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "mark contract paid");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "mark contract paid"));
return;
}
}
@@ -2113,23 +1974,30 @@ execute_pay_transaction (struct PayContext *pc)
pc->order_serial);
{
enum GNUNET_DB_QueryStatus qs;
-
+ json_t *jhook;
+
+ jhook = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_incref ("contract_terms",
+ pc->contract_terms),
+ GNUNET_JSON_pack_string ("order_id",
+ pc->order_id)
+ );
+ GNUNET_assert (NULL != jhook);
qs = TMH_trigger_webhook (pc->hc->instance->settings.id,
"pay",
- pc->contract_terms);
+ jhook);
+ json_decref (jhook);
if (qs < 0)
{
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- execute_pay_transaction (pc);
- return;
- }
+ return; /* do it again */
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "failed to trigger webhooks");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "failed to trigger webhooks"));
return;
}
}
@@ -2143,237 +2011,17 @@ execute_pay_transaction (struct PayContext *pc)
/* commit failed */
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- execute_pay_transaction (pc);
- return;
- }
+ return; /* do it again */
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_COMMIT_FAILED,
- NULL);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ NULL));
return;
}
- trigger_payment_notification (pc);
- }
- generate_success_response (pc);
-}
-
-
-/**
- * Try to parse the pay request into the given pay context.
- * Schedules an error response in the connection on failure.
- *
- * @param[in,out] pc context we use to handle the payment
- * @return #GNUNET_OK on success,
- * #GNUNET_NO on failure (response was queued with MHD)
- * #GNUNET_SYSERR on hard error (MHD connection must be dropped)
- */
-static enum GNUNET_GenericReturnValue
-parse_pay (struct PayContext *pc)
-{
- const char *session_id = NULL;
- json_t *coins;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("coins",
- &coins),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("session_id",
- &session_id),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (pc->connection,
- pc->hc->request_body,
- spec);
- if (GNUNET_YES != res)
- {
- GNUNET_break_op (0);
- return res;
- }
- }
-
- /* copy session ID (if set) */
- if (NULL != session_id)
- {
- pc->session_id = GNUNET_strdup (session_id);
- }
- else
- {
- /* use empty string as default if client didn't specify it */
- pc->session_id = GNUNET_strdup ("");
- }
-
- if (! json_is_array (coins))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MISSING,
- "'coins' must be an array"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
-
- pc->coins_cnt = json_array_size (coins);
- if (pc->coins_cnt > MAX_COIN_ALLOWED_COINS)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (
- pc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "'coins' array too long"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
-
- /* note: 1 coin = 1 deposit confirmation expected */
- pc->dc = GNUNET_new_array (pc->coins_cnt,
- struct DepositConfirmation);
-
- /* This loop populates the array 'dc' in 'pc' */
- {
- unsigned int coins_index;
- json_t *coin;
-
- json_array_foreach (coins, coins_index, coin)
- {
- struct DepositConfirmation *dc = &pc->dc[coins_index];
- const char *exchange_url;
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &dc->cdd.coin_sig),
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &dc->cdd.coin_pub),
- TALER_JSON_spec_denom_sig ("ub_sig",
- &dc->cdd.denom_sig),
- GNUNET_JSON_spec_fixed_auto ("h_denom",
- &dc->cdd.h_denom_pub),
- TALER_JSON_spec_amount ("contribution",
- TMH_currency,
- &dc->cdd.amount),
- GNUNET_JSON_spec_string ("exchange_url",
- &exchange_url),
- /* if a minimum age was required, the minimum_age_sig and
- * age_commitment must be provided */
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("minimum_age_sig",
- &dc->minimum_age_sig),
- &dc->no_minimum_age_sig),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_age_commitment ("age_commitment",
- &dc->age_commitment),
- &dc->no_age_commitment),
- /* if minimum age was not required, but coin with age restriction set
- * was used, h_age_commitment must be provided. */
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
- &dc->cdd.h_age_commitment),
- &dc->no_h_age_commitment),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
- bool have_eg = false;
-
- res = TALER_MHD_parse_json_data (pc->connection,
- coin,
- ispec);
- if (GNUNET_YES != res)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return res;
- }
-
- for (unsigned int j = 0; j<coins_index; j++)
- {
- if (0 ==
- GNUNET_memcmp (&dc->cdd.coin_pub,
- &pc->dc[j].cdd.coin_pub))
- {
- GNUNET_break_op (0);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "duplicate coin in list"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- }
-
- dc->exchange_url = GNUNET_strdup (exchange_url);
- dc->index = coins_index;
- dc->pc = pc;
-
- if (0 !=
- strcasecmp (dc->cdd.amount.currency,
- TMH_currency))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_GENERIC_CURRENCY_MISMATCH,
- TMH_currency))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
-
- /* Check the consistency of the (potential) age restriction
- * information. */
- if (dc->no_age_commitment != dc->no_minimum_age_sig)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (
- pc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "inconsistent: 'age_commitment' vs. 'minimum_age_sig'"
- )
- )
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
-
- /* Setup exchange group */
- for (unsigned int i = 0; i<pc->num_exchanges; i++)
- {
- if (0 ==
- strcmp (pc->egs[i]->exchange_url,
- GNUNET_strdup (exchange_url)))
- {
- have_eg = true;
- break;
- }
- }
- if (! have_eg)
- {
- struct ExchangeGroup *eg;
-
- eg = GNUNET_new (struct ExchangeGroup);
- eg->pc = pc;
- eg->exchange_url = dc->exchange_url;
- GNUNET_array_append (pc->egs,
- pc->num_exchanges,
- eg);
- }
- }
}
- GNUNET_JSON_parse_free (spec);
- return GNUNET_OK;
+ pc->phase = PP_PAYMENT_NOTIFICATION;
}
@@ -2401,7 +2049,7 @@ deposit_paid_check (
{
struct PayContext *pc = cls;
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dci = &pc->dc[i];
@@ -2411,6 +2059,9 @@ deposit_paid_check (
(0 ==
strcmp (dci->exchange_url,
exchange_url)) &&
+ (GNUNET_YES ==
+ TALER_amount_cmp_currency (&dci->cdd.amount,
+ amount_with_fee)) &&
(0 ==
TALER_amount_cmp (&dci->cdd.amount,
amount_with_fee)) )
@@ -2427,11 +2078,9 @@ deposit_paid_check (
* the payment is idempotent, or refunds the excess payment.
*
* @param[in,out] pc context we use to handle the payment
- * @return #GNUNET_NO if response was queued with MHD
- * #GNUNET_SYSERR on hard error (MHD connection must be dropped)
*/
-static enum GNUNET_GenericReturnValue
-handle_contract_paid (struct PayContext *pc)
+static void
+phase_contract_paid (struct PayContext *pc)
{
enum GNUNET_DB_QueryStatus qs;
bool unmatched = false;
@@ -2444,15 +2093,14 @@ handle_contract_paid (struct PayContext *pc)
if (qs <= 0)
{
GNUNET_break (0);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_deposits_by_order"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_deposits_by_order"));
+ return;
}
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dci = &pc->dc[i];
@@ -2462,7 +2110,7 @@ handle_contract_paid (struct PayContext *pc)
if (! unmatched)
{
/* Everything fine, idempotent request */
- struct GNUNET_CRYPTO_EddsaSignature sig;
+ struct TALER_MerchantSignatureP sig;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Idempotent pay request for order `%s', signing again\n",
@@ -2470,14 +2118,13 @@ handle_contract_paid (struct PayContext *pc)
TALER_merchant_pay_sign (&pc->h_contract_terms,
&pc->hc->instance->merchant_priv,
&sig);
- return (MHD_YES ==
- TALER_MHD_REPLY_JSON_PACK (
- pc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_data_auto ("sig",
- &sig)))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_REPLY_JSON_PACK (
+ pc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto ("sig",
+ &sig)));
+ return;
}
/* Conflict, double-payment detected! */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -2485,7 +2132,7 @@ handle_contract_paid (struct PayContext *pc)
pc->order_id);
refunds = json_array ();
GNUNET_assert (NULL != refunds);
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dci = &pc->dc[i];
struct TALER_MerchantSignatureP merchant_sig;
@@ -2514,29 +2161,25 @@ handle_contract_paid (struct PayContext *pc)
GNUNET_JSON_pack_uint64 ("rtransaction_id",
0))));
}
- return (MHD_YES ==
- TALER_MHD_REPLY_JSON_PACK (
- pc->connection,
- MHD_HTTP_CONFLICT,
- TALER_MHD_PACK_EC (
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_ALREADY_PAID),
- GNUNET_JSON_pack_array_steal ("refunds",
- refunds)))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_REPLY_JSON_PACK (
+ pc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_MHD_PACK_EC (
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_ALREADY_PAID),
+ GNUNET_JSON_pack_array_steal ("refunds",
+ refunds)));
}
/**
- * Check the database state for the given order. * Schedules an error response in the connection on failure.
+ * Check the database state for the given order.
+ * Schedules an error response in the connection on failure.
*
- * @param pc context we use to handle the payment
- * @return #GNUNET_OK on success,
- * #GNUNET_NO on failure (response was queued with MHD)
- * #GNUNET_SYSERR on hard error (MHD connection must be dropped)
+ * @param[in,out] pc context we use to handle the payment
*/
-static enum GNUNET_GenericReturnValue
-check_contract (struct PayContext *pc)
+static void
+phase_check_contract (struct PayContext *pc)
{
/* obtain contract terms */
enum GNUNET_DB_QueryStatus qs;
@@ -2547,13 +2190,15 @@ check_contract (struct PayContext *pc)
json_decref (pc->contract_terms);
pc->contract_terms = NULL;
}
- qs = TMH_db->lookup_contract_terms (TMH_db->cls,
- pc->hc->instance->settings.id,
- pc->order_id,
- &pc->contract_terms,
- &pc->order_serial,
- &paid,
- NULL);
+ qs = TMH_db->lookup_contract_terms2 (TMH_db->cls,
+ pc->hc->instance->settings.id,
+ pc->order_id,
+ &pc->contract_terms,
+ &pc->order_serial,
+ &paid,
+ NULL,
+ &pc->pos_key,
+ &pc->pos_alg);
if (0 > qs)
{
/* single, read-only SQL statements should never cause
@@ -2561,44 +2206,45 @@ check_contract (struct PayContext *pc)
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
/* Always report on hard error to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "contract terms"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "contract terms"));
+ return;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
- pc->order_id))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
+ pc->order_id));
+ return;
}
/* hash contract (needed later) */
+ json_dumpf (pc->contract_terms,
+ stderr,
+ JSON_INDENT (2));
if (GNUNET_OK !=
TALER_JSON_contract_hash (pc->contract_terms,
&pc->h_contract_terms))
{
GNUNET_break (0);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
- NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+ NULL));
+ return;
}
if (paid)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Order `%s' paid, checking for double-payment\n",
pc->order_id);
- return handle_contract_paid (pc);
+ pc->phase = PP_CONTRACT_PAID;
+ return;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Handling payment for order `%s' with contract hash `%s'\n",
@@ -2611,34 +2257,27 @@ check_contract (struct PayContext *pc)
{
/* invalid contract */
GNUNET_break (0);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_MERCHANT_FIELD_MISSING,
- NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_MERCHANT_FIELD_MISSING,
+ NULL));
+ return;
}
/* Get details from contract and check fundamentals */
{
const char *fulfillment_url = NULL;
struct GNUNET_JSON_Specification espec[] = {
- TALER_JSON_spec_amount ("amount",
- TMH_currency,
- &pc->amount),
+ TALER_JSON_spec_amount_any ("amount",
+ &pc->amount),
GNUNET_JSON_spec_mark_optional (
+ /* This one does not have to be a Web URL */
GNUNET_JSON_spec_string ("fulfillment_url",
&fulfillment_url),
NULL),
- TALER_JSON_spec_amount ("max_fee",
- TMH_currency,
- &pc->max_fee),
- TALER_JSON_spec_amount ("max_wire_fee",
- TMH_currency,
- &pc->max_wire_fee),
- GNUNET_JSON_spec_uint32 ("wire_fee_amortization",
- &pc->wire_fee_amortization),
+ TALER_JSON_spec_amount_any ("max_fee",
+ &pc->max_fee),
GNUNET_JSON_spec_timestamp ("timestamp",
&pc->timestamp),
GNUNET_JSON_spec_timestamp ("refund_deadline",
@@ -2666,7 +2305,42 @@ check_contract (struct PayContext *pc)
if (GNUNET_YES != res)
{
GNUNET_break (0);
- return res;
+ pay_end (pc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
+ }
+ }
+
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&pc->max_fee,
+ &pc->amount))
+ {
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "'max_fee' in database does not match currency of contract price"));
+ return;
+ }
+
+ for (size_t i = 0; i<pc->coins_cnt; i++)
+ {
+ struct DepositConfirmation *dc = &pc->dc[i];
+
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&dc->cdd.amount,
+ &pc->amount))
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ pc->amount.currency));
+ return;
}
}
@@ -2676,21 +2350,22 @@ check_contract (struct PayContext *pc)
{
/* This should already have been checked when creating the order! */
GNUNET_break (0);
- return TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE,
- NULL);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE,
+ NULL));
+ return;
}
if (GNUNET_TIME_absolute_is_past (pc->pay_deadline.abs_time))
{
/* too late */
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_GONE,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED,
- NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_GONE,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED,
+ NULL));
+ return;
}
/* Make sure wire method (still) exists for this instance */
@@ -2704,36 +2379,252 @@ check_contract (struct PayContext *pc)
if (NULL == wm)
{
GNUNET_break (0);
- return TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_HASH_UNKNOWN,
- NULL);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_HASH_UNKNOWN,
+ NULL));
+ return;
}
pc->wm = wm;
}
+ pc->phase = PP_PAY_TRANSACTION;
+}
+
+
+/**
+ * Try to parse the pay request into the given pay context.
+ * Schedules an error response in the connection on failure.
+ *
+ * @param[in,out] pc context we use to handle the payment
+ */
+static void
+phase_parse_pay (struct PayContext *pc)
+{
+ const char *session_id = NULL;
+ const json_t *coins;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("coins",
+ &coins),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("session_id",
+ &session_id),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_assert (PP_INIT == pc->phase);
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (pc->connection,
+ pc->hc->request_body,
+ spec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
+ }
+ }
+
+ /* copy session ID (if set) */
+ if (NULL != session_id)
+ {
+ pc->session_id = GNUNET_strdup (session_id);
+ }
+ else
+ {
+ /* use empty string as default if client didn't specify it */
+ pc->session_id = GNUNET_strdup ("");
+ }
+ pc->coins_cnt = json_array_size (coins);
+ if (pc->coins_cnt > MAX_COIN_ALLOWED_COINS)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "'coins' array too long"));
+ return;
+ }
+
+ /* note: 1 coin = 1 deposit confirmation expected */
+ pc->dc = GNUNET_new_array (pc->coins_cnt,
+ struct DepositConfirmation);
+
+ /* This loop populates the array 'dc' in 'pc' */
+ {
+ unsigned int coins_index;
+ json_t *coin;
+
+ json_array_foreach (coins, coins_index, coin)
+ {
+ struct DepositConfirmation *dc = &pc->dc[coins_index];
+ const char *exchange_url;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &dc->cdd.coin_sig),
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &dc->cdd.coin_pub),
+ TALER_JSON_spec_denom_sig ("ub_sig",
+ &dc->cdd.denom_sig),
+ GNUNET_JSON_spec_fixed_auto ("h_denom",
+ &dc->cdd.h_denom_pub),
+ TALER_JSON_spec_amount_any ("contribution",
+ &dc->cdd.amount),
+ TALER_JSON_spec_web_url ("exchange_url",
+ &exchange_url),
+ /* if a minimum age was required, the minimum_age_sig and
+ * age_commitment must be provided */
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("minimum_age_sig",
+ &dc->minimum_age_sig),
+ &dc->no_minimum_age_sig),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_age_commitment ("age_commitment",
+ &dc->age_commitment),
+ &dc->no_age_commitment),
+ /* if minimum age was not required, but coin with age restriction set
+ * was used, h_age_commitment must be provided. */
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &dc->cdd.h_age_commitment),
+ &dc->no_h_age_commitment),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+ bool have_eg = false;
+
+ res = TALER_MHD_parse_json_data (pc->connection,
+ coin,
+ ispec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
+ }
+ for (unsigned int j = 0; j<coins_index; j++)
+ {
+ if (0 ==
+ GNUNET_memcmp (&dc->cdd.coin_pub,
+ &pc->dc[j].cdd.coin_pub))
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "duplicate coin in list"));
+ return;
+ }
+ }
+
+ dc->exchange_url = GNUNET_strdup (exchange_url);
+ dc->index = coins_index;
+ dc->pc = pc;
- return GNUNET_OK;
+ /* Check the consistency of the (potential) age restriction
+ * information. */
+ if (dc->no_age_commitment != dc->no_minimum_age_sig)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inconsistent: 'age_commitment' vs. 'minimum_age_sig'"
+ ));
+ return;
+ }
+
+ /* Setup exchange group */
+ for (unsigned int i = 0; i<pc->num_exchanges; i++)
+ {
+ if (0 ==
+ strcmp (pc->egs[i]->exchange_url,
+ exchange_url))
+ {
+ have_eg = true;
+ break;
+ }
+ }
+ if (! have_eg)
+ {
+ struct ExchangeGroup *eg;
+
+ eg = GNUNET_new (struct ExchangeGroup);
+ eg->pc = pc;
+ eg->exchange_url = dc->exchange_url;
+ GNUNET_array_append (pc->egs,
+ pc->num_exchanges,
+ eg);
+ }
+ }
+ }
+ pc->phase = PP_CHECK_CONTRACT;
}
/**
- * Handle a timeout for the processing of the pay request.
+ * Custom cleanup routine for a `struct PayContext`.
*
- * @param cls our `struct PayContext`
+ * @param cls the `struct PayContext` to clean up.
*/
static void
-handle_pay_timeout (void *cls)
+pay_context_cleanup (void *cls)
{
struct PayContext *pc = cls;
- pc->timeout_task = NULL;
- GNUNET_assert (GNUNET_YES == pc->suspended);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming pay with error after timeout\n");
- resume_pay_with_error (pc,
- MHD_HTTP_GATEWAY_TIMEOUT,
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
- NULL);
+ if (NULL != pc->timeout_task)
+ {
+ GNUNET_SCHEDULER_cancel (pc->timeout_task);
+ pc->timeout_task = NULL;
+ }
+ if (NULL != pc->contract_terms)
+ {
+ json_decref (pc->contract_terms);
+ pc->contract_terms = NULL;
+ }
+ for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ {
+ struct DepositConfirmation *dc = &pc->dc[i];
+
+ TALER_denom_sig_free (&dc->cdd.denom_sig);
+ GNUNET_free (dc->exchange_url);
+ }
+ GNUNET_free (pc->dc);
+ for (unsigned int i = 0; i<pc->num_exchanges; i++)
+ {
+ struct ExchangeGroup *eg = pc->egs[i];
+
+ if (NULL != eg->fo)
+ TMH_EXCHANGES_keys4exchange_cancel (eg->fo);
+ GNUNET_free (eg);
+ }
+ GNUNET_free (pc->egs);
+ if (NULL != pc->response)
+ {
+ MHD_destroy_response (pc->response);
+ pc->response = NULL;
+ }
+ GNUNET_free (pc->fulfillment_url);
+ GNUNET_free (pc->session_id);
+ GNUNET_CONTAINER_DLL_remove (pc_head,
+ pc_tail,
+ pc);
+ GNUNET_free (pc->pos_key);
+ GNUNET_free (pc);
}
@@ -2743,59 +2634,76 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh,
struct TMH_HandlerContext *hc)
{
struct PayContext *pc = hc->ctx;
- enum GNUNET_GenericReturnValue ret;
GNUNET_assert (NULL != hc->infix);
if (NULL == pc)
{
pc = GNUNET_new (struct PayContext);
- GNUNET_CONTAINER_DLL_insert (pc_head,
- pc_tail,
- pc);
pc->connection = connection;
pc->hc = hc;
pc->order_id = hc->infix;
hc->ctx = pc;
hc->cc = &pay_context_cleanup;
- ret = parse_pay (pc);
- if (GNUNET_OK != ret)
- return (GNUNET_NO == ret)
- ? MHD_YES
- : MHD_NO;
- }
- if (GNUNET_SYSERR == pc->suspended)
- return MHD_NO; /* during shutdown, we don't generate any more replies */
- GNUNET_assert (GNUNET_NO == pc->suspended);
- if (0 != pc->response_code)
- {
- /* We are *done* processing the request, just queue the response (!) */
- if (UINT_MAX == pc->response_code)
+ GNUNET_CONTAINER_DLL_insert (pc_head,
+ pc_tail,
+ pc);
+ }
+ while (1)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Processing /pay in phase %d\n",
+ (int) pc->phase);
+ switch (pc->phase)
{
- GNUNET_break (0);
- return MHD_NO; /* hard error */
+ case PP_INIT:
+ phase_parse_pay (pc);
+ break;
+ case PP_CHECK_CONTRACT:
+ phase_check_contract (pc);
+ break;
+ case PP_CONTRACT_PAID:
+ phase_contract_paid (pc);
+ break;
+ case PP_PAY_TRANSACTION:
+ phase_execute_pay_transaction (pc);
+ break;
+ case PP_BATCH_DEPOSITS:
+ phase_batch_deposits (pc);
+ break;
+ case PP_PAYMENT_NOTIFICATION:
+ phase_payment_notification (pc);
+ break;
+ case PP_SUCCESS_RESPONSE:
+ phase_success_response (pc);
+ break;
+ case PP_RETURN_RESPONSE:
+ phase_return_response (pc);
+ break;
+ case PP_END_YES:
+ return MHD_YES;
+ case PP_END_NO:
+ return MHD_NO;
+ }
+ switch (pc->suspended)
+ {
+ case GNUNET_SYSERR:
+ /* during shutdown, we don't generate any more replies */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Processing /pay ends due to shutdown in phase %d\n",
+ (int) pc->phase);
+ return MHD_NO;
+ case GNUNET_NO:
+ /* continue to next phase */
+ break;
+ case GNUNET_YES:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Processing /pay suspended in phase %d\n",
+ (int) pc->phase);
+ return MHD_YES;
}
- return MHD_queue_response (connection,
- pc->response_code,
- pc->response);
}
- ret = check_contract (pc);
- if (GNUNET_OK != ret)
- return (GNUNET_NO == ret)
- ? MHD_YES
- : MHD_NO;
-
- /* Payment not finished, suspend while we interact with the exchange */
- MHD_suspend_connection (connection);
- pc->suspended = GNUNET_YES;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Suspending pay handling while working with the exchange\n");
- GNUNET_assert (NULL == pc->timeout_task);
- pc->timeout_task
- = GNUNET_SCHEDULER_add_delayed (get_pay_timeout (pc->coins_cnt),
- &handle_pay_timeout,
- pc);
- GNUNET_assert (NULL != pc->wm);
- execute_pay_transaction (pc);
+ /* impossible to get here */
+ GNUNET_assert (0);
return MHD_YES;
}
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-refund.c b/src/backend/taler-merchant-httpd_post-orders-ID-refund.c
index 40c89712..e5595296 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-refund.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-refund.c
@@ -28,7 +28,6 @@
#include <taler/taler_json_lib.h>
#include <taler/taler_exchange_service.h>
#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_auditors.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_post-orders-ID-refund.h"
@@ -52,7 +51,7 @@ struct CoinRefund
/**
* Request to connect to the target exchange.
*/
- struct TMH_EXCHANGES_FindOperation *fo;
+ struct TMH_EXCHANGES_KeysOperation *fo;
/**
* Handle for the refund operation with the exchange.
@@ -279,7 +278,7 @@ refund_cleanup (void *ctx)
GNUNET_free (cr->exchange_url);
if (NULL != cr->fo)
{
- TMH_EXCHANGES_find_exchange_cancel (cr->fo);
+ TMH_EXCHANGES_keys4exchange_cancel (cr->fo);
cr->fo = NULL;
}
if (NULL != cr->rh)
@@ -386,17 +385,14 @@ notify_refund_obtained (struct PostRefundData *prd)
* refund request to an exchange.
*
* @param cls a `struct CoinRefund`
- * @param hr HTTP response data
- * @param exchange_pub exchange key used to sign refund confirmation
- * @param exchange_sig exchange's signature over refund
+ * @param rr response data
*/
static void
refund_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const struct TALER_ExchangeSignatureP *exchange_sig)
+ const struct TALER_EXCHANGE_RefundResponse *rr)
{
struct CoinRefund *cr = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
cr->rh = NULL;
cr->exchange_status = hr->http_status;
@@ -413,12 +409,12 @@ refund_cb (void *cls,
{
enum GNUNET_DB_QueryStatus qs;
- cr->exchange_pub = *exchange_pub;
- cr->exchange_sig = *exchange_sig;
+ cr->exchange_pub = rr->details.ok.exchange_pub;
+ cr->exchange_sig = rr->details.ok.exchange_sig;
qs = TMH_db->insert_refund_proof (TMH_db->cls,
cr->refund_serial,
- exchange_sig,
- exchange_pub);
+ &rr->details.ok.exchange_sig,
+ &rr->details.ok.exchange_pub);
if (0 >= qs)
{
/* generally, this is relatively harmless for the merchant, but let's at
@@ -437,53 +433,42 @@ refund_cb (void *cls,
/**
- * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * Function called with the result of a
+ * #TMH_EXCHANGES_keys4exchange()
* operation.
*
* @param cls a `struct CoinRefund *`
- * @param hr HTTP response details
- * @param eh handle to the exchange context
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
- * @param exchange_trusted true if this exchange is trusted by config
+ * @param keys keys of exchange, NULL on error
+ * @param exchange representation of the exchange
*/
static void
exchange_found_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *eh,
- const char *payto_uri,
- const struct TALER_Amount *wire_fee,
- bool exchange_trusted)
+ struct TALER_EXCHANGE_Keys *keys,
+ struct TMH_Exchange *exchange)
{
struct CoinRefund *cr = cls;
struct PostRefundData *prd = cr->prd;
- (void) payto_uri;
- (void) wire_fee;
- (void) exchange_trusted;
+ (void) exchange;
cr->fo = NULL;
- if (NULL == hr)
+ if (NULL == keys)
{
prd->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
prd->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT;
check_resume_prd (prd);
return;
}
- if (NULL == eh)
- {
- prd->http_status = MHD_HTTP_BAD_GATEWAY;
- prd->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE;
- check_resume_prd (prd);
- return;
- }
- cr->rh = TALER_EXCHANGE_refund (eh,
- &cr->refund_amount,
- &prd->h_contract_terms,
- &cr->coin_pub,
- cr->rtransaction_id,
- &prd->hc->instance->merchant_priv,
- &refund_cb,
- cr);
+ cr->rh = TALER_EXCHANGE_refund (
+ TMH_curl_ctx,
+ cr->exchange_url,
+ keys,
+ &cr->refund_amount,
+ &prd->h_contract_terms,
+ &cr->coin_pub,
+ cr->rtransaction_id,
+ &prd->hc->instance->merchant_priv,
+ &refund_cb,
+ cr);
}
@@ -596,14 +581,12 @@ TMH_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
{
json_t *contract_terms;
uint64_t order_serial;
- bool paid;
qs = TMH_db->lookup_contract_terms (TMH_db->cls,
hc->instance->settings.id,
hc->infix,
&contract_terms,
&order_serial,
- &paid,
NULL);
if (0 > qs)
{
@@ -667,7 +650,7 @@ TMH_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
{
if (NULL != cr->fo)
{
- TMH_EXCHANGES_find_exchange_cancel (cr->fo);
+ TMH_EXCHANGES_keys4exchange_cancel (cr->fo);
cr->fo = NULL;
}
if (NULL != cr->rh)
@@ -724,9 +707,8 @@ TMH_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
if (NULL == cr->exchange_reply)
{
/* We need to talk to the exchange */
- cr->fo = TMH_EXCHANGES_find_exchange (cr->exchange_url,
- NULL,
- GNUNET_NO,
+ cr->fo = TMH_EXCHANGES_keys4exchange (cr->exchange_url,
+ false,
&exchange_found_cb,
cr);
}
diff --git a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
deleted file mode 100644
index 32d78eca..00000000
--- a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
+++ /dev/null
@@ -1,1001 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-merchant-httpd_post-tips-ID-pickup.c
- * @brief implementation of a POST /tips/ID/pickup handler
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <microhttpd.h>
-#include <jansson.h>
-#include <taler/taler_json_lib.h>
-#include <taler/taler_signatures.h>
-#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_mhd.h"
-#include "taler-merchant-httpd_helper.h"
-#include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_post-tips-ID-pickup.h"
-
-
-/**
- * How often do we retry on serialization errors?
- */
-#define MAX_RETRIES 3
-
-/**
- * How long do we give the exchange operation to complete withdrawing
- * all of the planchets?
- */
-#define EXCHANGE_TIMEOUT GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_SECONDS, 45)
-
-
-/**
- * Active pickup operations.
- */
-struct PickupContext;
-
-
-/**
- * Handle for an individual planchet we are processing for a tip.
- */
-struct PlanchetOperation
-{
- /**
- * Active pickup operation this planchet belongs with.
- */
- struct PickupContext *pc;
-
- /**
- * Kept in a DLL.
- */
- struct PlanchetOperation *prev;
-
- /**
- * Kept in a DLL.
- */
- struct PlanchetOperation *next;
-
- /**
- * Find operation (while active), later NULL.
- */
- struct TMH_EXCHANGES_FindOperation *fo;
-
- /**
- * Withdraw handle (NULL while @e fo is active).
- */
- struct TALER_EXCHANGE_Withdraw2Handle *w2h;
-
- /**
- * Details about the planchet for withdrawing.
- */
- struct TALER_PlanchetDetail pd;
-
- /**
- * Offset of this planchet in the original request.
- */
- unsigned int offset;
-};
-
-
-/**
- * Active pickup operations.
- */
-struct PickupContext
-{
- /**
- * Kept in a DLL.
- */
- struct PickupContext *next;
-
- /**
- * Kept in a DLL.
- */
- struct PickupContext *prev;
-
- /**
- * The connection.
- */
- struct MHD_Connection *connection;
-
- /**
- * Timeout task.
- */
- struct GNUNET_SCHEDULER_Task *tt;
-
- /**
- * Head of DLL of exchange operations on planchets.
- */
- struct PlanchetOperation *po_head;
-
- /**
- * Tail of DLL of exchange operations on planchets.
- */
- struct PlanchetOperation *po_tail;
-
- /**
- * HTTP response to return (set on errors).
- */
- struct MHD_Response *response;
-
- /**
- * Find operation (while active), later NULL.
- */
- struct TMH_EXCHANGES_FindOperation *fo;
-
- /**
- * Which reserve are we draining?
- */
- struct TALER_ReservePrivateKeyP reserve_priv;
-
- /**
- * Which tip is being picked up?
- */
- struct TALER_TipIdentifierP tip_id;
-
- /**
- * What is the ID of the pickup operation? (Basically a
- * hash over the key inputs).
- */
- struct TALER_PickupIdentifierP pickup_id;
-
- /**
- * Array of our planchets.
- */
- struct TALER_PlanchetDetail *planchets;
-
- /**
- * Length of the @e planchets array.
- */
- unsigned int planchets_length;
-
- /**
- * HTTP status to use (set on errors).
- */
- unsigned int http_status;
-
- /**
- * Total amount requested in the pick up operation. Computed by
- * totaling up the amounts of all the @e planchets.
- */
- struct TALER_Amount total_requested;
-
- /**
- * True if @e total_requested has been initialized.
- */
- bool tr_initialized;
-};
-
-
-/**
- * Head of DLL.
- */
-static struct PickupContext *pc_head;
-
-/**
- * Tail of DLL.
- */
-static struct PickupContext *pc_tail;
-
-
-/**
- * Stop all ongoing operations associated with @a pc.
- */
-static void
-stop_operations (struct PickupContext *pc)
-{
- struct PlanchetOperation *po;
-
- if (NULL != pc->tt)
- {
- GNUNET_SCHEDULER_cancel (pc->tt);
- pc->tt = NULL;
- }
- if (NULL != pc->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (pc->fo);
- pc->fo = NULL;
- }
- while (NULL != (po = pc->po_head))
- {
- if (NULL != po->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (po->fo);
- po->fo = NULL;
- }
- if (NULL != po->w2h)
- {
- TALER_EXCHANGE_withdraw2_cancel (po->w2h);
- po->w2h = NULL;
- }
- GNUNET_CONTAINER_DLL_remove (pc->po_head,
- pc->po_tail,
- po);
- GNUNET_free (po);
- }
-}
-
-
-/**
- * Function called to clean up.
- *
- * @param cls a `struct PickupContext *` to clean up
- */
-static void
-pick_context_cleanup (void *cls)
-{
- struct PickupContext *pc = cls;
-
- stop_operations (pc); /* should not be any... */
- for (unsigned int i = 0; i<pc->planchets_length; i++)
- TALER_planchet_detail_free (&pc->planchets[i]);
- GNUNET_array_grow (pc->planchets,
- pc->planchets_length,
- 0);
- GNUNET_free (pc);
-}
-
-
-void
-TMH_force_tip_pickup_resume ()
-{
- struct PickupContext *nxt;
-
- for (struct PickupContext *pc = pc_head;
- NULL != pc;
- pc = nxt)
- {
- nxt = pc->next;
- stop_operations (pc);
- GNUNET_CONTAINER_DLL_remove (pc_head,
- pc_tail,
- pc);
- MHD_resume_connection (pc->connection);
- }
-}
-
-
-/**
- * Callbacks of this type are used to serve the result of submitting a
- * withdraw request to a exchange without the (un)blinding factor.
- * We persist the result in the database and, if we were the last
- * planchet operation, resume HTTP processing.
- *
- * @param cls closure with a `struct PlanchetOperation *`
- * @param hr HTTP response data
- * @param blind_sig blind signature over the coin, NULL on error
- */
-static void
-withdraw_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_BlindedDenominationSignature *blind_sig)
-{
- struct PlanchetOperation *po = cls;
- struct PickupContext *pc = po->pc;
- enum GNUNET_DB_QueryStatus qs;
-
- GNUNET_CONTAINER_DLL_remove (pc->po_head,
- pc->po_tail,
- po);
- TMH_db->preflight (TMH_db->cls);
- if (NULL == blind_sig)
- {
- GNUNET_free (po);
- stop_operations (pc);
- pc->http_status = MHD_HTTP_BAD_GATEWAY;
- pc->response =
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (TALER_EC_MERCHANT_TIP_PICKUP_EXCHANGE_ERROR),
- TMH_pack_exchange_reply (hr));
- GNUNET_CONTAINER_DLL_remove (pc_head,
- pc_tail,
- pc);
- MHD_resume_connection (pc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- return;
- }
- qs = TMH_db->insert_pickup_blind_signature (TMH_db->cls,
- &pc->pickup_id,
- po->offset,
- blind_sig);
- GNUNET_free (po);
- if (qs < 0)
- {
- stop_operations (pc);
- pc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
- pc->response = TALER_MHD_make_error (
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "blind signature");
- GNUNET_CONTAINER_DLL_remove (pc_head,
- pc_tail,
- pc);
- MHD_resume_connection (pc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- return;
- }
- if (NULL == pc->po_head)
- {
- stop_operations (pc); /* stops timeout job */
- GNUNET_CONTAINER_DLL_remove (pc_head,
- pc_tail,
- pc);
- MHD_resume_connection (pc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- }
-}
-
-
-/**
- * Function called with the result of a #TMH_EXCHANGES_find_exchange()
- * operation as part of a withdraw objective. If the exchange is ready,
- * withdraws the planchet from the exchange.
- *
- * @param cls closure, with our `struct PlanchetOperation *`
- * @param hr HTTP response details
- * @param eh handle to the exchange context
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
- * @param exchange_trusted true if this exchange is trusted by config
- */
-static void
-do_withdraw (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *eh,
- const char *payto_uri,
- const struct TALER_Amount *wire_fee,
- bool exchange_trusted)
-{
- struct PlanchetOperation *po = cls;
- struct PickupContext *pc = po->pc;
-
- po->fo = NULL;
- TMH_db->preflight (TMH_db->cls);
- if (NULL == hr)
- {
- stop_operations (pc);
- GNUNET_CONTAINER_DLL_remove (pc->po_head,
- pc->po_tail,
- po);
- GNUNET_free (po);
- pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
- pc->response = TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT));
- MHD_resume_connection (pc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- return;
- }
- if (NULL == eh)
- {
- stop_operations (pc);
- GNUNET_CONTAINER_DLL_remove (pc->po_head,
- pc->po_tail,
- po);
- GNUNET_free (po);
- pc->http_status = MHD_HTTP_BAD_GATEWAY;
- pc->response =
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
- TMH_pack_exchange_reply (hr));
- MHD_resume_connection (pc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- return;
- }
- po->w2h = TALER_EXCHANGE_withdraw2 (eh,
- &po->pd,
- &pc->reserve_priv,
- &withdraw_cb,
- po);
-}
-
-
-/**
- * Withdraw @a planchet from @a exchange_url for @a pc operation at planchet
- * @a offset. Sets up the respective operation and adds it @a pc's operation
- * list. Once the operation is complete, the resulting blind signature is
- * committed to the merchant's database. If all planchet operations are
- * completed, the HTTP processing is resumed.
- *
- * @param[in,out] pc a pending pickup operation that includes @a planchet
- * @param exchange_url identifies an exchange to do the pickup from
- * @param planchet details about the coin to pick up
- * @param offset offset of @a planchet in the list, needed to process the reply
- */
-static void
-try_withdraw (struct PickupContext *pc,
- const char *exchange_url,
- const struct TALER_PlanchetDetail *planchet,
- unsigned int offset)
-{
- struct PlanchetOperation *po;
-
- TMH_db->preflight (TMH_db->cls);
- po = GNUNET_new (struct PlanchetOperation);
- po->pc = pc;
- po->pd = *planchet;
- po->offset = offset;
- po->fo = TMH_EXCHANGES_find_exchange (exchange_url,
- NULL,
- GNUNET_NO,
- &do_withdraw,
- po);
- GNUNET_assert (NULL != po->fo);
- GNUNET_CONTAINER_DLL_insert (pc->po_head,
- pc->po_tail,
- po);
-}
-
-
-/**
- * Handle timeout for pickup.
- *
- * @param cls a `struct PickupContext *`
- */
-static void
-do_timeout (void *cls)
-{
- struct PickupContext *pc = cls;
-
- pc->tt = NULL;
- stop_operations (pc);
- pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
- pc->response = TALER_MHD_make_error (
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
- NULL);
- GNUNET_CONTAINER_DLL_remove (pc_head,
- pc_tail,
- pc);
- MHD_resume_connection (pc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
-}
-
-
-/**
- * Function called with the result of a #TMH_EXCHANGES_find_exchange()
- * operation as part of a withdraw objective. Here, we initialize
- * the "total_requested" amount by adding up the cost of the planchets
- * provided by the client.
- *
- * @param cls closure, with our `struct PickupContext *`
- * @param hr HTTP response details
- * @param eh handle to the exchange context
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
- * @param exchange_trusted true if this exchange is trusted by config
- */
-static void
-compute_total_requested (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *eh,
- const char *payto_uri,
- const struct TALER_Amount *wire_fee,
- bool exchange_trusted)
-{
- struct PickupContext *pc = cls;
- const struct TALER_EXCHANGE_Keys *keys;
-
- pc->fo = NULL;
- stop_operations (pc); /* stops timeout job */
- if (NULL == hr)
- {
- pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
- pc->response = TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT));
- MHD_resume_connection (pc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- return;
- }
- if (NULL == eh)
- {
- pc->http_status = MHD_HTTP_BAD_GATEWAY;
- pc->response =
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
- TMH_pack_exchange_reply (hr));
- MHD_resume_connection (pc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- return;
- }
- if (NULL == (keys = TALER_EXCHANGE_get_keys (eh)))
- {
- pc->http_status = MHD_HTTP_BAD_GATEWAY;
- pc->response =
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE),
- TMH_pack_exchange_reply (hr));
- MHD_resume_connection (pc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- return;
- }
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TMH_currency,
- &pc->total_requested));
- for (unsigned int i = 0; i<pc->planchets_length; i++)
- {
- struct TALER_PlanchetDetail *pd = &pc->planchets[i];
- const struct TALER_EXCHANGE_DenomPublicKey *dpk;
-
- dpk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
- &pd->denom_pub_hash);
- if (NULL == dpk)
- {
- pc->http_status = MHD_HTTP_CONFLICT;
- pc->response =
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_TIP_PICKUP_DENOMINATION_UNKNOWN),
- TMH_pack_exchange_reply (hr));
- MHD_resume_connection (pc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- return;
- }
-
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&pc->total_requested,
- &dpk->value)) ||
- (0 >
- TALER_amount_add (&pc->total_requested,
- &pc->total_requested,
- &dpk->value)) )
- {
- pc->http_status = MHD_HTTP_BAD_REQUEST;
- pc->response =
- TALER_MHD_make_error (TALER_EC_MERCHANT_TIP_PICKUP_SUMMATION_FAILED,
- "Could not add up values to compute pickup total");
- MHD_resume_connection (pc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- return;
- }
- }
- pc->tr_initialized = true;
- MHD_resume_connection (pc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
-}
-
-
-/**
- * The tip lookup operation failed. Generate an error response based on the @a qs.
- *
- * @param connection connection to generate error for
- * @param qs DB status to base error creation on
- * @return MHD result code
- */
-static MHD_RESULT
-reply_lookup_tip_failed (struct MHD_Connection *connection,
- enum GNUNET_DB_QueryStatus qs)
-{
- unsigned int response_code;
- enum TALER_ErrorCode ec;
-
- TMH_db->rollback (TMH_db->cls);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- ec = TALER_EC_MERCHANT_GENERIC_TIP_ID_UNKNOWN;
- response_code = MHD_HTTP_NOT_FOUND;
- break;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
- response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- case GNUNET_DB_STATUS_HARD_ERROR:
- ec = TALER_EC_GENERIC_DB_COMMIT_FAILED;
- response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- default:
- GNUNET_break (0);
- ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- }
- return TALER_MHD_reply_with_error (connection,
- response_code,
- ec,
- NULL);
-}
-
-
-MHD_RESULT
-TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
-{
- struct PickupContext *pc = hc->ctx;
- char *exchange_url;
- struct TALER_Amount total_authorized;
- struct TALER_Amount total_picked_up;
- struct TALER_Amount total_remaining;
- struct GNUNET_TIME_Timestamp expiration;
- enum GNUNET_DB_QueryStatus qs;
- unsigned int num_retries;
-
- if (NULL == pc)
- {
- json_t *planchets;
- json_t *planchet;
- size_t index;
-
- pc = GNUNET_new (struct PickupContext);
- hc->ctx = pc;
- hc->cc = &pick_context_cleanup;
-
- GNUNET_assert (NULL != hc->infix);
- if (GNUNET_OK !=
- GNUNET_CRYPTO_hash_from_string (hc->infix,
- &pc->tip_id.hash))
- {
- /* tip_id has wrong encoding */
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- hc->infix);
- }
-
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("planchets",
- &planchets),
- GNUNET_JSON_spec_end ()
- };
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- hc->request_body,
- spec);
- if (GNUNET_OK != res)
- return (GNUNET_NO == res)
- ? MHD_YES
- : MHD_NO;
- }
- }
- if (! json_is_array (planchets))
- {
- GNUNET_break_op (0);
- json_decref (planchets);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "planchets");
- }
-
- GNUNET_array_grow (pc->planchets,
- pc->planchets_length,
- json_array_size (planchets));
- json_array_foreach (planchets, index, planchet) {
- struct TALER_PlanchetDetail *pd = &pc->planchets[index];
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
- &pd->denom_pub_hash),
- TALER_JSON_spec_blinded_planchet ("coin_ev",
- &pd->blinded_planchet),
- GNUNET_JSON_spec_end ()
- };
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- planchet,
- spec);
- if (GNUNET_OK != res)
- {
- json_decref (planchets);
- return (GNUNET_NO == res)
- ? MHD_YES
- : MHD_NO;
- }
- }
- }
- json_decref (planchets);
- {
- struct GNUNET_HashContext *hc;
-
- hc = GNUNET_CRYPTO_hash_context_start ();
- GNUNET_CRYPTO_hash_context_read (hc,
- &pc->tip_id,
- sizeof (pc->tip_id));
- for (unsigned int i = 0; i<pc->planchets_length; i++)
- {
- struct TALER_PlanchetDetail *pd = &pc->planchets[i];
-
- GNUNET_CRYPTO_hash_context_read (hc,
- &pd->denom_pub_hash,
- sizeof (pd->denom_pub_hash));
- TALER_blinded_planchet_hash_ (&pd->blinded_planchet,
- hc);
- }
- GNUNET_CRYPTO_hash_context_finish (hc,
- &pc->pickup_id.hash);
- }
- }
-
- if (NULL != pc->response)
- {
- MHD_RESULT ret;
-
- ret = MHD_queue_response (connection,
- pc->http_status,
- pc->response);
- pc->response = NULL;
- return ret;
- }
-
- if (! pc->tr_initialized)
- {
- qs = TMH_db->lookup_tip (TMH_db->cls,
- hc->instance->settings.id,
- &pc->tip_id,
- &total_authorized,
- &total_picked_up,
- &expiration,
- &exchange_url,
- &pc->reserve_priv);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- return reply_lookup_tip_failed (connection,
- qs);
- MHD_suspend_connection (connection);
- pc->connection = connection;
- pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
- &do_timeout,
- pc);
- pc->fo = TMH_EXCHANGES_find_exchange (exchange_url,
- NULL,
- GNUNET_NO,
- &compute_total_requested,
- pc);
- GNUNET_free (exchange_url);
- return MHD_YES;
- }
-
-
- TMH_db->preflight (TMH_db->cls);
- num_retries = 0;
-RETRY:
- num_retries++;
- if (num_retries > MAX_RETRIES)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_SOFT_FAILURE,
- NULL);
- }
- if (GNUNET_OK !=
- TMH_db->start (TMH_db->cls,
- "pickup tip"))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- NULL);
- }
- {
- struct TALER_BlindedDenominationSignature sigs[
- GNUNET_NZL (pc->planchets_length)];
-
- memset (sigs,
- 0,
- sizeof (sigs));
- qs = TMH_db->lookup_pickup (TMH_db->cls,
- hc->instance->settings.id,
- &pc->tip_id,
- &pc->pickup_id,
- &exchange_url,
- &pc->reserve_priv,
- pc->planchets_length,
- sigs);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Lookup pickup `%s' resulted in %d\n",
- GNUNET_h2s (&pc->pickup_id.hash),
- qs);
- if (qs > GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)
- qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- bool rollback = false;
-
- for (unsigned int i = 0; i< pc->planchets_length; i++)
- {
- if (TALER_DENOMINATION_INVALID != sigs[i].cipher)
- continue;
- if (! rollback)
- {
- TMH_db->rollback (TMH_db->cls);
- MHD_suspend_connection (connection);
- GNUNET_CONTAINER_DLL_insert (pc_head,
- pc_tail,
- pc);
- pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
- &do_timeout,
- pc);
- rollback = true;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Lookup pickup `%s' initiated withdraw #%u\n",
- GNUNET_h2s (&pc->pickup_id.hash),
- i);
- try_withdraw (pc,
- exchange_url,
- &pc->planchets[i],
- i);
- }
- GNUNET_free (exchange_url);
- if (rollback)
- return MHD_YES;
- /* we got _all_ signatures, can continue! */
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
- {
- unsigned int response_code;
- enum TALER_ErrorCode ec;
-
- TMH_db->rollback (TMH_db->cls);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- {
- json_t *blind_sigs;
-
- blind_sigs = json_array ();
- GNUNET_assert (NULL != blind_sigs);
- for (unsigned int i = 0; i<pc->planchets_length; i++)
- {
- GNUNET_assert (0 ==
- json_array_append_new (
- blind_sigs,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_blinded_denom_sig ("blind_sig",
- &sigs[i]))));
- TALER_blinded_denom_sig_free (&sigs[i]);
- }
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("blind_sigs",
- blind_sigs));
- }
- break;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- goto RETRY;
- case GNUNET_DB_STATUS_HARD_ERROR:
- ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
- response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- default:
- GNUNET_break (0);
- ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- }
- return TALER_MHD_reply_with_error (connection,
- response_code,
- ec,
- NULL);
- }
- }
-
- qs = TMH_db->lookup_tip (TMH_db->cls,
- hc->instance->settings.id,
- &pc->tip_id,
- &total_authorized,
- &total_picked_up,
- &expiration,
- &exchange_url,
- &pc->reserve_priv);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- TMH_db->rollback (TMH_db->cls);
- goto RETRY;
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- TMH_db->rollback (TMH_db->cls);
- return reply_lookup_tip_failed (connection,
- qs);
- }
- if (GNUNET_TIME_absolute_is_past (expiration.abs_time))
- {
- GNUNET_free (exchange_url);
- TMH_db->rollback (TMH_db->cls);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_GONE,
- TALER_EC_MERCHANT_TIP_PICKUP_HAS_EXPIRED,
- hc->infix);
- }
- if (0 >
- TALER_amount_subtract (&total_remaining,
- &total_authorized,
- &total_picked_up))
- {
- GNUNET_free (exchange_url);
- GNUNET_break_op (0);
- TMH_db->rollback (TMH_db->cls);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "picked up amount exceeds authorized amount");
- }
-
- if (0 >
- TALER_amount_cmp (&total_remaining,
- &pc->total_requested))
- {
- /* total_remaining < pc->total_requested */
- GNUNET_free (exchange_url);
- GNUNET_break_op (0);
- TMH_db->rollback (TMH_db->cls);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_MERCHANT_TIP_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING,
- hc->infix);
- }
-
- GNUNET_assert (0 <
- TALER_amount_add (&total_picked_up,
- &total_picked_up,
- &pc->total_requested));
- qs = TMH_db->insert_pickup (TMH_db->cls,
- hc->instance->settings.id,
- &pc->tip_id,
- &total_picked_up,
- &pc->pickup_id,
- &pc->total_requested);
- if (qs < 0)
- {
- TMH_db->rollback (TMH_db->cls);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- goto RETRY;
- GNUNET_free (exchange_url);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "pickup");
- }
- qs = TMH_db->commit (TMH_db->cls);
- if (qs < 0)
- {
- TMH_db->rollback (TMH_db->cls);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- goto RETRY;
- GNUNET_free (exchange_url);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_COMMIT_FAILED,
- NULL);
- }
- MHD_suspend_connection (connection);
- GNUNET_CONTAINER_DLL_insert (pc_head,
- pc_tail,
- pc);
- pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
- &do_timeout,
- pc);
- for (unsigned int i = 0; i<pc->planchets_length; i++)
- {
- try_withdraw (pc,
- exchange_url,
- &pc->planchets[i],
- i);
- }
- GNUNET_free (exchange_url);
- return MHD_YES;
-}
diff --git a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.h b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.h
deleted file mode 100644
index 3a5ef112..00000000
--- a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-merchant-httpd_post-tips-ID-pickup.h
- * @brief headers for POST /tips/ID/pickup handler
- * @author Christian Grothoff
- */
-#ifndef TALER_MERCHANT_HTTPD_POST_TIPS_ID_PICKUP_H
-#define TALER_MERCHANT_HTTPD_POST_TIPS_ID_PICKUP_H
-#include <microhttpd.h>
-#include "taler-merchant-httpd.h"
-
-
-/**
- * We are shutting down, force resuming all suspended pickup operations.
- */
-void
-TMH_force_tip_pickup_resume (void);
-
-
-/**
- * Manages a POST /tips/$ID/pickup call, checking that the tip is authorized,
- * and if so, returning the blind signatures.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] hc context with further information about the request
- * @return MHD result code
- */
-MHD_RESULT
-TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc);
-
-
-#endif
diff --git a/src/backend/taler-merchant-httpd_post-using-templates.c b/src/backend/taler-merchant-httpd_post-using-templates.c
index 9252afc8..cdaf917e 100644
--- a/src/backend/taler-merchant-httpd_post-using-templates.c
+++ b/src/backend/taler-merchant-httpd_post-using-templates.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2022 Taler Systems SA
+ (C) 2022-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -21,6 +21,7 @@
* @file taler-merchant-httpd_post-using-templates.c
* @brief implementing POST /using-templates request handling
* @author Priscilla HUANG
+ * @author Christian Grothoff
*/
#include "platform.h"
#include "taler-merchant-httpd_post-using-templates.h"
@@ -29,13 +30,49 @@
#include <taler/taler_json_lib.h>
+/**
+ * Our context.
+ */
+struct UseContext
+{
+ /**
+ * Internal handler context we are passing into the
+ * POST /private/orders handler.
+ */
+ struct TMH_HandlerContext ihc;
+
+ /**
+ * Our template details from the DB.
+ */
+ struct TALER_MERCHANTDB_TemplateDetails etp;
+
+};
+
+
+/**
+ * Clean up a `struct UseContext *`
+ *
+ * @param cls a `struct UseContext *`
+ */
+static void
+cleanup_use_context (void *cls)
+{
+ struct UseContext *uc = cls;
+
+ TALER_MERCHANTDB_template_details_free (&uc->etp);
+ if (NULL != uc->ihc.cc)
+ uc->ihc.cc (uc->ihc.ctx);
+ json_decref (uc->ihc.request_body);
+ GNUNET_free (uc);
+}
+
+
MHD_RESULT
TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
struct TMH_MerchantInstance *mi = hc->instance;
- MHD_RESULT mret;
const char *template_id = hc->infix;
const char *summary = NULL;
const char *fulfillment_url = NULL;
@@ -44,20 +81,26 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh,
bool no_amount;
json_t *fake_body;
bool no_summary;
- struct TALER_MERCHANTDB_TemplateDetails etp;
-
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("summary",
&summary),
NULL),
GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount ("amount",
- TMH_currency,
- &amount),
+ TALER_JSON_spec_amount_any ("amount",
+ &amount),
&no_amount),
GNUNET_JSON_spec_end ()
};
+ struct UseContext *uc = hc->ctx;
+
+ if (NULL == uc)
+ {
+ uc = GNUNET_new (struct UseContext);
+ hc->ctx = uc;
+ hc->cc = &cleanup_use_context;
+ uc->ihc.instance = hc->instance;
+ }
{
enum GNUNET_GenericReturnValue res;
@@ -80,156 +123,179 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh,
qs = TMH_db->lookup_template (TMH_db->cls,
mi->settings.id,
template_id,
- &etp);
+ &uc->etp);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
/* Clean up and fail hard */
GNUNET_break (0);
GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- NULL);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
case GNUNET_DB_STATUS_SOFT_ERROR:
/* this should be impossible (single select) */
GNUNET_break (0);
GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- NULL);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
/* template not found! */
GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
- template_id);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
+ template_id);
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
/* all good */
break;
} /* End of the switch */
}
- const char *tsummary;
- uint32_t min_age;
- struct GNUNET_TIME_Relative pay_duration;
- struct TALER_Amount tamount;
- bool no_tamount;
+ {
+ /* template */
+ const char *tsummary = NULL;
+ const char *tcurrency = NULL;
+ uint32_t min_age;
+ struct GNUNET_TIME_Relative pay_duration;
+ struct TALER_Amount tamount;
+ bool no_tamount;
+ struct GNUNET_JSON_Specification tspec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("summary",
+ &tsummary),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("currency",
+ &tcurrency),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("amount",
+ &tamount),
+ &no_tamount),
+ GNUNET_JSON_spec_uint32 ("minimum_age",
+ &min_age),
+ GNUNET_JSON_spec_relative_time ("pay_duration",
+ &pay_duration),
+ GNUNET_JSON_spec_end ()
+ };
- struct GNUNET_JSON_Specification tspec[] = {
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("summary",
- &tsummary),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount ("amount",
- TMH_currency,
- &tamount),
- &no_tamount),
- GNUNET_JSON_spec_uint32 ("minimum_age",
- &min_age),
- GNUNET_JSON_spec_relative_time ("pay_duration",
- &pay_duration),
- GNUNET_JSON_spec_end ()
- };
+ {
+ enum GNUNET_GenericReturnValue res;
+ const char *err_name;
+ unsigned int err_line;
- {
- enum GNUNET_GenericReturnValue res;
- const char *err_name;
- unsigned int err_line;
+ res = GNUNET_JSON_parse (uc->etp.template_contract,
+ tspec,
+ &err_name,
+ &err_line);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ json_dumpf (uc->etp.template_contract,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ err_name);
+ }
+ }
- res = GNUNET_JSON_parse (etp.template_contract,
- tspec,
- &err_name,
- &err_line);
- if (GNUNET_OK != res)
+ if ( (! no_amount) &&
+ (! no_tamount) )
{
- GNUNET_break (0);
- json_dumpf (etp.template_contract,
- stderr,
- JSON_INDENT (2));
GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- err_name);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
+ NULL);
}
- }
- // if amount given in template AND request
- if ((! no_amount) && (! no_tamount))
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
- NULL);
- }
-
- // if there is no amount in template and request
- if (no_amount && no_tamount)
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_AMOUNT,
- NULL);
- }
+ if ( (! no_amount) &&
+ (NULL != tcurrency) &&
+ (0 != strcmp (tcurrency,
+ amount.currency)) )
+ {
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ tcurrency);
+ }
- if ( (NULL!=summary) && (NULL != tsummary))
- {
+ if (no_amount && no_tamount)
+ {
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_AMOUNT,
+ NULL);
+ }
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT,
- NULL);
- }
+ if ( (NULL != summary) &&
+ (NULL != tsummary) )
+ {
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT,
+ NULL);
+ }
- // if there is no summary in template and request
- if ( (NULL == summary) && (NULL == tsummary) )
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY,
- NULL);
+ if ( (NULL == summary) &&
+ (NULL == tsummary) )
+ {
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY,
+ NULL);
+ }
+ no_summary = (NULL == summary);
+ fake_body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("otp_id",
+ uc->etp.otp_id)),
+ GNUNET_JSON_pack_object_steal (
+ "order",
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("amount",
+ no_amount ?
+ &tamount :
+ &amount),
+ GNUNET_JSON_pack_string ("summary",
+ no_summary ?
+ tsummary :
+ summary),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string (
+ "fulfillment_url",
+ fulfillment_url)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string (
+ "fulfillment_message",
+ fulfillment_message))
+ ))
+ );
}
- no_summary = (NULL == summary);
- fake_body = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_object_steal ("order",
- GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("amount",
- no_amount ?
- &tamount :
- &amount),
- GNUNET_JSON_pack_string ("summary",
- no_summary ?
- tsummary :
- summary),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string (
- "fulfillment_url",
- fulfillment_url)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string (
- "fulfillment_message",
- fulfillment_message))
- ))
- );
- TALER_MERCHANTDB_template_details_free (&etp);
- {
- struct TMH_HandlerContext fake_hc = {
- .request_body = fake_body,
- .instance = hc->instance
- };
- mret = TMH_private_post_orders (NULL, /* not even used */
- connection,
- &fake_hc);
- }
- json_decref (fake_body);
- return mret;
+ uc->ihc.request_body = fake_body;
+ return TMH_private_post_orders (
+ NULL, /* not even used */
+ connection,
+ &uc->ihc);
}
diff --git a/src/backend/taler-merchant-httpd_private-delete-account-ID.c b/src/backend/taler-merchant-httpd_private-delete-account-ID.c
new file mode 100644
index 00000000..7b7aa6e0
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-account-ID.c
@@ -0,0 +1,94 @@
+/*
+ This file is part of TALER
+ (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_private-delete-account-ID.c
+ * @brief implement DELETE /account/$H_WIRE
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-delete-account-ID.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_dbevents.h>
+
+
+MHD_RESULT
+TMH_private_delete_account_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ struct TALER_MerchantWireHashP h_wire;
+ enum GNUNET_DB_QueryStatus qs;
+
+ (void) rh;
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (hc->infix,
+ strlen (hc->infix),
+ &h_wire,
+ sizeof (h_wire)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "h_wire");
+ }
+ GNUNET_assert (NULL != mi);
+ qs = TMH_db->inactivate_account (TMH_db->cls,
+ mi->settings.id,
+ &h_wire);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "inactivate_account");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_PRIVATE_ACCOUNT_DELETE_UNKNOWN_ACCOUNT,
+ "account unknown");
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_MERCHANT_ACCOUNTS_CHANGED)
+ };
+
+ TMH_db->event_notify (TMH_db->cls,
+ &es,
+ NULL,
+ 0);
+ }
+ TMH_reload_instances (mi->settings.id);
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-merchant-httpd_private-delete-account-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-delete-account-ID.h b/src/backend/taler-merchant-httpd_private-delete-account-ID.h
new file mode 100644
index 00000000..b9004b9f
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-account-ID.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_private-delete-account-ID.h
+ * @brief implement DELETE /account/$PAYTO
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_ACCOUNT_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_ACCOUNT_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a DELETE "/private/account/$H_WIRE" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_delete_account_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+
+/* end of taler-merchant-httpd_private-delete-account-ID.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-delete-instances-ID-token.c b/src/backend/taler-merchant-httpd_private-delete-instances-ID-token.c
new file mode 100644
index 00000000..28690433
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-instances-ID-token.c
@@ -0,0 +1,110 @@
+/*
+ This file is part of GNU Taler
+ (C) 2023 Taler Systems SA
+
+ GNU Taler is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_private-post-instances-ID-token.c
+ * @brief implementing DELETE /instances/$ID/token request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-delete-instances-ID-token.h"
+#include "taler-merchant-httpd_helper.h"
+#include <taler/taler_json_lib.h>
+
+
+MHD_RESULT
+TMH_private_delete_instances_ID_token (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ const char *bearer = "Bearer ";
+ struct TMH_MerchantInstance *mi = hc->instance;
+ const char *tok;
+ struct TALER_MERCHANTDB_LoginTokenP btoken;
+ enum GNUNET_DB_QueryStatus qs;
+
+ tok = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_AUTHORIZATION);
+ /* This was presumably checked before... */
+ if (0 !=
+ strncmp (tok,
+ bearer,
+ strlen (bearer)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "login token (in 'Authorization' header)");
+ }
+ tok += strlen (bearer);
+ while (' ' == *tok)
+ tok++;
+ if (0 != strncasecmp (tok,
+ RFC_8959_PREFIX,
+ strlen (RFC_8959_PREFIX)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "login token (in 'Authorization' header)");
+ }
+ tok += strlen (RFC_8959_PREFIX);
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (tok,
+ strlen (tok),
+ &btoken,
+ sizeof (btoken)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "login token (in 'Authorization' header)");
+ }
+ qs = TMH_db->delete_login_token (TMH_db->cls,
+ mi->settings.id,
+ &btoken);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "delete_login_token");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* No 404, as the login token must have existed
+ when we got the request as it was accepted as
+ valid. So we can only get here due to concurrent
+ modification, and then the client should still
+ simply see the success. Hence, fall-through */
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ }
+ GNUNET_break (0);
+ return MHD_NO;
+}
+
+
+/* end of taler-merchant-httpd_private-delete-instances-ID-login.c */
diff --git a/src/backend/taler-merchant-httpd_private-delete-instances-ID-token.h b/src/backend/taler-merchant-httpd_private-delete-instances-ID-token.h
new file mode 100644
index 00000000..bccd07ae
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-instances-ID-token.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of GNU Taler
+ (C) 2023 Taler Systems SA
+
+ GNU Taler is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_private-delete-instances-ID-token.h
+ * @brief implements DELETE /instances/$ID/token request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_INSTANCES_ID_TOKEN_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_INSTANCES_ID_TOKEN_H
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Delete login token for an instance.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_delete_instances_ID_token (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-delete-instances-ID.c b/src/backend/taler-merchant-httpd_private-delete-instances-ID.c
index 2791d6d7..8862eadd 100644
--- a/src/backend/taler-merchant-httpd_private-delete-instances-ID.c
+++ b/src/backend/taler-merchant-httpd_private-delete-instances-ID.c
@@ -21,6 +21,7 @@
#include "platform.h"
#include "taler-merchant-httpd_private-delete-instances-ID.h"
#include <taler/taler_json_lib.h>
+#include <taler/taler_dbevents.h>
/**
@@ -52,6 +53,17 @@ delete_instances_ID (struct TMH_MerchantInstance *mi,
else
qs = TMH_db->delete_instance_private_key (TMH_db->cls,
mi->settings.id);
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_MERCHANT_ACCOUNTS_CHANGED)
+ };
+
+ TMH_db->event_notify (TMH_db->cls,
+ &es,
+ NULL,
+ 0);
+ }
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
diff --git a/src/backend/taler-merchant-httpd_private-delete-orders-ID.c b/src/backend/taler-merchant-httpd_private-delete-orders-ID.c
index dfdb486e..cd8aa10c 100644
--- a/src/backend/taler-merchant-httpd_private-delete-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_private-delete-orders-ID.c
@@ -78,6 +78,8 @@ TMH_private_delete_orders_ID (const struct TMH_RequestHandler *rh,
struct TALER_MerchantPostDataHashP unused;
uint64_t order_serial;
bool paid = false;
+ bool wired = false;
+ bool matches = false;
qs = TMH_db->lookup_order (TMH_db->cls,
mi->settings.id,
@@ -87,13 +89,16 @@ TMH_private_delete_orders_ID (const struct TMH_RequestHandler *rh,
NULL);
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
- qs = TMH_db->lookup_contract_terms (TMH_db->cls,
- mi->settings.id,
- hc->infix,
- NULL,
- &order_serial,
- &paid,
- NULL);
+ qs = TMH_db->lookup_contract_terms3 (TMH_db->cls,
+ mi->settings.id,
+ hc->infix,
+ NULL,
+ NULL,
+ &order_serial,
+ &paid,
+ &wired,
+ &matches,
+ NULL);
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
return TALER_MHD_reply_with_error (connection,
diff --git a/src/backend/taler-merchant-httpd_private-delete-reserves-ID.c b/src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.c
index b6daf9e6..b147b84f 100644
--- a/src/backend/taler-merchant-httpd_private-delete-reserves-ID.c
+++ b/src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2020 Taler Systems SA
+ (C) 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -14,17 +14,17 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file taler-merchant-httpd_private-delete-reserves-ID.c
- * @brief implement DELETE /reserves/$ID
+ * @file taler-merchant-httpd_private-delete-otp-devices-ID.c
+ * @brief implement DELETE /otp-devices/$ID
* @author Christian Grothoff
*/
#include "platform.h"
-#include "taler-merchant-httpd_private-delete-reserves-ID.h"
+#include "taler-merchant-httpd_private-delete-otp-devices-ID.h"
#include <taler/taler_json_lib.h>
/**
- * Handle a DELETE "/reserves/$ID" request.
+ * Handle a DELETE "/otp-devices/$ID" request.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
@@ -32,59 +32,36 @@
* @return MHD result code
*/
MHD_RESULT
-TMH_private_delete_reserves_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
+TMH_private_delete_otp_devices_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
{
struct TMH_MerchantInstance *mi = hc->instance;
enum GNUNET_DB_QueryStatus qs;
- struct TALER_ReservePublicKeyP reserve_pub;
- const char *purge;
(void) rh;
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (hc->infix,
- strlen (hc->infix),
- &reserve_pub,
- sizeof (reserve_pub)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
- hc->infix);
- }
- purge = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "purge");
GNUNET_assert (NULL != mi);
- if ( (NULL != purge) &&
- (0 == strcasecmp (purge,
- "yes")) )
- qs = TMH_db->purge_reserve (TMH_db->cls,
- mi->settings.id,
- &reserve_pub);
- else
- qs = TMH_db->delete_reserve (TMH_db->cls,
- mi->settings.id,
- &reserve_pub);
+ GNUNET_assert (NULL != hc->infix);
+ qs = TMH_db->delete_otp (TMH_db->cls,
+ mi->settings.id,
+ hc->infix);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
- NULL);
+ "delete_otp");
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_SOFT_FAILURE,
- "Serialization error for single SQL statement");
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "delete_otp (soft)");
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_PRIVATE_DELETE_RESERVES_NO_SUCH_RESERVE,
+ TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
hc->infix);
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
return TALER_MHD_reply_static (connection,
@@ -98,4 +75,4 @@ TMH_private_delete_reserves_ID (const struct TMH_RequestHandler *rh,
}
-/* end of taler-merchant-httpd_private-delete-reserves-ID.c */
+/* end of taler-merchant-httpd_private-delete-otp-devices-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.h b/src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.h
new file mode 100644
index 00000000..cd129d0d
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_private-delete-otp-devices-ID.h
+ * @brief implement DELETE /otp-devices/$ID/
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_OTP_DEVICES_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_OTP_DEVICES_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a DELETE "/otp-devices/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_delete_otp_devices_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-delete-otp-devices-ID.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-delete-templates-ID.c b/src/backend/taler-merchant-httpd_private-delete-templates-ID.c
index 0c2570fa..9602b652 100644
--- a/src/backend/taler-merchant-httpd_private-delete-templates-ID.c
+++ b/src/backend/taler-merchant-httpd_private-delete-templates-ID.c
@@ -33,8 +33,8 @@
*/
MHD_RESULT
TMH_private_delete_templates_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
{
struct TMH_MerchantInstance *mi = hc->instance;
enum GNUNET_DB_QueryStatus qs;
@@ -43,8 +43,8 @@ TMH_private_delete_templates_ID (const struct TMH_RequestHandler *rh,
GNUNET_assert (NULL != mi);
GNUNET_assert (NULL != hc->infix);
qs = TMH_db->delete_template (TMH_db->cls,
- mi->settings.id,
- hc->infix);
+ mi->settings.id,
+ hc->infix);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
diff --git a/src/backend/taler-merchant-httpd_private-delete-templates-ID.h b/src/backend/taler-merchant-httpd_private-delete-templates-ID.h
index aa7adc4b..21fb46ac 100644
--- a/src/backend/taler-merchant-httpd_private-delete-templates-ID.h
+++ b/src/backend/taler-merchant-httpd_private-delete-templates-ID.h
@@ -34,8 +34,8 @@
*/
MHD_RESULT
TMH_private_delete_templates_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc);
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
/* end of taler-merchant-httpd_private-delete-templates-ID.h */
#endif
diff --git a/src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.c b/src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.c
new file mode 100644
index 00000000..de7b6471
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.c
@@ -0,0 +1,75 @@
+/*
+ This file is part of TALER
+ (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_private-delete-token-families-SLUG.c
+ * @brief implement DELETE /tokenfamilies/$SLUG
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-delete-token-families-SLUG.h"
+#include <gnunet/gnunet_db_lib.h>
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a DELETE "/tokenfamilies/$SLUG" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_delete_token_families_SLUG (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ enum GNUNET_DB_QueryStatus qs;
+
+ (void) rh;
+ GNUNET_assert (NULL != mi);
+ GNUNET_assert (NULL != hc->infix);
+ qs = TMH_db->delete_token_family (TMH_db->cls,
+ mi->settings.id,
+ hc->infix);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "delete_token_family");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "delete_token_family (soft)");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ }
+ GNUNET_assert (0);
+ return MHD_NO;
+}
+
+
+/* end of taler-merchant-httpd_private-delete-token-families-SLUG.c */
diff --git a/src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.h b/src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.h
new file mode 100644
index 00000000..e8b72fc6
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_private-delete-token-families-SLUG.h
+ * @brief implement DELETE /tokenfamilies/$SLUG/
+ * @author Christian Blättler
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_TOKEN_FAMILIES_SLUG_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_TOKEN_FAMILIES_SLUG_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a DELETE "/tokenfamilies/$SLUG" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_delete_token_families_SLUG (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-delete-token-families-SLUG.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-accounts-ID.c b/src/backend/taler-merchant-httpd_private-get-accounts-ID.c
new file mode 100644
index 00000000..703beeca
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-accounts-ID.c
@@ -0,0 +1,103 @@
+/*
+ This file is part of TALER
+ (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_private-get-accounts-ID.c
+ * @brief implement GET /accounts/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-accounts-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a GET "/accounts/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_accounts_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ const char *h_wire_s = hc->infix;
+ struct TALER_MerchantWireHashP h_wire;
+ struct TALER_MERCHANTDB_AccountDetails tp = { 0 };
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_assert (NULL != mi);
+ GNUNET_assert (NULL != h_wire_s);
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (h_wire_s,
+ strlen (h_wire_s),
+ &h_wire,
+ sizeof (h_wire)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_GENERIC_H_WIRE_MALFORMED,
+ h_wire_s);
+ }
+ qs = TMH_db->select_account (TMH_db->cls,
+ mi->settings.id,
+ &h_wire,
+ &tp);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_account");
+ }
+ if (0 == qs)
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_ACCOUNT_UNKNOWN,
+ hc->infix);
+ }
+ {
+ MHD_RESULT ret;
+
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("payto_uri",
+ tp.payto_uri),
+ GNUNET_JSON_pack_data_auto ("h_wire",
+ &tp.h_wire),
+ GNUNET_JSON_pack_data_auto ("salt",
+ &tp.salt),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("credit_facade_url",
+ tp.credit_facade_url)));
+ /* We do not return the credentials, as they may
+ be sensitive */
+ json_decref (tp.credit_facade_credentials);
+ GNUNET_free (tp.payto_uri);
+ GNUNET_free (tp.credit_facade_url);
+ return ret;
+ }
+}
+
+
+/* end of taler-merchant-httpd_private-get-accounts-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-tips-ID.h b/src/backend/taler-merchant-httpd_private-get-accounts-ID.h
index a99dc365..da5cb729 100644
--- a/src/backend/taler-merchant-httpd_private-get-tips-ID.h
+++ b/src/backend/taler-merchant-httpd_private-get-accounts-ID.h
@@ -1,9 +1,9 @@
/*
This file is part of TALER
- (C) 2017 Taler Systems SA
+ (C) 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
+ terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
@@ -14,18 +14,18 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file taler-merchant-httpd_get-tips-ID.h
- * @brief headers for GET /tips/ID handler
+ * @file taler-merchant-httpd_private-get-accounts-ID.h
+ * @brief implement GET /accounts/$ID/
* @author Christian Grothoff
*/
-#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_TIPS_ID_H
-#define TALER_MERCHANT_HTTPD_PRIVATE_GET_TIPS_ID_H
-#include <microhttpd.h>
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_ACCOUNTS_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_ACCOUNTS_ID_H
+
#include "taler-merchant-httpd.h"
/**
- * Manages a GET /tips/$ID call, returning the status of the tip.
+ * Handle a GET "/accounts/$ID" request.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
@@ -33,9 +33,9 @@
* @return MHD result code
*/
MHD_RESULT
-TMH_private_get_tips_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc);
-
+TMH_private_get_accounts_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+/* end of taler-merchant-httpd_private-get-accounts-ID.h */
#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-accounts.c b/src/backend/taler-merchant-httpd_private-get-accounts.c
new file mode 100644
index 00000000..92ebb368
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-accounts.c
@@ -0,0 +1,78 @@
+/*
+ This file is part of TALER
+ (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_private-get-accounts.c
+ * @brief implement GET /accounts
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-accounts.h"
+
+
+/**
+ * Add account details to our JSON array.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param ad details about the account
+ */
+static void
+add_account (void *cls,
+ const struct TALER_MERCHANTDB_AccountDetails *ad)
+{
+ json_t *pa = cls;
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ pa,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto_uri",
+ ad->payto_uri),
+ GNUNET_JSON_pack_data_auto ("h_wire",
+ &ad->h_wire))));
+}
+
+
+MHD_RESULT
+TMH_private_get_accounts (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ json_t *pa;
+ enum GNUNET_DB_QueryStatus qs;
+
+ pa = json_array ();
+ GNUNET_assert (NULL != pa);
+ qs = TMH_db->select_accounts (TMH_db->cls,
+ hc->instance->settings.id,
+ &add_account,
+ pa);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ json_decref (pa);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("accounts",
+ pa));
+}
+
+
+/* end of taler-merchant-httpd_private-get-accounts.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-reserves.h b/src/backend/taler-merchant-httpd_private-get-accounts.h
index 705f0761..0e9897cf 100644
--- a/src/backend/taler-merchant-httpd_private-get-reserves.h
+++ b/src/backend/taler-merchant-httpd_private-get-accounts.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2019, 2020 Taler Systems SA
+ (C) 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -14,18 +14,18 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file taler-merchant-httpd_private-get-reserves.h
- * @brief implement GET /reserves
- * @author Christian Grothoff
+ * @file taler-merchant-httpd_private-get-accounts.h
+ * @brief implement GET /accounts
+ * @author Priscilla HUANG
*/
-#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_RESERVES_H
-#define TALER_MERCHANT_HTTPD_PRIVATE_GET_RESERVES_H
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_ACCOUNTS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_ACCOUNTS_H
#include "taler-merchant-httpd.h"
/**
- * Handle a GET "/reserves" request.
+ * Handle a GET "/accounts" request.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
@@ -33,9 +33,9 @@
* @return MHD result code
*/
MHD_RESULT
-TMH_private_get_reserves (const struct TMH_RequestHandler *rh,
+TMH_private_get_accounts (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc);
-/* end of taler-merchant-httpd_private-get-reserves.h */
+/* end of taler-merchant-httpd_private-get-accounts.h */
#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c b/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c
index 10c76013..8a338e7d 100644
--- a/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c
+++ b/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c
@@ -1,6 +1,6 @@
/*
This file is part of GNU Taler
- (C) 2021 Taler Systems SA
+ (C) 2021-2023 Taler Systems SA
GNU Taler is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -39,13 +39,13 @@
* How long should clients cache a KYC failure response?
*/
#define EXPIRATION_KYC_FAILURE GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_MINUTES, 5)
+ GNUNET_TIME_UNIT_MINUTES, 5)
/**
* How long should clients cache a KYC success response?
*/
#define EXPIRATION_KYC_SUCCESS GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_HOURS, 1)
+ GNUNET_TIME_UNIT_HOURS, 1)
/**
@@ -73,7 +73,7 @@ struct ExchangeKycRequest
/**
* Find operation where we connect to the respective exchange.
*/
- struct TMH_EXCHANGES_FindOperation *fo;
+ struct TMH_EXCHANGES_KeysOperation *fo;
/**
* KYC request this exchange request is made for.
@@ -110,11 +110,6 @@ struct ExchangeKycRequest
*/
struct GNUNET_TIME_Timestamp last_check;
- /**
- * Last KYC status returned by the exchange.
- */
- bool kyc_ok;
-
};
@@ -197,7 +192,7 @@ struct KycContext
/**
* How long are we willing to wait for the exchange(s)?
*/
- struct GNUNET_TIME_Relative timeout;
+ struct GNUNET_TIME_Absolute timeout;
/**
* HTTP status code to use for the reply, i.e 200 for "OK".
@@ -283,7 +278,7 @@ kyc_context_cleanup (void *cls)
}
if (NULL != ekr->fo)
{
- TMH_EXCHANGES_find_exchange_cancel (ekr->fo);
+ TMH_EXCHANGES_keys4exchange_cancel (ekr->fo);
ekr->fo = NULL;
}
GNUNET_free (ekr->exchange_url);
@@ -310,9 +305,9 @@ kyc_context_cleanup (void *cls)
/**
- * Resume the given KYC context and send the given response.
- * Stores the response in the @a kc and signals MHD to resume
- * the connection. Also ensures MHD runs immediately.
+ * Resume the given KYC context and send the given response. Stores the
+ * response in the @a kc and signals MHD to resume the connection. Also
+ * ensures MHD runs immediately.
*
* @param kc KYC context
* @param response_code response code to use
@@ -400,7 +395,7 @@ handle_kyc_timeout (void *cls)
}
if (NULL != ekr->fo)
{
- TMH_EXCHANGES_find_exchange_cancel (ekr->fo);
+ TMH_EXCHANGES_keys4exchange_cancel (ekr->fo);
ekr->fo = NULL;
}
GNUNET_assert (
@@ -433,9 +428,8 @@ handle_kyc_timeout (void *cls)
/**
- * We are done with the KYC request @a ekr.
- * Remove it from the work list and check if
- * we are done overall.
+ * We are done with the KYC request @a ekr. Remove it from the work list and
+ * check if we are done overall.
*
* @param[in] ekr key request that is done (and will be freed)
*/
@@ -453,7 +447,7 @@ ekr_finished (struct ExchangeKycRequest *ekr)
if (NULL != kc->exchange_pending_head)
return; /* wait for more */
/* All exchange requests done, create final
- big response from cummulated replies */
+ big response from cumulated replies */
if ( (0 == json_array_size (kc->pending_kycs)) &&
(0 == json_array_size (kc->timeout_kycs)) )
{
@@ -500,15 +494,31 @@ exchange_check_cb (void *cls,
{
enum GNUNET_DB_QueryStatus qs;
+ if (TALER_AML_NORMAL != ks->details.ok.aml_status)
+ {
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ kc->pending_kycs,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 (
+ "aml_status",
+ ks->details.ok.aml_status),
+ GNUNET_JSON_pack_string ("exchange_url",
+ ekr->exchange_url),
+ GNUNET_JSON_pack_string ("payto_uri",
+ ekr->payto_uri))));
+ }
qs = TMH_db->account_kyc_set_status (TMH_db->cls,
kc->mi->settings.id,
&ekr->h_wire,
ekr->exchange_url,
ekr->exchange_kyc_serial,
- &ks->details.success.exchange_sig,
- &ks->details.success.exchange_pub,
- ks->details.success.timestamp,
- true);
+ &ks->details.ok.exchange_sig,
+ &ks->details.ok.exchange_pub,
+ ks->details.ok.timestamp,
+ true, /* KYC OK */
+ ks->details.ok.aml_status);
if (qs < 0)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@@ -517,18 +527,42 @@ exchange_check_cb (void *cls,
}
break;
case MHD_HTTP_ACCEPTED:
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- kc->pending_kycs,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("kyc_url",
- ks->details.accepted.kyc_url),
- GNUNET_JSON_pack_string ("exchange_url",
- ekr->exchange_url),
- GNUNET_JSON_pack_string ("payto_uri",
- ekr->payto_uri))));
- break;
+ {
+ struct GNUNET_TIME_Timestamp now;
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ kc->pending_kycs,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("kyc_url",
+ ks->details.accepted.kyc_url),
+ GNUNET_JSON_pack_uint64 ("aml_status",
+ ks->details.accepted.aml_status),
+ GNUNET_JSON_pack_string ("exchange_url",
+ ekr->exchange_url),
+ GNUNET_JSON_pack_string ("payto_uri",
+ ekr->payto_uri))));
+ now = GNUNET_TIME_timestamp_get ();
+ qs = TMH_db->account_kyc_set_status (
+ TMH_db->cls,
+ kc->mi->settings.id,
+ &ekr->h_wire,
+ ekr->exchange_url,
+ ekr->exchange_kyc_serial,
+ NULL,
+ NULL,
+ now,
+ false, /* KYC not OK */
+ ks->details.accepted.aml_status);
+ if (qs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to store KYC status in database!\n");
+ }
+ break;
+ }
case MHD_HTTP_NO_CONTENT:
{
struct GNUNET_TIME_Timestamp now;
@@ -543,7 +577,44 @@ exchange_check_cb (void *cls,
NULL,
NULL,
now,
- true);
+ true, /* KYC OK */
+ TALER_AML_NORMAL);
+ if (qs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to store KYC status in database!\n");
+ }
+ }
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ {
+ struct GNUNET_TIME_Timestamp now;
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ kc->pending_kycs,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 (
+ "aml_status",
+ ks->details.unavailable_for_legal_reasons.aml_status),
+ GNUNET_JSON_pack_string ("exchange_url",
+ ekr->exchange_url),
+ GNUNET_JSON_pack_string ("payto_uri",
+ ekr->payto_uri))));
+ now = GNUNET_TIME_timestamp_get ();
+ qs = TMH_db->account_kyc_set_status (
+ TMH_db->cls,
+ kc->mi->settings.id,
+ &ekr->h_wire,
+ ekr->exchange_url,
+ ekr->exchange_kyc_serial,
+ NULL,
+ NULL,
+ now,
+ true, /* KYC is OK, AML not... */
+ ks->details.unavailable_for_legal_reasons.aml_status);
if (qs < 0)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@@ -574,70 +645,55 @@ exchange_check_cb (void *cls,
/**
- * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * Function called with the result of a #TMH_EXCHANGES_keys4exchange()
* operation. Runs the KYC check against the exchange.
*
* @param cls closure with our `struct ExchangeKycRequest *`
- * @param hr HTTP response details
- * @param eh handle to the exchange context
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
- * @param exchange_trusted true if this exchange is trusted by config
+ * @param keys keys of the exchange context
+ * @param exchange representation of the exchange
*/
static void
kyc_with_exchange (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *eh,
- const char *payto_uri,
- const struct TALER_Amount *wire_fee,
- bool exchange_trusted)
+ struct TALER_EXCHANGE_Keys *keys,
+ struct TMH_Exchange *exchange)
{
struct ExchangeKycRequest *ekr = cls;
struct KycContext *kc = ekr->kc;
struct TALER_PaytoHashP h_payto;
- (void) payto_uri;
- (void) wire_fee;
- (void) exchange_trusted;
+ (void) exchange;
ekr->fo = NULL;
- if (MHD_HTTP_OK != hr->http_status)
+ if (NULL == keys)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Exchange responded with HTTP status %u (%d) to /kyc-check request!\n",
- hr->http_status,
- hr->ec);
kc->response_code = MHD_HTTP_BAD_GATEWAY;
GNUNET_assert (
0 ==
json_array_append_new (
kc->timeout_kycs,
GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("exchange_url",
- ekr->exchange_url),
- GNUNET_JSON_pack_uint64 ("exchange_code",
- hr->ec),
- GNUNET_JSON_pack_uint64 ("exchange_http_status",
- hr->http_status))));
+ TALER_JSON_pack_ec (
+ TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE))));
ekr_finished (ekr);
return;
}
TALER_payto_hash (ekr->payto_uri,
&h_payto);
ekr->kyc = TALER_EXCHANGE_kyc_check (
- eh,
+ TMH_curl_ctx,
+ ekr->exchange_url,
+ keys,
ekr->exchange_kyc_serial,
&h_payto,
- /* FIXME: get from settings! */
- TALER_KYCLOGIC_KYC_UT_BUSINESS,
- kc->timeout,
+ ekr->kc->mi->settings.ut,
+ GNUNET_TIME_absolute_get_remaining (kc->timeout),
&exchange_check_cb,
ekr);
}
/**
- * Function called from ``account_kyc_get_status``
- * with KYC status information for this merchant.
+ * Function called from account_kyc_get_status() with KYC status information
+ * for this merchant.
*
* @param cls our `struct KycContext *`
* @param h_wire hash of the wire account
@@ -646,6 +702,7 @@ kyc_with_exchange (void *cls,
* @param exchange_url base URL of the exchange for which this is a status
* @param last_check when did we last get an update on our KYC status from the exchange
* @param kyc_ok true if we satisfied the KYC requirements
+ * @param aml_decision latest AML decision known to us
*/
static void
kyc_status_cb (void *cls,
@@ -654,12 +711,14 @@ kyc_status_cb (void *cls,
const char *payto_uri,
const char *exchange_url,
struct GNUNET_TIME_Timestamp last_check,
- bool kyc_ok)
+ bool kyc_ok,
+ enum TALER_AmlDecisionState aml_decision)
{
struct KycContext *kc = cls;
struct ExchangeKycRequest *ekr;
if (kyc_ok &&
+ (TALER_AML_PENDING != aml_decision) &&
(GNUNET_TIME_relative_cmp (
GNUNET_TIME_absolute_get_duration (last_check.abs_time),
<,
@@ -680,11 +739,9 @@ kyc_status_cb (void *cls,
ekr->exchange_url = GNUNET_strdup (exchange_url);
ekr->payto_uri = GNUNET_strdup (payto_uri);
ekr->last_check = last_check;
- ekr->kyc_ok = kyc_ok;
ekr->kc = kc;
- ekr->fo = TMH_EXCHANGES_find_exchange (exchange_url,
- NULL,
- GNUNET_NO,
+ ekr->fo = TMH_EXCHANGES_keys4exchange (exchange_url,
+ false,
&kyc_with_exchange,
ekr);
}
@@ -721,39 +778,13 @@ get_instances_ID_kyc (struct TMH_MerchantInstance *mi,
kc->timeout_kycs = json_array ();
GNUNET_assert (NULL != kc->timeout_kycs);
- /* process 'timeout_ms' argument */
- {
- const char *long_poll_timeout_s;
-
- long_poll_timeout_s
- = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "timeout_ms");
- if (NULL != long_poll_timeout_s)
- {
- unsigned int timeout_ms;
- char dummy;
-
- if (1 != sscanf (long_poll_timeout_s,
- "%u%c",
- &timeout_ms,
- &dummy))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "timeout_ms must be non-negative number");
- }
- kc->timeout = GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_MILLISECONDS,
- timeout_ms);
- kc->timeout_task
- = GNUNET_SCHEDULER_add_delayed (kc->timeout,
- &handle_kyc_timeout,
- kc);
- }
- } /* end timeout processing */
+ TALER_MHD_parse_request_timeout (connection,
+ &kc->timeout);
+ if (! GNUNET_TIME_absolute_is_past (kc->timeout))
+ kc->timeout_task
+ = GNUNET_SCHEDULER_add_at (kc->timeout,
+ &handle_kyc_timeout,
+ kc);
/* process 'exchange_url' argument */
kc->exchange_url = MHD_lookup_connection_value (connection,
@@ -775,32 +806,10 @@ get_instances_ID_kyc (struct TMH_MerchantInstance *mi,
"exchange_url must be a valid HTTP(s) URL");
}
- /* process 'h_wire' argument */
- {
- const char *h_wire_s;
-
- h_wire_s
- = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "h_wire");
- if (NULL != h_wire_s)
- {
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (h_wire_s,
- strlen (h_wire_s),
- &kc->h_wire,
- sizeof (kc->h_wire)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "h_wire must be Crockford base32 encoded hash");
- }
- kc->have_h_wire = true;
- }
- } /* end of h_wire processing */
-
+ TALER_MHD_parse_request_arg_auto (connection,
+ "h_wire",
+ &kc->h_wire,
+ kc->have_h_wire);
/* Check our database */
{
enum GNUNET_DB_QueryStatus qs;
diff --git a/src/backend/taler-merchant-httpd_private-get-instances-ID.c b/src/backend/taler-merchant-httpd_private-get-instances-ID.c
index 5cc7764f..adc99c39 100644
--- a/src/backend/taler-merchant-httpd_private-get-instances-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-instances-ID.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2019-2021 Taler Systems SA
+ (C) 2019-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -52,6 +52,10 @@ get_instances_ID (struct TMH_MerchantInstance *mi,
GNUNET_JSON_pack_string (
"payto_uri",
wm->payto_uri),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string (
+ "credit_facade_url",
+ wm->credit_facade_url)),
GNUNET_JSON_pack_data_auto ("h_wire",
&wm->h_wire),
GNUNET_JSON_pack_data_auto (
@@ -73,6 +77,9 @@ get_instances_ID (struct TMH_MerchantInstance *mi,
ja),
GNUNET_JSON_pack_string ("name",
mi->settings.name),
+ GNUNET_JSON_pack_string (
+ "user_type",
+ TALER_KYCLOGIC_kyc_user_type2s (mi->settings.ut)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("website",
mi->settings.website)),
@@ -88,12 +95,8 @@ get_instances_ID (struct TMH_MerchantInstance *mi,
mi->settings.address),
GNUNET_JSON_pack_object_incref ("jurisdiction",
mi->settings.jurisdiction),
- TALER_JSON_pack_amount ("default_max_wire_fee",
- &mi->settings.default_max_wire_fee),
- TALER_JSON_pack_amount ("default_max_deposit_fee",
- &mi->settings.default_max_deposit_fee),
- GNUNET_JSON_pack_uint64 ("default_wire_fee_amortization",
- mi->settings.default_wire_fee_amortization),
+ GNUNET_JSON_pack_bool ("use_stefan",
+ mi->settings.use_stefan),
GNUNET_JSON_pack_time_rel ("default_wire_transfer_delay",
mi->settings.default_wire_transfer_delay),
GNUNET_JSON_pack_time_rel ("default_pay_delay",
diff --git a/src/backend/taler-merchant-httpd_private-get-instances.c b/src/backend/taler-merchant-httpd_private-get-instances.c
index dc0f566f..50b5c0c2 100644
--- a/src/backend/taler-merchant-httpd_private-get-instances.c
+++ b/src/backend/taler-merchant-httpd_private-get-instances.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2019-2021 Taler Systems SA
+ (C) 2019-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -74,6 +74,9 @@ add_instance (void *cls,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("name",
mi->settings.name),
+ GNUNET_JSON_pack_string (
+ "user_type",
+ TALER_KYCLOGIC_kyc_user_type2s (mi->settings.ut)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("website",
mi->settings.website)),
diff --git a/src/backend/taler-merchant-httpd_private-get-orders-ID.c b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
index f4e67da9..1c850990 100644
--- a/src/backend/taler-merchant-httpd_private-get-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2017-2022 Taler Systems SA
+ (C) 2017-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -31,13 +31,6 @@
/**
- * How long do we wait on the exchange?
- */
-#define EXCHANGE_TIMEOUT GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_SECONDS, 30)
-
-
-/**
* Data structure we keep for a check payment request.
*/
struct GetOrderRequestContext;
@@ -66,16 +59,6 @@ struct TransferQuery
char *exchange_url;
/**
- * Handle to query exchange about deposit status.
- */
- struct TALER_EXCHANGE_DepositGetHandle *dgh;
-
- /**
- * Handle for ongoing exchange operation.
- */
- struct TMH_EXCHANGES_FindOperation *fo;
-
- /**
* Overall request this TQ belongs with.
*/
struct GetOrderRequestContext *gorc;
@@ -109,12 +92,101 @@ struct TransferQuery
/**
+ * Phases of order processing.
+ */
+enum GetOrderPhase
+{
+ /**
+ * Initialization.
+ */
+ GOP_INIT = 0,
+
+ /**
+ * Obtain contract terms from database.
+ */
+ GOP_FETCH_CONTRACT = 1,
+
+ /**
+ * Parse the contract terms.
+ */
+ GOP_PARSE_CONTRACT = 2,
+
+ /**
+ * Check if the contract was fully paid.
+ */
+ GOP_CHECK_PAID = 3,
+
+ /**
+ * Check if the wallet may have purchased an equivalent
+ * order before and we need to redirect the wallet to
+ * an existing paid order.
+ */
+ GOP_CHECK_REPURCHASE = 4,
+
+ /**
+ * Terminate processing of unpaid orders, either by
+ * suspending until payment or by returning the
+ * unpaid order status.
+ */
+ GOP_UNPAID_FINISH = 5,
+
+ /**
+ * Check if the (paid) order was refunded.
+ */
+ GOP_CHECK_REFUNDS = 6,
+
+ /**
+ * Load all deposits associated with the order.
+ */
+ GOP_CHECK_DEPOSITS = 7,
+
+ /**
+ * Check local records for transfers of funds to
+ * the merchant.
+ */
+ GOP_CHECK_LOCAL_TRANSFERS = 8,
+
+ /**
+ * Generate final comprehensive result.
+ */
+ GOP_REPLY_RESULT = 9,
+
+ /**
+ * End with the HTTP status and error code in
+ * wire_hc and wire_ec.
+ */
+ GOP_ERROR = 10,
+
+ /**
+ * We are suspended awaiting payment.
+ */
+ GOP_SUSPENDED_ON_UNPAID = 11,
+
+ /**
+ * Processing is done, return #MHD_YES.
+ */
+ GOP_END_YES = 12,
+
+ /**
+ * Processing is done, return #MHD_NO.
+ */
+ GOP_END_NO = 13
+
+};
+
+
+/**
* Data structure we keep for a check payment request.
*/
struct GetOrderRequestContext
{
/**
+ * Processing phase we are in.
+ */
+ enum GetOrderPhase phase;
+
+ /**
* Entry in the #resume_timeout_heap for this check payment, if we are
* suspended.
*/
@@ -181,16 +253,25 @@ struct GetOrderRequestContext
json_t *contract_terms;
/**
- * Wire details for the payment, to be returned in the reply. NULL
- * if not available.
+ * Claim token of the order.
*/
- json_t *wire_details;
+ struct TALER_ClaimTokenP claim_token;
/**
- * Problems we encountered when looking up Wire details
- * for the payment, to be returned. NULL if not available.
+ * Timestamp from the @e contract_terms.
*/
- json_t *wire_reports;
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Order summary. Pointer into @e contract_terms.
+ */
+ const char *summary;
+
+ /**
+ * Wire details for the payment, to be returned in the reply. NULL
+ * if not available.
+ */
+ json_t *wire_details;
/**
* Details about refunds, NULL if there are no refunds.
@@ -254,6 +335,12 @@ struct GetOrderRequestContext
enum TALER_ErrorCode wire_ec;
/**
+ * Set to YES if refunded orders should be included when
+ * doing repurchase detection.
+ */
+ enum TALER_EXCHANGE_YesNoAll allow_refunded_for_repurchase;
+
+ /**
* HTTP status to return with @e wire_ec, 0 if @e wire_ec is #TALER_EC_NONE.
*/
unsigned int wire_hc;
@@ -274,17 +361,45 @@ struct GetOrderRequestContext
bool refunded;
/**
+ * True if the order was paid.
+ */
+ bool paid;
+
+ /**
+ * True if the paid session in the database matches
+ * our @e session_id.
+ */
+ bool paid_session_matches;
+
+ /**
+ * True if the exchange wired the money to the merchant.
+ */
+ bool wired;
+
+ /**
+ * True if the order remains unclaimed.
+ */
+ bool order_only;
+
+ /**
* Set to true if this payment has been refunded and
* some refunds remain to be picked up by the wallet.
*/
bool refund_pending;
/**
- * Did the client request us to fetch the wire transfer status?
- * If false, we may still return it if it is available.
+ * Set to true if our database (incorrectly) has refunds
+ * in a different currency than the currency of the
+ * original payment for the order.
*/
- bool transfer_status_requested;
+ bool refund_currency_mismatch;
+ /**
+ * Set to true if our database (incorrectly) has deposits
+ * in a different currency than the currency of the
+ * original payment for the order.
+ */
+ bool deposit_currency_mismatch;
};
@@ -317,51 +432,6 @@ TMH_force_gorc_resume (void)
/**
- * Resume processing the request, cancelling all pending asynchronous
- * operations.
- *
- * @param gorc request to resume
- * @param http_status HTTP status to return, 0 to continue with success
- * @param ec error code for the request, #TALER_EC_NONE on success
- */
-static void
-gorc_resume (struct GetOrderRequestContext *gorc,
- unsigned int http_status,
- enum TALER_ErrorCode ec)
-{
- struct TransferQuery *tq;
-
- if (NULL != gorc->tt)
- {
- GNUNET_SCHEDULER_cancel (gorc->tt);
- gorc->tt = NULL;
- }
- while (NULL != (tq = gorc->tq_head))
- {
- if (NULL != tq->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (tq->fo);
- tq->fo = NULL;
- }
- if (NULL != tq->dgh)
- {
- TALER_EXCHANGE_deposits_get_cancel (tq->dgh);
- tq->dgh = NULL;
- }
- }
- gorc->wire_hc = http_status;
- gorc->wire_ec = ec;
- GNUNET_assert (GNUNET_YES == gorc->suspended);
- GNUNET_CONTAINER_DLL_remove (gorc_head,
- gorc_tail,
- gorc);
- gorc->suspended = GNUNET_NO;
- MHD_resume_connection (gorc->sc.con);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
-}
-
-
-/**
* We have received a trigger from the database
* that we should (possibly) resume the request.
*
@@ -379,11 +449,12 @@ resume_by_event (void *cls,
(void) extra;
(void) extra_size;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming request %p by trigger\n",
- gorc);
+ "Resuming request for order %s by trigger\n",
+ gorc->hc->infix);
if (GNUNET_NO == gorc->suspended)
return; /* duplicate event is possible */
gorc->suspended = GNUNET_NO;
+ gorc->phase = GOP_FETCH_CONTRACT;
GNUNET_CONTAINER_DLL_remove (gorc_head,
gorc_tail,
gorc);
@@ -393,352 +464,520 @@ resume_by_event (void *cls,
/**
- * Add a report about trouble obtaining wire transfer data to the reply.
+ * Clean up the session state for a GET /private/order/ID request.
*
- * @param gorc request to add wire report to
- * @param ec error code to add
- * @param coin_pub public key of the affected coin
- * @param exchange_hr details from exchange, NULL if exchange is blameless
+ * @param cls closure, must be a `struct GetOrderRequestContext *`
*/
static void
-gorc_report (struct GetOrderRequestContext *gorc,
- enum TALER_ErrorCode ec,
- struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_EXCHANGE_HttpResponse *exchange_hr)
+gorc_cleanup (void *cls)
{
- if (NULL != exchange_hr)
- GNUNET_assert (0 ==
- json_array_append_new (
- gorc->wire_reports,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_ec (ec),
- TMH_pack_exchange_reply (exchange_hr),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- coin_pub))));
- else
- GNUNET_assert (0 ==
- json_array_append_new (
- gorc->wire_reports,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_ec (ec),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- coin_pub))));
+ struct GetOrderRequestContext *gorc = cls;
+
+ if (NULL != gorc->contract_terms)
+ json_decref (gorc->contract_terms);
+ if (NULL != gorc->wire_details)
+ json_decref (gorc->wire_details);
+ if (NULL != gorc->refund_details)
+ json_decref (gorc->refund_details);
+ if (NULL != gorc->tt)
+ {
+ GNUNET_SCHEDULER_cancel (gorc->tt);
+ gorc->tt = NULL;
+ }
+ if (NULL != gorc->eh)
+ {
+ TMH_db->event_listen_cancel (gorc->eh);
+ gorc->eh = NULL;
+ }
+ if (NULL != gorc->session_eh)
+ {
+ TMH_db->event_listen_cancel (gorc->session_eh);
+ gorc->session_eh = NULL;
+ }
+ GNUNET_free (gorc);
}
/**
- * Timeout trying to get current wire transfer data from the exchange.
- * Clean up and continue.
+ * Processing the request @a gorc is finished, set the
+ * final return value in phase based on @a mret.
*
- * @param cls closure, must be a `struct GetOrderRequestContext *`
+ * @param[in,out] gorc order context to initialize
+ * @param mret MHD HTTP response status to return
*/
static void
-exchange_timeout_cb (void *cls)
+phase_end (struct GetOrderRequestContext *gorc,
+ MHD_RESULT mret)
{
- struct GetOrderRequestContext *gorc = cls;
+ gorc->phase = (MHD_YES == mret)
+ ? GOP_END_YES
+ : GOP_END_NO;
+}
+
- gorc->tt = NULL;
- gorc_resume (gorc,
- MHD_HTTP_REQUEST_TIMEOUT,
- TALER_EC_GENERIC_TIMEOUT);
+/**
+ * Initialize event callbacks for the order processing.
+ *
+ * @param[in,out] gorc order context to initialize
+ */
+static void
+phase_init (struct GetOrderRequestContext *gorc)
+{
+ struct TMH_HandlerContext *hc = gorc->hc;
+ struct TMH_OrderPayEventP pay_eh = {
+ .header.size = htons (sizeof (pay_eh)),
+ .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
+ .merchant_pub = hc->instance->merchant_pub
+ };
+
+ if (! GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
+ {
+ gorc->phase++;
+ return;
+ }
+
+ GNUNET_CRYPTO_hash (hc->infix,
+ strlen (hc->infix),
+ &pay_eh.h_order_id);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Subscribing to payment triggers for %p\n",
+ gorc);
+ gorc->eh = TMH_db->event_listen (
+ TMH_db->cls,
+ &pay_eh.header,
+ GNUNET_TIME_absolute_get_remaining (gorc->sc.long_poll_timeout),
+ &resume_by_event,
+ gorc);
+ if ( (NULL != gorc->session_id) &&
+ (NULL != gorc->fulfillment_url) )
+ {
+ struct TMH_SessionEventP session_eh = {
+ .header.size = htons (sizeof (session_eh)),
+ .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
+ .merchant_pub = hc->instance->merchant_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Subscribing to session triggers for %p\n",
+ gorc);
+ GNUNET_CRYPTO_hash (gorc->session_id,
+ strlen (gorc->session_id),
+ &session_eh.h_session_id);
+ GNUNET_CRYPTO_hash (gorc->fulfillment_url,
+ strlen (gorc->fulfillment_url),
+ &session_eh.h_fulfillment_url);
+ gorc->session_eh
+ = TMH_db->event_listen (
+ TMH_db->cls,
+ &session_eh.header,
+ GNUNET_TIME_absolute_get_remaining (gorc->sc.long_poll_timeout),
+ &resume_by_event,
+ gorc);
+ }
+ gorc->phase++;
}
/**
- * Function called with detailed wire transfer data.
+ * Obtain latest contract terms from the database.
*
- * @param cls closure with a `struct TransferQuery *`
- * @param dr HTTP response data
+ * @param[in,out] gorc order context to update
*/
static void
-deposit_get_cb (void *cls,
- const struct TALER_EXCHANGE_GetDepositResponse *dr)
+phase_fetch_contract (struct GetOrderRequestContext *gorc)
{
- struct TransferQuery *tq = cls;
- struct GetOrderRequestContext *gorc = tq->gorc;
+ struct TMH_HandlerContext *hc = gorc->hc;
+ enum GNUNET_DB_QueryStatus qs;
- GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
- gorc->tq_tail,
- tq);
- switch (dr->hr.http_status)
+ if (NULL != gorc->contract_terms)
{
- case MHD_HTTP_OK:
- {
- enum GNUNET_DB_QueryStatus qs;
+ /* Free memory filled with old contract terms before fetching the latest
+ ones from the DB. Note that we cannot simply skip the database
+ interaction as the contract terms loaded previously might be from an
+ earlier *unclaimed* order state (which we loaded in a previous
+ invocation of this function and we are back here due to long polling)
+ and thus the contract terms could have changed during claiming. Thus,
+ we need to fetch the latest contract terms from the DB again. */
+ json_decref (gorc->contract_terms);
+ gorc->contract_terms = NULL;
+ gorc->fulfillment_url = NULL;
+ gorc->summary = NULL;
+ gorc->order_only = false;
+ }
+ TMH_db->preflight (TMH_db->cls);
+ qs = TMH_db->lookup_contract_terms3 (TMH_db->cls,
+ hc->instance->settings.id,
+ hc->infix,
+ gorc->session_id,
+ &gorc->contract_terms,
+ &gorc->order_serial,
+ &gorc->paid,
+ &gorc->wired,
+ &gorc->paid_session_matches,
+ &gorc->claim_token);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "lookup_contract_terms (%s) returned %d\n",
+ hc->infix,
+ (int) qs);
+ if (0 > qs)
+ {
+ /* single, read-only SQL statements should never cause
+ serialization problems */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "contract terms"));
+ return;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order %s is %s (%s) according to database\n",
+ hc->infix,
+ gorc->paid ? "paid" : "unpaid",
+ gorc->wired ? "wired" : "unwired");
+ gorc->phase++;
+ return;
+ }
+ GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
+ GNUNET_assert (! gorc->paid);
+ /* No contract, only order, fetch from orders table */
+ gorc->order_only = true;
+ {
+ struct TALER_MerchantPostDataHashP unused;
- qs = TMH_db->insert_deposit_to_transfer (TMH_db->cls,
- tq->deposit_serial,
- &dr->details.success);
- if (qs < 0)
- {
- gorc_report (gorc,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- &tq->coin_pub,
- NULL);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- if (NULL == gorc->tq_head)
- gorc_resume (gorc,
- 0,
- TALER_EC_NONE);
- return;
- }
- /* Compute total amount *wired* */
- if (0 >
- TALER_amount_add (&gorc->deposits_total,
- &gorc->deposits_total,
- &dr->details.success.coin_contribution))
- {
- gorc_report (gorc,
- TALER_EC_MERCHANT_PRIVATE_GET_ORDERS_ID_AMOUNT_ARITHMETIC_FAILURE,
- &tq->coin_pub,
- NULL);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- if (NULL == gorc->tq_head)
- gorc_resume (gorc,
- 0,
- TALER_EC_NONE);
- return;
- }
- if (0 >
- TALER_amount_add (&gorc->deposit_fees_total,
- &gorc->deposit_fees_total,
- &tq->deposit_fee))
- {
- gorc_report (gorc,
- TALER_EC_MERCHANT_PRIVATE_GET_ORDERS_ID_AMOUNT_ARITHMETIC_FAILURE,
- &tq->coin_pub,
- NULL);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- if (NULL == gorc->tq_head)
- gorc_resume (gorc,
- 0,
- TALER_EC_NONE);
- return;
- }
- break;
- }
- case MHD_HTTP_ACCEPTED:
- {
- /* got a 'preliminary' reply from the exchange,
- remember our target UUID */
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Timestamp now;
-
- now = GNUNET_TIME_timestamp_get ();
- qs = TMH_db->account_kyc_set_status (
- TMH_db->cls,
- gorc->hc->instance->settings.id,
- &tq->h_wire,
- tq->exchange_url,
- dr->details.accepted.requirement_row,
- NULL,
- NULL,
- now,
- false);
- if (qs < 0)
- {
- gorc_report (gorc,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- &tq->coin_pub,
- NULL);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- if (NULL == gorc->tq_head)
- gorc_resume (gorc,
- 0,
- TALER_EC_NONE);
- return;
- }
- gorc_report (gorc,
- TALER_EC_NONE,
- &tq->coin_pub,
- &dr->hr);
- break;
- }
- default:
+ /* We need the order for two cases: Either when the contract doesn't exist yet,
+ * or when the order is claimed but unpaid, and we need the claim token. */
+ qs = TMH_db->lookup_order (TMH_db->cls,
+ hc->instance->settings.id,
+ hc->infix,
+ &gorc->claim_token,
+ &unused,
+ &gorc->contract_terms);
+ }
+ if (0 > qs)
+ {
+ /* single, read-only SQL statements should never cause
+ serialization problems */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "order"));
+ return;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
+ hc->infix));
+ return;
+ }
+ gorc->phase++;
+}
+
+
+/**
+ * Obtain parse contract terms of the order. Extracts the fulfillment URL,
+ * total amount, summary and timestamp from the contract terms!
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_parse_contract (struct GetOrderRequestContext *gorc)
+{
+ struct TMH_HandlerContext *hc = gorc->hc;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &gorc->contract_amount),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("fulfillment_url",
+ &gorc->fulfillment_url),
+ NULL),
+ GNUNET_JSON_spec_string ("summary",
+ &gorc->summary),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &gorc->timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (gorc->contract_terms,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (
+ gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ hc->infix));
+ return;
+ }
+ if (! gorc->order_only)
+ {
+ if (GNUNET_OK !=
+ TALER_JSON_contract_hash (gorc->contract_terms,
+ &gorc->h_contract_terms))
{
- gorc_report (gorc,
- TALER_EC_MERCHANT_GET_ORDERS_EXCHANGE_TRACKING_FAILURE,
- &tq->coin_pub,
- &dr->hr);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- if (NULL == gorc->tq_head)
- gorc_resume (gorc,
- 0,
- TALER_EC_NONE);
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+ NULL));
return;
}
- } /* end switch */
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- if (NULL != gorc->tq_head)
- return;
- /* *all* are done, resume! */
- gorc_resume (gorc,
- 0,
- TALER_EC_NONE);
+ }
+ GNUNET_assert (NULL != gorc->contract_terms);
+ gorc->phase++;
}
/**
- * Function called with the result of a #TMH_EXCHANGES_find_exchange()
- * operation.
+ * Check payment status of the order.
*
- * @param cls closure with a `struct GetOrderRequestContext *`
- * @param hr HTTP response details
- * @param eh handle to the exchange context
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
- * @param exchange_trusted true if this exchange is trusted by config
+ * @param[in,out] gorc order context to update
*/
static void
-exchange_found_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *eh,
- const char *payto_uri,
- const struct TALER_Amount *wire_fee,
- bool exchange_trusted)
+phase_check_paid (struct GetOrderRequestContext *gorc)
{
- struct TransferQuery *tq = cls;
- struct GetOrderRequestContext *gorc = tq->gorc;
+ struct TMH_HandlerContext *hc = gorc->hc;
- tq->fo = NULL;
- if (NULL == hr)
+ if (gorc->order_only)
{
- /* failed */
- GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
- gorc->tq_tail,
- tq);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- gorc_resume (gorc,
- MHD_HTTP_GATEWAY_TIMEOUT,
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order %s unclaimed, no need to lookup payment status\n",
+ hc->infix);
+ GNUNET_assert (! gorc->paid);
+ GNUNET_assert (! gorc->wired);
+ gorc->phase++;
return;
}
- if (NULL == eh)
+ if (NULL == gorc->session_id)
{
- /* failed */
- GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
- gorc->tq_tail,
- tq);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- gorc->exchange_hc = hr->http_status;
- gorc->exchange_ec = hr->ec;
- gorc_resume (gorc,
- MHD_HTTP_BAD_GATEWAY,
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No session ID, do not need to lookup session-ID specific payment status (%s/%s)\n",
+ gorc->paid ? "paid" : "unpaid",
+ gorc->wired ? "wired" : "unwired");
+ gorc->phase++;
return;
}
- tq->dgh = TALER_EXCHANGE_deposits_get (eh,
- &gorc->hc->instance->merchant_priv,
- &tq->h_wire,
- &gorc->h_contract_terms,
- &tq->coin_pub,
- &deposit_get_cb,
- tq);
- if (NULL == tq->dgh)
+ if (! gorc->paid_session_matches)
{
- GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
- gorc->tq_tail,
- tq);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- gorc_resume (gorc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_GET_ORDERS_ID_EXCHANGE_REQUEST_FAILURE);
+ gorc->paid = false;
+ gorc->wired = false;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order %s %s for session %s (%s)\n",
+ hc->infix,
+ gorc->paid ? "paid" : "unpaid",
+ gorc->session_id,
+ gorc->wired ? "wired" : "unwired");
+ gorc->phase++;
}
/**
- * Function called with each @a coin_pub that was deposited into the
- * @a h_wire account of the merchant for the @a deposit_serial as part
- * of the payment for the order identified by @a cls.
+ * Check if re-purchase detection applies to the order.
*
- * Queries the exchange for the payment status associated with the
- * given coin.
- *
- * @param cls a `struct GetOrderRequestContext`
- * @param deposit_serial identifies the deposit operation
- * @param exchange_url URL of the exchange that issued @a coin_pub
- * @param amount_with_fee amount the exchange will deposit for this coin
- * @param deposit_fee fee the exchange will charge for this coin
- * @param h_wire hash of the merchant's wire account into which the deposit was made
- * @param coin_pub public key of the deposited coin
+ * @param[in,out] gorc order context to update
*/
static void
-deposit_cb (void *cls,
- uint64_t deposit_serial,
- const char *exchange_url,
- const struct TALER_MerchantWireHashP *h_wire,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct TALER_CoinSpendPublicKeyP *coin_pub)
+phase_check_repurchase (struct GetOrderRequestContext *gorc)
{
- struct GetOrderRequestContext *gorc = cls;
- struct TransferQuery *tq;
+ struct TMH_HandlerContext *hc = gorc->hc;
+ char *already_paid_order_id = NULL;
+ enum GNUNET_DB_QueryStatus qs;
+ char *taler_pay_uri;
+ char *order_status_url;
+ MHD_RESULT ret;
- tq = GNUNET_new (struct TransferQuery);
- tq->gorc = gorc;
- tq->exchange_url = GNUNET_strdup (exchange_url);
- tq->deposit_serial = deposit_serial;
- GNUNET_CONTAINER_DLL_insert (gorc->tq_head,
- gorc->tq_tail,
- tq);
- tq->coin_pub = *coin_pub;
- tq->h_wire = *h_wire;
- tq->amount_with_fee = *amount_with_fee;
- tq->deposit_fee = *deposit_fee;
- tq->fo = TMH_EXCHANGES_find_exchange (exchange_url,
- NULL,
- GNUNET_NO,
- &exchange_found_cb,
- tq);
- if (NULL == tq->fo)
+ if ( (gorc->paid) ||
+ (NULL == gorc->fulfillment_url) ||
+ (NULL == gorc->session_id) )
{
- gorc_resume (gorc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_GET_ORDERS_ID_EXCHANGE_LOOKUP_START_FAILURE);
+ /* Repurchase cannot apply */
+ gorc->phase++;
+ return;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Running re-purchase detection for %s/%s\n",
+ gorc->session_id,
+ gorc->fulfillment_url);
+ qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls,
+ hc->instance->settings.id,
+ gorc->fulfillment_url,
+ gorc->session_id,
+ TALER_EXCHANGE_YNA_NO !=
+ gorc->allow_refunded_for_repurchase,
+ &already_paid_order_id);
+ if (0 > qs)
+ {
+ /* single, read-only SQL statements should never cause
+ serialization problems, and the entry should exist as per above */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "order by fulfillment"));
+ return;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "No already paid order for %s/%s\n",
+ gorc->session_id,
+ gorc->fulfillment_url);
+ gorc->phase++;
+ return;
+ }
+
+ /* User did pay for this order, but under a different session; ask wallet to
+ switch order ID */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Found already paid order %s\n",
+ already_paid_order_id);
+ taler_pay_uri = TMH_make_taler_pay_uri (gorc->sc.con,
+ hc->infix,
+ gorc->session_id,
+ hc->instance->settings.id,
+ &gorc->claim_token);
+ order_status_url = TMH_make_order_status_url (gorc->sc.con,
+ hc->infix,
+ gorc->session_id,
+ hc->instance->settings.id,
+ &gorc->claim_token,
+ NULL);
+ if ( (NULL == taler_pay_uri) ||
+ (NULL == order_status_url) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (taler_pay_uri);
+ GNUNET_free (order_status_url);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
+ "host"));
+ return;
+ }
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ gorc->sc.con,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("taler_pay_uri",
+ taler_pay_uri),
+ GNUNET_JSON_pack_string ("order_status_url",
+ order_status_url),
+ GNUNET_JSON_pack_string ("order_status",
+ "unpaid"),
+ GNUNET_JSON_pack_string ("already_paid_order_id",
+ already_paid_order_id),
+ GNUNET_JSON_pack_string ("already_paid_fulfillment_url",
+ gorc->fulfillment_url),
+ TALER_JSON_pack_amount ("total_amount",
+ &gorc->contract_amount),
+ GNUNET_JSON_pack_string ("summary",
+ gorc->summary),
+ GNUNET_JSON_pack_timestamp ("creation_time",
+ gorc->timestamp));
+ GNUNET_free (taler_pay_uri);
+ GNUNET_free (already_paid_order_id);
+ phase_end (gorc,
+ ret);
}
/**
- * Clean up the session state for a GET /private/order/ID request.
+ * Check if we should suspend until the order is paid.
*
- * @param cls closure, must be a `struct GetOrderRequestContext *`
+ * @param[in,out] gorc order context to update
*/
static void
-gorc_cleanup (void *cls)
+phase_unpaid_finish (struct GetOrderRequestContext *gorc)
{
- struct GetOrderRequestContext *gorc = cls;
+ struct TMH_HandlerContext *hc = gorc->hc;
+ char *taler_pay_uri;
+ char *order_status_url;
+ MHD_RESULT ret;
- if (NULL != gorc->contract_terms)
- json_decref (gorc->contract_terms);
- if (NULL != gorc->wire_details)
- json_decref (gorc->wire_details);
- if (NULL != gorc->refund_details)
- json_decref (gorc->refund_details);
- if (NULL != gorc->wire_reports)
- json_decref (gorc->wire_reports);
- GNUNET_assert (NULL == gorc->tt);
- if (NULL != gorc->eh)
+ if (gorc->paid)
{
- TMH_db->event_listen_cancel (gorc->eh);
- gorc->eh = NULL;
+ gorc->phase++;
+ return;
}
- if (NULL != gorc->session_eh)
+ /* User never paid for this order, suspend waiting
+ on payment or return details. */
+ if (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
{
- TMH_db->event_listen_cancel (gorc->session_eh);
- gorc->session_eh = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending GET /private/orders/%s\n",
+ hc->infix);
+ GNUNET_CONTAINER_DLL_insert (gorc_head,
+ gorc_tail,
+ gorc);
+ gorc->phase = GOP_SUSPENDED_ON_UNPAID;
+ gorc->suspended = GNUNET_YES;
+ MHD_suspend_connection (gorc->sc.con);
+ return;
}
- GNUNET_free (gorc);
+ if (! gorc->order_only)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order %s claimed but not paid yet\n",
+ hc->infix);
+ phase_end (gorc,
+ TALER_MHD_REPLY_JSON_PACK (
+ gorc->sc.con,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_object_incref ("contract_terms",
+ gorc->contract_terms),
+ GNUNET_JSON_pack_string ("order_status",
+ "claimed")));
+ return;
+ }
+ taler_pay_uri = TMH_make_taler_pay_uri (gorc->sc.con,
+ hc->infix,
+ gorc->session_id,
+ hc->instance->settings.id,
+ &gorc->claim_token);
+ order_status_url = TMH_make_order_status_url (gorc->sc.con,
+ hc->infix,
+ gorc->session_id,
+ hc->instance->settings.id,
+ &gorc->claim_token,
+ NULL);
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ gorc->sc.con,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("taler_pay_uri",
+ taler_pay_uri),
+ GNUNET_JSON_pack_string ("order_status_url",
+ order_status_url),
+ GNUNET_JSON_pack_string ("order_status",
+ "unpaid"),
+ TALER_JSON_pack_amount ("total_amount",
+ &gorc->contract_amount),
+ GNUNET_JSON_pack_string ("summary",
+ gorc->summary),
+ GNUNET_JSON_pack_timestamp ("creation_time",
+ gorc->timestamp));
+ GNUNET_free (taler_pay_uri);
+ GNUNET_free (order_status_url);
+ phase_end (gorc,
+ ret);
+
}
@@ -769,6 +1008,11 @@ process_refunds_cb (void *cls,
{
struct GetOrderRequestContext *gorc = cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found refund %llu over %s for reason %s\n",
+ (unsigned long long) rtransaction_id,
+ TALER_amount2s (refund_amount),
+ reason);
GNUNET_assert (0 ==
json_array_append_new (
gorc->refund_details,
@@ -791,12 +1035,29 @@ process_refunds_cb (void *cls,
GNUNET_memcmp (&tq->coin_pub,
coin_pub))
{
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (
+ &gorc->deposit_fees_total,
+ &tq->deposit_fee))
+ {
+ gorc->refund_currency_mismatch = true;
+ return;
+ }
+
GNUNET_assert (0 <=
TALER_amount_subtract (&gorc->deposit_fees_total,
&gorc->deposit_fees_total,
&tq->deposit_fee));
}
}
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (
+ &gorc->refund_amount,
+ refund_amount))
+ {
+ gorc->refund_currency_mismatch = true;
+ return;
+ }
GNUNET_assert (0 <=
TALER_amount_add (&gorc->refund_amount,
&gorc->refund_amount,
@@ -807,6 +1068,123 @@ process_refunds_cb (void *cls,
/**
+ * Check refund status for the order.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_check_refunds (struct GetOrderRequestContext *gorc)
+{
+ struct TMH_HandlerContext *hc = gorc->hc;
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_assert (! gorc->order_only);
+ GNUNET_assert (gorc->paid);
+ /* Accumulate refunds, if any. */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (gorc->contract_amount.currency,
+ &gorc->refund_amount));
+ qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
+ hc->instance->settings.id,
+ &gorc->h_contract_terms,
+ &process_refunds_cb,
+ gorc);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "detailed refunds"));
+ return;
+ }
+ if (gorc->refund_currency_mismatch)
+ {
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "refunds in different currency than original order price"));
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Total refunds are %s\n",
+ TALER_amount2s (&gorc->refund_amount));
+ gorc->phase++;
+}
+
+
+/**
+ * Function called with each @a coin_pub that was deposited into the
+ * @a h_wire account of the merchant for the @a deposit_serial as part
+ * of the payment for the order identified by @a cls.
+ *
+ * Queries the exchange for the payment status associated with the
+ * given coin.
+ *
+ * @param cls a `struct GetOrderRequestContext`
+ * @param deposit_serial identifies the deposit operation
+ * @param exchange_url URL of the exchange that issued @a coin_pub
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param h_wire hash of the merchant's wire account into which the deposit was made
+ * @param coin_pub public key of the deposited coin
+ */
+static void
+deposit_cb (void *cls,
+ uint64_t deposit_serial,
+ const char *exchange_url,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub)
+{
+ struct GetOrderRequestContext *gorc = cls;
+ struct TransferQuery *tq;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Checking deposit status for coin %s (over %s)\n",
+ TALER_B2S (coin_pub),
+ TALER_amount2s (amount_with_fee));
+ tq = GNUNET_new (struct TransferQuery);
+ tq->gorc = gorc;
+ tq->exchange_url = GNUNET_strdup (exchange_url);
+ tq->deposit_serial = deposit_serial;
+ GNUNET_CONTAINER_DLL_insert (gorc->tq_head,
+ gorc->tq_tail,
+ tq);
+ tq->coin_pub = *coin_pub;
+ tq->h_wire = *h_wire;
+ tq->amount_with_fee = *amount_with_fee;
+ tq->deposit_fee = *deposit_fee;
+}
+
+
+/**
+ * Check wire transfer status for the order at the exchange.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_check_deposits (struct GetOrderRequestContext *gorc)
+{
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (gorc->contract_amount.currency,
+ &gorc->deposits_total));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (gorc->contract_amount.currency,
+ &gorc->deposit_fees_total));
+ TMH_db->lookup_deposits_by_order (TMH_db->cls,
+ gorc->order_serial,
+ &deposit_cb,
+ gorc);
+ gorc->phase++;
+}
+
+
+/**
* Function called with available wire details, to be added to
* the response.
*
@@ -820,32 +1198,43 @@ process_refunds_cb (void *cls,
* @a wtid over the total amount happened?
*/
static void
-process_transfer_details (void *cls,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const char *exchange_url,
- struct GNUNET_TIME_Timestamp execution_time,
- const struct TALER_Amount *deposit_value,
- const struct TALER_Amount *deposit_fee,
- bool transfer_confirmed)
+process_transfer_details (
+ void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const char *exchange_url,
+ struct GNUNET_TIME_Timestamp execution_time,
+ const struct TALER_Amount *deposit_value,
+ const struct TALER_Amount *deposit_fee,
+ bool transfer_confirmed)
{
struct GetOrderRequestContext *gorc = cls;
json_t *wire_details = gorc->wire_details;
struct TALER_Amount wired;
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (&gorc->deposits_total,
+ deposit_value)) ||
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&gorc->deposit_fees_total,
+ deposit_fee)) )
+ {
+ GNUNET_break (0);
+ gorc->deposit_currency_mismatch = true;
+ return;
+ }
+
/* Compute total amount *wired* */
- GNUNET_assert (0 <
+ GNUNET_assert (0 <=
TALER_amount_add (&gorc->deposits_total,
&gorc->deposits_total,
deposit_value));
- GNUNET_assert (0 <
+ GNUNET_assert (0 <=
TALER_amount_add (&gorc->deposit_fees_total,
&gorc->deposit_fees_total,
deposit_fee));
-
- GNUNET_assert
- (0 <= TALER_amount_subtract (&wired,
- deposit_value,
- deposit_fee));
+ GNUNET_assert (0 <= TALER_amount_subtract (&wired,
+ deposit_value,
+ deposit_fee));
GNUNET_assert (0 ==
json_array_append_new (
wire_details,
@@ -863,652 +1252,288 @@ process_transfer_details (void *cls,
}
-MHD_RESULT
-TMH_private_get_orders_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
+/**
+ * Check transfer status in local database.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_check_local_transfers (struct GetOrderRequestContext *gorc)
{
- struct GetOrderRequestContext *gorc = hc->ctx;
+ struct TMH_HandlerContext *hc = gorc->hc;
enum GNUNET_DB_QueryStatus qs;
- bool paid;
- bool wired;
- bool order_only = false;
- struct TALER_ClaimTokenP claim_token = { 0 };
- const char *summary;
- struct GNUNET_TIME_Timestamp timestamp;
- if (NULL == gorc)
- {
- /* First time here, parse request and check order is known */
- GNUNET_assert (NULL != hc->infix);
- gorc = GNUNET_new (struct GetOrderRequestContext);
- hc->cc = &gorc_cleanup;
- hc->ctx = gorc;
- gorc->sc.con = connection;
- gorc->hc = hc;
- gorc->wire_details = json_array ();
- GNUNET_assert (NULL != gorc->wire_details);
- gorc->refund_details = json_array ();
- GNUNET_assert (NULL != gorc->refund_details);
- gorc->wire_reports = json_array ();
- GNUNET_assert (NULL != gorc->wire_reports);
- gorc->session_id = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "session_id");
- /* process 'transfer' argument */
- {
- const char *transfer_s;
-
- transfer_s = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "transfer");
- if ( (NULL != transfer_s) &&
- (0 == strcasecmp (transfer_s,
- "yes")) )
- gorc->transfer_status_requested = true;
- }
-
- /* process 'timeout_ms' argument */
- {
- const char *long_poll_timeout_s;
-
- long_poll_timeout_s = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "timeout_ms");
- if (NULL != long_poll_timeout_s)
- {
- unsigned int timeout_ms;
- char dummy;
- struct GNUNET_TIME_Relative timeout;
-
- if (1 != sscanf (long_poll_timeout_s,
- "%u%c",
- &timeout_ms,
- &dummy))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "timeout_ms must be non-negative number");
- }
- timeout = GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_MILLISECONDS,
- timeout_ms);
- gorc->sc.long_poll_timeout
- = GNUNET_TIME_relative_to_absolute (timeout);
- if (! GNUNET_TIME_relative_is_zero (timeout))
- {
- struct TMH_OrderPayEventP pay_eh = {
- .header.size = htons (sizeof (pay_eh)),
- .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
- .merchant_pub = hc->instance->merchant_pub
- };
-
- GNUNET_CRYPTO_hash (hc->infix,
- strlen (hc->infix),
- &pay_eh.h_order_id);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Subscribing to payment triggers for %p\n",
- gorc);
- gorc->eh = TMH_db->event_listen (TMH_db->cls,
- &pay_eh.header,
- timeout,
- &resume_by_event,
- gorc);
- if ( (NULL != gorc->session_id) &&
- (NULL != gorc->fulfillment_url) )
- {
- struct TMH_SessionEventP session_eh = {
- .header.size = htons (sizeof (session_eh)),
- .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
- .merchant_pub = hc->instance->merchant_pub
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Subscribing to session triggers for %p\n",
- gorc);
- GNUNET_CRYPTO_hash (gorc->session_id,
- strlen (gorc->session_id),
- &session_eh.h_session_id);
- GNUNET_CRYPTO_hash (gorc->fulfillment_url,
- strlen (gorc->fulfillment_url),
- &session_eh.h_fulfillment_url);
- gorc->session_eh = TMH_db->event_listen (TMH_db->cls,
- &session_eh.header,
- timeout,
- &resume_by_event,
- gorc);
- }
- }
- }
- else
- {
- gorc->sc.long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
- }
- }
- } /* end first-time per-request initialization */
-
- if (GNUNET_SYSERR == gorc->suspended)
- return MHD_NO; /* we are in shutdown */
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Starting GET /private/orders/%s processing with timeout %s\n",
- hc->infix,
- GNUNET_STRINGS_absolute_time_to_string (
- gorc->sc.long_poll_timeout));
- if (NULL != gorc->contract_terms)
- {
- /* Free memory filled with old contract terms before fetching the latest
- ones from the DB. Note that we cannot simply skip the database
- interaction as the contract terms loaded previously might be from an
- earlier *unclaimed* order state (which we loaded in a previous
- invocation of this function and we are back here due to long polling)
- and thus the contract terms could have changed during claiming. Thus,
- we need to fetch the latest contract terms from the DB again. */
- json_decref (gorc->contract_terms);
- gorc->contract_terms = NULL;
- gorc->fulfillment_url = NULL;
- }
- TMH_db->preflight (TMH_db->cls);
- {
- bool paid = false;
-
- qs = TMH_db->lookup_contract_terms (TMH_db->cls,
- hc->instance->settings.id,
- hc->infix,
- &gorc->contract_terms,
- &gorc->order_serial,
- &paid,
- NULL);
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (gorc->contract_amount.currency,
+ &gorc->deposits_total));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (gorc->contract_amount.currency,
+ &gorc->deposit_fees_total));
+ qs = TMH_db->lookup_transfer_details_by_order (TMH_db->cls,
+ gorc->order_serial,
+ &process_transfer_details,
+ gorc);
+ if (0 > qs)
{
- order_only = true;
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "transfer details"));
+ return;
}
- if (0 > qs)
+ if (gorc->deposit_currency_mismatch)
{
- /* single, read-only SQL statements should never cause
- serialization problems */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- /* Always report on hard error as well to enable diagnostics */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "contract terms");
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "deposits in different currency than original order price"));
+ return;
}
+ if (! gorc->wired)
{
- struct TALER_MerchantPostDataHashP unused;
- json_t *ct = NULL;
-
- /* We need the order for two cases: Either when the contract doesn't exist yet,
- * or when the order is claimed but unpaid, and we need the claim token. */
- qs = TMH_db->lookup_order (TMH_db->cls,
- hc->instance->settings.id,
- hc->infix,
- &claim_token,
- &unused,
- &ct);
-
- if (0 > qs)
- {
- /* single, read-only SQL statements should never cause
- serialization problems */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- /* Always report on hard error as well to enable diagnostics */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "order");
- }
- if (order_only && (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) )
+ /* we believe(d) the wire transfer did not happen yet, check if maybe
+ in light of new evidence it did */
+ struct TALER_Amount expect_total;
+
+ if (0 >
+ TALER_amount_subtract (&expect_total,
+ &gorc->contract_amount,
+ &gorc->refund_amount))
{
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
- hc->infix);
- }
- if (order_only)
- {
- gorc->contract_terms = ct;
- }
- else if (NULL != ct)
- {
- json_decref (ct);
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (
+ gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ "refund exceeds contract value"));
+ return;
}
- }
- /* extract the fulfillment URL, total amount, summary and timestamp
- from the contract terms! */
- {
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("amount",
- TMH_currency,
- &gorc->contract_amount),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("fulfillment_url",
- &gorc->fulfillment_url),
- NULL),
- GNUNET_JSON_spec_string ("summary",
- &summary),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &timestamp),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (gorc->contract_terms,
- spec,
- NULL, NULL))
+ if (0 >
+ TALER_amount_subtract (&expect_total,
+ &expect_total,
+ &gorc->deposit_fees_total))
{
GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
- hc->infix);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (
+ gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ "deposit fees exceed total minus refunds"));
+ return;
}
- }
- if (! order_only)
- {
- if (GNUNET_OK !=
- TALER_JSON_contract_hash (gorc->contract_terms,
- &gorc->h_contract_terms))
+ if (0 >=
+ TALER_amount_cmp (&expect_total,
+ &gorc->deposits_total))
{
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
- NULL);
+ /* expect_total <= gorc->deposits_total: good: we got the wire transfer */
+ gorc->wired = true;
+ qs = TMH_db->mark_order_wired (TMH_db->cls,
+ gorc->order_serial);
+ GNUNET_break (qs >= 0); /* just warn if transaction failed */
+ TMH_notify_order_change (hc->instance,
+ TMH_OSF_PAID
+ | TMH_OSF_WIRED,
+ gorc->timestamp,
+ gorc->order_serial);
}
}
- if (TALER_EC_NONE != gorc->wire_ec)
- {
- return TALER_MHD_reply_with_error (connection,
- gorc->wire_hc,
- gorc->wire_ec,
- NULL);
- }
+ gorc->phase++;
+}
- GNUNET_assert (NULL != gorc->contract_terms);
- TMH_db->preflight (TMH_db->cls);
- if (order_only)
- {
- paid = false;
- wired = false;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Order %s unclaimed, no need to lookup payment status\n",
- hc->infix);
- }
- else
- {
- qs = TMH_db->lookup_payment_status (TMH_db->cls,
- gorc->order_serial,
- gorc->session_id,
- &paid,
- &wired);
- if (0 > qs)
- {
- /* single, read-only SQL statements should never cause
- serialization problems, and the entry should exist as per above */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "payment status");
- }
- }
- if ( (! paid) &&
- (NULL != gorc->fulfillment_url) &&
- (NULL != gorc->session_id) )
- {
- char *already_paid_order_id = NULL;
+/**
+ * Generate final result for the status request.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_reply_result (struct GetOrderRequestContext *gorc)
+{
+ struct TMH_HandlerContext *hc = gorc->hc;
+ MHD_RESULT ret;
+ char *order_status_url;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Running re-purchase detection for %s/%s\n",
- gorc->session_id,
- gorc->fulfillment_url);
- qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls,
- hc->instance->settings.id,
- gorc->fulfillment_url,
- gorc->session_id,
- &already_paid_order_id);
- if (0 > qs)
- {
- /* single, read-only SQL statements should never cause
- serialization problems, and the entry should exist as per above */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "order by fulfillment");
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- /* User did pay for this order, but under a different session; ask wallet
- to switch order ID */
- char *taler_pay_uri;
- char *order_status_url;
- MHD_RESULT ret;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Found already paid order %s\n",
- already_paid_order_id);
- taler_pay_uri = TMH_make_taler_pay_uri (connection,
- hc->infix,
- gorc->session_id,
- hc->instance->settings.id,
- &claim_token);
- order_status_url = TMH_make_order_status_url (connection,
- hc->infix,
- gorc->session_id,
- hc->instance->settings.id,
- &claim_token,
- NULL);
- if ( (NULL == taler_pay_uri) ||
- (NULL == order_status_url) )
- {
- GNUNET_break_op (0);
- GNUNET_free (taler_pay_uri);
- GNUNET_free (order_status_url);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
- "host");
- }
- ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_string ("taler_pay_uri",
- taler_pay_uri),
- GNUNET_JSON_pack_string ("order_status_url",
- order_status_url),
- GNUNET_JSON_pack_string ("order_status",
- "unpaid"),
- GNUNET_JSON_pack_string ("already_paid_order_id",
- already_paid_order_id),
- GNUNET_JSON_pack_string ("already_paid_fulfillment_url",
- gorc->fulfillment_url),
- TALER_JSON_pack_amount ("total_amount",
- &gorc->contract_amount),
- GNUNET_JSON_pack_string ("summary",
- summary),
- GNUNET_JSON_pack_timestamp ("creation_time",
- timestamp));
- GNUNET_free (taler_pay_uri);
- GNUNET_free (already_paid_order_id);
- return ret;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "No already paid order for %s/%s\n",
- gorc->session_id,
- gorc->fulfillment_url);
- }
- if ( (! paid) &&
- (! order_only) )
{
- if (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Suspending GET /private/orders/%s\n",
- hc->infix);
- GNUNET_CONTAINER_DLL_insert (gorc_head,
- gorc_tail,
- gorc);
- gorc->suspended = GNUNET_YES;
- MHD_suspend_connection (gorc->sc.con);
- return MHD_YES;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Order %s claimed but not paid yet\n",
- hc->infix);
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_object_incref ("contract_terms",
- gorc->contract_terms),
- GNUNET_JSON_pack_string ("order_status",
- "claimed"));
- }
- if (paid &&
- (! wired) &&
- gorc->transfer_status_requested)
- {
- /* suspend connection, wait for exchange to check wire transfer status there */
- gorc->transfer_status_requested = false; /* only try ONCE */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TMH_currency,
- &gorc->deposits_total));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TMH_currency,
- &gorc->deposit_fees_total));
- TMH_db->lookup_deposits_by_order (TMH_db->cls,
- gorc->order_serial,
- &deposit_cb,
- gorc);
- if (NULL != gorc->tq_head)
- {
- GNUNET_CONTAINER_DLL_insert (gorc_head,
- gorc_tail,
- gorc);
- gorc->suspended = GNUNET_YES;
- MHD_suspend_connection (connection);
- gorc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
- &exchange_timeout_cb,
- gorc);
- return MHD_YES;
- }
+ struct TALER_PrivateContractHashP *h_contract = NULL;
+
+ /* In a session-bound payment, allow the browser to check the order
+ * status page (e.g. to get a refund).
+ *
+ * Note that we don't allow this outside of session-based payment, as
+ * otherwise this becomes an oracle to convert order_id to h_contract.
+ */
+ if (NULL != gorc->session_id)
+ h_contract = &gorc->h_contract_terms;
+
+ order_status_url =
+ TMH_make_order_status_url (gorc->sc.con,
+ hc->infix,
+ gorc->session_id,
+ hc->instance->settings.id,
+ &gorc->claim_token,
+ h_contract);
}
- if ( (! paid) &&
- (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout)) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Suspending GET /private/orders/%s\n",
- hc->infix);
- GNUNET_assert (GNUNET_NO == gorc->suspended);
- GNUNET_CONTAINER_DLL_insert (gorc_head,
- gorc_tail,
- gorc);
- gorc->suspended = GNUNET_YES;
- MHD_suspend_connection (gorc->sc.con);
- return MHD_YES;
- }
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ gorc->sc.con,
+ MHD_HTTP_OK,
+ // Deprecated in protocol v6.
+ GNUNET_JSON_pack_array_steal ("wire_reports",
+ json_array ()),
+ GNUNET_JSON_pack_uint64 ("exchange_code",
+ gorc->exchange_ec),
+ GNUNET_JSON_pack_uint64 ("exchange_http_status",
+ gorc->exchange_hc),
+ /* legacy: */
+ GNUNET_JSON_pack_uint64 ("exchange_ec",
+ gorc->exchange_ec),
+ /* legacy: */
+ GNUNET_JSON_pack_uint64 ("exchange_hc",
+ gorc->exchange_hc),
+ TALER_JSON_pack_amount ("deposit_total",
+ &gorc->deposits_total),
+ GNUNET_JSON_pack_object_incref ("contract_terms",
+ gorc->contract_terms),
+ GNUNET_JSON_pack_string ("order_status",
+ "paid"),
+ GNUNET_JSON_pack_bool ("refunded",
+ gorc->refunded),
+ GNUNET_JSON_pack_bool ("wired",
+ gorc->wired),
+ GNUNET_JSON_pack_bool ("refund_pending",
+ gorc->refund_pending),
+ TALER_JSON_pack_amount ("refund_amount",
+ &gorc->refund_amount),
+ GNUNET_JSON_pack_array_steal ("wire_details",
+ gorc->wire_details),
+ GNUNET_JSON_pack_array_steal ("refund_details",
+ gorc->refund_details),
+ GNUNET_JSON_pack_string ("order_status_url",
+ order_status_url));
+ GNUNET_free (order_status_url);
+ gorc->wire_details = NULL;
+ gorc->refund_details = NULL;
+ phase_end (gorc,
+ ret);
+}
- if (! paid)
- {
- /* User never paid for this order */
- char *taler_pay_uri;
- char *order_status_url;
- MHD_RESULT ret;
- taler_pay_uri = TMH_make_taler_pay_uri (connection,
- hc->infix,
- gorc->session_id,
- hc->instance->settings.id,
- &claim_token);
- order_status_url = TMH_make_order_status_url (connection,
- hc->infix,
- gorc->session_id,
- hc->instance->settings.id,
- &claim_token,
- NULL);
- ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_string ("taler_pay_uri",
- taler_pay_uri),
- GNUNET_JSON_pack_string ("order_status_url",
- order_status_url),
- GNUNET_JSON_pack_string ("order_status",
- "unpaid"),
- TALER_JSON_pack_amount ("total_amount",
- &gorc->contract_amount),
- GNUNET_JSON_pack_string ("summary",
- summary),
- GNUNET_JSON_pack_timestamp ("creation_time",
- timestamp));
- GNUNET_free (taler_pay_uri);
- GNUNET_free (order_status_url);
- return ret;
- }
+/**
+ * End with error status in wire_hc and wire_ec.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_error (struct GetOrderRequestContext *gorc)
+{
+ GNUNET_assert (TALER_EC_NONE != gorc->wire_ec);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ gorc->wire_hc,
+ gorc->wire_ec,
+ NULL));
+}
- /* Here we know the user DID pay, compute refunds... */
- GNUNET_assert (! order_only);
- GNUNET_assert (paid);
- /* Accumulate refunds, if any. */
- {
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TMH_currency,
- &gorc->refund_amount));
- qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
- hc->instance->settings.id,
- &gorc->h_contract_terms,
- &process_refunds_cb,
- gorc);
- }
- if (0 > qs)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "detailed refunds");
- }
- /* Generate final reply, including wire details if we have them */
+MHD_RESULT
+TMH_private_get_orders_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct GetOrderRequestContext *gorc = hc->ctx;
+
+ if (NULL == gorc)
{
- MHD_RESULT ret;
- char *order_status_url;
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TMH_currency,
- &gorc->deposits_total));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TMH_currency,
- &gorc->deposit_fees_total));
- qs = TMH_db->lookup_transfer_details_by_order (TMH_db->cls,
- gorc->order_serial,
- &process_transfer_details,
- gorc);
- if (0 > qs)
- {
- GNUNET_break (0);
+ /* First time here, parse request and check order is known */
+ GNUNET_assert (NULL != hc->infix);
+ gorc = GNUNET_new (struct GetOrderRequestContext);
+ hc->cc = &gorc_cleanup;
+ hc->ctx = gorc;
+ gorc->sc.con = connection;
+ gorc->hc = hc;
+ gorc->wire_details = json_array ();
+ GNUNET_assert (NULL != gorc->wire_details);
+ gorc->refund_details = json_array ();
+ GNUNET_assert (NULL != gorc->refund_details);
+ gorc->session_id = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "session_id");
+ if (! (TALER_arg_to_yna (connection,
+ "allow_refunded_for_repurchase",
+ TALER_EXCHANGE_YNA_NO,
+ &gorc->allow_refunded_for_repurchase)) )
return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "transfer details");
- }
-
- if (! wired)
- {
- /* we believe(d) the wire transfer did not happen yet, check if maybe
- in light of new evidence it did */
- struct TALER_Amount expect_total;
-
- if (0 >
- TALER_amount_subtract (&expect_total,
- &gorc->contract_amount,
- &gorc->refund_amount))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
- "refund exceeds contract value");
- }
- if (0 >
- TALER_amount_subtract (&expect_total,
- &expect_total,
- &gorc->deposit_fees_total))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
- "deposit fees exceed total minus refunds");
- }
- if (0 >=
- TALER_amount_cmp (&expect_total,
- &gorc->deposits_total))
- {
- /* expect_total <= gorc->deposits_total: good: we got paid */
- wired = true;
- qs = TMH_db->mark_order_wired (TMH_db->cls,
- gorc->order_serial);
- GNUNET_break (qs >= 0); /* just warn if transaction failed */
- TMH_notify_order_change (hc->instance,
- TMH_OSF_PAID
- | TMH_OSF_WIRED,
- timestamp,
- gorc->order_serial);
- }
- }
-
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "allow_refunded_for_repurchase");
+ TALER_MHD_parse_request_timeout (connection,
+ &gorc->sc.long_poll_timeout);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Starting GET /private/orders/%s processing with timeout %s\n",
+ hc->infix,
+ GNUNET_STRINGS_absolute_time_to_string (
+ gorc->sc.long_poll_timeout));
+ }
+ if (GNUNET_SYSERR == gorc->suspended)
+ return MHD_NO; /* we are in shutdown */
+ while (1)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Processing order %s in phase %d\n",
+ hc->infix,
+ (int) gorc->phase);
+ switch (gorc->phase)
{
- struct TALER_PrivateContractHashP *h_contract = NULL;
-
- /* In a session-bound payment, allow the browser to check the order
- * status page (e.g. to get a refund).
- *
- * Note that we don't allow this outside of session-based payment, as
- * otherwise this becomes an oracle to convert order_id to h_contract.
- */if (NULL != gorc->session_id)
- h_contract = &gorc->h_contract_terms;
-
- order_status_url =
- TMH_make_order_status_url (connection,
- hc->infix,
- gorc->session_id,
- hc->instance->settings.id,
- &claim_token,
- h_contract);
+ case GOP_INIT:
+ phase_init (gorc);
+ break;
+ case GOP_FETCH_CONTRACT:
+ phase_fetch_contract (gorc);
+ break;
+ case GOP_PARSE_CONTRACT:
+ phase_parse_contract (gorc);
+ break;
+ case GOP_CHECK_PAID:
+ phase_check_paid (gorc);
+ break;
+ case GOP_CHECK_REPURCHASE:
+ phase_check_repurchase (gorc);
+ break;
+ case GOP_UNPAID_FINISH:
+ phase_unpaid_finish (gorc);
+ break;
+ case GOP_CHECK_REFUNDS:
+ phase_check_refunds (gorc);
+ break;
+ case GOP_CHECK_DEPOSITS:
+ phase_check_deposits (gorc);
+ break;
+ case GOP_CHECK_LOCAL_TRANSFERS:
+ phase_check_local_transfers (gorc);
+ break;
+ case GOP_REPLY_RESULT:
+ phase_reply_result (gorc);
+ break;
+ case GOP_ERROR:
+ phase_error (gorc);
+ break;
+ case GOP_SUSPENDED_ON_UNPAID:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending order request awaiting payment\n");
+ return MHD_YES;
+ case GOP_END_YES:
+ return MHD_YES;
+ case GOP_END_NO:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Closing connection, no response generated\n");
+ return MHD_NO;
}
-
- ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("wire_reports",
- gorc->wire_reports),
- GNUNET_JSON_pack_uint64 ("exchange_code",
- gorc->exchange_ec),
- GNUNET_JSON_pack_uint64 ("exchange_http_status",
- gorc->exchange_hc),
- /* legacy: */
- GNUNET_JSON_pack_uint64 ("exchange_ec",
- gorc->exchange_ec),
- /* legacy: */
- GNUNET_JSON_pack_uint64 ("exchange_hc",
- gorc->exchange_hc),
- TALER_JSON_pack_amount ("deposit_total",
- &gorc->deposits_total),
- GNUNET_JSON_pack_object_incref ("contract_terms",
- gorc->contract_terms),
- GNUNET_JSON_pack_string ("order_status",
- "paid"),
- GNUNET_JSON_pack_bool ("refunded",
- gorc->refunded),
- GNUNET_JSON_pack_bool ("wired",
- wired),
- GNUNET_JSON_pack_bool ("refund_pending",
- gorc->refund_pending),
- TALER_JSON_pack_amount ("refund_amount",
- &gorc->refund_amount),
- GNUNET_JSON_pack_array_steal ("wire_details",
- gorc->wire_details),
- GNUNET_JSON_pack_array_steal ("refund_details",
- gorc->refund_details),
- GNUNET_JSON_pack_string ("order_status_url",
- order_status_url));
- GNUNET_free (order_status_url);
- gorc->wire_details = NULL;
- gorc->wire_reports = NULL;
- gorc->refund_details = NULL;
- return ret;
- }
+ } /* end first-time per-request initialization */
}
diff --git a/src/backend/taler-merchant-httpd_private-get-orders.c b/src/backend/taler-merchant-httpd_private-get-orders.c
index d4f74184..5fc91188 100644
--- a/src/backend/taler-merchant-httpd_private-get-orders.c
+++ b/src/backend/taler-merchant-httpd_private-get-orders.c
@@ -225,6 +225,23 @@ cleanup (void *ctx)
/**
+ * Closure for #process_refunds_cb().
+ */
+struct ProcessRefundsClosure
+{
+ /**
+ * Place where we accumulate the refunds.
+ */
+ struct TALER_Amount total_refund_amount;
+
+ /**
+ * Set to an error code if something goes wrong.
+ */
+ enum TALER_ErrorCode ec;
+};
+
+
+/**
* Function called with information about a refund.
* It is responsible for summing up the refund amount.
*
@@ -249,11 +266,20 @@ process_refunds_cb (void *cls,
const struct TALER_Amount *refund_amount,
bool pending)
{
- struct TALER_Amount *total_refund_amount = cls;
+ struct ProcessRefundsClosure *prc = cls;
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&prc->total_refund_amount,
+ refund_amount))
+ {
+ /* Database error, refunds in mixed currency in DB. Not OK! */
+ prc->ec = TALER_EC_GENERIC_DB_INVARIANT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
GNUNET_assert (0 <=
- TALER_amount_add (total_refund_amount,
- total_refund_amount,
+ TALER_amount_add (&prc->total_refund_amount,
+ &prc->total_refund_amount,
refund_amount));
}
@@ -316,14 +342,12 @@ add_order (void *cls,
{
/* First try to find the order in the contracts */
uint64_t os;
- bool paid = false;
qs = TMH_db->lookup_contract_terms (TMH_db->cls,
po->instance_id,
order_id,
&contract_terms,
&os,
- &paid,
NULL);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
GNUNET_break (os == order_serial);
@@ -361,9 +385,8 @@ add_order (void *cls,
{
struct GNUNET_TIME_Timestamp rd;
struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("amount",
- TMH_currency,
- &order_amount),
+ TALER_JSON_spec_amount_any ("amount",
+ &order_amount),
GNUNET_JSON_spec_timestamp ("refund_deadline",
&rd),
GNUNET_JSON_spec_string ("summary",
@@ -386,16 +409,18 @@ add_order (void *cls,
if (GNUNET_TIME_absolute_is_future (rd.abs_time) &&
paid)
{
- struct TALER_Amount refund_amount;
+ struct ProcessRefundsClosure prc = {
+ .ec = TALER_EC_NONE
+ };
GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TMH_currency,
- &refund_amount));
+ TALER_amount_set_zero (order_amount.currency,
+ &prc.total_refund_amount));
qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
po->instance_id,
&h_contract_terms,
&process_refunds_cb,
- &refund_amount);
+ &prc);
if (0 > qs)
{
GNUNET_break (0);
@@ -404,7 +429,15 @@ add_order (void *cls,
GNUNET_free (order_id);
return;
}
- if (0 > TALER_amount_cmp (&refund_amount,
+ if (TALER_EC_NONE != prc.ec)
+ {
+ GNUNET_break (0);
+ po->result = prc.ec;
+ json_decref (contract_terms);
+ GNUNET_free (order_id);
+ return;
+ }
+ if (0 > TALER_amount_cmp (&prc.total_refund_amount,
&order_amount))
refundable = true;
}
@@ -602,7 +635,6 @@ TMH_private_get_orders (const struct TMH_RequestHandler *rh,
{
struct TMH_PendingOrder *po = hc->ctx;
enum GNUNET_DB_QueryStatus qs;
- struct TALER_MERCHANTDB_OrderFilter of;
if (NULL != po)
{
@@ -622,11 +654,19 @@ TMH_private_get_orders (const struct TMH_RequestHandler *rh,
GNUNET_JSON_pack_array_incref ("orders",
po->pa));
}
+ po = GNUNET_new (struct TMH_PendingOrder);
+ hc->ctx = po;
+ hc->cc = &cleanup;
+ po->con = connection;
+ po->pa = json_array ();
+ GNUNET_assert (NULL != po->pa);
+ po->instance_id = hc->instance->settings.id;
+ po->mi = hc->instance;
if (! (TALER_arg_to_yna (connection,
"paid",
TALER_EXCHANGE_YNA_ALL,
- &of.paid)) )
+ &po->of.paid)) )
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
@@ -634,7 +674,7 @@ TMH_private_get_orders (const struct TMH_RequestHandler *rh,
if (! (TALER_arg_to_yna (connection,
"refunded",
TALER_EXCHANGE_YNA_ALL,
- &of.refunded)) )
+ &po->of.refunded)) )
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
@@ -642,49 +682,28 @@ TMH_private_get_orders (const struct TMH_RequestHandler *rh,
if (! (TALER_arg_to_yna (connection,
"wired",
TALER_EXCHANGE_YNA_ALL,
- &of.wired)) )
+ &po->of.wired)) )
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"wired");
+ po->of.delta = -20;
+ /* deprecated in protocol v12 */
+ TALER_MHD_parse_request_snumber (connection,
+ "delta",
+ &po->of.delta);
+ /* since protocol v12 */
+ TALER_MHD_parse_request_snumber (connection,
+ "limit",
+ &po->of.delta);
+ if ( (-MAX_DELTA > po->of.delta) ||
+ (po->of.delta > MAX_DELTA) )
{
- const char *delta_str;
-
- delta_str = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "delta");
- if (NULL == delta_str)
- {
- of.delta = -20;
- }
- else
- {
- char dummy;
- long long ll;
-
- if (1 !=
- sscanf (delta_str,
- "%lld%c",
- &ll,
- &dummy))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "delta");
- }
- of.delta = (int64_t) ll;
- if ( (-MAX_DELTA > of.delta) ||
- (of.delta > MAX_DELTA) )
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "delta");
- }
- }
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "delta");
}
{
const char *date_s_str;
@@ -694,10 +713,10 @@ TMH_private_get_orders (const struct TMH_RequestHandler *rh,
"date_s");
if (NULL == date_s_str)
{
- if (of.delta > 0)
- of.date = GNUNET_TIME_UNIT_ZERO_TS;
+ if (po->of.delta > 0)
+ po->of.date = GNUNET_TIME_UNIT_ZERO_TS;
else
- of.date = GNUNET_TIME_UNIT_FOREVER_TS;
+ po->of.date = GNUNET_TIME_UNIT_FOREVER_TS;
}
else
{
@@ -717,9 +736,9 @@ TMH_private_get_orders (const struct TMH_RequestHandler *rh,
"date_ms");
}
- of.date = GNUNET_TIME_absolute_to_timestamp (
+ po->of.date = GNUNET_TIME_absolute_to_timestamp (
GNUNET_TIME_absolute_from_s (ll));
- if (GNUNET_TIME_absolute_is_never (of.date.abs_time))
+ if (GNUNET_TIME_absolute_is_never (po->of.date.abs_time))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
@@ -729,99 +748,56 @@ TMH_private_get_orders (const struct TMH_RequestHandler *rh,
}
}
}
+ if (po->of.delta > 0)
+ po->of.start_row = 0;
+ else
+ po->of.start_row = INT64_MAX;
+ /* deprecated in protocol v12 */
+ TALER_MHD_parse_request_number (connection,
+ "start",
+ &po->of.start_row);
+ /* since protocol v12 */
+ TALER_MHD_parse_request_number (connection,
+ "offset",
+ &po->of.start_row);
+ if (INT64_MAX < po->of.start_row)
{
- const char *start_row_str;
-
- start_row_str = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "start");
- if (NULL == start_row_str)
- {
- if (of.delta > 0)
- of.start_row = 0;
- else
- of.start_row = INT64_MAX;
- }
- else
- {
- char dummy;
- unsigned long long ull;
-
- if (1 !=
- sscanf (start_row_str,
- "%llu%c",
- &ull,
- &dummy))
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "start");
- of.start_row = (uint64_t) ull;
- if (INT64_MAX < of.start_row)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "start");
- }
- }
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "start");
}
+ po->of.session_id
+ = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "session_id");
+ po->of.fulfillment_url
+ = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "fulfillment_url");
+ TALER_MHD_parse_request_timeout (connection,
+ &po->long_poll_timeout);
+ if (GNUNET_TIME_absolute_is_never (po->long_poll_timeout))
{
- const char *timeout_ms_str;
-
- timeout_ms_str = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "timeout_ms");
- if (NULL == timeout_ms_str)
- {
- of.timeout = GNUNET_TIME_UNIT_ZERO;
- }
- else
- {
- char dummy;
- unsigned long long ull;
-
- if (1 !=
- sscanf (timeout_ms_str,
- "%llu%c",
- &ull,
- &dummy))
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "timeout_ms");
- of.timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
- ull);
- if (GNUNET_TIME_relative_is_forever (of.timeout))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "timeout_ms");
- }
- }
-
- if ( (0 >= of.delta) &&
- (! GNUNET_TIME_relative_is_zero (of.timeout)) )
- {
- GNUNET_break_op (0);
- of.timeout = GNUNET_TIME_UNIT_ZERO;
- }
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "timeout_ms");
+ }
+ po->of.timeout = GNUNET_TIME_absolute_get_remaining (po->long_poll_timeout);
+ if ( (0 >= po->of.delta) &&
+ (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) )
+ {
+ GNUNET_break_op (0);
+ po->of.timeout = GNUNET_TIME_UNIT_ZERO;
+ po->long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
}
- po = GNUNET_new (struct TMH_PendingOrder);
- hc->ctx = po;
- hc->cc = &cleanup;
- po->con = connection;
- po->pa = json_array ();
- GNUNET_assert (NULL != po->pa);
- po->instance_id = hc->instance->settings.id;
- po->mi = hc->instance;
qs = TMH_db->lookup_orders (TMH_db->cls,
po->instance_id,
- &of,
+ &po->of,
&add_order,
po);
if (0 > qs)
@@ -838,7 +814,7 @@ TMH_private_get_orders (const struct TMH_RequestHandler *rh,
NULL);
}
if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) &&
- (! GNUNET_TIME_relative_is_zero (of.timeout)) )
+ (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) )
{
struct TMH_MerchantInstance *mi = hc->instance;
@@ -849,8 +825,6 @@ TMH_private_get_orders (const struct TMH_RequestHandler *rh,
po->hn = GNUNET_CONTAINER_heap_insert (order_timeout_heap,
po,
po->long_poll_timeout.abs_value_us);
- po->long_poll_timeout = GNUNET_TIME_relative_to_absolute (of.timeout);
- po->of = of;
GNUNET_CONTAINER_DLL_insert (mi->po_head,
mi->po_tail,
po);
diff --git a/src/backend/taler-merchant-httpd_private-get-otp-devices-ID.c b/src/backend/taler-merchant-httpd_private-get-otp-devices-ID.c
new file mode 100644
index 00000000..63f3f43d
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-otp-devices-ID.c
@@ -0,0 +1,110 @@
+/*
+ This file is part of TALER
+ (C) 2022-2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_private-get-otp-devices-ID.c
+ * @brief implement GET /otp-devices/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-otp-devices-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a GET "/otp-devices/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_otp_devices_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ struct TALER_MERCHANTDB_OtpDeviceDetails tp = { 0 };
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t faketime_s
+ = GNUNET_TIME_timestamp_to_s (GNUNET_TIME_timestamp_get ());
+ struct GNUNET_TIME_Timestamp my_time;
+ struct TALER_Amount price;
+
+ TALER_MHD_parse_request_number (connection,
+ "faketime",
+ &faketime_s);
+ memset (&price,
+ 0,
+ sizeof (price));
+ TALER_MHD_parse_request_amount (connection,
+ "price",
+ &price);
+ my_time = GNUNET_TIME_timestamp_from_s (faketime_s);
+ GNUNET_assert (NULL != mi);
+ qs = TMH_db->select_otp (TMH_db->cls,
+ mi->settings.id,
+ hc->infix,
+ &tp);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_otp");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_OTP_DEVICE_UNKNOWN,
+ hc->infix);
+ }
+ {
+ MHD_RESULT ret;
+ char *pos_confirmation;
+
+ pos_confirmation = (NULL == tp.otp_key)
+ ? NULL
+ : TALER_build_pos_confirmation (tp.otp_key,
+ tp.otp_algorithm,
+ &price,
+ my_time);
+ /* Note: we deliberately (by design) do not return the otp_key */
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("device_description",
+ tp.otp_description),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("otp_code",
+ pos_confirmation)),
+ GNUNET_JSON_pack_uint64 ("otp_timestamp",
+ faketime_s),
+ GNUNET_JSON_pack_uint64 ("otp_algorithm",
+ tp.otp_algorithm),
+ GNUNET_JSON_pack_uint64 ("otp_ctr",
+ tp.otp_ctr));
+ GNUNET_free (pos_confirmation);
+ GNUNET_free (tp.otp_description);
+ GNUNET_free (tp.otp_key);
+ return ret;
+ }
+}
+
+
+/* end of taler-merchant-httpd_private-get-otp-devices-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-delete-reserves-ID.h b/src/backend/taler-merchant-httpd_private-get-otp-devices-ID.h
index 9180767f..78834f67 100644
--- a/src/backend/taler-merchant-httpd_private-delete-reserves-ID.h
+++ b/src/backend/taler-merchant-httpd_private-get-otp-devices-ID.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2019, 2020 Taler Systems SA
+ (C) 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -14,18 +14,18 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file taler-merchant-httpd_private-delete-reserves-ID.h
- * @brief implement DELETE /reserves/$ID/
+ * @file taler-merchant-httpd_private-get-otp-devices-ID.h
+ * @brief implement GET /otp-devices/$ID/
* @author Christian Grothoff
*/
-#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_RESERVES_ID_H
-#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_RESERVES_ID_H
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_OTP_DEVICES_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_OTP_DEVICES_ID_H
#include "taler-merchant-httpd.h"
/**
- * Handle a DELETE "/reserves/$ID" request.
+ * Handle a GET "/otp-devices/$ID" request.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
@@ -33,9 +33,9 @@
* @return MHD result code
*/
MHD_RESULT
-TMH_private_delete_reserves_ID (const struct TMH_RequestHandler *rh,
+TMH_private_get_otp_devices_ID (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc);
-/* end of taler-merchant-httpd_private-delete-reserves-ID.h */
+/* end of taler-merchant-httpd_private-get-otp-devices-ID.h */
#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-otp-devices.c b/src/backend/taler-merchant-httpd_private-get-otp-devices.c
new file mode 100644
index 00000000..189b1e09
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-otp-devices.c
@@ -0,0 +1,80 @@
+/*
+ This file is part of TALER
+ (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_private-get-otp-devices.c
+ * @brief implement GET /otp-devices
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-otp-devices.h"
+
+
+/**
+ * Add OTP device details to our JSON array.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param otp_id ID of the OTP device
+ * @param otp_description human-readable description for the OTP device
+ */
+static void
+add_otp (void *cls,
+ const char *otp_id,
+ const char *otp_description)
+{
+ json_t *pa = cls;
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ pa,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("otp_device_id",
+ otp_id),
+ GNUNET_JSON_pack_string ("device_description",
+ otp_description))));
+}
+
+
+MHD_RESULT
+TMH_private_get_otp_devices (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ json_t *pa;
+ enum GNUNET_DB_QueryStatus qs;
+
+ pa = json_array ();
+ GNUNET_assert (NULL != pa);
+ qs = TMH_db->lookup_otp_devices (TMH_db->cls,
+ hc->instance->settings.id,
+ &add_otp,
+ pa);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ json_decref (pa);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("otp_devices",
+ pa));
+}
+
+
+/* end of taler-merchant-httpd_private-get-otp-devices.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-reserves-ID.h b/src/backend/taler-merchant-httpd_private-get-otp-devices.h
index d72805c4..a97ca179 100644
--- a/src/backend/taler-merchant-httpd_private-get-reserves-ID.h
+++ b/src/backend/taler-merchant-httpd_private-get-otp-devices.h
@@ -1,9 +1,9 @@
/*
This file is part of TALER
- (C) 2017 Taler Systems SA
+ (C) 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
+ terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
@@ -14,17 +14,18 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file taler-merchant-httpd_private-get-reserves-ID.h
- * @brief headers for /tip-query handler
- * @author Florian Dold
+ * @file taler-merchant-httpd_private-get-otp-devices.h
+ * @brief implement GET /otp-devices
+ * @author Christian Grothoff
*/
-#ifndef TALER_MERCHANT_HTTPD_GET_RESERVES_ID_H
-#define TALER_MERCHANT_HTTPD_GET_RESERVES_ID_H
-#include <microhttpd.h>
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_OTP_DEVICES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_OTP_DEVICES_H
+
#include "taler-merchant-httpd.h"
+
/**
- * Manages a GET /reserves/$RESERVE_PUB call.
+ * Handle a GET "/otp-devices" request.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
@@ -32,8 +33,9 @@
* @return MHD result code
*/
MHD_RESULT
-TMH_private_get_reserves_ID (const struct TMH_RequestHandler *rh,
+TMH_private_get_otp_devices (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc);
+/* end of taler-merchant-httpd_private-get-otp-devices.h */
#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-products.c b/src/backend/taler-merchant-httpd_private-get-products.c
index bc90c94d..d9fa4e49 100644
--- a/src/backend/taler-merchant-httpd_private-get-products.c
+++ b/src/backend/taler-merchant-httpd_private-get-products.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2019, 2020, 2021 Taler Systems SA
+ (C) 2019, 2020, 2021, 2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -30,6 +30,7 @@
*/
static void
add_product (void *cls,
+ uint64_t product_serial,
const char *product_id)
{
json_t *pa = cls;
@@ -38,6 +39,8 @@ add_product (void *cls,
json_array_append_new (
pa,
GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("product_serial",
+ product_serial),
GNUNET_JSON_pack_string ("product_id",
product_id))));
}
@@ -50,11 +53,26 @@ TMH_private_get_products (const struct TMH_RequestHandler *rh,
{
json_t *pa;
enum GNUNET_DB_QueryStatus qs;
+ int64_t limit;
+ uint64_t offset;
+ limit = 20; /* default */
+ TALER_MHD_parse_request_snumber (connection,
+ "limit",
+ &limit);
+ if (limit < 0)
+ offset = INT64_MAX;
+ else
+ offset = 0;
+ TALER_MHD_parse_request_number (connection,
+ "offset",
+ &offset);
pa = json_array ();
GNUNET_assert (NULL != pa);
qs = TMH_db->lookup_products (TMH_db->cls,
hc->instance->settings.id,
+ offset,
+ limit,
&add_product,
pa);
if (0 > qs)
diff --git a/src/backend/taler-merchant-httpd_private-get-reserves-ID.c b/src/backend/taler-merchant-httpd_private-get-reserves-ID.c
deleted file mode 100644
index 5545b02f..00000000
--- a/src/backend/taler-merchant-httpd_private-get-reserves-ID.c
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- This file is part of TALER
- (C) 2018, 2020, 2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-merchant-httpd_private-get-reserves-ID.c
- * @brief implement GET /reserves/$RESERVE_PUB endpoint
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <taler/taler_util.h>
-#include <taler/taler_json_lib.h>
-#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_mhd.h"
-#include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_private-get-reserves-ID.h"
-
-
-/**
- * Closure for handle_reserve_details().
- */
-struct GetReserveContext
-{
- /**
- * Connection we are handling.
- */
- struct MHD_Connection *connection;
-
- /**
- * Value to return from the callback.
- */
- MHD_RESULT res;
-
- /**
- * Should we return details about tips?
- */
- bool tips;
-};
-
-
-/**
- * Callback with reserve details.
- *
- * @param cls closure with a `struct GetReserveContext`
- * @param creation_time time when the reserve was setup
- * @param expiration_time time when the reserve will be closed by the exchange
- * @param merchant_initial_amount initial amount that the merchant claims to have filled the
- * reserve with
- * @param exchange_initial_amount initial amount that the exchange claims to have received
- * @param picked_up_amount total of tips that were picked up from this reserve
- * @param committed_amount total of tips that the merchant committed to, but that were not
- * picked up yet
- * @param active true if the reserve is still active (we have the private key)
- * @param exchange_url URL of the exchange, NULL if not active
- * @param payto_uri payto:// URI to fill the reserve, NULL if not active or already paid
- * @param tips_length length of the @a tips array
- * @param tips information about the tips created by this reserve
- */
-static void
-handle_reserve_details (void *cls,
- struct GNUNET_TIME_Timestamp creation_time,
- struct GNUNET_TIME_Timestamp expiration_time,
- const struct TALER_Amount *merchant_initial_amount,
- const struct TALER_Amount *exchange_initial_amount,
- const struct TALER_Amount *picked_up_amount,
- const struct TALER_Amount *committed_amount,
- bool active,
- const char *exchange_url,
- const char *payto_uri,
- unsigned int tips_length,
- const struct TALER_MERCHANTDB_TipDetails *tips)
-{
- struct GetReserveContext *ctx = cls;
- json_t *tips_json;
-
- if (NULL != tips)
- {
- tips_json = json_array ();
- GNUNET_assert (NULL != tips_json);
- for (unsigned int i = 0; i<tips_length; i++)
- {
- GNUNET_assert (0 ==
- json_array_append_new (
- tips_json,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("tip_id",
- &tips[i].tip_id),
- TALER_JSON_pack_amount ("total_amount",
- &tips[i].total_amount),
- GNUNET_JSON_pack_string ("reason",
- tips[i].reason))));
- }
- }
- else
- {
- tips_json = NULL;
- }
- ctx->res = TALER_MHD_REPLY_JSON_PACK (
- ctx->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_timestamp ("creation_time",
- creation_time),
- GNUNET_JSON_pack_timestamp ("expiration_time",
- expiration_time),
- TALER_JSON_pack_amount ("merchant_initial_amount",
- merchant_initial_amount),
- TALER_JSON_pack_amount ("exchange_initial_amount",
- exchange_initial_amount),
- TALER_JSON_pack_amount ("pickup_amount",
- picked_up_amount),
- TALER_JSON_pack_amount ("committed_amount",
- committed_amount),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_array_steal ("tips",
- tips_json)),
- GNUNET_JSON_pack_bool ("active",
- active),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("exchange_url",
- exchange_url)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("payto_uri",
- payto_uri)));
-}
-
-
-/**
- * Manages a GET /reserves/$RESERVE_PUB call.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] hc context with further information about the request
- * @return MHD result code
- */
-MHD_RESULT
-TMH_private_get_reserves_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
-{
- struct TALER_ReservePublicKeyP reserve_pub;
- bool tips;
-
- (void) rh;
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (hc->infix,
- strlen (hc->infix),
- &reserve_pub,
- sizeof (reserve_pub)))
- {
- /* tip_id has wrong encoding */
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- hc->infix);
- }
- {
- const char *tstr;
-
- tstr = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "tips");
- tips = (NULL != tstr)
- ? 0 == strcasecmp (tstr, "yes")
- : false;
- }
- {
- struct GetReserveContext ctx = {
- .connection = connection,
- .tips = tips
- };
- enum GNUNET_DB_QueryStatus qs;
-
- TMH_db->preflight (TMH_db->cls);
- qs = TMH_db->lookup_reserve (TMH_db->cls,
- hc->instance->settings.id,
- &reserve_pub,
- tips,
- &handle_reserve_details,
- &ctx);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- unsigned int response_code;
- enum TALER_ErrorCode ec;
-
- switch (qs)
- {
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- ec = TALER_EC_MERCHANT_GENERIC_TIP_ID_UNKNOWN;
- response_code = MHD_HTTP_NOT_FOUND;
- break;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
- response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- case GNUNET_DB_STATUS_HARD_ERROR:
- ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
- response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- default:
- GNUNET_break (0);
- ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- }
- return TALER_MHD_reply_with_error (connection,
- response_code,
- ec,
- hc->infix);
- }
- return ctx.res;
- }
-}
-
-
-/* end of taler-merchant-httpd_private-get-reserves-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-reserves.c b/src/backend/taler-merchant-httpd_private-get-reserves.c
deleted file mode 100644
index 6621ffb4..00000000
--- a/src/backend/taler-merchant-httpd_private-get-reserves.c
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- This file is part of TALER
- (C) 2019-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-merchant-httpd_private-get-reserves.c
- * @brief implement GET /reserves
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <taler/taler_json_lib.h>
-#include "taler-merchant-httpd_private-get-reserves.h"
-
-
-/**
- * Add reserve details to our JSON array.
- *
- * @param cls a `json_t *` JSON array to build
- * @param reserve_pub public key of the reserve
- * @param creation_time time when the reserve was setup
- * @param expiration_time time when the reserve will be closed by the exchange
- * @param merchant_initial_amount initial amount that the merchant claims to have filled the
- * reserve with
- * @param exchange_initial_amount initial amount that the exchange claims to have received
- * @param pickup_amount total of tips that were picked up from this reserve
- * @param committed_amount total of tips that the merchant committed to, but that were not
- * picked up yet
- * @param active true if the reserve is still active (we have the private key)
- */
-static void
-add_reserve (void *cls,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- struct GNUNET_TIME_Timestamp creation_time,
- struct GNUNET_TIME_Timestamp expiration_time,
- const struct TALER_Amount *merchant_initial_amount,
- const struct TALER_Amount *exchange_initial_amount,
- const struct TALER_Amount *pickup_amount,
- const struct TALER_Amount *committed_amount,
- bool active)
-{
- json_t *pa = cls;
-
- GNUNET_assert (0 ==
- json_array_append_new (
- pa,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("reserve_pub",
- reserve_pub),
- GNUNET_JSON_pack_timestamp ("creation_time",
- creation_time),
- GNUNET_JSON_pack_timestamp ("expiration_time",
- expiration_time),
- TALER_JSON_pack_amount ("merchant_initial_amount",
- merchant_initial_amount),
- TALER_JSON_pack_amount ("exchange_initial_amount",
- exchange_initial_amount),
- TALER_JSON_pack_amount ("pickup_amount",
- pickup_amount),
- TALER_JSON_pack_amount ("committed_amount",
- committed_amount),
- GNUNET_JSON_pack_bool ("active",
- active))));
-}
-
-
-/**
- * Handle a GET "/reserves" request.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] hc context with further information about the request
- * @return MHD result code
- */
-MHD_RESULT
-TMH_private_get_reserves (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
-{
- json_t *ra;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Timestamp created_after
- = GNUNET_TIME_UNIT_ZERO_TS;
- enum TALER_EXCHANGE_YesNoAll active;
- enum TALER_EXCHANGE_YesNoAll failures;
-
- (void) rh;
- if (! (TALER_arg_to_yna (connection,
- "active",
- TALER_EXCHANGE_YNA_ALL,
- &active)) )
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "active");
- }
-
- if (! (TALER_arg_to_yna (connection,
- "failures",
- TALER_EXCHANGE_YNA_ALL,
- &failures)) )
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "failures");
- }
-
- ra = json_array ();
- GNUNET_assert (NULL != ra);
- qs = TMH_db->lookup_reserves (TMH_db->cls,
- hc->instance->settings.id,
- created_after,
- active,
- failures,
- &add_reserve,
- ra);
- if (0 > qs)
- {
- GNUNET_break (0);
- json_decref (ra);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "reserves");
- }
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("reserves",
- ra));
-}
-
-
-/* end of taler-merchant-httpd_private-get-reserves.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-templates-ID.c b/src/backend/taler-merchant-httpd_private-get-templates-ID.c
index 953582ca..35fdd1d0 100644
--- a/src/backend/taler-merchant-httpd_private-get-templates-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-templates-ID.c
@@ -23,18 +23,11 @@
#include <taler/taler_json_lib.h>
-/**
- * Handle a GET "/templates/$ID" request.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] hc context with further information about the request
- * @return MHD result code
- */
MHD_RESULT
-TMH_private_get_templates_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
+TMH_private_get_templates_ID (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
{
struct TMH_MerchantInstance *mi = hc->instance;
struct TALER_MERCHANTDB_TemplateDetails tp = { 0 };
@@ -48,17 +41,19 @@ TMH_private_get_templates_ID (const struct TMH_RequestHandler *rh,
if (0 > qs)
{
GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_template");
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_template");
}
- if (0 == qs)
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
- hc->infix);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
+ hc->infix);
}
{
MHD_RESULT ret;
@@ -66,16 +61,20 @@ TMH_private_get_templates_ID (const struct TMH_RequestHandler *rh,
ret = TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_OK,
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("required_currency",
+ tp.required_currency)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("editable_defaults",
+ tp.editable_defaults)),
GNUNET_JSON_pack_string ("template_description",
tp.template_description),
GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("image",
- tp.image)),
- GNUNET_JSON_pack_object_steal ("template_contract",
- tp.template_contract));
- GNUNET_free (tp.template_description);
- GNUNET_free (tp.image);
-
+ GNUNET_JSON_pack_string ("otp_id",
+ tp.otp_id)),
+ GNUNET_JSON_pack_object_incref ("template_contract",
+ tp.template_contract));
+ TALER_MERCHANTDB_template_details_free (&tp);
return ret;
}
}
diff --git a/src/backend/taler-merchant-httpd_private-get-templates-ID.h b/src/backend/taler-merchant-httpd_private-get-templates-ID.h
index cf5e4d83..fcdd6a2e 100644
--- a/src/backend/taler-merchant-httpd_private-get-templates-ID.h
+++ b/src/backend/taler-merchant-httpd_private-get-templates-ID.h
@@ -33,9 +33,10 @@
* @return MHD result code
*/
MHD_RESULT
-TMH_private_get_templates_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc);
+TMH_private_get_templates_ID (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
/* end of taler-merchant-httpd_private-get-templates-ID.h */
#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-templates.c b/src/backend/taler-merchant-httpd_private-get-templates.c
index e59e47c9..d0bec884 100644
--- a/src/backend/taler-merchant-httpd_private-get-templates.c
+++ b/src/backend/taler-merchant-httpd_private-get-templates.c
@@ -40,8 +40,7 @@ add_template (void *cls,
json_array_append_new (
pa,
GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("template_id",
- template_id),
+ GNUNET_JSON_pack_string ("template_id", template_id),
GNUNET_JSON_pack_string ("template_description",
template_description))));
}
diff --git a/src/backend/taler-merchant-httpd_private-get-templates.h b/src/backend/taler-merchant-httpd_private-get-templates.h
index b5b5f025..d791bba7 100644
--- a/src/backend/taler-merchant-httpd_private-get-templates.h
+++ b/src/backend/taler-merchant-httpd_private-get-templates.h
@@ -34,8 +34,8 @@
*/
MHD_RESULT
TMH_private_get_templates (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc);
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
/* end of taler-merchant-httpd_private-get-templates.h */
#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-tips-ID.c b/src/backend/taler-merchant-httpd_private-get-tips-ID.c
deleted file mode 100644
index 17002168..00000000
--- a/src/backend/taler-merchant-httpd_private-get-tips-ID.c
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-merchant-httpd_get-tips-ID.c
- * @brief implementation of a GET /tips/ID handler
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <microhttpd.h>
-#include <jansson.h>
-#include <taler/taler_json_lib.h>
-#include <taler/taler_signatures.h>
-#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_mhd.h"
-#include "taler-merchant-httpd_exchanges.h"
-
-
-MHD_RESULT
-TMH_private_get_tips_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
-{
- struct TALER_TipIdentifierP tip_id;
- struct TALER_Amount total_authorized;
- struct TALER_Amount total_picked_up;
- char *reason;
- struct GNUNET_TIME_Timestamp expiration;
- struct TALER_ReservePublicKeyP reserve_pub;
- unsigned int pickups_length = 0;
- struct TALER_MERCHANTDB_PickupDetails *pickups = NULL;
- enum GNUNET_DB_QueryStatus qs;
- bool fpu;
- json_t *pickups_json = NULL;
-
- (void) rh;
- GNUNET_assert (NULL != hc->infix);
- // FIXME: support long-polling (client-side already implemented)
- if (GNUNET_OK !=
- GNUNET_CRYPTO_hash_from_string (hc->infix,
- &tip_id.hash))
- {
- /* tip_id has wrong encoding */
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- hc->infix);
- }
- {
- const char *pstr;
-
- pstr = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "pickups");
- fpu = (NULL != pstr)
- ? 0 == strcasecmp (pstr, "yes")
- : false;
- }
- TMH_db->preflight (TMH_db->cls);
- qs = TMH_db->lookup_tip_details (TMH_db->cls,
- hc->instance->settings.id,
- &tip_id,
- fpu,
- &total_authorized,
- &total_picked_up,
- &reason,
- &expiration,
- &reserve_pub,
- &pickups_length,
- &pickups);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- unsigned int response_code;
- enum TALER_ErrorCode ec;
-
- switch (qs)
- {
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- ec = TALER_EC_MERCHANT_GENERIC_TIP_ID_UNKNOWN;
- response_code = MHD_HTTP_NOT_FOUND;
- break;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
- response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- case GNUNET_DB_STATUS_HARD_ERROR:
- ec = TALER_EC_GENERIC_DB_COMMIT_FAILED;
- response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- default:
- GNUNET_break (0);
- ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- }
- return TALER_MHD_reply_with_error (connection,
- response_code,
- ec,
- NULL);
- }
- if (fpu)
- {
- pickups_json = json_array ();
- GNUNET_assert (NULL != pickups_json);
- for (unsigned int i = 0; i<pickups_length; i++)
- {
- GNUNET_assert (0 ==
- json_array_append_new (
- pickups_json,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("pickup_id",
- &pickups[i].pickup_id),
- GNUNET_JSON_pack_uint64 ("num_planchets",
- pickups[i].num_planchets),
- TALER_JSON_pack_amount ("requested_amount",
- &pickups[i].requested_amount))));
- }
- }
- GNUNET_array_grow (pickups,
- pickups_length,
- 0);
- {
- MHD_RESULT ret;
-
- ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- TALER_JSON_pack_amount ("total_authorized",
- &total_authorized),
- TALER_JSON_pack_amount ("total_picked_up",
- &total_picked_up),
- GNUNET_JSON_pack_string ("reason",
- reason),
- GNUNET_JSON_pack_timestamp ("expiration",
- expiration),
- GNUNET_JSON_pack_data_auto ("reserve_pub",
- &reserve_pub),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_array_steal ("pickups",
- pickups_json)));
- GNUNET_free (reason);
- return ret;
- }
-}
diff --git a/src/backend/taler-merchant-httpd_private-get-tips.c b/src/backend/taler-merchant-httpd_private-get-tips.c
deleted file mode 100644
index 91a05366..00000000
--- a/src/backend/taler-merchant-httpd_private-get-tips.c
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- This file is part of TALER
- (C) 2020-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-merchant-httpd_private-get-tips.c
- * @brief implementation of a GET /private/tips handler
- * @author Jonathan Buchanan
- */
-#include "platform.h"
-#include "taler-merchant-httpd_private-get-tips.h"
-#include <taler/taler_json_lib.h>
-
-/**
- * Add tip details to our JSON array.
- *
- * @param[in,out] cls a `json_t *` JSON array to build
- * @param row_id row number of the tip
- * @param tip_id ID of the tip
- * @param amount the amount of the tip
- */
-static void
-add_tip (void *cls,
- uint64_t row_id,
- struct TALER_TipIdentifierP tip_id,
- struct TALER_Amount amount)
-{
- json_t *pa = cls;
-
- GNUNET_assert (0 ==
- json_array_append_new (
- pa,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_uint64 ("row_id",
- row_id),
- GNUNET_JSON_pack_data_auto ("tip_id",
- &tip_id),
- TALER_JSON_pack_amount ("tip_amount",
- &amount))));
-}
-
-
-MHD_RESULT
-TMH_private_get_tips (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
-{
- json_t *pa;
- enum GNUNET_DB_QueryStatus qs;
- enum TALER_EXCHANGE_YesNoAll expired;
- uint64_t offset;
- int64_t limit;
-
- (void) rh;
- if (! (TALER_arg_to_yna (connection,
- "expired",
- TALER_EXCHANGE_YNA_NO,
- &expired)) )
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "expired");
- {
- const char *limit_str;
-
- limit_str = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "limit");
- if (NULL == limit_str)
- {
- limit = -20;
- }
- else
- {
- char dummy[2];
- long long ll;
-
- if (1 !=
- sscanf (limit_str,
- "%lld%1s",
- &ll,
- dummy))
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "limit");
- limit = (uint64_t) ll;
- }
- }
- {
- const char *offset_str;
-
- offset_str = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "offset");
- if (NULL == offset_str)
- {
- if (limit > 0)
- offset = 0;
- else
- offset = INT64_MAX;
- }
- else
- {
- char dummy[2];
- unsigned long long ull;
-
- if (1 !=
- sscanf (offset_str,
- "%llu%1s",
- &ull,
- dummy))
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "offset");
- offset = (uint64_t) ull;
- }
- }
-
- pa = json_array ();
- GNUNET_assert (NULL != pa);
- qs = TMH_db->lookup_tips (TMH_db->cls,
- hc->instance->settings.id,
- expired,
- limit,
- offset,
- &add_tip,
- pa);
-
- if (0 > qs)
- {
- GNUNET_break (0);
- json_decref (pa);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "tips");
- }
-
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("tips",
- pa));
-}
diff --git a/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c
new file mode 100644
index 00000000..06dbbdf9
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c
@@ -0,0 +1,105 @@
+/*
+ This file is part of TALER
+ (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_private-get-token-families-SLUG.c
+ * @brief implement GET /tokenfamilies/$SLUG/
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-token-families-SLUG.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a GET "/tokenfamilies/$SLUG" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_tokenfamilies_SLUG (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ struct TALER_MERCHANTDB_TokenFamilyDetails details = { 0 };
+ enum GNUNET_DB_QueryStatus status;
+
+ GNUNET_assert (NULL != mi);
+ status = TMH_db->lookup_token_family (TMH_db->cls,
+ mi->settings.id,
+ hc->infix,
+ &details);
+ if (0 > status)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_token_family");
+ }
+ if (0 == status)
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
+ hc->infix);
+ }
+ {
+ char *kind = NULL;
+ if (TALER_MERCHANTDB_TFK_Subscription == details.kind)
+ {
+ kind = GNUNET_strdup ("subscription");
+ }
+ else if (TALER_MERCHANTDB_TFK_Discount == details.kind)
+ {
+ kind = GNUNET_strdup ("discount");
+ }
+ else
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "invalid_token_family_kind");
+ }
+
+ MHD_RESULT result;
+
+ result = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("name", details.name),
+ GNUNET_JSON_pack_string ("description", details.description),
+ GNUNET_JSON_pack_object_steal ("description_i18n",
+ details.description_i18n),
+ GNUNET_JSON_pack_timestamp ("valid_after", details.valid_after),
+ GNUNET_JSON_pack_timestamp ("valid_before", details.valid_before),
+ GNUNET_JSON_pack_time_rel ("duration", details.duration),
+ GNUNET_JSON_pack_string ("kind", kind)
+ );
+
+ GNUNET_free (details.name);
+ GNUNET_free (details.description);
+ GNUNET_free (kind);
+ return result;
+ }
+}
+
+
+/* end of taler-merchant-httpd_private-get-products-SLUG.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.h b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.h
new file mode 100644
index 00000000..a7b02d8f
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_private-get-token-families-SLUG.h
+ * @brief implement GET /tokenfamilies/$SLUG/
+ * @author Christian Blättler
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_TOKENFAMILIES_SLUG_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_TOKENFAMILIES_SLUG_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a GET "/tokenfamilies/$SLUG" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_tokenfamilies_SLUG (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-get-token-families-SLUG.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-token-families.c b/src/backend/taler-merchant-httpd_private-get-token-families.c
new file mode 100644
index 00000000..fa364570
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-token-families.c
@@ -0,0 +1,88 @@
+/*
+ This file is part of TALER
+ (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_private-get-token-families.c
+ * @brief implement GET /tokenfamilies
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-token-families.h"
+
+
+
+/**
+ * Add token family details to our JSON array.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param slug slug of the token family
+ * @param name name of the token family
+ * @param valid_after start time of the token family's validity period
+ * @param valid_before end time of the token family's validity period
+ * @param kind kind of the token family
+ */
+static void
+add_token_family (void *cls,
+ const char *slug,
+ const char *name,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct GNUNET_TIME_Timestamp valid_before,
+ const char *kind)
+{
+ json_t *pa = cls;
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ pa,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("slug", slug),
+ GNUNET_JSON_pack_string ("name", name),
+ GNUNET_JSON_pack_timestamp ("valid_after", valid_after),
+ GNUNET_JSON_pack_timestamp ("valid_before", valid_before),
+ GNUNET_JSON_pack_string ("kind", kind))));
+}
+
+
+MHD_RESULT
+TMH_private_get_tokenfamilies (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ json_t *families;
+ enum GNUNET_DB_QueryStatus qs;
+
+ families = json_array ();
+ GNUNET_assert (NULL != families);
+ qs = TMH_db->lookup_token_families (TMH_db->cls,
+ hc->instance->settings.id,
+ &add_token_family,
+ families);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ json_decref (families);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("token_families",
+ families));
+}
+
+
+/* end of taler-merchant-httpd_private-get-token-families.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-token-families.h b/src/backend/taler-merchant-httpd_private-get-token-families.h
new file mode 100644
index 00000000..a02a42b0
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-token-families.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_private-get-token-families.h
+ * @brief implement GET /tokenfamilies
+ * @author Christian Blättler
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_TOKENFAMILIES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_TOKENFAMILIES_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a GET "/tokenfamilies" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_tokenfamilies (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-get-token-families.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-transfers.c b/src/backend/taler-merchant-httpd_private-get-transfers.c
index c43781dd..3e540297 100644
--- a/src/backend/taler-merchant-httpd_private-get-transfers.c
+++ b/src/backend/taler-merchant-httpd_private-get-transfers.c
@@ -137,59 +137,16 @@ TMH_private_get_transfers (const struct TMH_RequestHandler *rh,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"after");
}
- {
- const char *limit_s;
-
- limit_s = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "limit");
- if (NULL != limit_s)
- {
- char dummy[2];
- long long l;
-
- if (1 !=
- sscanf (limit_s,
- "%lld%1s",
- &l,
- dummy))
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "limit");
- limit = (int64_t) l;
- }
- }
- {
- const char *offset_s;
-
- offset_s = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "offset");
- if (NULL != offset_s)
- {
- char dummy[2];
- unsigned long long o;
-
- if (1 !=
- sscanf (offset_s,
- "%llu%1s",
- &o,
- dummy))
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "offset");
- offset = (uint64_t) o;
- }
- else
- {
- if (limit < 0)
- offset = INT64_MAX;
- else
- offset = 0;
- }
- }
+ TALER_MHD_parse_request_snumber (connection,
+ "limit",
+ &limit);
+ if (limit < 0)
+ offset = INT64_MAX;
+ else
+ offset = 0;
+ TALER_MHD_parse_request_number (connection,
+ "offset",
+ &offset);
if (! (TALER_arg_to_yna (connection,
"verified",
TALER_EXCHANGE_YNA_ALL,
diff --git a/src/backend/taler-merchant-httpd_private-get-webhooks-ID.c b/src/backend/taler-merchant-httpd_private-get-webhooks-ID.c
index 94371e9e..e1566efd 100644
--- a/src/backend/taler-merchant-httpd_private-get-webhooks-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-webhooks-ID.c
@@ -72,10 +72,12 @@ TMH_private_get_webhooks_ID (const struct TMH_RequestHandler *rh,
wb.url),
GNUNET_JSON_pack_string ("http_method",
wb.http_method),
- GNUNET_JSON_pack_string ("header_template",
- wb.header_template),
- GNUNET_JSON_pack_string ("body_template",
- wb.body_template));
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("header_template",
+ wb.header_template)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("body_template",
+ wb.body_template)));
GNUNET_free (wb.event_type);
GNUNET_free (wb.url);
GNUNET_free (wb.http_method);
diff --git a/src/backend/taler-merchant-httpd_private-patch-accounts-ID.c b/src/backend/taler-merchant-httpd_private-patch-accounts-ID.c
new file mode 100644
index 00000000..dd18281f
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-accounts-ID.c
@@ -0,0 +1,132 @@
+/*
+ This file is part of TALER
+ (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_private-patch-accounts-ID.c
+ * @brief implementing PATCH /accounts/$ID request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-patch-accounts-ID.h"
+#include "taler-merchant-httpd_helper.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * PATCH configuration of an existing instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_patch_accounts_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ const char *h_wire_s = hc->infix;
+ enum GNUNET_DB_QueryStatus qs;
+ const json_t *cfc;
+ const char *cfu;
+ struct TALER_MerchantWireHashP h_wire;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("credit_facade_url",
+ &cfu),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("credit_facade_credentials",
+ &cfc),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_assert (NULL != mi);
+ GNUNET_assert (NULL != h_wire_s);
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (h_wire_s,
+ strlen (h_wire_s),
+ &h_wire,
+ sizeof (h_wire)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_GENERIC_H_WIRE_MALFORMED,
+ h_wire_s);
+ }
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ if (GNUNET_OK != res)
+ return (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO;
+ }
+
+ qs = TMH_db->update_account (TMH_db->cls,
+ mi->settings.id,
+ &h_wire,
+ cfu,
+ cfc);
+ {
+ MHD_RESULT ret = MHD_NO;
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "update_account");
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "unexpected serialization problem");
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_ACCOUNT_UNKNOWN,
+ h_wire_s);
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ ret = TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ break;
+ }
+ GNUNET_JSON_parse_free (spec);
+ return ret;
+ }
+}
+
+
+/* end of taler-merchant-httpd_private-patch-accounts-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-patch-accounts-ID.h b/src/backend/taler-merchant-httpd_private-patch-accounts-ID.h
new file mode 100644
index 00000000..752fb958
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-accounts-ID.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_private-patch-accounts-ID.h
+ * @brief implementing PATCH /accounts request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_ACCOUNTS_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_ACCOUNTS_ID_H
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * PATCH configuration of an existing instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_patch_accounts_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-patch-instances-ID.c b/src/backend/taler-merchant-httpd_private-patch-instances-ID.c
index b8e0052d..027d5869 100644
--- a/src/backend/taler-merchant-httpd_private-patch-instances-ID.c
+++ b/src/backend/taler-merchant-httpd_private-patch-instances-ID.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2020-2021 Taler Systems SA
+ (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -26,6 +26,7 @@
#include "taler-merchant-httpd_private-patch-instances-ID.h"
#include "taler-merchant-httpd_helper.h"
#include <taler/taler_json_lib.h>
+#include <taler/taler_dbevents.h>
/**
@@ -62,16 +63,18 @@ patch_instances_ID (struct TMH_MerchantInstance *mi,
struct TMH_HandlerContext *hc)
{
struct TALER_MERCHANTDB_InstanceSettings is;
- json_t *payto_uris;
const char *name;
+ const char *uts = "business";
struct TMH_WireMethod *wm_head = NULL;
struct TMH_WireMethod *wm_tail = NULL;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("payto_uris",
- &payto_uris),
GNUNET_JSON_spec_string ("name",
&name),
GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("user_type",
+ &uts),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("website",
(const char **) &is.website),
NULL),
@@ -87,14 +90,8 @@ patch_instances_ID (struct TMH_MerchantInstance *mi,
&is.address),
GNUNET_JSON_spec_json ("jurisdiction",
&is.jurisdiction),
- TALER_JSON_spec_amount ("default_max_wire_fee",
- TMH_currency,
- &is.default_max_wire_fee),
- GNUNET_JSON_spec_uint32 ("default_wire_fee_amortization",
- &is.default_wire_fee_amortization),
- TALER_JSON_spec_amount ("default_max_deposit_fee",
- TMH_currency,
- &is.default_max_deposit_fee),
+ GNUNET_JSON_spec_bool ("use_stefan",
+ &is.use_stefan),
GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
&is.default_wire_transfer_delay),
GNUNET_JSON_spec_relative_time ("default_pay_delay",
@@ -102,7 +99,6 @@ patch_instances_ID (struct TMH_MerchantInstance *mi,
GNUNET_JSON_spec_end ()
};
enum GNUNET_DB_QueryStatus qs;
- bool committed = false;
GNUNET_assert (NULL != mi);
memset (&is,
@@ -119,6 +115,19 @@ patch_instances_ID (struct TMH_MerchantInstance *mi,
? MHD_YES
: MHD_NO;
}
+ if (NULL == uts)
+ uts = "business";
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_user_type_from_string (uts,
+ &is.ut))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "user_type");
+ }
if (! TMH_location_object_valid (is.address))
{
GNUNET_break_op (0);
@@ -149,15 +158,7 @@ patch_instances_ID (struct TMH_MerchantInstance *mi,
"jurisdiction");
}
- if (! TMH_payto_uri_array_valid (payto_uris))
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
- NULL);
- }
- for (unsigned int i = 0; i<MAX_RETRIES; i++)
+ for (unsigned int retry = 0; retry<MAX_RETRIES; retry++)
{
/* Cleanup after earlier loops */
{
@@ -184,6 +185,7 @@ patch_instances_ID (struct TMH_MerchantInstance *mi,
/* Check for equality of settings */
if (! ( (0 == strcmp (mi->settings.name,
name)) &&
+ (mi->settings.ut == is.ut) &&
((mi->settings.email == is.email) ||
(NULL != is.email && NULL != mi->settings.email &&
0 == strcmp (mi->settings.email,
@@ -200,18 +202,7 @@ patch_instances_ID (struct TMH_MerchantInstance *mi,
is.address)) &&
(1 == json_equal (mi->settings.jurisdiction,
is.jurisdiction)) &&
- (GNUNET_YES == TALER_amount_cmp_currency (
- &mi->settings.default_max_deposit_fee,
- &is.default_max_deposit_fee)) &&
- (0 == TALER_amount_cmp (&mi->settings.default_max_deposit_fee,
- &is.default_max_deposit_fee)) &&
- (GNUNET_YES == TALER_amount_cmp_currency (
- &mi->settings.default_max_wire_fee,
- &is.default_max_wire_fee)) &&
- (0 == TALER_amount_cmp (&mi->settings.default_max_wire_fee,
- &is.default_max_wire_fee)) &&
- (mi->settings.default_wire_fee_amortization ==
- is.default_wire_fee_amortization) &&
+ (mi->settings.use_stefan == is.use_stefan) &&
(GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay,
==,
is.default_wire_transfer_delay)) &&
@@ -233,161 +224,13 @@ patch_instances_ID (struct TMH_MerchantInstance *mi,
goto giveup;
}
}
-
- /* Check for changes in accounts */
- {
- unsigned int len = json_array_size (payto_uris);
- struct TMH_WireMethod *matches[GNUNET_NZL (len)];
- bool matched;
-
- memset (matches,
- 0,
- sizeof (matches));
- for (struct TMH_WireMethod *wm = mi->wm_head;
- NULL != wm;
- wm = wm->next)
- {
- const char *uri = wm->payto_uri;
-
- GNUNET_assert (NULL != uri);
- matched = false;
- for (unsigned int i = 0; i<len; i++)
- {
- const char *str = json_string_value (json_array_get (payto_uris,
- i));
- if (0 == strcasecmp (uri,
- str))
- {
- /* our own existing payto URIs should be unique, that is no
- duplicates in the list, so we cannot match twice */
- GNUNET_assert (NULL == matches[i]);
- matches[i] = wm;
- matched = true;
- break;
- }
- }
- /* delete unmatched (= removed) accounts */
- if ( (! matched) &&
- (wm->active) )
- {
- /* Account was REMOVED */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Existing account `%s' not found, inactivating it.\n",
- uri);
- wm->deleting = true;
- qs = TMH_db->inactivate_account (TMH_db->cls,
- mi->settings.id,
- &wm->h_wire);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- TMH_db->rollback (TMH_db->cls);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- goto retry;
- else
- goto giveup;
- }
- }
- }
- /* Find _new_ accounts */
- for (unsigned int i = 0; i<len; i++)
- {
- struct TALER_MERCHANTDB_AccountDetails ad;
- struct TMH_WireMethod *wm;
-
- if (NULL != matches[i])
- {
- wm = matches[i];
- if (! wm->active)
- {
- qs = TMH_db->activate_account (TMH_db->cls,
- mi->settings.id,
- &wm->h_wire);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- TMH_db->rollback (TMH_db->cls);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- goto retry;
- else
- goto giveup;
- }
- }
- wm->enabling = true;
- continue;
- }
- ad.payto_uri = json_string_value (json_array_get (payto_uris,
- i));
- GNUNET_assert (NULL != ad.payto_uri);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Adding NEW account `%s'\n",
- ad.payto_uri);
- wm = TMH_setup_wire_account (ad.payto_uri);
- GNUNET_assert (NULL != wm); /* checked payto_uri validity earlier */
- GNUNET_CONTAINER_DLL_insert (wm_head,
- wm_tail,
- wm);
- ad.h_wire = wm->h_wire;
- ad.salt = wm->wire_salt;
- ad.active = true;
- qs = TMH_db->insert_account (TMH_db->cls,
- mi->settings.id,
- &ad);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- TMH_db->rollback (TMH_db->cls);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- goto retry;
- else
- goto giveup;
- }
- }
- }
-
qs = TMH_db->commit (TMH_db->cls);
retry:
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
continue;
- if (qs >= 0)
- committed = true;
break;
} /* for(... MAX_RETRIES) */
giveup:
- /* Deactivate existing wire methods that were removed above */
- for (struct TMH_WireMethod *wm = mi->wm_head;
- NULL != wm;
- wm = wm->next)
- {
- /* We did not flip the 'active' bits earlier because the
- DB transaction could still fail. Now it is time to update our
- runtime state. */
- GNUNET_assert (! (wm->deleting & wm->enabling));
- if (committed)
- {
- if (wm->deleting)
- wm->active = false;
- if (wm->enabling)
- wm->active = true;
- }
- wm->deleting = false;
- wm->enabling = false;
- }
- if (! committed)
- {
- struct TMH_WireMethod *wm;
-
- while (NULL != (wm = wm_head))
- {
- GNUNET_CONTAINER_DLL_remove (wm_head,
- wm_tail,
- wm);
- free_wm (wm);
- }
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_COMMIT_FAILED,
- NULL);
- }
-
/* Update our 'settings' */
GNUNET_free (mi->settings.name);
GNUNET_free (mi->settings.email);
@@ -397,6 +240,7 @@ giveup:
json_decref (mi->settings.jurisdiction);
is.id = mi->settings.id;
mi->settings = is;
+ // mi->settings.user_type = (enum TALER_KYCLOGIC_KycUserType) is.user_type;
mi->settings.address = json_incref (mi->settings.address);
mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
mi->settings.name = GNUNET_strdup (name);
@@ -407,23 +251,6 @@ giveup:
if (NULL != is.logo)
mi->settings.logo = GNUNET_strdup (is.logo);
- /* Add 'new' wire methods to our list */
- {
- struct TMH_WireMethod *wm;
-
- /* Note: this _could_ be done more efficiently if
- someone wrote a GNUNET_CONTAINER_DLL_merge()... */
- while (NULL != (wm = wm_head))
- {
- GNUNET_CONTAINER_DLL_remove (wm_head,
- wm_tail,
- wm);
- GNUNET_CONTAINER_DLL_insert (mi->wm_head,
- mi->wm_tail,
- wm);
- }
- }
-
GNUNET_JSON_parse_free (spec);
TMH_reload_instances (mi->settings.id);
return TALER_MHD_reply_static (connection,
diff --git a/src/backend/taler-merchant-httpd_private-patch-orders-ID-forget.c b/src/backend/taler-merchant-httpd_private-patch-orders-ID-forget.c
index 798d610a..cb64d607 100644
--- a/src/backend/taler-merchant-httpd_private-patch-orders-ID-forget.c
+++ b/src/backend/taler-merchant-httpd_private-patch-orders-ID-forget.c
@@ -93,10 +93,9 @@ TMH_private_patch_orders_ID_forget (const struct TMH_RequestHandler *rh,
for (unsigned int i = 0; i<MAX_RETRIES; i++)
{
- json_t *fields;
+ const json_t *fields;
json_t *contract_terms;
bool changed = false;
- bool paid = false;
if (GNUNET_OK !=
TMH_db->start (TMH_db->cls,
@@ -112,7 +111,6 @@ TMH_private_patch_orders_ID_forget (const struct TMH_RequestHandler *rh,
order_id,
&contract_terms,
&order_serial,
- &paid,
NULL);
switch (qs)
{
@@ -138,8 +136,8 @@ TMH_private_patch_orders_ID_forget (const struct TMH_RequestHandler *rh,
{
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("fields",
- &fields),
+ GNUNET_JSON_spec_array_const ("fields",
+ &fields),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue res;
@@ -156,17 +154,6 @@ TMH_private_patch_orders_ID_forget (const struct TMH_RequestHandler *rh,
: MHD_NO;
}
}
- if (! (json_is_array (fields)))
- {
- TMH_db->rollback (TMH_db->cls);
- json_decref (contract_terms);
- json_decref (fields);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "fields");
- }
-
{
size_t index;
json_t *value;
@@ -179,7 +166,6 @@ TMH_private_patch_orders_ID_forget (const struct TMH_RequestHandler *rh,
{
TMH_db->rollback (TMH_db->cls);
json_decref (contract_terms);
- json_decref (fields);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_PATCH_ORDERS_ID_FORGET_PATH_SYNTAX_INCORRECT,
@@ -194,7 +180,6 @@ TMH_private_patch_orders_ID_forget (const struct TMH_RequestHandler *rh,
/* We tried to forget a field that isn't forgettable */
TMH_db->rollback (TMH_db->cls);
json_decref (contract_terms);
- json_decref (fields);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_CONFLICT,
TALER_EC_MERCHANT_PRIVATE_PATCH_ORDERS_ID_FORGET_PATH_NOT_FORGETTABLE,
@@ -207,7 +192,6 @@ TMH_private_patch_orders_ID_forget (const struct TMH_RequestHandler *rh,
/* One of the paths was malformed and couldn't be expanded */
TMH_db->rollback (TMH_db->cls);
json_decref (contract_terms);
- json_decref (fields);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_PATCH_ORDERS_ID_FORGET_PATH_SYNTAX_INCORRECT,
@@ -220,7 +204,6 @@ TMH_private_patch_orders_ID_forget (const struct TMH_RequestHandler *rh,
{
TMH_db->rollback (TMH_db->cls);
json_decref (contract_terms);
- json_decref (fields);
return TALER_MHD_reply_static (connection,
MHD_HTTP_NO_CONTENT,
NULL,
@@ -232,7 +215,6 @@ TMH_private_patch_orders_ID_forget (const struct TMH_RequestHandler *rh,
order_id,
contract_terms);
json_decref (contract_terms);
- json_decref (fields);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
TMH_db->rollback (TMH_db->cls);
diff --git a/src/backend/taler-merchant-httpd_private-patch-otp-devices-ID.c b/src/backend/taler-merchant-httpd_private-patch-otp-devices-ID.c
new file mode 100644
index 00000000..596b8b09
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-otp-devices-ID.c
@@ -0,0 +1,114 @@
+/*
+ This file is part of TALER
+ (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_private-patch-otp-devices-ID.c
+ * @brief implementing PATCH /otp-devices/$ID request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-patch-otp-devices-ID.h"
+#include "taler-merchant-httpd_helper.h"
+#include <taler/taler_json_lib.h>
+
+
+MHD_RESULT
+TMH_private_patch_otp_devices_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ const char *device_id = hc->infix;
+ struct TALER_MERCHANTDB_OtpDeviceDetails tp = {0};
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("otp_device_description",
+ (const char **) &tp.otp_description),
+ TALER_JSON_spec_otp_type ("otp_algorithm",
+ &tp.otp_algorithm),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint64 ("otp_ctr",
+ &tp.otp_ctr),
+ NULL),
+ GNUNET_JSON_spec_mark_optional(
+
+ TALER_JSON_spec_otp_key ("otp_key",
+ (const char **) &tp.otp_key),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_assert (NULL != mi);
+ GNUNET_assert (NULL != device_id);
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ if (GNUNET_OK != res)
+ return (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO;
+ }
+
+ qs = TMH_db->update_otp (TMH_db->cls,
+ mi->settings.id,
+ device_id,
+ &tp);
+ {
+ MHD_RESULT ret = MHD_NO;
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "update_pos");
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "unexpected serialization problem");
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_OTP_DEVICE_UNKNOWN,
+ device_id);
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ ret = TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ break;
+ }
+ GNUNET_JSON_parse_free (spec);
+ return ret;
+ }
+}
+
+
+/* end of taler-merchant-httpd_private-patch-otp-devices-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-patch-otp-devices-ID.h b/src/backend/taler-merchant-httpd_private-patch-otp-devices-ID.h
new file mode 100644
index 00000000..eef1dd0a
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-otp-devices-ID.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_private-patch-otp-devices-ID.h
+ * @brief implementing PATCH /otp-devices/$ID request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_OTP_DEVICES_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_OTP_DEVICES_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * PATCH configuration of an existing instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_patch_otp_devices_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-patch-products-ID.c b/src/backend/taler-merchant-httpd_private-patch-products-ID.c
index c4ba755b..7bc327cd 100644
--- a/src/backend/taler-merchant-httpd_private-patch-products-ID.c
+++ b/src/backend/taler-merchant-httpd_private-patch-products-ID.c
@@ -125,9 +125,8 @@ TMH_private_patch_products_ID (const struct TMH_RequestHandler *rh,
NULL),
GNUNET_JSON_spec_string ("unit",
(const char **) &pd.unit),
- TALER_JSON_spec_amount ("price",
- TMH_currency,
- &pd.price),
+ TALER_JSON_spec_amount_any ("price",
+ &pd.price),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("image",
(const char **) &pd.image),
diff --git a/src/backend/taler-merchant-httpd_private-patch-products-ID.h b/src/backend/taler-merchant-httpd_private-patch-products-ID.h
index e7f8fcfd..9ce0a7ae 100644
--- a/src/backend/taler-merchant-httpd_private-patch-products-ID.h
+++ b/src/backend/taler-merchant-httpd_private-patch-products-ID.h
@@ -19,7 +19,7 @@
/**
* @file taler-merchant-httpd_private-patch-products-ID.h
- * @brief implementing POST /products request handling
+ * @brief implementing PATCH /products/$ID request handling
* @author Christian Grothoff
*/
#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_PRODUCTS_ID_H
diff --git a/src/backend/taler-merchant-httpd_private-patch-templates-ID.c b/src/backend/taler-merchant-httpd_private-patch-templates-ID.c
index 6cd060f0..e8a6c531 100644
--- a/src/backend/taler-merchant-httpd_private-patch-templates-ID.c
+++ b/src/backend/taler-merchant-httpd_private-patch-templates-ID.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2022 Taler Systems SA
+ (C) 2022, 2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -29,12 +29,6 @@
/**
- * How often do we retry the simple INSERT database transaction?
- */
-#define MAX_RETRIES 3
-
-
-/**
* Determine the cause of the PATCH failure in more detail and report.
*
* @param connection connection to report on
@@ -52,9 +46,9 @@ determine_cause (struct MHD_Connection *connection,
enum GNUNET_DB_QueryStatus qs;
qs = TMH_db->lookup_template (TMH_db->cls,
- instance_id,
- template_id,
- &tpx);
+ instance_id,
+ template_id,
+ &tpx);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -102,8 +96,8 @@ determine_cause (struct MHD_Connection *connection,
*/
MHD_RESULT
TMH_private_patch_templates_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
{
struct TMH_MerchantInstance *mi = hc->instance;
const char *template_id = hc->infix;
@@ -113,11 +107,19 @@ TMH_private_patch_templates_ID (const struct TMH_RequestHandler *rh,
GNUNET_JSON_spec_string ("template_description",
(const char **) &tp.template_description),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("image",
- (const char **) &tp.image),
+ GNUNET_JSON_spec_string ("otp_id",
+ (const char **) &tp.otp_id),
NULL),
GNUNET_JSON_spec_json ("template_contract",
&tp.template_contract),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("required_currency",
+ (const char **) &tp.required_currency),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json ("editable_defaults",
+ &tp.editable_defaults),
+ NULL),
GNUNET_JSON_spec_end ()
};
@@ -135,7 +137,7 @@ TMH_private_patch_templates_ID (const struct TMH_RequestHandler *rh,
: MHD_NO;
}
- if (! TMH_template_contract_valid (tp.template_contract))
+ if (! TMH_template_contract_valid (tp.template_contract))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
@@ -144,22 +146,61 @@ TMH_private_patch_templates_ID (const struct TMH_RequestHandler *rh,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"template_contract");
}
-
-
- if (! TMH_image_data_url_valid (tp.image))
+ if ( (NULL != tp.required_currency) &&
+ (GNUNET_OK !=
+ TALER_check_currency (tp.required_currency)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "required_currency");
+ }
+ if ( (NULL != tp.required_currency) &&
+ (NULL != json_object_get (tp.template_contract,
+ "amount")) )
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "image");
+ "required_currency and contract::amount specified");
+ }
+ if (NULL != tp.editable_defaults)
+ {
+ const char *key;
+ json_t *val;
+
+ json_object_foreach (tp.editable_defaults, key, val)
+ {
+ if (NULL !=
+ json_object_get (tp.template_contract,
+ key))
+ {
+ char *msg;
+ MHD_RESULT ret;
+
+ GNUNET_break_op (0);
+ GNUNET_asprintf (&msg,
+ "editable_defaults::%s conflicts with template_contract",
+ key);
+ GNUNET_JSON_parse_free (spec);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ msg);
+ GNUNET_free (msg);
+ return ret;
+ }
+ }
}
qs = TMH_db->update_template (TMH_db->cls,
- mi->settings.id,
- template_id,
- &tp);
+ mi->settings.id,
+ template_id,
+ &tp);
{
MHD_RESULT ret = MHD_NO;
diff --git a/src/backend/taler-merchant-httpd_private-patch-templates-ID.h b/src/backend/taler-merchant-httpd_private-patch-templates-ID.h
index 29cf4830..63ec50cb 100644
--- a/src/backend/taler-merchant-httpd_private-patch-templates-ID.h
+++ b/src/backend/taler-merchant-httpd_private-patch-templates-ID.h
@@ -37,7 +37,7 @@
*/
MHD_RESULT
TMH_private_patch_templates_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc);
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
#endif
diff --git a/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c
new file mode 100644
index 00000000..755ed4c9
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c
@@ -0,0 +1,158 @@
+/*
+ This file is part of TALER
+ (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_private-patch-token-families-SLUG.c
+ * @brief implementing PATCH /tokenfamilies/$SLUG request handling
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-patch-token-families-SLUG.h"
+#include "taler-merchant-httpd_helper.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * How often do we retry the simple INSERT database transaction?
+ */
+#define MAX_RETRIES 3
+
+/**
+ * Handle a PATCH "/tokenfamilies/$slug" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_patch_token_family_SLUG (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ const char *slug = hc->infix;
+ struct TALER_MERCHANTDB_TokenFamilyDetails details = {0};
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("name",
+ (const char **) &details.name),
+ GNUNET_JSON_spec_string ("description",
+ (const char **) &details.description),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json ("description_i18n",
+ &details.description_i18n),
+ NULL),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &details.valid_after),
+ GNUNET_JSON_spec_timestamp ("valid_before",
+ &details.valid_before),
+ GNUNET_JSON_spec_relative_time ("duration",
+ &details.duration),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_assert (NULL != mi);
+ GNUNET_assert (NULL != slug);
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ if (GNUNET_OK != res)
+ return (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO;
+ }
+
+ struct GNUNET_TIME_Relative validity = GNUNET_TIME_absolute_get_difference (
+ details.valid_after.abs_time,
+ details.valid_before.abs_time);
+
+ // Check if start_time is before valid_before
+ if (0 == validity.rel_value_us)
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "invalid_validity_duration");
+ }
+
+ if (NULL == details.description_i18n)
+ {
+ details.description_i18n = json_object ();
+ GNUNET_assert (NULL != details.description_i18n);
+ }
+ if (! TALER_JSON_check_i18n (details.description_i18n))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "description_i18n");
+ }
+
+ qs = TMH_db->update_token_family (TMH_db->cls,
+ mi->settings.id,
+ slug,
+ &details);
+ {
+ MHD_RESULT ret = MHD_NO;
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ NULL);
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "unexpected serialization problem");
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_PATCH_TOKEN_FAMILY_NOT_FOUND,
+ slug);
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ ret = TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ break;
+ }
+ GNUNET_JSON_parse_free (spec);
+ return ret;
+ }
+}
+
+
+/* end of taler-merchant-httpd_private-patch-token-families-SLUG.c */
diff --git a/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.h b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.h
new file mode 100644
index 00000000..87ad86b3
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_private-patch-token-families-SLUG.h
+ * @brief implementing PATCH /tokenfamilies/$SLUG request handling
+ * @author Christian Blättler
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_TOKEN_FAMILIES_SLUG_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_TOKEN_FAMILIES_SLUG_H
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a PATCH "/tokenfamilies/$slug" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_patch_token_family_SLUG (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-patch-webhooks-ID.c b/src/backend/taler-merchant-httpd_private-patch-webhooks-ID.c
index d84a888c..80d889fa 100644
--- a/src/backend/taler-merchant-httpd_private-patch-webhooks-ID.c
+++ b/src/backend/taler-merchant-httpd_private-patch-webhooks-ID.c
@@ -112,15 +112,18 @@ TMH_private_patch_webhooks_ID (const struct TMH_RequestHandler *rh,
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("event_type",
(const char **) &wb.event_type),
- GNUNET_JSON_spec_string ("url",
+ TALER_JSON_spec_web_url ("url",
(const char **) &wb.url),
GNUNET_JSON_spec_string ("http_method",
(const char **) &wb.http_method),
- GNUNET_JSON_spec_string ("header_template",
- (const char **) &wb.header_template),
- GNUNET_JSON_spec_string ("body_template",
- (const char **) &wb.body_template),
-
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("header_template",
+ (const char **) &wb.header_template),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("body_template",
+ (const char **) &wb.body_template),
+ NULL),
GNUNET_JSON_spec_end ()
};
diff --git a/src/backend/taler-merchant-httpd_private-post-account.c b/src/backend/taler-merchant-httpd_private-post-account.c
new file mode 100644
index 00000000..dbaac7ba
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-account.c
@@ -0,0 +1,238 @@
+/*
+ This file is part of TALER
+ (C) 2020-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_private-post-account.c
+ * @brief implementing POST /private/accounts request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-post-account.h"
+#include "taler-merchant-httpd_helper.h"
+#include "taler_merchant_bank_lib.h"
+#include <taler/taler_dbevents.h>
+#include <taler/taler_json_lib.h>
+
+
+MHD_RESULT
+TMH_private_post_account (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ const char *credit_facade_url = NULL;
+ const json_t *credit_facade_credentials = NULL;
+ const char *uri;
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &uri),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("credit_facade_url",
+ &credit_facade_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("credit_facade_credentials",
+ &credit_facade_credentials),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ struct TMH_WireMethod *wm;
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ ispec);
+ if (GNUNET_OK != res)
+ return (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO;
+ }
+
+ {
+ char *err;
+
+ if (NULL !=
+ (err = TALER_payto_validate (uri)))
+ {
+ MHD_RESULT mret;
+
+ GNUNET_break_op (0);
+ mret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
+ err);
+ GNUNET_free (err);
+ return mret;
+ }
+ }
+
+ if ( (NULL == credit_facade_url) !=
+ (NULL == credit_facade_credentials) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ (NULL == credit_facade_url)
+ ? "credit_facade_url"
+ : "credit_facade_credentials");
+ }
+ if ( (NULL != credit_facade_url) ||
+ (NULL != credit_facade_credentials) )
+ {
+ struct TALER_MERCHANT_BANK_AuthenticationData auth;
+
+ if (GNUNET_OK !=
+ TALER_MERCHANT_BANK_auth_parse_json (credit_facade_credentials,
+ credit_facade_url,
+ &auth))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "credit_facade_url or credit_facade_credentials");
+ }
+ TALER_MERCHANT_BANK_auth_free (&auth);
+ }
+
+ /* convert provided payto URI into internal data structure with salts */
+ wm = TMH_setup_wire_account (uri,
+ credit_facade_url,
+ credit_facade_credentials);
+ GNUNET_assert (NULL != wm);
+ {
+ struct TALER_MERCHANTDB_AccountDetails ad = {
+ .payto_uri = wm->payto_uri,
+ .salt = wm->wire_salt,
+ .h_wire = wm->h_wire,
+ .credit_facade_url = wm->credit_facade_url,
+ .credit_facade_credentials = wm->credit_facade_credentials,
+ .active = wm->active
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->insert_account (TMH_db->cls,
+ mi->settings.id,
+ &ad);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* conflict: account exists */
+ {
+ struct TALER_MERCHANTDB_AccountDetails adx;
+
+ qs = TMH_db->select_account_by_uri (TMH_db->cls,
+ mi->settings.id,
+ ad.payto_uri,
+ &adx);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ if ( (0 == strcmp (adx.payto_uri,
+ ad.payto_uri) ) &&
+ ( (adx.credit_facade_credentials ==
+ ad.credit_facade_credentials) ||
+ ( (NULL != adx.credit_facade_credentials) &&
+ (NULL != ad.credit_facade_credentials) &&
+ (1 == json_equal (adx.credit_facade_credentials,
+ ad.credit_facade_credentials)) ) ) &&
+ ( (adx.credit_facade_url == ad.credit_facade_url) ||
+ ( (NULL != adx.credit_facade_url) &&
+ (NULL != ad.credit_facade_url) &&
+ (0 == strcmp (adx.credit_facade_url,
+ ad.credit_facade_url)) ) ) )
+ {
+ TMH_wire_method_free (wm);
+ GNUNET_free (adx.payto_uri);
+ GNUNET_free (adx.credit_facade_url);
+ json_decref (adx.credit_facade_credentials);
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto (
+ "salt",
+ &adx.salt),
+ GNUNET_JSON_pack_data_auto (
+ "h_wire",
+ &adx.h_wire));
+ }
+ GNUNET_free (adx.payto_uri);
+ GNUNET_free (adx.credit_facade_url);
+ json_decref (adx.credit_facade_credentials);
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TMH_wire_method_free (wm);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_account");
+ }
+ }
+ TMH_wire_method_free (wm);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_ACCOUNT_EXISTS,
+ uri);
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TMH_wire_method_free (wm);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_account");
+ }
+ }
+
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_MERCHANT_ACCOUNTS_CHANGED)
+ };
+
+ TMH_db->event_notify (TMH_db->cls,
+ &es,
+ NULL,
+ 0);
+ }
+ /* Finally, also update our running process */
+ GNUNET_CONTAINER_DLL_insert (mi->wm_head,
+ mi->wm_tail,
+ wm);
+ /* Note: we may not need to do this, as we notified
+ about the account change above. But also hardly hurts. */
+ TMH_reload_instances (mi->settings.id);
+ return TALER_MHD_REPLY_JSON_PACK (connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto ("salt",
+ &wm->wire_salt),
+ GNUNET_JSON_pack_data_auto ("h_wire",
+ &wm->h_wire));
+}
+
+
+/* end of taler-merchant-httpd_private-post-account.c */
diff --git a/src/backend/taler-merchant-httpd_private-post-account.h b/src/backend/taler-merchant-httpd_private-post-account.h
new file mode 100644
index 00000000..99f61090
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-account.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ (C) 2020-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_private-post-account.h
+ * @brief implementing POST /private/account request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_ACCOUNT_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_ACCOUNT_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Add bank account to an instance.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_account (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-instances-ID-auth.c b/src/backend/taler-merchant-httpd_private-post-instances-ID-auth.c
index 8feb44f2..374a60de 100644
--- a/src/backend/taler-merchant-httpd_private-post-instances-ID-auth.c
+++ b/src/backend/taler-merchant-httpd_private-post-instances-ID-auth.c
@@ -231,7 +231,6 @@ TMH_private_post_instances_default_ID_auth (const struct TMH_RequestHandler *rh,
ret = post_instances_ID_auth (mi,
connection,
hc);
- mi->auth_override = false;
return ret;
}
diff --git a/src/backend/taler-merchant-httpd_private-post-instances-ID-token.c b/src/backend/taler-merchant-httpd_private-post-instances-ID-token.c
new file mode 100644
index 00000000..a223a882
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-instances-ID-token.c
@@ -0,0 +1,149 @@
+/*
+ This file is part of GNU Taler
+ (C) 2023 Taler Systems SA
+
+ GNU Taler is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_private-post-instances-ID-token.c
+ * @brief implementing POST /instances/$ID/token request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-post-instances-ID-token.h"
+#include "taler-merchant-httpd_helper.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Default duration for the validity of a login token.
+ */
+#define DEFAULT_DURATION GNUNET_TIME_UNIT_DAYS
+
+
+MHD_RESULT
+TMH_private_post_instances_ID_token (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ json_t *jtoken = hc->request_body;
+ const char *scope;
+ uint32_t iscope = TMH_AS_NONE;
+ bool refreshable = false;
+ struct TALER_MERCHANTDB_LoginTokenP btoken;
+ struct GNUNET_TIME_Relative duration
+ = DEFAULT_DURATION;
+ struct GNUNET_TIME_Timestamp expiration_time;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("scope",
+ &scope),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_relative_time ("duration",
+ &duration),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("refreshable",
+ &refreshable),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ jtoken,
+ spec);
+ if (GNUNET_OK != res)
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ }
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &btoken,
+ sizeof (btoken));
+ expiration_time = GNUNET_TIME_relative_to_timestamp (duration);
+ if (0 == strcasecmp (scope,
+ "readonly"))
+ iscope = TMH_AS_READ_ONLY;
+ else if (0 == strcasecmp (scope,
+ "write"))
+ iscope = TMH_AS_ALL;
+ else
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "scope");
+ }
+ if (refreshable)
+ iscope |= TMH_AS_REFRESHABLE;
+ if (0 != (iscope & (~hc->auth_scope)))
+ {
+ /* more permissions requested for the new token, not allowed */
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_TOKEN_PERMISSION_INSUFFICIENT,
+ NULL);
+ }
+ qs = TMH_db->insert_login_token (TMH_db->cls,
+ mi->settings.id,
+ &btoken,
+ GNUNET_TIME_timestamp_get (),
+ expiration_time,
+ iscope);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_login_token");
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+
+ {
+ char *tok;
+ MHD_RESULT ret;
+ char *val;
+
+ val = GNUNET_STRINGS_data_to_string_alloc (&btoken,
+ sizeof (btoken));
+ GNUNET_asprintf (&tok,
+ RFC_8959_PREFIX "%s",
+ val);
+ GNUNET_free (val);
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("token",
+ tok),
+ GNUNET_JSON_pack_string ("scope",
+ scope),
+ GNUNET_JSON_pack_bool ("refreshable",
+ refreshable),
+ GNUNET_JSON_pack_timestamp ("expiration",
+ expiration_time));
+ GNUNET_free (tok);
+ return ret;
+ }
+}
+
+
+/* end of taler-merchant-httpd_private-post-instances-ID-token.c */
diff --git a/src/backend/taler-merchant-httpd_private-post-instances-ID-token.h b/src/backend/taler-merchant-httpd_private-post-instances-ID-token.h
new file mode 100644
index 00000000..884caa24
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-instances-ID-token.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of GNU Taler
+ (C) 2023 Taler Systems SA
+
+ GNU Taler is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_private-post-instances-ID-token.h
+ * @brief implements POST /instances/$ID/token request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_INSTANCES_ID_TOKEN_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_INSTANCES_ID_TOKEN_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Obtain a login token for an instance.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_instances_ID_token (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-instances.c b/src/backend/taler-merchant-httpd_private-post-instances.c
index 58a93b27..a4cf884d 100644
--- a/src/backend/taler-merchant-httpd_private-post-instances.c
+++ b/src/backend/taler-merchant-httpd_private-post-instances.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2020, 2021 Taler Systems SA
+ (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -25,6 +25,8 @@
#include "platform.h"
#include "taler-merchant-httpd_private-post-instances.h"
#include "taler-merchant-httpd_helper.h"
+#include "taler_merchant_bank_lib.h"
+#include <taler/taler_dbevents.h>
#include <taler/taler_json_lib.h>
#include <regex.h>
@@ -35,104 +37,6 @@
/**
- * Check if the array of @a payto_uris contains exactly the same
- * URIs as those already in @a mi (possibly in a different order).
- *
- * @param mi a merchant instance with accounts
- * @param payto_uris a JSON array with accounts (presumably)
- * @return true if they are 'equal', false if not or of payto_uris is not an array
- */
-static bool
-accounts_equal (const struct TMH_MerchantInstance *mi,
- json_t *payto_uris)
-{
- if (! json_is_array (payto_uris))
- return false;
- {
- unsigned int len = json_array_size (payto_uris);
- bool matches[GNUNET_NZL (len)];
- struct TMH_WireMethod *wm;
-
- memset (matches,
- 0,
- sizeof (matches));
- for (wm = mi->wm_head;
- NULL != wm;
- wm = wm->next)
- {
- const char *uri = wm->payto_uri;
-
- GNUNET_assert (NULL != uri);
- for (unsigned int i = 0; i<len; i++)
- {
- const char *str = json_string_value (json_array_get (payto_uris,
- i));
-
- GNUNET_assert (NULL != str);
- if (0 == strcasecmp (uri,
- str))
- {
- if (matches[i])
- {
- GNUNET_break (0);
- return false; /* duplicate entry!? */
- }
- matches[i] = true;
- break;
- }
- }
- }
- for (unsigned int i = 0; i<len; i++)
- if (! matches[i])
- return false;
- }
- return true;
-}
-
-
-/**
- * Free memory used by @a wm
- *
- * @param wm wire method to free
- */
-static void
-free_wm (struct TMH_WireMethod *wm)
-{
- GNUNET_free (wm->payto_uri);
- GNUNET_free (wm->wire_method);
- GNUNET_free (wm);
-}
-
-
-/**
- * Free memory used by @a mi.
- *
- * @param mi instance to free
- */
-static void
-free_mi (struct TMH_MerchantInstance *mi)
-{
- struct TMH_WireMethod *wm;
-
- while (NULL != (wm = mi->wm_head))
- {
- GNUNET_CONTAINER_DLL_remove (mi->wm_head,
- mi->wm_tail,
- wm);
- free_wm (wm);
- }
- GNUNET_free (mi->settings.id);
- GNUNET_free (mi->settings.name);
- GNUNET_free (mi->settings.website);
- GNUNET_free (mi->settings.email);
- GNUNET_free (mi->settings.logo);
- json_decref (mi->settings.address);
- json_decref (mi->settings.jurisdiction);
- GNUNET_free (mi);
-}
-
-
-/**
* Generate an instance, given its configuration.
*
* @param rh context of the handler
@@ -145,46 +49,42 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
- struct TALER_MERCHANTDB_InstanceSettings is;
+ struct TALER_MERCHANTDB_InstanceSettings is = { 0 };
struct TALER_MERCHANTDB_InstanceAuthSettings ias;
- json_t *payto_uris;
const char *auth_token = NULL;
+ const char *uts = "business";
struct TMH_WireMethod *wm_head = NULL;
struct TMH_WireMethod *wm_tail = NULL;
- json_t *jauth;
+ const json_t *jauth;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("payto_uris",
- &payto_uris),
GNUNET_JSON_spec_string ("id",
(const char **) &is.id),
GNUNET_JSON_spec_string ("name",
(const char **) &is.name),
- GNUNET_JSON_spec_mark_optional(
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("user_type",
+ &uts),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("email",
(const char **) &is.email),
NULL),
- GNUNET_JSON_spec_mark_optional(
+ GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("website",
(const char **) &is.website),
NULL),
- GNUNET_JSON_spec_mark_optional(
+ GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("logo",
(const char **) &is.logo),
NULL),
- GNUNET_JSON_spec_json ("auth",
- &jauth),
+ GNUNET_JSON_spec_object_const ("auth",
+ &jauth),
GNUNET_JSON_spec_json ("address",
&is.address),
GNUNET_JSON_spec_json ("jurisdiction",
&is.jurisdiction),
- TALER_JSON_spec_amount ("default_max_wire_fee",
- TMH_currency,
- &is.default_max_wire_fee),
- GNUNET_JSON_spec_uint32 ("default_wire_fee_amortization",
- &is.default_wire_fee_amortization),
- TALER_JSON_spec_amount ("default_max_deposit_fee",
- TMH_currency,
- &is.default_max_deposit_fee),
+ GNUNET_JSON_spec_bool ("use_stefan",
+ &is.use_stefan),
GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
&is.default_wire_transfer_delay),
GNUNET_JSON_spec_relative_time ("default_pay_delay",
@@ -203,6 +103,19 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh,
? MHD_YES
: MHD_NO;
}
+ if (NULL == uts)
+ uts = "business";
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_user_type_from_string (uts,
+ &is.ut))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "user_type");
+ }
{
enum GNUNET_GenericReturnValue ret;
@@ -211,16 +124,12 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh,
jauth,
&auth_token);
if (GNUNET_OK != ret)
+ {
+ GNUNET_JSON_parse_free (spec);
return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+ }
}
- /* check payto_uris for well-formedness */
- if (! TMH_payto_uri_array_valid (payto_uris))
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
- NULL);
-
/* check 'id' well-formed */
{
static bool once;
@@ -229,6 +138,7 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh,
if (! once)
{
+ once = true;
GNUNET_assert (0 ==
regcomp (&reg,
"^[A-Za-z0-9][A-Za-z0-9_.@-]+$",
@@ -240,10 +150,13 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh,
0, NULL, 0))
id_wellformed = false;
if (! id_wellformed)
+ {
+ GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"id");
+ }
}
if (! TMH_location_object_valid (is.address))
@@ -297,18 +210,18 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh,
is.id)) &&
(0 == strcmp (mi->settings.name,
is.name)) &&
- ((mi->settings.email == is.email) ||
- (NULL != is.email && NULL != mi->settings.email &&
- 0 == strcmp (mi->settings.email,
- is.email))) &&
- ((mi->settings.website == is.website) ||
- (NULL != is.website && NULL != mi->settings.website &&
- 0 == strcmp (mi->settings.website,
- is.website))) &&
- ((mi->settings.logo == is.logo) ||
- (NULL != is.logo && NULL != mi->settings.logo &&
- 0 == strcmp (mi->settings.logo,
- is.logo))) &&
+ ((mi->settings.email == is.email) ||
+ (NULL != is.email && NULL != mi->settings.email &&
+ 0 == strcmp (mi->settings.email,
+ is.email))) &&
+ ((mi->settings.website == is.website) ||
+ (NULL != is.website && NULL != mi->settings.website &&
+ 0 == strcmp (mi->settings.website,
+ is.website))) &&
+ ((mi->settings.logo == is.logo) ||
+ (NULL != is.logo && NULL != mi->settings.logo &&
+ 0 == strcmp (mi->settings.logo,
+ is.logo))) &&
( ( (NULL != auth_token) &&
(GNUNET_OK ==
TMH_check_auth (auth_token,
@@ -321,26 +234,13 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh,
is.address)) &&
(1 == json_equal (mi->settings.jurisdiction,
is.jurisdiction)) &&
- (GNUNET_OK == TALER_amount_cmp_currency (
- &mi->settings.default_max_deposit_fee,
- &is.default_max_deposit_fee)) &&
- (0 == TALER_amount_cmp (&mi->settings.default_max_deposit_fee,
- &is.default_max_deposit_fee)) &&
- (GNUNET_OK == TALER_amount_cmp_currency (
- &mi->settings.default_max_wire_fee,
- &is.default_max_wire_fee)) &&
- (0 == TALER_amount_cmp (&mi->settings.default_max_wire_fee,
- &is.default_max_wire_fee)) &&
- (mi->settings.default_wire_fee_amortization ==
- is.default_wire_fee_amortization) &&
+ (mi->settings.use_stefan == is.use_stefan) &&
(GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay,
==,
is.default_wire_transfer_delay)) &&
(GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay,
==,
- is.default_pay_delay)) &&
- (accounts_equal (mi,
- payto_uris)) )
+ is.default_pay_delay)) )
{
GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_static (connection,
@@ -349,31 +249,11 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh,
NULL,
0);
}
- else
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
- is.id);
- }
- }
- }
-
- /* convert provided payto URIs into internal data structure with salts */
- {
- unsigned int len = json_array_size (payto_uris);
-
- for (unsigned int i = 0; i<len; i++)
- {
- json_t *payto_uri = json_array_get (payto_uris,
- i);
- struct TMH_WireMethod *wm;
-
- wm = TMH_setup_wire_account (json_string_value (payto_uri));
- GNUNET_CONTAINER_DLL_insert (wm_head,
- wm_tail,
- wm);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
+ is.id);
}
}
@@ -425,8 +305,8 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh,
TMH_db->start (TMH_db->cls,
"post /instances"))
{
- GNUNET_JSON_parse_free (spec);
- free_mi (mi);
+ mi->rc = 1;
+ TMH_instance_decref (mi);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_START_FAILED,
@@ -437,45 +317,41 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh,
&mi->merchant_priv,
&mi->settings,
&mi->auth);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- MHD_RESULT ret;
-
- TMH_db->rollback (TMH_db->cls);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- goto retry;
- ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
- is.id);
- GNUNET_JSON_parse_free (spec);
- free_mi (mi);
- return ret;
- }
- for (struct TMH_WireMethod *wm = wm_head;
- NULL != wm;
- wm = wm->next)
- {
- struct TALER_MERCHANTDB_AccountDetails ad = {
- .payto_uri = wm->payto_uri,
- .salt = wm->wire_salt,
- .h_wire = wm->h_wire,
- .active = wm->active
- };
-
- qs = TMH_db->insert_account (TMH_db->cls,
- mi->settings.id,
- &ad);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- break;
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ switch (qs)
{
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- TMH_db->rollback (TMH_db->cls);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- break;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ {
+ MHD_RESULT ret;
+
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ is.id);
+ mi->rc = 1;
+ TMH_instance_decref (mi);
+ return ret;
+ }
+ case GNUNET_DB_STATUS_SOFT_ERROR:
goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ {
+ MHD_RESULT ret;
+
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
+ is.id);
+ mi->rc = 1;
+ TMH_instance_decref (mi);
+ return ret;
+ }
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* handled below */
+ break;
}
qs = TMH_db->commit (TMH_db->cls);
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
@@ -486,8 +362,8 @@ retry:
} /* for .. MAX_RETRIES */
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
- GNUNET_JSON_parse_free (spec);
- free_mi (mi);
+ mi->rc = 1;
+ TMH_instance_decref (mi);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_COMMIT_FAILED,
@@ -498,7 +374,6 @@ retry:
TMH_add_instance (mi));
TMH_reload_instances (mi->settings.id);
}
- GNUNET_JSON_parse_free (spec);
if (0 == strcmp (is.id,
"default"))
{
diff --git a/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c b/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
index 3953fa06..67e1410b 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2021 Taler Systems SA
+ (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -26,6 +26,7 @@
#include <taler/taler_json_lib.h>
#include "taler-merchant-httpd_private-post-orders-ID-refund.h"
#include "taler-merchant-httpd_private-get-orders.h"
+#include "taler-merchant-httpd_helper.h"
/**
@@ -82,43 +83,21 @@ make_taler_refund_uri (struct MHD_Connection *connection,
const char *instance_id,
const char *order_id)
{
- const char *host;
- const char *forwarded_host;
- const char *uri_path;
- struct GNUNET_Buffer buf = { 0 };
+ struct GNUNET_Buffer buf;
GNUNET_assert (NULL != instance_id);
GNUNET_assert (NULL != order_id);
- host = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- "Host");
- forwarded_host = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- "X-Forwarded-Host");
- uri_path = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- "X-Forwarded-Prefix");
- if (NULL != forwarded_host)
- host = forwarded_host;
- if (NULL == host)
+ if (GNUNET_OK !=
+ TMH_taler_uri_by_connection (connection,
+ "refund",
+ instance_id,
+ &buf))
{
- /* Should never happen, at least the host header should be defined */
GNUNET_break (0);
return NULL;
}
- GNUNET_buffer_write_str (&buf, "taler");
- if (GNUNET_NO == TALER_mhd_is_https (connection))
- GNUNET_buffer_write_str (&buf, "+http");
- GNUNET_buffer_write_str (&buf, "://refund/");
- GNUNET_buffer_write_str (&buf, host);
- if (NULL != uri_path)
- GNUNET_buffer_write_path (&buf, uri_path);
- if (0 != strcmp ("default", instance_id))
- {
- GNUNET_buffer_write_path (&buf, "instances");
- GNUNET_buffer_write_path (&buf, instance_id);
- }
- GNUNET_buffer_write_path (&buf, order_id);
+ GNUNET_buffer_write_path (&buf,
+ order_id);
GNUNET_buffer_write_path (&buf,
""); /* Trailing slash */
return GNUNET_buffer_reap_str (&buf);
@@ -142,34 +121,70 @@ TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
struct TALER_Amount refund;
const char *reason;
struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("refund",
- TMH_currency,
- &refund),
+ TALER_JSON_spec_amount_any ("refund",
+ &refund),
GNUNET_JSON_spec_string ("reason",
&reason),
GNUNET_JSON_spec_end ()
};
enum TALER_MERCHANTDB_RefundStatus rs;
struct TALER_PrivateContractHashP h_contract;
+ json_t *contract_terms;
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ if (GNUNET_OK != res)
+ {
+ return (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO;
+ }
+ }
{
enum GNUNET_DB_QueryStatus qs;
- json_t *contract_terms;
uint64_t order_serial;
struct GNUNET_TIME_Timestamp refund_deadline;
- struct GNUNET_TIME_Timestamp timestamp;
- bool paid = false;
qs = TMH_db->lookup_contract_terms (TMH_db->cls,
hc->instance->settings.id,
hc->infix,
&contract_terms,
&order_serial,
- &paid,
NULL);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
- struct GNUNET_JSON_Specification spec[] = {
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_contract_terms");
+ }
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
+ hc->infix);
+ }
+ if (GNUNET_OK !=
+ TALER_JSON_contract_hash (contract_terms,
+ &h_contract))
+ {
+ GNUNET_break (0);
+ json_decref (contract_terms);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+ "Could not hash contract terms");
+ }
+ {
+ struct GNUNET_JSON_Specification cspec[] = {
GNUNET_JSON_spec_timestamp ("refund_deadline",
&refund_deadline),
GNUNET_JSON_spec_timestamp ("timestamp",
@@ -179,11 +194,10 @@ TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
if (GNUNET_YES !=
GNUNET_JSON_parse (contract_terms,
- spec,
+ cspec,
NULL, NULL))
{
GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
json_decref (contract_terms);
return TALER_MHD_reply_with_error (
connection,
@@ -191,12 +205,12 @@ TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
"mandatory fields missing");
}
- json_decref (contract_terms);
if (GNUNET_TIME_timestamp_cmp (timestamp,
==,
refund_deadline))
{
/* refund was never allowed, so we should refuse hard */
+ json_decref (contract_terms);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_FORBIDDEN,
@@ -210,25 +224,6 @@ TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
wire the funds, so we will try to give the refund anyway */
}
}
- else
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
- hc->infix);
- }
- }
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- hc->request_body,
- spec);
- if (GNUNET_OK != res)
- return (GNUNET_NO == res)
- ? MHD_YES
- : MHD_NO;
}
TMH_db->preflight (TMH_db->cls);
@@ -239,6 +234,7 @@ TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
"increase refund"))
{
GNUNET_break (0);
+ json_decref (contract_terms);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_START_FAILED,
@@ -259,8 +255,41 @@ TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
if (TALER_MERCHANTDB_RS_SUCCESS == rs)
{
enum GNUNET_DB_QueryStatus qs;
+ json_t *rargs;
- qs = TMH_db->commit (TMH_db->cls);
+ rargs = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ timestamp),
+ GNUNET_JSON_pack_string ("order_id",
+ hc->infix),
+ GNUNET_JSON_pack_object_incref ("contract_terms",
+ contract_terms),
+ TALER_JSON_pack_amount ("refund_amount",
+ &refund),
+ GNUNET_JSON_pack_string ("reason",
+ reason)
+ );
+ GNUNET_assert (NULL != rargs);
+ qs = TMH_trigger_webhook (
+ hc->instance->settings.id,
+ "refund",
+ rargs);
+ json_decref (rargs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TMH_db->rollback (TMH_db->cls);
+ rs = TALER_MERCHANTDB_RS_HARD_ERROR;
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TMH_db->rollback (TMH_db->cls);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ qs = TMH_db->commit (TMH_db->cls);
+ break;
+ }
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
@@ -274,9 +303,18 @@ TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
}
break;
} /* retries loop */
+ json_decref (contract_terms);
switch (rs)
{
+ case TALER_MERCHANTDB_RS_BAD_CURRENCY:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Refund amount %s is not in the currency of the original payment\n",
+ TALER_amount2s (&refund));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ "Order was paid in a different currency");
case TALER_MERCHANTDB_RS_TOO_HIGH:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Refusing refund amount %s that is larger than original payment\n",
@@ -292,53 +330,19 @@ TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
TALER_EC_GENERIC_DB_COMMIT_FAILED,
NULL);
case TALER_MERCHANTDB_RS_NO_SUCH_ORDER:
- {
- /* We know the order exists from the
- "lookup_contract_terms" at the beginning;
- so if we get 'no such order' here, it
- must be read as "no PAID order" */
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_ORDER_UNPAID,
- hc->infix);
- }
+ /* We know the order exists from the
+ "lookup_contract_terms" at the beginning;
+ so if we get 'no such order' here, it
+ must be read as "no PAID order" */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_ORDER_UNPAID,
+ hc->infix);
case TALER_MERCHANTDB_RS_SUCCESS:
- {
- enum GNUNET_DB_QueryStatus qs;
- json_t *contract_terms;
- uint64_t order_serial;
- bool paid;
-
- qs = TMH_db->lookup_contract_terms (TMH_db->cls,
- hc->instance->settings.id,
- hc->infix,
- &contract_terms,
- &order_serial,
- &paid,
- NULL);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
- hc->infix);
- }
- if (GNUNET_OK !=
- TALER_JSON_contract_hash (contract_terms,
- &h_contract))
- {
- GNUNET_break (0);
- json_decref (contract_terms);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
- "Could not hash contract terms");
- }
- json_decref (contract_terms);
- }
+ /* continued below */
break;
- }
+ } /* end switch */
{
struct GNUNET_TIME_Timestamp timestamp;
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c
index bb64fb02..7ca56319 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014, 2015, 2016, 2018, 2020, 2021 Taler Systems SA
+ (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -24,11 +24,14 @@
* @author Marcello Stanisci
*/
#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
+#include <string.h>
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
#include "taler-merchant-httpd_private-post-orders.h"
-#include "taler-merchant-httpd_auditors.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_private-get-orders.h"
@@ -40,6 +43,11 @@
#define MAX_RETRIES 3
/**
+ * Maximum number of inventory products per order.
+ */
+#define MAX_PRODUCTS 1024
+
+/**
* What is the label under which we find/place the merchant's
* jurisdiction in the locations list by default?
*/
@@ -53,109 +61,6 @@
/**
- * Check that the given JSON array of products is well-formed.
- *
- * @param products JSON array to check
- * @return #GNUNET_OK if all is fine
- */
-static enum GNUNET_GenericReturnValue
-check_products (const json_t *products)
-{
- size_t index;
- json_t *value;
-
- if (! json_is_array (products))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- json_array_foreach (products, index, value) {
- const char *description;
- const char *product_id = NULL;
- uint64_t quantity;
- const char *unit = NULL;
- struct TALER_Amount price;
- const char *image = NULL;
- json_t *taxes = NULL;
- struct GNUNET_TIME_Timestamp delivery_date;
- const char *error_name;
- unsigned int error_line;
- enum GNUNET_GenericReturnValue res;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("product_id",
- &product_id),
- NULL),
- TALER_JSON_spec_i18n_str ("description",
- &description),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint64 ("quantity",
- &quantity),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("unit",
- &unit),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount ("price",
- TMH_currency,
- &price),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("image",
- &image),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("taxes",
- &taxes),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("delivery_date",
- &delivery_date),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- /* extract fields we need to sign separately */
- res = GNUNET_JSON_parse (value,
- spec,
- &error_name,
- &error_line);
- if (GNUNET_OK != res)
- {
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Product parsing failed at #%u: %s:%u\n",
- (unsigned int) index,
- error_name,
- error_line);
- return GNUNET_SYSERR;
- }
- if ( (NULL != taxes) &&
- (! TMH_taxes_array_valid (taxes) ) )
- {
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Product parsing failed for taxes\n");
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if ( (NULL != image) &&
- (! TMH_image_data_url_valid (image) ) )
- {
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Product parsing failed for image\n");
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- GNUNET_JSON_parse_free (spec);
- }
- return GNUNET_OK;
-}
-
-
-/**
* Generate the base URL for the given merchant instance.
*
* @param connection the MHD connection
@@ -166,46 +71,13 @@ static char *
make_merchant_base_url (struct MHD_Connection *connection,
const char *instance_id)
{
- const char *host;
- const char *forwarded_host;
- const char *uri_path;
- struct GNUNET_Buffer buf = { 0 };
+ struct GNUNET_Buffer buf;
- if (GNUNET_YES == TALER_mhd_is_https (connection))
- GNUNET_buffer_write_str (&buf, "https://");
- else
- GNUNET_buffer_write_str (&buf, "http://");
- host = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_HOST);
- forwarded_host = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- "X-Forwarded-Host");
- if (NULL != forwarded_host)
- {
- GNUNET_buffer_write_str (&buf,
- forwarded_host);
- }
- else
- {
- GNUNET_assert (NULL != host);
- GNUNET_buffer_write_str (&buf,
- host);
- }
- uri_path = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- "X-Forwarded-Prefix");
- if (NULL != uri_path)
- GNUNET_buffer_write_path (&buf, uri_path);
-
- if (0 != strcmp (instance_id,
- "default"))
- {
- GNUNET_buffer_write_path (&buf,
- "/instances/");
- GNUNET_buffer_write_str (&buf,
- instance_id);
- }
+ if (GNUNET_OK !=
+ TMH_base_url_by_connection (connection,
+ instance_id,
+ &buf))
+ return NULL;
GNUNET_buffer_write_path (&buf,
"");
return GNUNET_buffer_reap_str (&buf);
@@ -231,33 +103,558 @@ struct InventoryProduct
/**
+ * Handle for a rekey operation where we (re)request
+ * the /keys from the exchange.
+ */
+struct RekeyExchange
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct RekeyExchange *prev;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct RekeyExchange *next;
+
+ /**
+ * order this is for.
+ */
+ struct OrderContext *oc;
+
+ /**
+ * Base URL of the exchange.
+ */
+ char *url;
+
+ /**
+ * Request for keys.
+ */
+ struct TMH_EXCHANGES_KeysOperation *fo;
+
+};
+
+
+/**
+ * Information we keep per order we are processing.
+ */
+struct OrderContext
+{
+ /**
+ * Information set in the ORDER_PHASE_PARSE_REQUEST phase.
+ */
+ struct
+ {
+ /**
+ * Order field of the request
+ */
+ json_t *order;
+
+ /**
+ * Set to how long refunds will be allowed.
+ */
+ struct GNUNET_TIME_Relative refund_delay;
+
+ /**
+ * RFC8905 payment target type to find a matching merchant account
+ */
+ const char *payment_target;
+
+ /**
+ * Shared key to use with @e pos_algorithm.
+ */
+ const char *pos_key;
+
+ /**
+ * Selected algorithm (by template) when we are to
+ * generate an OTP code for payment confirmation.
+ */
+ enum TALER_MerchantConfirmationAlgorithm pos_algorithm;
+
+ /**
+ * Hash of the POST request data, used to detect
+ * idempotent requests.
+ */
+ struct TALER_MerchantPostDataHashP h_post_data;
+
+ /**
+ * Length of the @e inventory_products array.
+ */
+ unsigned int inventory_products_length;
+
+ /**
+ * Specifies that some products are to be included in the
+ * order from the inventory. For these inventory management
+ * is performed (so the products must be in stock).
+ */
+ struct InventoryProduct *inventory_products;
+
+ /**
+ * Length of the @e uuids array.
+ */
+ unsigned int uuids_length;
+
+ /**
+ * array of UUIDs used to reserve products from @a inventory_products.
+ */
+ struct GNUNET_Uuid *uuids;
+
+ /**
+ * Claim token for the request.
+ */
+ struct TALER_ClaimTokenP claim_token;
+
+ /**
+ * Session ID (optional) to use for the order.
+ */
+ const char *session_id;
+
+ } parse_request;
+
+
+ /**
+ * Information set in the ORDER_PHASE_ADD_PAYMENT_DETAILS phase.
+ */
+ struct
+ {
+ /**
+ * Wire method (and our bank account) we have selected
+ * to be included for this order.
+ */
+ const struct TMH_WireMethod *wm;
+ } add_payment_details;
+
+ /**
+ * Information set in the ORDER_PHASE_PARSE_ORDER phase.
+ */
+ struct
+ {
+ /**
+ * Our order ID.
+ */
+ const char *order_id;
+
+ /**
+ * Summary of the order.
+ */
+ const char *summary;
+
+ /**
+ * Internationalized summary.
+ */
+ json_t *summary_i18n;
+
+ /**
+ * URL where the same contract could be ordered again (if available).
+ */
+ const char *public_reorder_url;
+
+ /**
+ * URL that will show that the order was successful
+ * after it has been paid for.
+ */
+ const char *fulfillment_url;
+
+ /**
+ * Message shown to the customer after paying for the order.
+ * Either fulfillment_url or fulfillment_message must be specified.
+ */
+ const char *fulfillment_message;
+
+ /**
+ * Map from IETF BCP 47 language tags to localized fulfillment messages.
+ */
+ json_t *fulfillment_message_i18n;
+
+ /**
+ * Merchant base URL.
+ */
+ char *merchant_base_url;
+
+ /**
+ * Timestamp of the order.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Deadline for refunds.
+ */
+ struct GNUNET_TIME_Timestamp refund_deadline;
+
+ /**
+ * Payment deadline.
+ */
+ struct GNUNET_TIME_Timestamp pay_deadline;
+
+ /**
+ * Wire transfer deadline.
+ */
+ struct GNUNET_TIME_Timestamp wire_deadline;
+
+ /**
+ * Delivery date.
+ */
+ struct GNUNET_TIME_Timestamp delivery_date;
+
+ /**
+ * Delivery location.
+ */
+ json_t *delivery_location;
+
+ /**
+ * Array of products that are part of the purchase.
+ */
+ const json_t *products;
+
+ /**
+ * Gross amount value of the contract. Used to
+ * compute @e max_stefan_fee.
+ */
+ struct TALER_Amount brutto;
+
+ /**
+ * Maximum fee as given by the client request.
+ */
+ struct TALER_Amount max_fee;
+
+ /**
+ * Specifies for how long the wallet should try to get an
+ * automatic refund for the purchase.
+ */
+ struct GNUNET_TIME_Relative auto_refund;
+
+ /**
+ * Nonce generated by the wallet and echoed by the merchant
+ * in this field when the proposal is generated.
+ */
+ const char *nonce;
+
+ /**
+ * Extra data that is only interpreted by the merchant frontend.
+ */
+ const json_t *extra;
+
+ /**
+ * Minimum age required by the order.
+ */
+ uint32_t minimum_age;
+
+ } parse_order;
+
+ /**
+ * Information set in the ORDER_PHASE_MERGE_INVENTORY phase.
+ */
+ struct
+ {
+ /**
+ * Merged array of products in the @e order.
+ */
+ json_t *products;
+ } merge_inventory;
+
+ /**
+ * Information set in the ORDER_PHASE_SET_EXCHANGES phase.
+ */
+ struct
+ {
+ /**
+ * Array of exchanges we find acceptable for this
+ * order.
+ */
+ json_t *exchanges;
+
+ /**
+ * Forced requests to /keys to update our exchange
+ * information.
+ */
+ struct RekeyExchange *pending_reload_head;
+
+ /**
+ * Forced requests to /keys to update our exchange
+ * information.
+ */
+ struct RekeyExchange *pending_reload_tail;
+
+ /**
+ * Did we previously force reloading of /keys from
+ * all exchanges? Set to 'true' to prevent us from
+ * doing it again (and again...).
+ */
+ bool forced_reload;
+
+ /**
+ * Set to true once we are sure that we have at
+ * least one good exchange.
+ */
+ bool exchange_good;
+
+ /**
+ * Maximum fee for @e order based on STEFAN curves.
+ * Used to set @e max_fee if not provided as part of
+ * @e order.
+ */
+ struct TALER_Amount max_stefan_fee;
+ } set_exchanges;
+
+ /**
+ * Information set in the ORDER_PHASE_SET_MAX_FEE phase.
+ */
+ struct
+ {
+ /**
+ * Maximum fee
+ */
+ struct TALER_Amount max_fee;
+ } set_max_fee;
+
+ /**
+ * Information set in the ORDER_PHASE_EXECUTE_ORDER phase.
+ */
+ struct
+ {
+ /**
+ * Which product (by offset) is out of stock, UINT_MAX if all were in-stock.
+ */
+ unsigned int out_of_stock_index;
+
+ /**
+ * Set to a previous claim token *if* @e idempotent
+ * is also true.
+ */
+ struct TALER_ClaimTokenP token;
+
+ /**
+ * Set to true if the order was idempotent and there
+ * was an equivalent one before.
+ */
+ bool idempotent;
+
+ /**
+ * Set to true if the order is in conflict with a
+ * previous order with the same order ID.
+ */
+ bool conflict;
+ } execute_order;
+
+ struct
+ {
+ /**
+ * Contract terms to store in the database.
+ */
+ json_t *contract;
+ } serialize_order;
+
+ /**
+ * Connection of the request.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Kept in a DLL while suspended.
+ */
+ struct OrderContext *next;
+
+ /**
+ * Kept in a DLL while suspended.
+ */
+ struct OrderContext *prev;
+
+ /**
+ * Handler context for the request.
+ */
+ struct TMH_HandlerContext *hc;
+
+ /**
+ * #GNUNET_YES if suspended.
+ */
+ enum GNUNET_GenericReturnValue suspended;
+
+ /**
+ * Current phase of setting up the order.
+ */
+ enum
+ {
+ ORDER_PHASE_PARSE_REQUEST,
+ ORDER_PHASE_PARSE_ORDER,
+ ORDER_PHASE_MERGE_INVENTORY,
+ ORDER_PHASE_ADD_PAYMENT_DETAILS,
+ ORDER_PHASE_SET_EXCHANGES,
+ ORDER_PHASE_SET_MAX_FEE,
+ ORDER_PHASE_SERIALIZE_ORDER,
+ ORDER_PHASE_SALT_FORGETTABLE,
+ ORDER_PHASE_CHECK_CONTRACT,
+ ORDER_PHASE_EXECUTE_ORDER,
+
+ /**
+ * Processing is done, we should return #MHD_YES.
+ */
+ ORDER_PHASE_FINISHED_MHD_YES,
+
+ /**
+ * Processing is done, we should return #MHD_NO.
+ */
+ ORDER_PHASE_FINISHED_MHD_NO
+ } phase;
+
+
+};
+
+
+/**
+ * Kept in a DLL while suspended.
+ */
+static struct OrderContext *oc_head;
+
+/**
+ * Kept in a DLL while suspended.
+ */
+static struct OrderContext *oc_tail;
+
+
+void
+TMH_force_orders_resume ()
+{
+ struct OrderContext *oc;
+
+ while (NULL != (oc = oc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (oc_head,
+ oc_tail,
+ oc);
+ oc->suspended = GNUNET_SYSERR;
+ MHD_resume_connection (oc->connection);
+ }
+}
+
+
+/**
+ * Update the phase of @a oc based on @a mret.
+ *
+ * @param[in,out] oc order to update phase for
+ * @param mret #MHD_NO to close with #MHD_NO
+ * #MHD_YES to close with #MHD_YES
+ */
+static void
+finalize_order (struct OrderContext *oc,
+ MHD_RESULT mret)
+{
+ oc->phase = (MHD_YES == mret)
+ ? ORDER_PHASE_FINISHED_MHD_YES
+ : ORDER_PHASE_FINISHED_MHD_NO;
+}
+
+
+/**
+ * Update the phase of @a oc based on @a ret.
+ *
+ * @param[in,out] oc order to update phase for
+ * @param ret #GNUNET_SYSERR to close with #MHD_NO
+ * #GNUNET_NO to close with #MHD_YES
+ * #GNUNET_OK is not allowed!
+ */
+static void
+finalize_order2 (struct OrderContext *oc,
+ enum GNUNET_GenericReturnValue ret)
+{
+ GNUNET_assert (GNUNET_OK != ret);
+ oc->phase = (GNUNET_NO == ret)
+ ? ORDER_PHASE_FINISHED_MHD_YES
+ : ORDER_PHASE_FINISHED_MHD_NO;
+}
+
+
+/**
+ * Generate an error response for @a oc.
+ *
+ * @param[in,out] oc order context to respond to
+ * @param http_status HTTP status code to set
+ * @param ec error code to set
+ * @param detail error message detail to set
+ */
+static void
+reply_with_error (struct OrderContext *oc,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const char *detail)
+{
+ MHD_RESULT mret;
+
+ mret = TALER_MHD_reply_with_error (oc->connection,
+ http_status,
+ ec,
+ detail);
+ finalize_order (oc,
+ mret);
+}
+
+
+/**
+ * Clean up memory used by @a cls.
+ *
+ * @param[in] cls the `struct OrderContext` to clean up
+ */
+static void
+clean_order (void *cls)
+{
+ struct OrderContext *oc = cls;
+ struct RekeyExchange *rx;
+
+ while (NULL != (rx = oc->set_exchanges.pending_reload_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
+ oc->set_exchanges.pending_reload_tail,
+ rx);
+ TMH_EXCHANGES_keys4exchange_cancel (rx->fo);
+ GNUNET_free (rx->url);
+ GNUNET_free (rx);
+ }
+ if (NULL != oc->set_exchanges.exchanges)
+ {
+ json_decref (oc->set_exchanges.exchanges);
+ oc->set_exchanges.exchanges = NULL;
+ }
+ if (NULL != oc->parse_order.fulfillment_message_i18n)
+ {
+ json_decref (oc->parse_order.fulfillment_message_i18n);
+ oc->parse_order.fulfillment_message_i18n = NULL;
+ }
+ if (NULL != oc->parse_order.summary_i18n)
+ {
+ json_decref (oc->parse_order.summary_i18n);
+ oc->parse_order.summary_i18n = NULL;
+ }
+ if (NULL != oc->parse_order.delivery_location)
+ {
+ json_decref (oc->parse_order.delivery_location);
+ oc->parse_order.delivery_location = NULL;
+ }
+ if (NULL != oc->merge_inventory.products)
+ {
+ json_decref (oc->merge_inventory.products);
+ oc->merge_inventory.products = NULL;
+ }
+ GNUNET_array_grow (oc->parse_request.inventory_products,
+ oc->parse_request.inventory_products_length,
+ 0);
+ GNUNET_array_grow (oc->parse_request.uuids,
+ oc->parse_request.uuids_length,
+ 0);
+ json_decref (oc->parse_request.order);
+ json_decref (oc->serialize_order.contract);
+ GNUNET_free (oc->parse_order.merchant_base_url);
+ GNUNET_free (oc);
+}
+
+
+/**
* Execute the database transaction to setup the order.
*
- * @param hc handler context for the request
- * @param order_id unique ID for the order
- * @param h_post_data hash of the client's POST request, for idempotency checks
- * @param pay_deadline until when does the order have to be paid
- * @param[in] order order to process (not modified)
- * @param claim_token token to use for access control
- * @param inventory_products_length length of the @a inventory_products array
- * @param inventory_products array of products to add to @a order from our inventory
- * @param uuids_length length of the @a uuids array
- * @param uuids array of UUIDs used to reserve products from @a inventory_products
- * @param[out] out_of_stock_index which product (by offset) is out of stock, UINT_MAX if all were in-stock
- * @return transaction status, 0 if @a uuids were insufficient to reserve required inventory
+ * @param[in,out] oc order context
+ * @return transaction status, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a uuids were insufficient to reserve required inventory
*/
static enum GNUNET_DB_QueryStatus
-execute_transaction (struct TMH_HandlerContext *hc,
- const char *order_id,
- const struct TALER_MerchantPostDataHashP *h_post_data,
- struct GNUNET_TIME_Timestamp pay_deadline,
- const json_t *order,
- const struct TALER_ClaimTokenP *claim_token,
- unsigned int inventory_products_length,
- const struct InventoryProduct inventory_products[],
- unsigned int uuids_length,
- const struct GNUNET_Uuid uuids[],
- unsigned int *out_of_stock_index)
+execute_transaction (struct OrderContext *oc)
{
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_TIME_Timestamp timestamp;
@@ -270,14 +667,56 @@ execute_transaction (struct TMH_HandlerContext *hc,
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
+
+ /* Test if we already have an order with this id */
+ {
+ json_t *contract_terms;
+ struct TALER_MerchantPostDataHashP orig_post;
+
+ qs = TMH_db->lookup_order (TMH_db->cls,
+ oc->hc->instance->settings.id,
+ oc->parse_order.order_id,
+ &oc->execute_order.token,
+ &orig_post,
+ &contract_terms);
+ /* If yes, check for idempotency */
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ TMH_db->rollback (TMH_db->cls);
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ json_decref (contract_terms);
+ /* Comparing the contract terms is sufficient because all the other
+ params get added to it at some point. */
+ if (0 == GNUNET_memcmp (&orig_post,
+ &oc->parse_request.h_post_data))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order creation idempotent\n");
+ oc->execute_order.idempotent = true;
+ return qs;
+ }
+ GNUNET_break_op (0);
+ oc->execute_order.conflict = true;
+ return qs;
+ }
+ }
+
/* Setup order */
qs = TMH_db->insert_order (TMH_db->cls,
- hc->instance->settings.id,
- order_id,
- h_post_data,
- pay_deadline,
- claim_token,
- order); // called 'contract terms' at database.
+ oc->hc->instance->settings.id,
+ oc->parse_order.order_id,
+ oc->parse_request.session_id,
+ &oc->parse_request.h_post_data,
+ oc->parse_order.pay_deadline,
+ &oc->parse_request.claim_token,
+ oc->serialize_order.contract, /* called 'contract terms' at database. */
+ oc->parse_request.pos_key,
+ oc->parse_request.pos_algorithm);
if (qs <= 0)
{
/* qs == 0: probably instance does not exist (anymore) */
@@ -285,10 +724,10 @@ execute_transaction (struct TMH_HandlerContext *hc,
return qs;
}
/* Migrate locks from UUIDs to new order: first release old locks */
- for (unsigned int i = 0; i<uuids_length; i++)
+ for (unsigned int i = 0; i<oc->parse_request.uuids_length; i++)
{
qs = TMH_db->unlock_inventory (TMH_db->cls,
- &uuids[i]);
+ &oc->parse_request.uuids[i]);
if (qs < 0)
{
TMH_db->rollback (TMH_db->cls);
@@ -301,13 +740,14 @@ execute_transaction (struct TMH_HandlerContext *hc,
(note: this can basically ONLY fail on serializability OR
because the UUID locks were insufficient for the desired
quantities). */
- for (unsigned int i = 0; i<inventory_products_length; i++)
+ for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++)
{
- qs = TMH_db->insert_order_lock (TMH_db->cls,
- hc->instance->settings.id,
- order_id,
- inventory_products[i].product_id,
- inventory_products[i].quantity);
+ qs = TMH_db->insert_order_lock (
+ TMH_db->cls,
+ oc->hc->instance->settings.id,
+ oc->parse_order.order_id,
+ oc->parse_request.inventory_products[i].product_id,
+ oc->parse_request.inventory_products[i].quantity);
if (qs < 0)
{
TMH_db->rollback (TMH_db->cls);
@@ -317,17 +757,17 @@ execute_transaction (struct TMH_HandlerContext *hc,
{
/* qs == 0: lock acquisition failed due to insufficient stocks */
TMH_db->rollback (TMH_db->cls);
- *out_of_stock_index = i; /* indicate which product is causing the issue */
+ oc->execute_order.out_of_stock_index = i; /* indicate which product is causing the issue */
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
}
- *out_of_stock_index = UINT_MAX;
+ oc->execute_order.out_of_stock_index = UINT_MAX;
/* Get the order serial and timestamp for the order we just created to
update long-poll clients. */
qs = TMH_db->lookup_order_summary (TMH_db->cls,
- hc->instance->settings.id,
- order_id,
+ oc->hc->instance->settings.id,
+ oc->parse_order.order_id,
&timestamp,
&order_serial);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
@@ -335,7 +775,7 @@ execute_transaction (struct TMH_HandlerContext *hc,
TMH_db->rollback (TMH_db->cls);
return qs;
}
- TMH_notify_order_change (hc->instance,
+ TMH_notify_order_change (oc->hc->instance,
TMH_OSF_NONE,
timestamp,
order_serial);
@@ -353,268 +793,123 @@ execute_transaction (struct TMH_HandlerContext *hc,
* database. Write the resulting proposal or an error message
* of a MHD connection.
*
- * @param connection connection to write the result or error to
- * @param hc handler context for the request
- * @param h_post_data hash of the client's POST request, for idempotency checks
- * @param[in,out] order order to process (can be modified)
- * @param claim_token token to use for access control
- * @param inventory_products_length length of the @a inventory_products array
- * @param inventory_products array of products to add to @a order from our inventory
- * @param uuids_length length of the @a uuids array
- * @param uuids array of UUIDs used to reserve products from @a inventory_products
- * @return MHD result code
+ * @param[in,out] oc order context
*/
-static MHD_RESULT
-execute_order (struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc,
- const struct TALER_MerchantPostDataHashP *h_post_data,
- json_t *order,
- const struct TALER_ClaimTokenP *claim_token,
- unsigned int inventory_products_length,
- const struct InventoryProduct inventory_products[],
- unsigned int uuids_length,
- const struct GNUNET_Uuid uuids[])
+static void
+execute_order (struct OrderContext *oc)
{
const struct TALER_MERCHANTDB_InstanceSettings *settings =
- &hc->instance->settings;
- struct TALER_Amount total;
- const char *order_id;
- const char *summary;
- const char *fulfillment_msg = NULL;
- json_t *products;
- json_t *merchant;
- json_t *summary_i18n = NULL;
- json_t *fulfillment_i18n = NULL;
- struct GNUNET_TIME_Timestamp timestamp;
- struct GNUNET_TIME_Timestamp refund_deadline = { 0 };
- struct GNUNET_TIME_Timestamp wire_transfer_deadline;
- struct GNUNET_TIME_Timestamp pay_deadline;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("amount",
- TMH_currency,
- &total),
- GNUNET_JSON_spec_string ("order_id",
- &order_id),
- GNUNET_JSON_spec_string ("summary",
- &summary),
- /**
- * The following entries we don't actually need,
- * except to check that the order is well-formed */
- GNUNET_JSON_spec_json ("products",
- &products),
- GNUNET_JSON_spec_json ("merchant",
- &merchant),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("summary_i18n",
- &summary_i18n),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("fulfillment_message",
- &fulfillment_msg),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("fulfillment_message_i18n",
- &fulfillment_i18n),
- NULL),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &timestamp),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &refund_deadline),
- NULL),
- GNUNET_JSON_spec_timestamp ("pay_deadline",
- &pay_deadline),
- GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
- &wire_transfer_deadline),
- GNUNET_JSON_spec_end ()
- };
+ &oc->hc->instance->settings;
enum GNUNET_DB_QueryStatus qs;
- unsigned int out_of_stock_index;
-
- /* extract fields we need to sign separately */
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- order,
- spec);
- if (GNUNET_OK != res)
- {
- GNUNET_break_op (0);
- return (GNUNET_NO == res)
- ? MHD_YES
- : MHD_NO;
- }
- }
-
- /* check product list in contract is well-formed */
- if (GNUNET_OK != check_products (products))
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "order:products");
- }
- if ( (NULL != fulfillment_i18n) &&
- (! TALER_JSON_check_i18n (fulfillment_i18n)) )
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "order:fulfillment_message_i18n");
- }
- if ( (NULL != summary_i18n) &&
- (! TALER_JSON_check_i18n (summary_i18n)) )
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "order:summary_i18n");
- }
-
- /* Test if we already have an order with this id */
- {
- struct TALER_ClaimTokenP token;
- json_t *contract_terms;
- struct TALER_MerchantPostDataHashP orig_post;
-
- TMH_db->preflight (TMH_db->cls);
- qs = TMH_db->lookup_order (TMH_db->cls,
- hc->instance->settings.id,
- order_id,
- &token,
- &orig_post,
- &contract_terms);
- /* If yes, check for idempotency */
- if (0 > qs)
- {
- GNUNET_break (0);
- TMH_db->rollback (TMH_db->cls);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_order");
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- MHD_RESULT ret;
-
- json_decref (contract_terms);
- /* Comparing the contract terms is sufficient because all the other
- params get added to it at some point. */
- if (0 == GNUNET_memcmp (&orig_post,
- h_post_data))
- {
- ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_string ("order_id",
- order_id),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_varsize (
- "token",
- GNUNET_is_zero (&token)
- ? NULL
- : &token,
- sizeof (token))));
- }
- else
- {
- /* This request is not idempotent */
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
- order_id);
- }
- GNUNET_JSON_parse_free (spec);
- return ret;
- }
- }
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Executing database transaction to create order '%s' for instance '%s'\n",
- order_id,
+ oc->parse_order.order_id,
settings->id);
for (unsigned int i = 0; i<MAX_RETRIES; i++)
{
TMH_db->preflight (TMH_db->cls);
- qs = execute_transaction (hc,
- order_id,
- h_post_data,
- pay_deadline,
- order,
- claim_token,
- inventory_products_length,
- inventory_products,
- uuids_length,
- uuids,
- &out_of_stock_index);
+ qs = execute_transaction (oc);
if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
break;
}
if (0 >= qs)
{
- GNUNET_JSON_parse_free (spec);
/* Special report if retries insufficient */
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_SOFT_FAILURE,
- NULL);
+ reply_with_error (oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ NULL);
+ return;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
/* should be: contract (!) with same order ID
already exists */
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_CONFLICT,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
- order_id);
+ oc->parse_order.order_id);
+ return;
}
/* Other hard transaction error (disk full, etc.) */
GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_COMMIT_FAILED,
NULL);
+ return;
+ }
+
+ /* DB transaction succeeded, check for idempotent */
+ if (oc->execute_order.idempotent)
+ {
+ MHD_RESULT ret;
+
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ oc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("order_id",
+ oc->parse_order.order_id),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_varsize (
+ "token",
+ GNUNET_is_zero (&oc->execute_order.token)
+ ? NULL
+ : &oc->execute_order.token,
+ sizeof (oc->execute_order.token))));
+ finalize_order (oc,
+ ret);
+ return;
+ }
+ if (oc->execute_order.conflict)
+ {
+ reply_with_error (
+ oc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
+ oc->parse_order.order_id);
+ return;
}
/* DB transaction succeeded, check for out-of-stock */
- if (out_of_stock_index < UINT_MAX)
+ if (oc->execute_order.out_of_stock_index < UINT_MAX)
{
/* We had a product that has insufficient quantities,
generate the details for the response. */
struct TALER_MERCHANTDB_ProductDetails pd;
MHD_RESULT ret;
+ const struct InventoryProduct *ip;
- memset (&pd, 0, sizeof (pd));
+ ip = &oc->parse_request.inventory_products[
+ oc->execute_order.out_of_stock_index];
+ memset (&pd,
+ 0,
+ sizeof (pd));
qs = TMH_db->lookup_product (
TMH_db->cls,
- hc->instance->settings.id,
- inventory_products[out_of_stock_index].product_id,
+ oc->hc->instance->settings.id,
+ ip->product_id,
&pd);
- GNUNET_JSON_parse_free (spec);
switch (qs)
{
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order creation failed: product out of stock\n");
ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
+ oc->connection,
MHD_HTTP_GONE,
GNUNET_JSON_pack_string (
"product_id",
- inventory_products[out_of_stock_index].product_id),
+ ip->product_id),
GNUNET_JSON_pack_uint64 (
"requested_quantity",
- inventory_products[out_of_stock_index].quantity),
+ ip->quantity),
GNUNET_JSON_pack_uint64 (
"available_quantity",
pd.total_stock - pd.total_sold - pd.total_lost),
@@ -623,188 +918,731 @@ execute_order (struct MHD_Connection *connection,
"restock_expected",
pd.next_restock)));
TALER_MERCHANTDB_product_details_free (&pd);
- return ret;
+ finalize_order (oc,
+ ret);
+ return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_GONE,
- GNUNET_JSON_pack_string (
- "product_id",
- inventory_products[out_of_stock_index].product_id),
- GNUNET_JSON_pack_uint64 (
- "requested_quantity",
- inventory_products[out_of_stock_index].quantity),
- GNUNET_JSON_pack_uint64 (
- "available_quantity",
- 0));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order creation failed: unknown product out of stock\n");
+ finalize_order (oc,
+ TALER_MHD_REPLY_JSON_PACK (
+ oc->connection,
+ MHD_HTTP_GONE,
+ GNUNET_JSON_pack_string (
+ "product_id",
+ ip->product_id),
+ GNUNET_JSON_pack_uint64 (
+ "requested_quantity",
+ ip->quantity),
+ GNUNET_JSON_pack_uint64 (
+ "available_quantity",
+ 0)));
+ return;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_SOFT_FAILURE,
NULL);
+ return;
case GNUNET_DB_STATUS_HARD_ERROR:
- return TALER_MHD_reply_with_error (
- connection,
+ GNUNET_break (0);
+ reply_with_error (
+ oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
NULL);
+ return;
}
GNUNET_break (0);
- return MHD_NO;
- }
+ oc->phase = ORDER_PHASE_FINISHED_MHD_NO;
+ return;
+ } /* end 'out of stock' case */
/* Everything in-stock, generate positive response */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order creation succeeded\n");
{
MHD_RESULT ret;
ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
+ oc->connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_string ("order_id",
- order_id),
+ oc->parse_order.order_id),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_data_varsize (
"token",
- GNUNET_is_zero (claim_token)
+ GNUNET_is_zero (&oc->parse_request.claim_token)
? NULL
- : claim_token,
- sizeof (*claim_token))));
- GNUNET_JSON_parse_free (spec);
- return ret;
+ : &oc->parse_request.claim_token,
+ sizeof (oc->parse_request.claim_token))));
+ finalize_order (oc,
+ ret);
}
}
/**
- * Add missing fields to the order. Upon success, continue
+ * Check that the contract is now well-formed. Upon success, continue
* processing with execute_order().
*
- * @param connection connection to write the result or error to
- * @param hc handler context for the request
- * @param h_post_data hash of the client's POST request, for idempotency checks
- * @param[in,out] order order to process (can be modified)
- * @param claim_token token to use for access control
- * @param refund_delay refund delay
- * @param inventory_products_length length of the @a inventory_products array
- * @param inventory_products array of products to add to @a order from our inventory
- * @param uuids_length length of the @a uuids array
- * @param uuids array of UUIDs used to reserve products from @a inventory_products
- * @return MHD result code
+ * @param[in,out] oc order context
*/
-static MHD_RESULT
-patch_order (struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc,
- const struct TALER_MerchantPostDataHashP *h_post_data,
- json_t *order,
- const struct TALER_ClaimTokenP *claim_token,
- struct GNUNET_TIME_Relative refund_delay,
- unsigned int inventory_products_length,
- const struct InventoryProduct inventory_products[],
- unsigned int uuids_length,
- const struct GNUNET_Uuid uuids[])
+static void
+check_contract (struct OrderContext *oc)
{
+ struct TALER_PrivateContractHashP h_control;
+
+ json_dumpf (oc->serialize_order.contract,
+ stderr,
+ JSON_INDENT (2));
+ switch (TALER_JSON_contract_hash (oc->serialize_order.contract,
+ &h_control))
+ {
+ case GNUNET_SYSERR:
+ GNUNET_break (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+ "could not compute hash of serialized order");
+ return;
+ case GNUNET_NO:
+ GNUNET_break_op (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+ "order contained unallowed values");
+ return;
+ case GNUNET_OK:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Contract hash is %s\n",
+ GNUNET_h2s (&h_control.hash));
+ oc->phase++;
+ return;
+ }
+ GNUNET_assert (0);
+}
+
+
+/**
+ * Modify the final contract terms adding salts for
+ * items that are forgettable.
+ *
+ * @param[in,out] oc order context
+ */
+static void
+salt_forgettable (struct OrderContext *oc)
+{
+ if (GNUNET_OK !=
+ TALER_JSON_contract_seed_forgettable (oc->parse_request.order,
+ oc->serialize_order.contract))
+ {
+ GNUNET_break_op (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_JSON_INVALID,
+ "could not compute hash of order due to bogus forgettable fields");
+ return;
+ }
+ oc->phase++;
+}
+
+
+/**
+ * Update MAX STEFAN fees based on @a keys.
+ *
+ * @param[in,out] oc order context to update
+ * @param keys keys to derive STEFAN fees from
+ */
+static void
+update_stefan (struct OrderContext *oc,
+ const struct TALER_EXCHANGE_Keys *keys)
+{
+ struct TALER_Amount net;
+
+ if (GNUNET_SYSERR !=
+ TALER_EXCHANGE_keys_stefan_b2n (keys,
+ &oc->parse_order.brutto,
+ &net))
+ {
+ struct TALER_Amount fee;
+
+ TALER_EXCHANGE_keys_stefan_round (keys,
+ &net);
+ if (-1 == TALER_amount_cmp (&oc->parse_order.brutto,
+ &net))
+ {
+ /* brutto < netto! */
+ /* => after rounding, there is no real difference */
+ net = oc->parse_order.brutto;
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&fee,
+ &oc->parse_order.brutto,
+ &net));
+ if ( (GNUNET_OK !=
+ TALER_amount_is_valid (&oc->set_exchanges.max_stefan_fee)) ||
+ (-1 == TALER_amount_cmp (&oc->set_exchanges.max_stefan_fee,
+ &fee)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Updated STEFAN-based fee to %s\n",
+ TALER_amount2s (&fee));
+ oc->set_exchanges.max_stefan_fee = fee;
+ }
+ }
+}
+
+
+/**
+ * Compute the set of exchanges that would be acceptable
+ * for this order.
+ *
+ * @param cls our `struct OrderContext`
+ * @param url base URL of an exchange (not used)
+ * @param exchange internal handle for the exchange
+ */
+static void
+get_acceptable (void *cls,
+ const char *url,
+ const struct TMH_Exchange *exchange)
+{
+ struct OrderContext *oc = cls;
+ unsigned int priority = 42; /* make compiler happy */
+ json_t *j_exchange;
+ enum GNUNET_GenericReturnValue res;
+
+ res = TMH_exchange_check_debit (exchange,
+ oc->add_payment_details.wm);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange %s evaluated at %d\n",
+ url,
+ res);
+ switch (res)
+ {
+ case GNUNET_OK:
+ priority = 1024; /* high */
+ oc->set_exchanges.exchange_good = true;
+ break;
+ case GNUNET_NO:
+ if (oc->set_exchanges.forced_reload)
+ priority = 0; /* fresh negative response */
+ else
+ priority = 512; /* stale negative response */
+ break;
+ case GNUNET_SYSERR:
+ if (oc->set_exchanges.forced_reload)
+ priority = 256; /* fresh, no accounts yet */
+ else
+ priority = 768; /* stale, no accounts yet */
+ break;
+ }
+ j_exchange = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("url",
+ url),
+ GNUNET_JSON_pack_uint64 ("priority",
+ priority),
+ GNUNET_JSON_pack_data_auto ("master_pub",
+ TMH_EXCHANGES_get_master_pub (exchange)));
+ GNUNET_assert (NULL != j_exchange);
+ GNUNET_assert (0 ==
+ json_array_append_new (oc->set_exchanges.exchanges,
+ j_exchange));
+}
+
+
+/**
+ * Exchange `/keys` processing is done, resume handling
+ * the order.
+ *
+ * @param[in,out] oc context to resume
+ */
+static void
+resume_with_keys (struct OrderContext *oc)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming order processing after /keys downloads (now have %u accounts)\n",
+ (unsigned int) json_array_size (oc->set_exchanges.exchanges));
+ GNUNET_assert (GNUNET_YES == oc->suspended);
+ GNUNET_CONTAINER_DLL_remove (oc_head,
+ oc_tail,
+ oc);
+ oc->suspended = GNUNET_NO;
+ MHD_resume_connection (oc->connection);
+ TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_keys4exchange()
+ * operation.
+ *
+ * @param cls closure with our `struct RekeyExchange *`
+ * @param keys the keys of the exchange
+ * @param exchange representation of the exchange
+ */
+static void
+keys_cb (
+ void *cls,
+ struct TALER_EXCHANGE_Keys *keys,
+ struct TMH_Exchange *exchange)
+{
+ struct RekeyExchange *rx = cls;
+ struct OrderContext *oc = rx->oc;
const struct TALER_MERCHANTDB_InstanceSettings *settings =
- &hc->instance->settings;
- const char *order_id = NULL;
- const char *fulfillment_url = NULL;
+ &oc->hc->instance->settings;
+
+ rx->fo = NULL;
+ GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
+ oc->set_exchanges.pending_reload_tail,
+ rx);
+ if (NULL == keys)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to download %skeys\n",
+ rx->url);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got response for %skeys\n",
+ rx->url);
+ if ( (settings->use_stefan) &&
+ (GNUNET_OK !=
+ TALER_amount_is_valid (&oc->parse_order.max_fee)) )
+ update_stefan (oc,
+ keys);
+ get_acceptable (oc,
+ rx->url,
+ exchange);
+ }
+ GNUNET_free (rx->url);
+ GNUNET_free (rx);
+ if (NULL != oc->set_exchanges.pending_reload_head)
+ return;
+ resume_with_keys (oc);
+}
+
+
+/**
+ * Force re-downloading of /keys from @a exchange,
+ * we currently have no acceptable exchange, so we
+ * should try to get one.
+ *
+ * @param cls closure with our `struct OrderContext`
+ * @param url base URL of the exchange
+ * @param exchange internal handle for the exchange
+ */
+static void
+get_exchange_keys (void *cls,
+ const char *url,
+ const struct TMH_Exchange *exchange)
+{
+ struct OrderContext *oc = cls;
+ struct RekeyExchange *rx;
+
+ rx = GNUNET_new (struct RekeyExchange);
+ rx->oc = oc;
+ rx->url = GNUNET_strdup (url);
+ GNUNET_CONTAINER_DLL_insert (oc->set_exchanges.pending_reload_head,
+ oc->set_exchanges.pending_reload_tail,
+ rx);
+ if (oc->set_exchanges.forced_reload)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Forcing download of %skeys\n",
+ url);
+ rx->fo = TMH_EXCHANGES_keys4exchange (url,
+ oc->set_exchanges.forced_reload,
+ &keys_cb,
+ rx);
+}
+
+
+/**
+ * Serialize order into @a oc->serialize_order.contract,
+ * ready to be stored in the database. Upon success, continue
+ * processing with check_contract().
+ *
+ * @param[in,out] oc order context
+ */
+static void
+serialize_order (struct OrderContext *oc)
+{
+ const struct TALER_MERCHANTDB_InstanceSettings *settings =
+ &oc->hc->instance->settings;
+ json_t *merchant;
+
+ merchant = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("name",
+ settings->name),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("website",
+ settings->website)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("email",
+ settings->email)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("logo",
+ settings->logo)));
+ GNUNET_assert (NULL != merchant);
+ {
+ json_t *loca = settings->address;
+
+ if (NULL != loca)
+ {
+ GNUNET_assert (0 ==
+ json_object_set (merchant,
+ "address",
+ loca));
+ }
+ }
+ {
+ json_t *juri = settings->jurisdiction;
+
+ /* Handle merchant jurisdiction */
+ if (NULL != juri)
+ {
+ GNUNET_assert (0 ==
+ json_object_set (merchant,
+ "jurisdiction",
+ juri));
+ }
+ }
+
+ oc->serialize_order.contract = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("summary",
+ oc->parse_order.summary),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("summary_i18n",
+ oc->parse_order.summary_i18n)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("public_reorder_url",
+ oc->parse_order.public_reorder_url)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("fulfillment_message",
+ oc->parse_order.fulfillment_message)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("fulfillment_message_i18n",
+ oc->parse_order.fulfillment_message_i18n)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("fulfillment_url",
+ oc->parse_order.fulfillment_url)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_uint64 ("minimum_age",
+ oc->parse_order.minimum_age)),
+ GNUNET_JSON_pack_array_incref ("products",
+ oc->merge_inventory.products),
+ GNUNET_JSON_pack_data_auto ("h_wire",
+ &oc->add_payment_details.wm->h_wire),
+ GNUNET_JSON_pack_string ("wire_method",
+ oc->add_payment_details.wm->wire_method),
+ GNUNET_JSON_pack_string ("order_id",
+ oc->parse_order.order_id),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ oc->parse_order.timestamp),
+ GNUNET_JSON_pack_timestamp ("pay_deadline",
+ oc->parse_order.pay_deadline),
+ GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
+ oc->parse_order.wire_deadline),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("delivery_date",
+ oc->parse_order.delivery_date)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("delivery_location",
+ oc->parse_order.delivery_location)),
+ GNUNET_JSON_pack_string ("merchant_base_url",
+ oc->parse_order.merchant_base_url),
+ GNUNET_JSON_pack_object_steal ("merchant",
+ merchant),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ &oc->hc->instance->merchant_pub),
+ GNUNET_JSON_pack_array_incref ("exchanges",
+ oc->set_exchanges.exchanges),
+ TALER_JSON_pack_amount ("max_fee",
+ &oc->set_max_fee.max_fee),
+ TALER_JSON_pack_amount ("amount",
+ &oc->parse_order.brutto),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("extra",
+ (json_t *) oc->parse_order.extra))
+ );
+
+ /* Pack does not work here, because it doesn't set zero-values for timestamps */
+ GNUNET_assert (0 ==
+ json_object_set_new (oc->serialize_order.contract,
+ "refund_deadline",
+ GNUNET_JSON_from_timestamp (
+ oc->parse_order.refund_deadline)));
+ GNUNET_log (
+ GNUNET_ERROR_TYPE_INFO,
+ "Refund deadline for contact is %llu\n",
+ (unsigned long long) oc->parse_order.refund_deadline.abs_time.abs_value_us);
+ GNUNET_log (
+ GNUNET_ERROR_TYPE_INFO,
+ "Wallet timestamp for contact is %llu\n",
+ (unsigned long long) oc->parse_order.timestamp.abs_time.abs_value_us);
+
+ /* Pack does not work here, because it sets zero-values for relative times */
+ /* auto_refund should only be set if it is not 0 */
+ if (! GNUNET_TIME_relative_is_zero (oc->parse_order.auto_refund))
+ {
+ GNUNET_assert (0 ==
+ json_object_set_new (oc->serialize_order.contract,
+ "auto_refund",
+ GNUNET_JSON_from_time_rel (
+ oc->parse_order.auto_refund)));
+ }
+
+ oc->phase++;
+}
+
+
+/**
+ * Set max_fee in @a oc based on STEFAN value if
+ * not yet present. Upon success, continue
+ * processing with serialize_order().
+ *
+ * @param[in,out] oc order context
+ */
+static void
+set_max_fee (struct OrderContext *oc)
+{
+ const struct TALER_MERCHANTDB_InstanceSettings *settings =
+ &oc->hc->instance->settings;
+
+ if (GNUNET_OK !=
+ TALER_amount_is_valid (&oc->parse_order.max_fee))
+ {
+ struct TALER_Amount stefan;
+
+ if ( (settings->use_stefan) &&
+ (GNUNET_OK ==
+ TALER_amount_is_valid (&oc->set_exchanges.max_stefan_fee)) )
+ stefan = oc->set_exchanges.max_stefan_fee;
+ else
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (oc->parse_order.brutto.currency,
+ &stefan));
+ oc->set_max_fee.max_fee = stefan;
+ }
+ else
+ {
+ oc->set_max_fee.max_fee = oc->parse_order.max_fee;
+ }
+ oc->phase++;
+}
+
+
+/**
+ * Set list of acceptable exchanges in @a oc. Upon success, continue
+ * processing with set_max_fee().
+ *
+ * @param[in,out] oc order context
+ * @return true to suspend execution
+ */
+static bool
+set_exchanges (struct OrderContext *oc)
+{
+ /* Note: re-building 'oc->exchanges' every time here might be a tad
+ expensive; could likely consider caching the result if it starts to
+ matter. */
+ if (NULL == oc->set_exchanges.exchanges)
+ {
+ oc->set_exchanges.exchanges = json_array ();
+ GNUNET_assert (NULL != oc->set_exchanges.exchanges);
+ TMH_exchange_get_trusted (&get_exchange_keys,
+ oc);
+ }
+ else if (! oc->set_exchanges.exchange_good)
+ {
+ if (! oc->set_exchanges.forced_reload)
+ {
+ oc->set_exchanges.forced_reload = true;
+ GNUNET_assert (0 ==
+ json_array_clear (oc->set_exchanges.exchanges));
+ TMH_exchange_get_trusted (&get_exchange_keys,
+ oc);
+ }
+ }
+ if (NULL != oc->set_exchanges.pending_reload_head)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Still trying to (re)load %skeys\n",
+ oc->set_exchanges.pending_reload_head->url);
+ MHD_suspend_connection (oc->connection);
+ oc->suspended = GNUNET_YES;
+ GNUNET_CONTAINER_DLL_insert (oc_head,
+ oc_tail,
+ oc);
+ return true; /* reloads pending */
+ }
+ if (0 == json_array_size (oc->set_exchanges.exchanges))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Cannot create order: lacking trusted exchanges\n");
+ reply_with_error (
+ oc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGES_FOR_WIRE_METHOD,
+ oc->add_payment_details.wm->wire_method);
+ return false;
+ }
+ if (! oc->set_exchanges.exchange_good)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Creating order, but possibly without usable trusted exchanges\n");
+ }
+ oc->phase++;
+ return false;
+}
+
+
+/**
+ * Add missing fields to the order. Upon success, continue
+ * processing with merge_inventory().
+ *
+ * @param[in,out] oc order context
+ */
+static void
+parse_order (struct OrderContext *oc)
+{
+ const struct TALER_MERCHANTDB_InstanceSettings *settings =
+ &oc->hc->instance->settings;
const char *merchant_base_url = NULL;
- json_t *jmerchant = NULL;
- json_t *delivery_location = NULL;
- struct TALER_Amount max_wire_fee = { 0 };
- struct TALER_Amount max_fee = { 0 };
- uint32_t wire_fee_amortization = 0;
- struct GNUNET_TIME_Timestamp timestamp
- = GNUNET_TIME_UNIT_ZERO_TS;
- struct GNUNET_TIME_Timestamp delivery_date
- = GNUNET_TIME_UNIT_ZERO_TS;
- struct GNUNET_TIME_Timestamp refund_deadline
- = GNUNET_TIME_UNIT_FOREVER_TS;
- struct GNUNET_TIME_Timestamp pay_deadline
- = GNUNET_TIME_UNIT_ZERO_TS;
- struct GNUNET_TIME_Timestamp wire_deadline
- = GNUNET_TIME_UNIT_FOREVER_TS;
+ const json_t *jmerchant = NULL;
/* auto_refund only needs to be type-checked,
* mostly because in GNUnet relative times can't
* be negative. */
- struct GNUNET_TIME_Relative auto_refund;
+ bool no_fee;
struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &oc->parse_order.brutto),
+ GNUNET_JSON_spec_string ("summary",
+ &oc->parse_order.summary),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("merchant_base_url",
- &merchant_base_url),
+ GNUNET_JSON_spec_array_const ("products",
+ &oc->parse_order.products),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("merchant",
- &jmerchant),
+ GNUNET_JSON_spec_json ("summary_i18n",
+ &oc->parse_order.summary_i18n),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("order_id",
- &order_id),
+ &oc->parse_order.order_id),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("public_reorder_url",
+ &oc->parse_order.public_reorder_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("fulfillment_message",
+ &oc->parse_order.fulfillment_message),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json ("fulfillment_message_i18n",
+ &oc->parse_order.fulfillment_message_i18n),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("fulfillment_url",
- &fulfillment_url),
+ &oc->parse_order.fulfillment_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("merchant_base_url",
+ &merchant_base_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("merchant",
+ &jmerchant),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("timestamp",
- &timestamp),
+ &oc->parse_order.timestamp),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("refund_deadline",
- &refund_deadline),
+ &oc->parse_order.refund_deadline),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("pay_deadline",
- &pay_deadline),
+ &oc->parse_order.pay_deadline),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
- &wire_deadline),
+ &oc->parse_order.wire_deadline),
NULL),
GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount ("max_fee",
- TMH_currency,
- &max_fee),
- NULL),
+ TALER_JSON_spec_amount_any ("max_fee",
+ &oc->parse_order.max_fee),
+ &no_fee),
GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount ("max_wire_fee",
- TMH_currency,
- &max_wire_fee),
+ GNUNET_JSON_spec_json ("delivery_location",
+ &oc->parse_order.delivery_location),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint32 ("wire_fee_amortization",
- &wire_fee_amortization),
+ GNUNET_JSON_spec_timestamp ("delivery_date",
+ &oc->parse_order.delivery_date),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("delivery_date",
- &delivery_date),
+ GNUNET_JSON_spec_uint32 ("minimum_age",
+ &oc->parse_order.minimum_age),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_relative_time ("auto_refund",
- &auto_refund),
+ &oc->parse_order.auto_refund),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("delivery_location",
- &delivery_location),
+ GNUNET_JSON_spec_object_const ("extra",
+ &oc->parse_order.extra),
NULL),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue ret;
- ret = TALER_MHD_parse_json_data (connection,
- order,
+ oc->parse_order.refund_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
+ oc->parse_order.wire_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
+ ret = TALER_MHD_parse_json_data (oc->connection,
+ oc->parse_request.order,
spec);
if (GNUNET_OK != ret)
{
GNUNET_break_op (0);
- return (GNUNET_NO == ret)
- ? MHD_YES
- : MHD_NO;
+ finalize_order2 (oc,
+ ret);
+ return;
+ }
+ if (! TMH_test_exchange_configured_for_currency (
+ oc->parse_order.brutto.currency))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ "no trusted exchange for this currency");
+ return;
+ }
+ if ( (! no_fee) &&
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&oc->parse_order.brutto,
+ &oc->parse_order.max_fee)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ "different currencies used for 'max_fee' and 'amount' currency");
+ return;
}
/* Add order_id if it doesn't exist. */
- if (NULL == order_id)
+ if (NULL == oc->parse_order.order_id)
{
char buf[256];
time_t timer;
@@ -812,18 +1650,18 @@ patch_order (struct MHD_Connection *connection,
size_t off;
uint64_t rand;
char *last;
- json_t *jbuf;
time (&timer);
tm_info = localtime (&timer);
if (NULL == tm_info)
{
GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_LOCALTIME,
NULL);
+ return;
}
off = strftime (buf,
sizeof (buf) - 1,
@@ -840,25 +1678,21 @@ patch_order (struct MHD_Connection *connection,
sizeof (buf) - off);
GNUNET_assert (NULL != last);
*last = '\0';
- jbuf = json_string (buf);
- GNUNET_assert (NULL != jbuf);
+
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Assigning order ID `%s' server-side\n",
buf);
- GNUNET_break (0 ==
- json_object_set_new (order,
- "order_id",
- jbuf));
- order_id = json_string_value (jbuf);
- GNUNET_assert (NULL != order_id);
+
+ oc->parse_order.order_id = GNUNET_strdup (buf);
+ GNUNET_assert (NULL != oc->parse_order.order_id);
}
/* Patch fulfillment URL with order_id (implements #6467). */
- if (NULL != fulfillment_url)
+ if (NULL != oc->parse_order.fulfillment_url)
{
const char *pos;
- pos = strstr (fulfillment_url,
+ pos = strstr (oc->parse_order.fulfillment_url,
"${ORDER_ID}");
if (NULL != pos)
{
@@ -870,27 +1704,25 @@ patch_order (struct MHD_Connection *connection,
"${ORDER_ID}"))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "fulfillment_url");
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "fulfillment_url");
+ return;
}
GNUNET_asprintf (&nurl,
"%.*s%s%s",
/* first output URL until ${ORDER_ID} */
- (int) (pos - fulfillment_url),
- fulfillment_url,
+ (int) (pos - oc->parse_order.fulfillment_url),
+ oc->parse_order.fulfillment_url,
/* replace ${ORDER_ID} with the right order_id */
- order_id,
+ oc->parse_order.order_id,
/* append rest of original URL */
pos + strlen ("${ORDER_ID}"));
- /* replace in JSON of the order */
- GNUNET_break (0 ==
- json_object_set_new (order,
- "fulfillment_url",
- json_string (nurl)));
+
+ oc->parse_order.fulfillment_url = GNUNET_strdup (nurl);
+
GNUNET_free (nurl);
}
}
@@ -901,437 +1733,250 @@ patch_order (struct MHD_Connection *connection,
struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
/* Add timestamp if it doesn't exist (or is zero) */
- if (GNUNET_TIME_absolute_is_zero (timestamp.abs_time))
+ if (GNUNET_TIME_absolute_is_zero (oc->parse_order.timestamp.abs_time))
{
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "timestamp",
- GNUNET_JSON_from_timestamp (now)));
+ oc->parse_order.timestamp = now;
}
/* If no refund_deadline given, set one based on refund_delay. */
- if (GNUNET_TIME_absolute_is_never (refund_deadline.abs_time))
+ if (GNUNET_TIME_absolute_is_never (
+ oc->parse_order.refund_deadline.abs_time))
{
- if (GNUNET_TIME_relative_is_zero (refund_delay))
+ if (GNUNET_TIME_relative_is_zero (oc->parse_request.refund_delay))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Refund delay is zero, no refunds are possible for this order\n");
- refund_deadline = now; /* if delay was 0, ensure that refund_deadline == timestamp */
+ oc->parse_order.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS;
}
else
{
- refund_deadline = GNUNET_TIME_relative_to_timestamp (refund_delay);
+ oc->parse_order.refund_deadline = GNUNET_TIME_relative_to_timestamp (
+ oc->parse_request.refund_delay);
}
-
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "refund_deadline",
- GNUNET_JSON_from_timestamp (
- refund_deadline)));
}
- if ( (! GNUNET_TIME_absolute_is_zero (delivery_date.abs_time)) &&
- (GNUNET_TIME_timestamp_cmp (delivery_date,
- <,
- now)) )
+
+ if ( (! GNUNET_TIME_absolute_is_zero (
+ oc->parse_order.delivery_date.abs_time)) &&
+ (GNUNET_TIME_absolute_is_past (
+ oc->parse_order.delivery_date.abs_time)) )
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_DELIVERY_DATE_IN_PAST,
NULL);
+ return;
}
}
- if (GNUNET_TIME_absolute_is_zero (pay_deadline.abs_time))
+ if (GNUNET_TIME_absolute_is_zero (oc->parse_order.pay_deadline.abs_time))
{
- struct GNUNET_TIME_Timestamp t;
+ oc->parse_order.pay_deadline = GNUNET_TIME_relative_to_timestamp (
+ settings->default_pay_delay);
+ }
+ else if (GNUNET_TIME_absolute_is_past (oc->parse_order.pay_deadline.abs_time))
+ {
+ GNUNET_break_op (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PAY_DEADLINE_IN_PAST,
+ NULL);
+ return;
+ }
- t = GNUNET_TIME_relative_to_timestamp (settings->default_pay_delay);
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "pay_deadline",
- GNUNET_JSON_from_timestamp (t)));
+ if ( (! GNUNET_TIME_absolute_is_zero (
+ oc->parse_order.refund_deadline.abs_time)) &&
+ (GNUNET_TIME_absolute_is_past (
+ oc->parse_order.refund_deadline.abs_time)) )
+ {
+ GNUNET_break_op (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_REFUND_DEADLINE_IN_PAST,
+ NULL);
+ return;
}
- if (GNUNET_TIME_absolute_is_never (wire_deadline.abs_time))
+ if (GNUNET_TIME_absolute_is_never (oc->parse_order.wire_deadline.abs_time))
{
struct GNUNET_TIME_Timestamp t;
t = GNUNET_TIME_relative_to_timestamp (
GNUNET_TIME_relative_max (settings->default_wire_transfer_delay,
- refund_delay));
- wire_deadline = GNUNET_TIME_timestamp_max (refund_deadline,
- t);
- if (GNUNET_TIME_absolute_is_never (wire_deadline.abs_time))
+ oc->parse_request.refund_delay));
+ oc->parse_order.wire_deadline = GNUNET_TIME_timestamp_max (
+ oc->parse_order.refund_deadline,
+ t);
+ if (GNUNET_TIME_absolute_is_never (oc->parse_order.wire_deadline.abs_time))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_WIRE_DEADLINE_IS_NEVER,
"order:wire_transfer_deadline");
-
+ return;
}
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "wire_transfer_deadline",
- GNUNET_JSON_from_timestamp (
- wire_deadline)));
}
- if (GNUNET_TIME_timestamp_cmp (wire_deadline,
+ if (GNUNET_TIME_timestamp_cmp (oc->parse_order.wire_deadline,
<,
- refund_deadline))
+ oc->parse_order.refund_deadline))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_REFUND_AFTER_WIRE_DEADLINE,
"order:wire_transfer_deadline;order:refund_deadline");
+ return;
}
- /* Note: total amount currency match checked
- later in execute_order() */
- if (GNUNET_OK !=
- TALER_amount_is_valid (&max_wire_fee))
+ if (NULL != merchant_base_url)
{
- GNUNET_assert (0 ==
- json_object_set_new (
- order,
- "max_wire_fee",
- TALER_JSON_from_amount (&settings->default_max_wire_fee)));
- }
-
- if (GNUNET_OK !=
- TALER_amount_is_valid (&max_fee))
- {
- GNUNET_assert (0 ==
- json_object_set_new (
- order,
- "max_fee",
- TALER_JSON_from_amount (
- &settings->default_max_deposit_fee)));
- }
- if (0 == wire_fee_amortization)
- {
- GNUNET_assert (0 ==
- json_object_set_new (
- order,
- "wire_fee_amortization",
- json_integer (
- (json_int_t) settings->default_wire_fee_amortization)));
+ if (('\0' == *merchant_base_url) ||
+ ('/' != merchant_base_url[strlen (merchant_base_url) - 1]))
+ {
+ GNUNET_break_op (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
+ "merchant_base_url is not valid");
+ return;
+ }
+ oc->parse_order.merchant_base_url
+ = GNUNET_strdup (merchant_base_url);
}
- if (NULL == merchant_base_url)
+ else
{
char *url;
- url = make_merchant_base_url (connection,
+ url = make_merchant_base_url (oc->connection,
settings->id);
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "merchant_base_url",
- json_string (url)));
- GNUNET_free (url);
+ if (NULL == url)
+ {
+ GNUNET_break_op (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ "order:merchant_base_url");
+ return;
+ }
+ oc->parse_order.merchant_base_url = url;
}
- else if (('\0' == *merchant_base_url) ||
- ('/' != merchant_base_url[strlen (merchant_base_url) - 1]))
+
+ if ( (NULL != oc->parse_order.products) &&
+ (! TMH_products_array_valid (oc->parse_order.products)) )
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_BAD_REQUEST,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
- "merchant_base_url is not valid");
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "order.products");
+ return;
}
- /* Fill in merchant information if necessary */
+ /* Merchant information must not already be present */
if (NULL != jmerchant)
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
"'merchant' field already set, but must be provided by backend");
+ return;
}
- {
- json_t *jm;
-
- jm = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("name",
- settings->name),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("website",
- settings->website)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("email",
- settings->email)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("logo",
- settings->logo)));
- GNUNET_assert (NULL != jm);
- {
- json_t *loca;
-
- /* Handle merchant address */
- loca = settings->address;
- if (NULL != loca)
- {
- loca = json_deep_copy (loca);
- GNUNET_assert (0 ==
- json_object_set_new (jm,
- "address",
- loca));
- }
- }
- {
- json_t *locj;
-
- /* Handle merchant jurisdiction */
- locj = settings->jurisdiction;
- if (NULL != locj)
- {
- locj = json_deep_copy (locj);
- GNUNET_assert (0 ==
- json_object_set_new (jm,
- "jurisdiction",
- locj));
- }
- }
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "merchant",
- jm));
- }
-
- /* add fields to the contract that the backend should provide */
- GNUNET_assert (0 ==
- json_object_set (order,
- "exchanges",
- TMH_trusted_exchanges));
- GNUNET_assert (0 ==
- json_object_set (order,
- "auditors",
- j_auditors));
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "merchant_pub",
- GNUNET_JSON_from_data_auto (
- &hc->instance->merchant_pub)));
- if (GNUNET_OK !=
- TALER_JSON_contract_seed_forgettable (order))
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_JSON_INVALID,
- "could not compute hash of order due to bogus forgettable fields");
- }
-
- if ( (NULL != delivery_location) &&
- (! TMH_location_object_valid (delivery_location)) )
+ if ( (NULL != oc->parse_order.delivery_location) &&
+ (! TMH_location_object_valid (oc->parse_order.delivery_location)) )
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "delivery_location");
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "delivery_location");
+ return;
}
- /* sanity check result */
- {
- struct TALER_PrivateContractHashP h_control;
-
- switch (TALER_JSON_contract_hash (order,
- &h_control))
- {
- case GNUNET_SYSERR:
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
- "could not compute hash of patched order");
- case GNUNET_NO:
- GNUNET_JSON_parse_free (spec);
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
- "order contained unallowed values");
- case GNUNET_OK:
- break;
- }
- }
- {
- MHD_RESULT mres;
-
- mres = execute_order (connection,
- hc,
- h_post_data,
- order,
- claim_token,
- inventory_products_length,
- inventory_products,
- uuids_length,
- uuids);
- GNUNET_JSON_parse_free (spec);
- return mres;
- }
+ oc->phase++;
}
/**
* Process the @a payment_target and add the details of how the
* order could be paid to @a order. On success, continue
- * processing with patch_order().
+ * processing with set_exchanges().
*
- * @param connection connection to write the result or error to
- * @param hc handler context for the request
- * @param h_post_data hash of the client's POST request, for idempotency checks
- * @param[in,out] order order to process (can be modified)
- * @param claim_token token to use for access control
- * @param refund_delay refund delay
- * @param payment_target desired wire method, NULL for no preference
- * @param inventory_products_length length of the @a inventory_products array
- * @param inventory_products array of products to add to @a order from our inventory
- * @param uuids_length length of the @a uuids array
- * @param uuids array of UUIDs used to reserve products from @a inventory_products
- * @return MHD result code
+ * @param[in,out] oc order context
*/
-static MHD_RESULT
-add_payment_details (struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc,
- const struct TALER_MerchantPostDataHashP *h_post_data,
- json_t *order,
- const struct TALER_ClaimTokenP *claim_token,
- struct GNUNET_TIME_Relative refund_delay,
- const char *payment_target,
- unsigned int inventory_products_length,
- const struct InventoryProduct inventory_products[],
- unsigned int uuids_length,
- const struct GNUNET_Uuid uuids[])
+static void
+add_payment_details (struct OrderContext *oc)
{
struct TMH_WireMethod *wm;
- wm = hc->instance->wm_head;
+ wm = oc->hc->instance->wm_head;
/* Locate wire method that has a matching payment target */
while ( (NULL != wm) &&
( (! wm->active) ||
- ( (NULL != payment_target) &&
- (0 != strcasecmp (payment_target,
+ ( (NULL != oc->parse_request.payment_target) &&
+ (0 != strcasecmp (oc->parse_request.payment_target,
wm->wire_method) ) ) ) )
wm = wm->next;
if (NULL == wm)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"No wire method available for instance '%s'\n",
- hc->instance->settings.id);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_INSTANCE_CONFIGURATION_LACKS_WIRE,
- payment_target);
+ oc->hc->instance->settings.id);
+ reply_with_error (oc,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_INSTANCE_CONFIGURATION_LACKS_WIRE,
+ oc->parse_request.payment_target);
+ return;
}
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "h_wire",
- GNUNET_JSON_from_data_auto (
- &wm->h_wire)));
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "wire_method",
- json_string (wm->wire_method)));
- return patch_order (connection,
- hc,
- h_post_data,
- order,
- claim_token,
- refund_delay,
- inventory_products_length,
- inventory_products,
- uuids_length,
- uuids);
+ oc->add_payment_details.wm = wm;
+ oc->phase++;
}
/**
- * Merge the inventory products into @a order, querying the
+ * Merge the inventory products into products, querying the
* database about the details of those products. Upon success,
* continue processing by calling add_payment_details().
*
- * @param connection connection to write the result or error to
- * @param hc handler context for the request
- * @param h_post_data hash of the client's POST request, for idempotency checks
- * @param[in,out] order order to process (can be modified)
- * @param claim_token token to use for access control
- * @param refund_delay time window where it is possible to ask a refund
- * @param payment_target RFC8905 payment target type to find a matching merchant account
- * @param inventory_products_length length of the @a inventory_products array
- * @param inventory_products array of products to add to @a order from our inventory
- * @param uuids_length length of the @a uuids array
- * @param uuids array of UUIDs used to reserve products from @a inventory_products
- * @return MHD result code
+ * @param[in,out] oc order context to process
*/
-static MHD_RESULT
-merge_inventory (struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc,
- const struct TALER_MerchantPostDataHashP *h_post_data,
- json_t *order,
- const struct TALER_ClaimTokenP *claim_token,
- struct GNUNET_TIME_Relative refund_delay,
- const char *payment_target,
- unsigned int inventory_products_length,
- const struct InventoryProduct inventory_products[],
- unsigned int uuids_length,
- const struct GNUNET_Uuid uuids[])
+static void
+merge_inventory (struct OrderContext *oc)
{
/**
- * inventory_products => instructions to add products to contract terms
- * order.products => contains products that are not from the backend-managed inventory.
+ * parse_request.inventory_products => instructions to add products to contract terms
+ * parse_order.products => contains products that are not from the backend-managed inventory.
*/
- GNUNET_assert (NULL != order);
- {
- json_t *jprod = json_object_get (order,
- "products");
- if (NULL == jprod)
- {
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "products",
- json_array ()));
- }
- else if (! TMH_products_array_valid (jprod))
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "order.products");
- }
- }
-
+ if (NULL != oc->parse_order.products)
+ oc->merge_inventory.products
+ = json_deep_copy (oc->parse_order.products);
+ else
+ oc->merge_inventory.products
+ = json_array ();
/* Populate products from inventory product array and database */
{
- json_t *np = json_array ();
-
- for (unsigned int i = 0; i<inventory_products_length; i++)
+ GNUNET_assert (NULL != oc->merge_inventory.products);
+ for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++)
{
+ const struct InventoryProduct *ip
+ = &oc->parse_request.inventory_products[i];
struct TALER_MERCHANTDB_ProductDetails pd;
enum GNUNET_DB_QueryStatus qs;
qs = TMH_db->lookup_product (TMH_db->cls,
- hc->instance->settings.id,
- inventory_products[i].product_id,
+ oc->hc->instance->settings.id,
+ ip->product_id,
&pd);
if (qs <= 0)
{
@@ -1353,7 +1998,7 @@ merge_inventory (struct MHD_Connection *connection,
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Product %s from order unknown\n",
- inventory_products[i].product_id);
+ ip->product_id);
http_status = MHD_HTTP_NOT_FOUND;
ec = TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN;
break;
@@ -1361,12 +2006,16 @@ merge_inventory (struct MHD_Connection *connection,
/* case listed to make compilers happy */
GNUNET_assert (0);
}
- json_decref (np);
- return TALER_MHD_reply_with_error (connection,
- http_status,
- ec,
- inventory_products[i].product_id);
+ json_decref (oc->merge_inventory.products);
+ reply_with_error (oc,
+ http_status,
+ ec,
+ ip->product_id);
+ return;
}
+ oc->parse_order.minimum_age
+ = GNUNET_MAX (oc->parse_order.minimum_age,
+ pd.minimum_age);
{
json_t *p;
@@ -1383,12 +2032,12 @@ merge_inventory (struct MHD_Connection *connection,
pd.taxes),
GNUNET_JSON_pack_string ("image",
pd.image),
- GNUNET_JSON_pack_uint64 ("quantity",
- inventory_products[i].
- quantity));
+ GNUNET_JSON_pack_uint64 (
+ "quantity",
+ ip->quantity));
GNUNET_assert (NULL != p);
GNUNET_assert (0 ==
- json_array_append_new (np,
+ json_array_append_new (oc->merge_inventory.products,
p));
}
GNUNET_free (pd.description);
@@ -1396,160 +2045,181 @@ merge_inventory (struct MHD_Connection *connection,
GNUNET_free (pd.image);
json_decref (pd.address);
}
- /* merge into existing products list */
- {
- json_t *xp;
-
- xp = json_object_get (order,
- "products");
- GNUNET_assert (NULL != xp);
- json_array_extend (xp, np);
- json_decref (np);
- }
}
- return add_payment_details (connection,
- hc,
- h_post_data,
- order,
- claim_token,
- refund_delay,
- payment_target,
- inventory_products_length,
- inventory_products,
- uuids_length,
- uuids);
+ /* check if final product list is well-formed */
+ if (! TMH_products_array_valid (oc->merge_inventory.products))
+ {
+ GNUNET_break_op (0);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "order:products");
+ return;
+ }
+ oc->phase++;
}
/**
- * Generate an order. We add the fields 'exchanges', 'merchant_pub', and
- * 'H_wire' to the order gotten from the frontend, as well as possibly other
- * fields if the frontend did not provide them. Returns the order_id.
+ * Parse the client request. Upon success,
+ * continue processing by calling parse_order().
*
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] hc context with further information about the request
- * @return MHD result code
+ * @param[in,out] oc order context to process
*/
-MHD_RESULT
-TMH_private_post_orders (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
+static void
+parse_request (struct OrderContext *oc)
{
- json_t *order;
- struct GNUNET_TIME_Relative refund_delay = GNUNET_TIME_UNIT_ZERO;
- const char *payment_target = NULL;
- json_t *ip = NULL;
- unsigned int ips_len = 0;
- struct InventoryProduct *ips = NULL;
- unsigned int uuids_len = 0;
- json_t *uuid;
- struct GNUNET_Uuid *uuids = NULL;
- struct TALER_ClaimTokenP claim_token;
+ const json_t *ip = NULL;
+ const json_t *uuid = NULL;
+ const char *otp_id = NULL;
bool create_token = true; /* default */
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_json ("order",
- &order),
+ &oc->parse_request.order),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_relative_time ("refund_delay",
- &refund_delay),
+ &oc->parse_request.refund_delay),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("payment_target",
- &payment_target),
+ &oc->parse_request.payment_target),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("inventory_products",
+ &ip),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("inventory_products",
- &ip),
+ GNUNET_JSON_spec_string ("session_id",
+ &oc->parse_request.session_id),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("lock_uuids",
- &uuid),
+ GNUNET_JSON_spec_array_const ("lock_uuids",
+ &uuid),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_bool ("create_token",
&create_token),
NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("otp_id",
+ &otp_id),
+ NULL),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue ret;
- struct TALER_MerchantPostDataHashP h_post_data;
- (void) rh;
- ret = TALER_MHD_parse_json_data (connection,
- hc->request_body,
+ ret = TALER_MHD_parse_json_data (oc->connection,
+ oc->hc->request_body,
spec);
if (GNUNET_OK != ret)
- return (GNUNET_NO == ret)
- ? MHD_YES
- : MHD_NO;
-
+ {
+ GNUNET_break_op (0);
+ finalize_order2 (oc,
+ ret);
+ return;
+ }
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Refund delay is %s\n",
- GNUNET_TIME_relative2s (refund_delay,
+ GNUNET_TIME_relative2s (oc->parse_request.refund_delay,
false));
-
TMH_db->expire_locks (TMH_db->cls);
- if (create_token)
+ if (NULL != otp_id)
{
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
- &claim_token,
- sizeof (claim_token));
+ struct TALER_MERCHANTDB_OtpDeviceDetails td;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->select_otp (TMH_db->cls,
+ oc->hc->instance->settings.id,
+ otp_id,
+ &td);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ reply_with_error (oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_otp");
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ reply_with_error (oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "select_otp");
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ reply_with_error (oc,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_OTP_DEVICE_UNKNOWN,
+ otp_id);
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ oc->parse_request.pos_key = td.otp_key;
+ oc->parse_request.pos_algorithm = td.otp_algorithm;
}
- else
+ if (create_token)
{
- /* we use all-zeros for 'no token' */
- memset (&claim_token,
- 0,
- sizeof (claim_token));
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &oc->parse_request.claim_token,
+ sizeof (oc->parse_request.claim_token));
}
-
/* Compute h_post_data (for idempotency check) */
{
char *req_body_enc;
/* Dump normalized JSON to string. */
- if (NULL == (req_body_enc = json_dumps (hc->request_body,
- JSON_ENCODE_ANY
- | JSON_COMPACT
- | JSON_SORT_KEYS)))
+ if (NULL == (req_body_enc
+ = json_dumps (oc->hc->request_body,
+ JSON_ENCODE_ANY
+ | JSON_COMPACT
+ | JSON_SORT_KEYS)))
{
GNUNET_break (0);
GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_ALLOCATION_FAILURE,
- "request body normalization for hashing");
+ reply_with_error (oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ "request body normalization for hashing");
+ return;
}
GNUNET_CRYPTO_hash (req_body_enc,
strlen (req_body_enc),
- &h_post_data.hash);
+ &oc->parse_request.h_post_data.hash);
GNUNET_free (req_body_enc);
}
/* parse the inventory_products (optionally given) */
if (NULL != ip)
{
- if (! json_is_array (ip))
+ unsigned int ipl = (unsigned int) json_array_size (ip);
+
+ if ( (json_array_size (ip) != (size_t) ipl) ||
+ (ipl > MAX_PRODUCTS) )
{
+ GNUNET_break (0);
GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "inventory_products");
+ reply_with_error (oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ "inventory products too long");
+ return;
}
- GNUNET_array_grow (ips,
- ips_len,
- json_array_size (ip));
- for (unsigned int i = 0; i<ips_len; i++)
+ GNUNET_array_grow (oc->parse_request.inventory_products,
+ oc->parse_request.inventory_products_length,
+ (unsigned int) json_array_size (ip));
+ for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++)
{
+ struct InventoryProduct *ipr = &oc->parse_request.inventory_products[i];
const char *error_name;
unsigned int error_line;
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_string ("product_id",
- &ips[i].product_id),
+ &ipr->product_id),
GNUNET_JSON_spec_uint32 ("quantity",
- &ips[i].quantity),
+ &ipr->quantity),
GNUNET_JSON_spec_end ()
};
@@ -1561,19 +2231,16 @@ TMH_private_post_orders (const struct TMH_RequestHandler *rh,
if (GNUNET_OK != ret)
{
GNUNET_break_op (0);
- GNUNET_array_grow (ips,
- ips_len,
- 0);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Product parsing failed at #%u: %s:%u\n",
i,
error_name,
error_line);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "inventory_products");
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inventory_products");
+ return;
}
}
}
@@ -1581,21 +2248,10 @@ TMH_private_post_orders (const struct TMH_RequestHandler *rh,
/* parse the lock_uuids (optionally given) */
if (NULL != uuid)
{
- if (! json_is_array (uuid))
- {
- GNUNET_array_grow (ips,
- ips_len,
- 0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "lock_uuids");
- }
- GNUNET_array_grow (uuids,
- uuids_len,
+ GNUNET_array_grow (oc->parse_request.uuids,
+ oc->parse_request.uuids_length,
json_array_size (uuid));
- for (unsigned int i = 0; i<uuids_len; i++)
+ for (unsigned int i = 0; i<oc->parse_request.uuids_length; i++)
{
json_t *ui = json_array_get (uuid,
i);
@@ -1603,49 +2259,86 @@ TMH_private_post_orders (const struct TMH_RequestHandler *rh,
if (! json_is_string (ui))
{
GNUNET_break_op (0);
- GNUNET_array_grow (ips,
- ips_len,
- 0);
- GNUNET_array_grow (uuids,
- uuids_len,
- 0);
- GNUNET_JSON_parse_free (spec);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"UUID parsing failed at #%u\n",
i);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "lock_uuids");
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "lock_uuids");
+ return;
}
TMH_uuid_from_string (json_string_value (ui),
- &uuids[i]);
+ &oc->parse_request.uuids[i]);
}
}
+ oc->phase++;
+}
- /* Finally, start by completing the order */
- {
- MHD_RESULT res;
-
- res = merge_inventory (connection,
- hc,
- &h_post_data,
- order,
- &claim_token,
- refund_delay,
- payment_target,
- ips_len,
- ips,
- uuids_len,
- uuids);
- GNUNET_array_grow (ips,
- ips_len,
- 0);
- GNUNET_array_grow (uuids,
- uuids_len,
- 0);
- GNUNET_JSON_parse_free (spec);
- return res;
+
+MHD_RESULT
+TMH_private_post_orders (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct OrderContext *oc = hc->ctx;
+
+ if (NULL == oc)
+ {
+ oc = GNUNET_new (struct OrderContext);
+ hc->ctx = oc;
+ hc->cc = &clean_order;
+ oc->connection = connection;
+ oc->hc = hc;
+ }
+ while (1)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Processing order in phase %d\n",
+ oc->phase);
+ switch (oc->phase)
+ {
+ case ORDER_PHASE_PARSE_REQUEST:
+ parse_request (oc);
+ break;
+ case ORDER_PHASE_PARSE_ORDER:
+ parse_order (oc);
+ break;
+ case ORDER_PHASE_MERGE_INVENTORY:
+ merge_inventory (oc);
+ break;
+ case ORDER_PHASE_ADD_PAYMENT_DETAILS:
+ add_payment_details (oc);
+ break;
+ case ORDER_PHASE_SET_EXCHANGES:
+ if (set_exchanges (oc))
+ return MHD_YES;
+ break;
+ case ORDER_PHASE_SET_MAX_FEE:
+ set_max_fee (oc);
+ break;
+ case ORDER_PHASE_SERIALIZE_ORDER:
+ serialize_order (oc);
+ break;
+ case ORDER_PHASE_CHECK_CONTRACT:
+ check_contract (oc);
+ break;
+ case ORDER_PHASE_SALT_FORGETTABLE:
+ salt_forgettable (oc);
+ break;
+ case ORDER_PHASE_EXECUTE_ORDER:
+ execute_order (oc);
+ break;
+ case ORDER_PHASE_FINISHED_MHD_YES:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Finished processing order (1)\n");
+ return MHD_YES;
+ case ORDER_PHASE_FINISHED_MHD_NO:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Finished processing order (0)\n");
+ return MHD_NO;
+ }
}
}
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.h b/src/backend/taler-merchant-httpd_private-post-orders.h
index 9bffb6aa..f1127bec 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.h
+++ b/src/backend/taler-merchant-httpd_private-post-orders.h
@@ -23,6 +23,14 @@
#include "taler-merchant-httpd.h"
+
+/**
+ * Force resuming all suspended orders on shutdown.
+ */
+void
+TMH_force_orders_resume (void);
+
+
/**
* Generate an order. We add the fields 'exchanges', 'merchant_pub', and
* 'H_wire' to the order gotten from the frontend, as well as possibly other
diff --git a/src/backend/taler-merchant-httpd_private-post-otp-devices.c b/src/backend/taler-merchant-httpd_private-post-otp-devices.c
new file mode 100644
index 00000000..ff70fb58
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-otp-devices.c
@@ -0,0 +1,199 @@
+/*
+ This file is part of TALER
+ (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_private-post-otp-devices.c
+ * @brief implementing POST /otp-devices request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-post-otp-devices.h"
+#include "taler-merchant-httpd_helper.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * How often do we retry the simple INSERT database transaction?
+ */
+#define MAX_RETRIES 3
+
+
+/**
+ * Check if the two otp-devices are identical.
+ *
+ * @param t1 device to compare
+ * @param t2 other device to compare
+ * @return true if they are 'equal', false if not or of payto_uris is not an array
+ */
+static bool
+otp_devices_equal (const struct TALER_MERCHANTDB_OtpDeviceDetails *t1,
+ const struct TALER_MERCHANTDB_OtpDeviceDetails *t2)
+{
+ return ( (0 == strcmp (t1->otp_description,
+ t2->otp_description)) &&
+ (0 == strcmp (t1->otp_key,
+ t2->otp_key) ) &&
+ (t1->otp_ctr == t2->otp_ctr) &&
+ (t1->otp_algorithm == t2->otp_algorithm) );
+}
+
+
+MHD_RESULT
+TMH_private_post_otp_devices (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ struct TALER_MERCHANTDB_OtpDeviceDetails tp = { 0 };
+ const char *device_id;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("otp_device_id",
+ &device_id),
+ GNUNET_JSON_spec_string ("otp_device_description",
+ (const char **) &tp.otp_description),
+ TALER_JSON_spec_otp_type ("otp_algorithm",
+ &tp.otp_algorithm),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint64 ("otp_ctr",
+ &tp.otp_ctr),
+ NULL),
+ TALER_JSON_spec_otp_key ("otp_key",
+ (const char **) &tp.otp_key),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_assert (NULL != mi);
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ return (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO;
+ }
+ }
+
+ /* finally, interact with DB until no serialization error */
+ for (unsigned int i = 0; i<MAX_RETRIES; i++)
+ {
+ /* Test if a OTP device of this id is known */
+ struct TALER_MERCHANTDB_OtpDeviceDetails etp;
+
+ if (GNUNET_OK !=
+ TMH_db->start (TMH_db->cls,
+ "/post otp-devices"))
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ NULL);
+ }
+ qs = TMH_db->select_otp (TMH_db->cls,
+ mi->settings.id,
+ device_id,
+ &etp);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ /* Clean up and fail hard */
+ GNUNET_break (0);
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* restart transaction */
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* Good, we can proceed! */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* idempotency check: is etp == tp? */
+ {
+ bool eq;
+
+ eq = otp_devices_equal (&tp,
+ &etp);
+ GNUNET_free (etp.otp_description);
+ GNUNET_free (etp.otp_key);
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_JSON_parse_free (spec);
+ return eq
+ ? TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0)
+ : TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_OTP_DEVICES_CONFLICT_OTP_DEVICE_EXISTS,
+ device_id);
+ }
+ } /* end switch (qs) */
+
+ qs = TMH_db->insert_otp (TMH_db->cls,
+ mi->settings.id,
+ device_id,
+ &tp);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ break;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ qs = TMH_db->commit (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+ break;
+ }
+retry:
+ GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ TMH_db->rollback (TMH_db->cls);
+ } /* for RETRIES loop */
+ GNUNET_JSON_parse_free (spec);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ ? TALER_EC_GENERIC_DB_SOFT_FAILURE
+ : TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ NULL);
+ }
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-merchant-httpd_private-post-otp-devices.c */
diff --git a/src/backend/taler-merchant-httpd_private-post-reserves.h b/src/backend/taler-merchant-httpd_private-post-otp-devices.h
index ca06fe2f..96564d08 100644
--- a/src/backend/taler-merchant-httpd_private-post-reserves.h
+++ b/src/backend/taler-merchant-httpd_private-post-otp-devices.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2020 Taler Systems SA
+ (C) 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -18,24 +18,18 @@
*/
/**
- * @file taler-merchant-httpd_private-post-reserves.h
- * @brief implementing POST /reserves request handling
+ * @file taler-merchant-httpd_private-post-otp-devices.h
+ * @brief implementing POST /otp-devices request handling
* @author Christian Grothoff
*/
-#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_RESERVES_H
-#define TALER_MERCHANT_HTTPD_PRIVATE_POST_RESERVES_H
-#include "taler-merchant-httpd.h"
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_OTP_DEVICES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_OTP_DEVICES_H
-/**
- * Force all post reserve contexts to be resumed as we are about
- * to shut down MHD.
- */
-void
-TMH_force_rc_resume ();
+#include "taler-merchant-httpd.h"
/**
- * Generate a reserve entry in our inventory.
+ * Generate an OTP device.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
@@ -43,8 +37,8 @@ TMH_force_rc_resume ();
* @return MHD result code
*/
MHD_RESULT
-TMH_private_post_reserves (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc);
+TMH_private_post_otp_devices (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-products.c b/src/backend/taler-merchant-httpd_private-post-products.c
index 0c20cdac..3cad91a9 100644
--- a/src/backend/taler-merchant-httpd_private-post-products.c
+++ b/src/backend/taler-merchant-httpd_private-post-products.c
@@ -93,9 +93,8 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
NULL),
GNUNET_JSON_spec_string ("unit",
(const char **) &pd.unit),
- TALER_JSON_spec_amount ("price",
- TMH_currency,
- &pd.price),
+ TALER_JSON_spec_amount_any ("price",
+ &pd.price),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("image",
(const char **) &pd.image),
diff --git a/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c b/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c
deleted file mode 100644
index 283321db..00000000
--- a/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- This file is part of TALER
- (C) 2014-2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c
- * @brief implement API for authorizing tips to be paid to visitors
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <taler/taler_util.h>
-#include <taler/taler_json_lib.h>
-#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_mhd.h"
-#include "taler-merchant-httpd_get-tips-ID.h"
-#include "taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h"
-
-
-/**
- * Handle a "tip-authorize" request.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] hc context with further information about the request
- * @param reserve_pub reserve to use, or NULL for "any"
- * @return MHD result code
- */
-static MHD_RESULT
-authorize_tip (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc,
- const struct TALER_ReservePublicKeyP *reserve_pub)
-{
- enum TALER_ErrorCode ec;
- struct GNUNET_TIME_Timestamp expiration;
- struct TALER_TipIdentifierP tip_id;
- const char *justification;
- const char *next_url;
- struct TALER_Amount amount;
- {
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("amount",
- TMH_currency,
- &amount),
- GNUNET_JSON_spec_string ("justification",
- &justification),
- GNUNET_JSON_spec_string ("next_url",
- &next_url),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- hc->request_body,
- spec);
- if (GNUNET_YES != res)
- {
- GNUNET_break_op (0);
- return (GNUNET_NO == res)
- ? MHD_YES
- : MHD_NO;
- }
- }
- TMH_db->preflight (TMH_db->cls);
- ec = TMH_db->authorize_tip (TMH_db->cls,
- hc->instance->settings.id,
- reserve_pub,
- &amount,
- justification,
- next_url,
- &tip_id,
- &expiration);
- /* handle errors */
- if (TALER_EC_NONE != ec)
- {
- unsigned int http_status;
-
- switch (ec)
- {
- case TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_INSUFFICIENT_FUNDS:
- http_status = MHD_HTTP_PRECONDITION_FAILED;
- break;
- case TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_EXPIRED:
- http_status = MHD_HTTP_GONE;
- break;
- case TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_UNKNOWN:
- http_status = MHD_HTTP_SERVICE_UNAVAILABLE;
- break;
- case TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_NOT_FOUND:
- http_status = MHD_HTTP_NOT_FOUND;
- break;
- default:
- http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- }
-
- return TALER_MHD_reply_with_error (connection,
- http_status,
- ec,
- NULL);
- }
-
- /* generate success response */
- {
- char *taler_tip_uri;
- char *tip_status_url;
- struct GNUNET_CRYPTO_HashAsciiEncoded hash_enc;
- MHD_RESULT res;
-
- GNUNET_CRYPTO_hash_to_enc (&tip_id.hash,
- &hash_enc);
- taler_tip_uri = TMH_make_taler_tip_uri (connection,
- &tip_id,
- hc->instance->settings.id);
- tip_status_url = TMH_make_tip_status_url (connection,
- &tip_id,
- hc->instance->settings.id);
- res = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_string ("tip_id",
- (const char *) hash_enc.encoding),
- GNUNET_JSON_pack_string ("taler_tip_uri",
- taler_tip_uri),
- GNUNET_JSON_pack_string ("tip_status_url",
- tip_status_url),
- GNUNET_JSON_pack_timestamp ("tip_expiration",
- expiration));
- GNUNET_free (taler_tip_uri);
- GNUNET_free (tip_status_url);
- return res;
- }
-}
-
-
-MHD_RESULT
-TMH_private_post_reserves_ID_authorize_tip (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
-{
- struct TALER_ReservePublicKeyP reserve_pub;
-
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (hc->infix,
- strlen (hc->infix),
- &reserve_pub,
- sizeof (reserve_pub)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
- hc->infix);
- }
- return authorize_tip (rh,
- connection,
- hc,
- &reserve_pub);
-}
-
-
-MHD_RESULT
-TMH_private_post_tips (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
-{
- return authorize_tip (rh,
- connection,
- hc,
- NULL);
-}
-
-
-/* end of taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c */
diff --git a/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h b/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h
deleted file mode 100644
index ff27df78..00000000
--- a/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017, 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h
- * @brief headers for /reserves/ID/tip-authorize
- * @author Christian Grothoff
- */
-#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_RESERVES_ID_AUTHORIZE_TIP_H
-#define TALER_MERCHANT_HTTPD_PRIVATE_POST_RESERVES_ID_AUTHORIZE_TIP_H
-#include <microhttpd.h>
-#include "taler-merchant-httpd.h"
-
-
-/**
- * Handle a "/reserves/$ID/tip-authorize" request.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] hc context with further information about the request
- * @return MHD result code
- */
-MHD_RESULT
-TMH_private_post_reserves_ID_authorize_tip (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc);
-
-
-/**
- * Handle a POST "/tips" request.
- * Here the client does not specify the reserve public key, so we
- * are free to pick "any" available reserve.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] hc context with further information about the request
- * @return MHD result code
- */
-MHD_RESULT
-TMH_private_post_tips (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc);
-
-
-#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-reserves.c b/src/backend/taler-merchant-httpd_private-post-reserves.c
deleted file mode 100644
index 82fc865f..00000000
--- a/src/backend/taler-merchant-httpd_private-post-reserves.c
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- This file is part of TALER
- (C) 2021, 2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation; either version 3,
- or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public
- License along with TALER; see the file COPYING. If not,
- see <http://www.gnu.org/licenses/>
-*/
-
-/**
- * @file taler-merchant-httpd_private-post-reserves.c
- * @brief implementing POST /reserves request handling
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_private-post-reserves.h"
-#include "taler-merchant-httpd_reserves.h"
-#include <taler/taler_json_lib.h>
-
-
-/**
- * How long to wait before giving up processing with the exchange?
- */
-#define EXCHANGE_GENERIC_TIMEOUT (GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_SECONDS, \
- 15))
-
-
-/**
- * Information we keep for an individual call to the POST /reserves handler.
- */
-struct PostReserveContext
-{
-
- /**
- * Stored in a DLL.
- */
- struct PostReserveContext *next;
-
- /**
- * Stored in a DLL.
- */
- struct PostReserveContext *prev;
-
- /**
- * Array with @e coins_cnt coins we are despositing.
- */
- struct DepositConfirmation *dc;
-
- /**
- * MHD connection to return to
- */
- struct MHD_Connection *connection;
-
- /**
- * Details about the client's request.
- */
- struct TMH_HandlerContext *hc;
-
- /**
- * URL of the exchange.
- */
- const char *exchange_url;
-
- /**
- * URI of the exchange where the payment needs to be made to.
- */
- char *payto_uri;
-
- /**
- * Handle for contacting the exchange.
- */
- struct TMH_EXCHANGES_FindOperation *fo;
-
- /**
- * Task run on timeout.
- */
- struct GNUNET_SCHEDULER_Task *timeout_task;
-
- /**
- * Initial balance of the reserve.
- */
- struct TALER_Amount initial_balance;
-
- /**
- * When will the reserve expire.
- */
- struct GNUNET_TIME_Timestamp reserve_expiration;
-
- /**
- * Which HTTP status should we return?
- */
- unsigned int http_status;
-
- /**
- * Which error code should we return?
- */
- enum TALER_ErrorCode ec;
-
- /**
- * Did we suspend @a connection and are thus in
- * the #rc_head DLL (#GNUNET_YES). Set to
- * #GNUNET_NO if we are not suspended, and to
- * #GNUNET_SYSERR if we should close the connection
- * without a response due to shutdown.
- */
- enum GNUNET_GenericReturnValue suspended;
-};
-
-
-/**
- * Stored in a DLL.
- */
-static struct PostReserveContext *rc_head;
-
-/**
- * Stored in a DLL.
- */
-static struct PostReserveContext *rc_tail;
-
-
-/**
- * Force all post reserve contexts to be resumed as we are about
- * to shut down MHD.
- */
-void
-TMH_force_rc_resume ()
-{
- struct PostReserveContext *rcn;
-
- for (struct PostReserveContext *rc = rc_head;
- NULL != rc;
- rc = rcn)
- {
- rcn = rc->next;
- if (NULL != rc->timeout_task)
- {
- GNUNET_SCHEDULER_cancel (rc->timeout_task);
- rc->timeout_task = NULL;
- }
- if (GNUNET_YES == rc->suspended)
- {
- rc->suspended = GNUNET_SYSERR;
- MHD_resume_connection (rc->connection);
- GNUNET_CONTAINER_DLL_remove (rc_head,
- rc_tail,
- rc);
- }
- if (NULL != rc->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (rc->fo);
- rc->fo = NULL;
- }
- }
-}
-
-
-/**
- * Custom cleanup routine for a `struct PostReserveContext`.
- *
- * @param cls the `struct PostReserveContext` to clean up.
- */
-static void
-reserve_context_cleanup (void *cls)
-{
- struct PostReserveContext *rc = cls;
-
- if (NULL != rc->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (rc->fo);
- rc->fo = NULL;
- }
- if (NULL != rc->timeout_task)
- {
- GNUNET_SCHEDULER_cancel (rc->timeout_task);
- rc->timeout_task = NULL;
- }
- GNUNET_assert (GNUNET_YES != rc->suspended);
- GNUNET_free (rc->payto_uri);
- GNUNET_free (rc);
-}
-
-
-/**
- * Function called with the result of a #TMH_EXCHANGES_find_exchange()
- * operation.
- *
- * @param cls closure with our `struct PostReserveContext *`
- * @param hr HTTP response details
- * @param payto_uri URI of the exchange for the wire transfer, NULL on errors
- * @param eh handle to the exchange context
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
- * @param exchange_trusted true if this exchange is trusted by config
- */
-static void
-handle_exchange (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *eh,
- const char *payto_uri,
- const struct TALER_Amount *wire_fee,
- bool exchange_trusted)
-{
- struct PostReserveContext *rc = cls;
- const struct TALER_EXCHANGE_Keys *keys;
-
- rc->fo = NULL;
- if (NULL != rc->timeout_task)
- {
- GNUNET_SCHEDULER_cancel (rc->timeout_task);
- rc->timeout_task = NULL;
- }
- rc->suspended = GNUNET_NO;
- MHD_resume_connection (rc->connection);
- GNUNET_CONTAINER_DLL_remove (rc_head,
- rc_tail,
- rc);
- if (NULL == hr)
- {
- rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT;
- rc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- return;
- }
- if (NULL == eh)
- {
- rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE;
- rc->http_status = MHD_HTTP_BAD_GATEWAY;
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- return;
- }
- keys = TALER_EXCHANGE_get_keys (eh);
- if (NULL == keys)
- {
- rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE;
- rc->http_status = MHD_HTTP_BAD_GATEWAY;
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- return;
- }
- if (MHD_HTTP_OK != hr->http_status)
- {
- rc->ec = hr->ec;
- rc->http_status = hr->http_status;
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- return;
- }
- if (NULL == payto_uri)
- {
- rc->ec = TALER_EC_MERCHANT_PRIVATE_POST_RESERVES_UNSUPPORTED_WIRE_METHOD;
- rc->http_status = MHD_HTTP_CONFLICT;
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- return;
- }
- rc->reserve_expiration
- = GNUNET_TIME_relative_to_timestamp (keys->reserve_closing_delay);
- rc->payto_uri = GNUNET_strdup (payto_uri);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
-}
-
-
-/**
- * Handle a timeout for the processing of the wire request.
- *
- * @param cls closure
- */
-static void
-handle_exchange_timeout (void *cls)
-{
- struct PostReserveContext *rc = cls;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Resuming POST /private/reserves with error after timeout\n");
- rc->timeout_task = NULL;
- if (NULL != rc->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (rc->fo);
- rc->fo = NULL;
- }
- rc->suspended = GNUNET_NO;
- MHD_resume_connection (rc->connection);
- GNUNET_CONTAINER_DLL_remove (rc_head,
- rc_tail,
- rc);
- rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT;
- rc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
-}
-
-
-MHD_RESULT
-TMH_private_post_reserves (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
-{
- struct PostReserveContext *rc = hc->ctx;
- struct TMH_MerchantInstance *mi = hc->instance;
-
- GNUNET_assert (NULL != mi);
- if (NULL == rc)
- {
- const char *wire_method;
-
- rc = GNUNET_new (struct PostReserveContext);
- rc->connection = connection;
- rc->hc = hc;
- hc->ctx = rc;
- hc->cc = &reserve_context_cleanup;
-
- {
- enum GNUNET_GenericReturnValue res;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("exchange_url",
- &rc->exchange_url),
- GNUNET_JSON_spec_string ("wire_method",
- &wire_method),
- TALER_JSON_spec_amount ("initial_balance",
- TMH_currency,
- &rc->initial_balance),
- GNUNET_JSON_spec_end ()
- };
- res = TALER_MHD_parse_json_data (connection,
- hc->request_body,
- spec);
- if (GNUNET_OK != res)
- return (GNUNET_NO == res)
- ? MHD_YES
- : MHD_NO;
- }
- rc->fo = TMH_EXCHANGES_find_exchange (rc->exchange_url,
- wire_method,
- GNUNET_NO,
- &handle_exchange,
- rc);
- rc->timeout_task
- = GNUNET_SCHEDULER_add_delayed (EXCHANGE_GENERIC_TIMEOUT,
- &handle_exchange_timeout,
- rc);
- rc->suspended = GNUNET_YES;
- GNUNET_CONTAINER_DLL_insert (rc_head,
- rc_tail,
- rc);
- MHD_suspend_connection (connection);
- return MHD_YES;
- }
- if (GNUNET_SYSERR == rc->suspended)
- return MHD_NO; /* we are in shutdown */
-
- GNUNET_assert (GNUNET_NO == rc->suspended);
- if (NULL == rc->payto_uri)
- {
- return TALER_MHD_reply_with_error (connection,
- rc->http_status,
- rc->ec,
- NULL);
- }
- {
- struct TALER_ReservePublicKeyP reserve_pub;
- struct TALER_ReservePrivateKeyP reserve_priv;
- enum GNUNET_DB_QueryStatus qs;
-
- GNUNET_CRYPTO_eddsa_key_create (&reserve_priv.eddsa_priv);
- GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv.eddsa_priv,
- &reserve_pub.eddsa_pub);
- qs = TMH_db->insert_reserve (TMH_db->cls,
- mi->settings.id,
- &reserve_priv,
- &reserve_pub,
- rc->exchange_url,
- rc->payto_uri,
- &rc->initial_balance,
- rc->reserve_expiration);
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- TMH_RESERVES_check (mi->settings.id,
- rc->exchange_url,
- &reserve_pub,
- &rc->initial_balance);
- if (qs < 0)
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "reserve");
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_data_auto ("reserve_pub",
- &reserve_pub),
- GNUNET_JSON_pack_string ("payto_uri",
- rc->payto_uri));
- }
-}
-
-
-/* end of taler-merchant-httpd_private-post-reserves.c */
diff --git a/src/backend/taler-merchant-httpd_private-post-templates.c b/src/backend/taler-merchant-httpd_private-post-templates.c
index 1dd324e4..7aa72992 100644
--- a/src/backend/taler-merchant-httpd_private-post-templates.c
+++ b/src/backend/taler-merchant-httpd_private-post-templates.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2022 Taler Systems SA
+ (C) 2022-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -29,12 +29,6 @@
/**
- * How often do we retry the simple INSERT database transaction?
- */
-#define MAX_RETRIES 3
-
-
-/**
* Check if the two templates are identical.
*
* @param t1 template to compare
@@ -47,10 +41,24 @@ templates_equal (const struct TALER_MERCHANTDB_TemplateDetails *t1,
{
return ( (0 == strcmp (t1->template_description,
t2->template_description)) &&
- ( ( (NULL == t1->image) && (NULL == t2->image) ) ||
- ( (NULL != t1->image) && (NULL != t2->image) &&
- (0 == strcmp (t1->image,
- t2->image)) ) ) &&
+ ( ( (NULL == t1->otp_id) &&
+ (NULL == t2->otp_id) ) ||
+ ( (NULL != t1->otp_id) &&
+ (NULL != t2->otp_id) &&
+ (0 == strcmp (t1->otp_id,
+ t2->otp_id))) ) &&
+ ( ( (NULL == t1->required_currency) &&
+ (NULL == t2->required_currency) ) ||
+ ( (NULL != t1->required_currency) &&
+ (NULL != t2->required_currency) &&
+ (0 == strcmp (t1->required_currency,
+ t2->required_currency))) ) &&
+ ( ( (NULL == t1->editable_defaults) &&
+ (NULL == t2->editable_defaults) ) ||
+ ( (NULL != t1->editable_defaults) &&
+ (NULL != t2->editable_defaults) &&
+ (1 == json_equal (t1->editable_defaults,
+ t2->editable_defaults))) ) &&
(1 == json_equal (t1->template_contract,
t2->template_contract)) );
}
@@ -71,13 +79,22 @@ TMH_private_post_templates (const struct TMH_RequestHandler *rh,
GNUNET_JSON_spec_string ("template_description",
(const char **) &tp.template_description),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("image",
- (const char **) &tp.image),
+ GNUNET_JSON_spec_string ("otp_id",
+ (const char **) &tp.otp_id),
NULL),
GNUNET_JSON_spec_json ("template_contract",
&tp.template_contract),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("required_currency",
+ (const char **) &tp.required_currency),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json ("editable_defaults",
+ &tp.editable_defaults),
+ NULL),
GNUNET_JSON_spec_end ()
};
+ uint64_t otp_serial = 0;
GNUNET_assert (NULL != mi);
{
@@ -107,34 +124,114 @@ TMH_private_post_templates (const struct TMH_RequestHandler *rh,
"template_contract");
}
- if ( (NULL != tp.image) &&
- (! TMH_image_data_url_valid (tp.image)) )
+ if ( (NULL != tp.required_currency) &&
+ (GNUNET_OK !=
+ TALER_check_currency (tp.required_currency)) )
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "image");
+ "required_currency");
}
-
- /* finally, interact with DB until no serialization error */
- for (unsigned int i = 0; i<MAX_RETRIES; i++)
+ if ( (NULL != tp.required_currency) &&
+ (NULL != json_object_get (tp.template_contract,
+ "amount")) )
{
- /* Test if a template of this id is known */
- struct TALER_MERCHANTDB_TemplateDetails etp;
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "required_currency and contract::amount specified");
+ }
+ if (NULL != tp.editable_defaults)
+ {
+ const char *key;
+ json_t *val;
- if (GNUNET_OK !=
- TMH_db->start (TMH_db->cls,
- "/post templates"))
+ json_object_foreach (tp.editable_defaults, key, val)
{
+ if (NULL !=
+ json_object_get (tp.template_contract,
+ key))
+ {
+ char *msg;
+ MHD_RESULT ret;
+
+ GNUNET_break_op (0);
+ GNUNET_asprintf (&msg,
+ "editable_defaults::%s conflicts with template_contract",
+ key);
+ GNUNET_JSON_parse_free (spec);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ msg);
+ GNUNET_free (msg);
+ return ret;
+ }
+ }
+ }
+
+ if (NULL != tp.otp_id)
+ {
+ qs = TMH_db->select_otp_serial (TMH_db->cls,
+ mi->settings.id,
+ tp.otp_id,
+ &otp_serial);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "select_otp_serial");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_OTP_DEVICE_UNKNOWN,
NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
}
+ }
+
+ qs = TMH_db->insert_template (TMH_db->cls,
+ mi->settings.id,
+ template_id,
+ otp_serial,
+ &tp);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ break;
+ }
+
+ {
+ /* Test if a template of this id is known */
+ struct TALER_MERCHANTDB_TemplateDetails etp;
+
qs = TMH_db->lookup_template (TMH_db->cls,
mi->settings.id,
template_id,
@@ -142,79 +239,45 @@ TMH_private_post_templates (const struct TMH_RequestHandler *rh,
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
/* Clean up and fail hard */
GNUNET_break (0);
- TMH_db->rollback (TMH_db->cls);
GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
NULL);
- case GNUNET_DB_STATUS_SOFT_ERROR:
- /* restart transaction */
- goto retry;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* Good, we can proceed! */
- break;
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "logic error");
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* idempotency check: is etp == tp? */
- {
- bool eq;
-
- eq = templates_equal (&tp,
- &etp);
- TALER_MERCHANTDB_template_details_free (&etp);
- TMH_db->rollback (TMH_db->cls);
- GNUNET_JSON_parse_free (spec);
- return eq
- ? TALER_MHD_reply_static (connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0)
- : TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_POST_TEMPLATES_CONFLICT_TEMPLATE_EXISTS,
- template_id);
- }
- } /* end switch (qs) */
-
- qs = TMH_db->insert_template (TMH_db->cls,
- mi->settings.id,
- template_id,
- &tp);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- TMH_db->rollback (TMH_db->cls);
break;
}
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ /* idempotency check: is etp == tp? */
{
- qs = TMH_db->commit (TMH_db->cls);
- if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
- break;
+ bool eq;
+
+ eq = templates_equal (&tp,
+ &etp);
+ TALER_MERCHANTDB_template_details_free (&etp);
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_JSON_parse_free (spec);
+ return eq
+ ? TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0)
+ : TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_TEMPLATES_CONFLICT_TEMPLATE_EXISTS,
+ template_id);
}
-retry:
- GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- TMH_db->rollback (TMH_db->cls);
- } /* for RETRIES loop */
- GNUNET_JSON_parse_free (spec);
- if (qs < 0)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- ? TALER_EC_GENERIC_DB_SOFT_FAILURE
- : TALER_EC_GENERIC_DB_COMMIT_FAILED,
- NULL);
}
- return TALER_MHD_reply_static (connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
}
diff --git a/src/backend/taler-merchant-httpd_private-post-templates.h b/src/backend/taler-merchant-httpd_private-post-templates.h
index c3a7f741..35e37625 100644
--- a/src/backend/taler-merchant-httpd_private-post-templates.h
+++ b/src/backend/taler-merchant-httpd_private-post-templates.h
@@ -37,7 +37,7 @@
*/
MHD_RESULT
TMH_private_post_templates (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc);
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-token-families.c b/src/backend/taler-merchant-httpd_private-post-token-families.c
new file mode 100644
index 00000000..f4472c39
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-token-families.c
@@ -0,0 +1,244 @@
+/*
+ This file is part of TALER
+ (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_private-post-token-families.c
+ * @brief implementing POST /tokenfamilies request handling
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-post-token-families.h"
+#include "taler-merchant-httpd_helper.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * How often do we retry the simple INSERT database transaction?
+ */
+#define MAX_RETRIES 3
+
+
+/**
+ * Check if the two token families are identical.
+ *
+ * @param tf1 token family to compare
+ * @param tf2 other token family to compare
+ * @return true if they are 'equal', false if not
+ */
+static bool
+token_families_equal (const struct TALER_MERCHANTDB_TokenFamilyDetails *tf1,
+ const struct TALER_MERCHANTDB_TokenFamilyDetails *tf2)
+{
+ return ( (0 == strcmp (tf1->slug,
+ tf2->slug)) &&
+ (0 == strcmp (tf1->name,
+ tf2->name)) &&
+ (0 == strcmp (tf1->description,
+ tf2->description)) &&
+ (1 == json_equal (tf1->description_i18n,
+ tf2->description_i18n)) &&
+ (GNUNET_TIME_timestamp_cmp (tf1->valid_after,
+ ==,
+ tf2->valid_after)) &&
+ (GNUNET_TIME_timestamp_cmp (tf1->valid_before,
+ ==,
+ tf2->valid_before)) &&
+ (GNUNET_TIME_relative_cmp (tf1->duration,
+ ==,
+ tf2->duration)) &&
+ (tf1->kind == tf2->kind) );
+}
+
+
+MHD_RESULT
+TMH_private_post_token_families (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ struct TALER_MERCHANTDB_TokenFamilyDetails details = { 0 };
+ const char *kind = NULL;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("slug",
+ (const char **) &details.slug),
+ GNUNET_JSON_spec_string ("name",
+ (const char **) &details.name),
+ GNUNET_JSON_spec_string ("description",
+ (const char **) &details.description),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json ("description_i18n",
+ &details.description_i18n),
+ NULL),
+ GNUNET_JSON_spec_string ("kind", &kind),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &details.valid_after),
+ GNUNET_JSON_spec_timestamp ("valid_before",
+ &details.valid_before),
+ GNUNET_JSON_spec_relative_time ("duration",
+ &details.duration),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_assert (NULL != mi);
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ return (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO;
+ }
+ }
+
+
+ if (strcmp (kind, "discount") == 0)
+ details.kind = TALER_MERCHANTDB_TFK_Discount;
+ else if (strcmp (kind, "subscription") == 0)
+ details.kind = TALER_MERCHANTDB_TFK_Subscription;
+ else
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "kind");
+ }
+
+ if (NULL == details.description_i18n)
+ details.description_i18n = json_object ();
+
+ if (! TALER_JSON_check_i18n (details.description_i18n))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "description_i18n");
+ }
+
+
+ /* finally, interact with DB until no serialization error */
+ for (unsigned int i = 0; i<MAX_RETRIES; i++)
+ {
+ /* Test if a token family of this id is known */
+ struct TALER_MERCHANTDB_TokenFamilyDetails existing;
+
+ if (GNUNET_OK !=
+ TMH_db->start (TMH_db->cls,
+ "/post tokenfamilies"))
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ NULL);
+ }
+ qs = TMH_db->lookup_token_family (TMH_db->cls,
+ mi->settings.id,
+ details.slug,
+ &existing);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ /* Clean up and fail hard */
+ GNUNET_break (0);
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* restart transaction */
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* Good, we can proceed! */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* idempotency check: is existing == details? */
+ {
+ bool eq;
+
+ eq = token_families_equal (&details,
+ &existing);
+ TALER_MERCHANTDB_token_family_details_free (&existing);
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_JSON_parse_free (spec);
+ return eq
+ ? TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0)
+ : TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_TOKEN_FAMILY_CONFLICT,
+ details.slug);
+ }
+ } /* end switch (qs) */
+
+ qs = TMH_db->insert_token_family (TMH_db->cls,
+ mi->settings.id,
+ details.slug,
+ &details);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ break;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ qs = TMH_db->commit (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+ break;
+ }
+retry:
+ GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ TMH_db->rollback (TMH_db->cls);
+ } /* for RETRIES loop */
+ GNUNET_JSON_parse_free (spec);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ ? TALER_EC_GENERIC_DB_SOFT_FAILURE
+ : TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ NULL);
+ }
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-merchant-httpd_private-post-token-families.c */
diff --git a/src/backend/taler-merchant-httpd_private-post-token-families.h b/src/backend/taler-merchant-httpd_private-post-token-families.h
new file mode 100644
index 00000000..ada1c7c9
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-token-families.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_private-post-token-families.h
+ * @brief implementing POST /tokenfamilies request handling
+ * @author Christian Blättler
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_TOKEN_FAMILIES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_TOKEN_FAMILIES_H
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Create a new token family.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_token_families (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-transfers.c b/src/backend/taler-merchant-httpd_private-post-transfers.c
index aa21c747..cf6eebaf 100644
--- a/src/backend/taler-merchant-httpd_private-post-transfers.c
+++ b/src/backend/taler-merchant-httpd_private-post-transfers.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2022 Taler Systems SA
+ (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -23,964 +23,57 @@
#include <jansson.h>
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
-#include "taler-merchant-httpd_auditors.h"
+#include <taler/taler_dbevents.h>
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_private-post-transfers.h"
/**
- * How long to wait before giving up processing with the exchange?
- */
-#define TRANSFER_GENERIC_TIMEOUT (GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_SECONDS, \
- 15))
-
-/**
* How often do we retry the simple INSERT database transaction?
*/
#define MAX_RETRIES 3
-/**
- * Context used for handing POST /private/transfers requests.
- */
-struct PostTransfersContext
-{
-
- /**
- * Kept in a DLL.
- */
- struct PostTransfersContext *next;
-
- /**
- * Kept in a DLL.
- */
- struct PostTransfersContext *prev;
-
- /**
- * Argument for the /wire/transfers request.
- */
- struct TALER_WireTransferIdentifierRawP wtid;
-
- /**
- * Amount of the wire transfer.
- */
- struct TALER_Amount amount;
-
- /**
- * URL of the exchange.
- */
- const char *exchange_url;
-
- /**
- * payto:// URI used for the transfer.
- */
- const char *payto_uri;
-
- /**
- * Master public key of the exchange at @e exchange_url.
- */
- struct TALER_MasterPublicKeyP master_pub;
-
- /**
- * Handle for the /wire/transfers request.
- */
- struct TALER_EXCHANGE_TransfersGetHandle *wdh;
-
- /**
- * For which merchant instance is this tracking request?
- */
- struct TMH_HandlerContext *hc;
-
- /**
- * HTTP connection we are handling.
- */
- struct MHD_Connection *connection;
-
- /**
- * Response to return upon resume.
- */
- struct MHD_Response *response;
-
- /**
- * Handle for operation to lookup /keys (and auditors) from
- * the exchange used for this transaction; NULL if no operation is
- * pending.
- */
- struct TMH_EXCHANGES_FindOperation *fo;
-
- /**
- * Task run on timeout.
- */
- struct GNUNET_SCHEDULER_Task *timeout_task;
-
- /**
- * Pointer to the detail that we are currently
- * checking in #check_transfer().
- */
- const struct TALER_TrackTransferDetails *current_detail;
-
- /**
- * Which transaction detail are we currently looking at?
- */
- unsigned int current_offset;
-
- /**
- * Response code to return.
- */
- unsigned int response_code;
-
- /**
- * #GNUNET_NO if we did not find a matching coin.
- * #GNUNET_SYSERR if we found a matching coin, but the amounts do not match.
- * #GNUNET_OK if we did find a matching coin.
- */
- enum GNUNET_GenericReturnValue check_transfer_result;
-
- /**
- * Did we suspend @a connection and are thus in
- * the #ptc_head DLL (#GNUNET_YES). Set to
- * #GNUNET_NO if we are not suspended, and to
- * #GNUNET_SYSERR if we should close the connection
- * without a response due to shutdown.
- */
- enum GNUNET_GenericReturnValue suspended;
-
- /**
- * Should we retry the transaction due to a serialization error?
- */
- bool soft_retry;
-
- /**
- * Did we just download the exchange reply?
- */
- bool downloaded;
-
-};
-
-
-/**
- * Head of list of suspended requests.
- */
-static struct PostTransfersContext *ptc_head;
-
-/**
- * Tail of list of suspended requests.
- */
-static struct PostTransfersContext *ptc_tail;
-
-
-void
-TMH_force_post_transfers_resume ()
-{
- struct PostTransfersContext *ptc;
-
- while (NULL != (ptc = ptc_head))
- {
- GNUNET_CONTAINER_DLL_remove (ptc_head,
- ptc_tail,
- ptc);
- ptc->suspended = GNUNET_SYSERR;
- MHD_resume_connection (ptc->connection);
- if (NULL != ptc->timeout_task)
- {
- GNUNET_SCHEDULER_cancel (ptc->timeout_task);
- ptc->timeout_task = NULL;
- }
- }
-}
-
-
-/**
- * Resume the given /track/transfer operation and send the given response.
- * Stores the response in the @a ptc and signals MHD to resume
- * the connection. Also ensures MHD runs immediately.
- *
- * @param ptc transfer tracking context
- * @param response_code response code to use
- * @param response response data to send back
- */
-static void
-resume_transfer_with_response (struct PostTransfersContext *ptc,
- unsigned int response_code,
- struct MHD_Response *response)
-{
- ptc->response_code = response_code;
- ptc->response = response;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Resuming POST /transfers handling as exchange interaction is done (%u)\n",
- response_code);
- if (NULL != ptc->timeout_task)
- {
- GNUNET_SCHEDULER_cancel (ptc->timeout_task);
- ptc->timeout_task = NULL;
- }
- GNUNET_CONTAINER_DLL_remove (ptc_head,
- ptc_tail,
- ptc);
- ptc->suspended = GNUNET_NO;
- MHD_resume_connection (ptc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
-}
-
-
-/**
- * Resume the given POST /transfers operation with an error.
- *
- * @param ptc transfer tracking context
- * @param response_code response code to use
- * @param ec error code to use
- * @param hint hint text to provide
- */
-static void
-resume_transfer_with_error (struct PostTransfersContext *ptc,
- unsigned int response_code,
- enum TALER_ErrorCode ec,
- const char *hint)
-{
- resume_transfer_with_response (ptc,
- response_code,
- TALER_MHD_make_error (ec,
- hint));
-}
-
-
-/**
- * Custom cleanup routine for a `struct PostTransfersContext`.
- *
- * @param cls the `struct PostTransfersContext` to clean up.
- */
-static void
-transfer_cleanup (void *cls)
-{
- struct PostTransfersContext *ptc = cls;
-
- if (NULL != ptc->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (ptc->fo);
- ptc->fo = NULL;
- }
- if (NULL != ptc->timeout_task)
- {
- GNUNET_SCHEDULER_cancel (ptc->timeout_task);
- ptc->timeout_task = NULL;
- }
- if (NULL != ptc->wdh)
- {
- TALER_EXCHANGE_transfers_get_cancel (ptc->wdh);
- ptc->wdh = NULL;
- }
- if (NULL != ptc->response)
- {
- MHD_destroy_response (ptc->response);
- ptc->response = NULL;
- }
- GNUNET_free (ptc);
-}
-
-
-/**
- * This function checks that the information about the coin which
- * was paid back by _this_ wire transfer matches what _we_ (the merchant)
- * knew about this coin.
- *
- * @param cls closure with our `struct PostTransfersContext *`
- * @param exchange_url URL of the exchange that issued @a coin_pub
- * @param amount_with_fee amount the exchange will transfer for this coin
- * @param deposit_fee fee the exchange will charge for this coin
- * @param refund_fee fee the exchange will charge for refunding this coin
- * @param wire_fee paid wire fee
- * @param h_wire hash of merchant's wire details
- * @param deposit_timestamp when did the exchange receive the deposit
- * @param refund_deadline until when are refunds allowed
- * @param exchange_sig signature by the exchange
- * @param exchange_pub exchange signing key used for @a exchange_sig
- */
-static void
-check_transfer (void *cls,
- const char *exchange_url,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct TALER_Amount *refund_fee,
- const struct TALER_Amount *wire_fee,
- const struct TALER_MerchantWireHashP *h_wire,
- struct GNUNET_TIME_Timestamp deposit_timestamp,
- struct GNUNET_TIME_Timestamp refund_deadline,
- const struct TALER_ExchangeSignatureP *exchange_sig,
- const struct TALER_ExchangePublicKeyP *exchange_pub)
-{
- struct PostTransfersContext *ptc = cls;
- const struct TALER_TrackTransferDetails *ttd = ptc->current_detail;
-
- if (GNUNET_SYSERR == ptc->check_transfer_result)
- return; /* already had a serious issue; odd that we're called more than once as well... */
- if ( (0 != TALER_amount_cmp (amount_with_fee,
- &ttd->coin_value)) ||
- (0 != TALER_amount_cmp (deposit_fee,
- &ttd->coin_fee)) )
- {
- /* Disagreement between the exchange and us about how much this
- coin is worth! */
- GNUNET_break_op (0);
- ptc->check_transfer_result = GNUNET_SYSERR;
- /* Build the `TrackTransferConflictDetails` */
- ptc->response_code = MHD_HTTP_ACCEPTED;
- ptc->response
- = TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_REPORTS),
- GNUNET_JSON_pack_string ("exchange_url",
- exchange_url),
- GNUNET_JSON_pack_timestamp ("deposit_timestamp",
- deposit_timestamp),
- GNUNET_JSON_pack_timestamp ("refund_deadline",
- refund_deadline),
- GNUNET_JSON_pack_uint64 ("conflict_offset",
- ptc->current_offset),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- &ttd->coin_pub),
- GNUNET_JSON_pack_data_auto ("h_wire",
- h_wire),
- GNUNET_JSON_pack_data_auto ("deposit_exchange_sig",
- exchange_sig),
- GNUNET_JSON_pack_data_auto ("deposit_exchange_pub",
- exchange_pub),
- GNUNET_JSON_pack_data_auto ("h_contract_terms",
- &ttd->h_contract_terms),
- TALER_JSON_pack_amount ("amount_with_fee",
- amount_with_fee),
- TALER_JSON_pack_amount ("coin_value",
- &ttd->coin_value),
- TALER_JSON_pack_amount ("coin_fee",
- &ttd->coin_fee),
- TALER_JSON_pack_amount ("deposit_fee",
- deposit_fee));
- return;
- }
- ptc->check_transfer_result = GNUNET_OK;
-}
-
-
-/**
- * Check that the given @a wire_fee is what the @a exchange_pub should charge
- * at the @a execution_time. If the fee is correct (according to our
- * database), return #GNUNET_OK. If we do not have the fee structure in our
- * DB, we just accept it and return #GNUNET_NO; if we have proof that the fee
- * is bogus, we respond with the proof to the client and return
- * #GNUNET_SYSERR.
- *
- * @param ptc context of the transfer to respond to
- * @param execution_time time of the wire transfer
- * @param wire_fee fee claimed by the exchange
- * @return #GNUNET_SYSERR if we returned hard proof of
- * missbehavior from the exchange to the client
- */
-static enum GNUNET_GenericReturnValue
-check_wire_fee (struct PostTransfersContext *ptc,
- struct GNUNET_TIME_Timestamp execution_time,
- const struct TALER_Amount *wire_fee)
-{
- struct TALER_WireFeeSet fees;
- struct TALER_MasterSignatureP master_sig;
- struct GNUNET_TIME_Timestamp start_date;
- struct GNUNET_TIME_Timestamp end_date;
- enum GNUNET_DB_QueryStatus qs;
- char *wire_method;
-
- wire_method = TALER_payto_get_method (ptc->payto_uri);
- qs = TMH_db->lookup_wire_fee (TMH_db->cls,
- &ptc->master_pub,
- wire_method,
- execution_time,
- &fees,
- &start_date,
- &end_date,
- &master_sig);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- ptc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- ptc->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_wire_fee");
- return GNUNET_SYSERR;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- ptc->soft_retry = true;
- return GNUNET_NO;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to find wire fee for `%s' and method `%s' at %s in DB, accepting blindly that the fee is %s\n",
- TALER_B2S (&ptc->master_pub),
- wire_method,
- GNUNET_TIME_timestamp2s (execution_time),
- TALER_amount2s (wire_fee));
- GNUNET_free (wire_method);
- return GNUNET_NO;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- if (0 <= TALER_amount_cmp (&fees.wire,
- wire_fee))
- {
- GNUNET_free (wire_method);
- return GNUNET_OK; /* expected_fee >= wire_fee */
- }
- /* Wire fee check failed, export proof to client */
- ptc->response_code = MHD_HTTP_ACCEPTED;
- ptc->response =
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_BAD_WIRE_FEE),
- TALER_JSON_pack_amount ("wire_fee",
- wire_fee),
- GNUNET_JSON_pack_timestamp ("execution_time",
- execution_time),
- TALER_JSON_pack_amount ("expected_wire_fee",
- &fees.wire),
- TALER_JSON_pack_amount ("expected_closing_fee",
- &fees.closing),
- GNUNET_JSON_pack_timestamp ("start_date",
- start_date),
- GNUNET_JSON_pack_timestamp ("end_date",
- end_date),
- GNUNET_JSON_pack_data_auto ("master_sig",
- &master_sig),
- GNUNET_JSON_pack_data_auto ("master_pub",
- &ptc->master_pub));
- GNUNET_free (wire_method);
- return GNUNET_SYSERR;
-}
-
-
-/**
- * Function called with detailed wire transfer data, including all
- * of the coin transactions that were combined into the wire transfer.
- *
- * @param cls closure
- * @param hr HTTP response details
- * @param td transfer data
- */
-static void
-wire_transfer_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_EXCHANGE_TransferData *td)
-{
- struct PostTransfersContext *ptc = cls;
- const char *instance_id = ptc->hc->instance->settings.id;
- enum GNUNET_DB_QueryStatus qs;
-
- ptc->wdh = NULL;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Got response code %u from exchange for GET /transfers/$WTID\n",
- hr->http_status);
- switch (hr->http_status)
- {
- case MHD_HTTP_OK:
- break;
- case MHD_HTTP_NOT_FOUND:
- resume_transfer_with_response (
- ptc,
- MHD_HTTP_BAD_GATEWAY,
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_EXCHANGE_UNKNOWN),
- TMH_pack_exchange_reply (hr)));
- return;
- default:
- resume_transfer_with_response (
- ptc,
- MHD_HTTP_BAD_GATEWAY,
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUS),
- TMH_pack_exchange_reply (hr)));
- return;
- }
- TMH_db->preflight (TMH_db->cls);
- /* Ok, exchange answer is acceptable, store it */
- qs = TMH_db->insert_transfer_details (TMH_db->cls,
- instance_id,
- ptc->exchange_url,
- ptc->payto_uri,
- &ptc->wtid,
- td);
- if (0 > qs)
- {
- /* Always report on DB error as well to enable diagnostics */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- resume_transfer_with_error (
- ptc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- (GNUNET_DB_STATUS_HARD_ERROR == qs)
- ? TALER_EC_GENERIC_DB_COMMIT_FAILED
- : TALER_EC_GENERIC_DB_SOFT_FAILURE,
- NULL);
- return;
- }
- if (0 == qs)
- {
- GNUNET_break (0);
- resume_transfer_with_error (
- ptc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert-transfer-details");
- return;
- }
- if (0 !=
- TALER_amount_cmp (&td->total_amount,
- &ptc->amount))
- {
- resume_transfer_with_error (
- ptc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_TRANSFERS,
- NULL);
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Transfer details inserted, resuming request...\n");
- /* resume processing, main function will build the response */
- resume_transfer_with_response (ptc,
- 0,
- NULL);
-}
-
-
-/**
- * Function called with the result of our exchange lookup.
- *
- * @param cls the `struct PostTransfersContext`
- * @param hr HTTP response details
- * @param eh NULL if exchange was not found to be acceptable
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee NULL (we did not specify a wire method)
- * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
- */
-static void
-process_transfer_with_exchange (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *eh,
- const char *payto_uri,
- const struct TALER_Amount *wire_fee,
- bool exchange_trusted)
-{
- struct PostTransfersContext *ptc = cls;
-
- (void) payto_uri;
- (void) exchange_trusted;
- ptc->fo = NULL;
- if (NULL == hr)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Exchange failed to respond!\n");
- resume_transfer_with_response (
- ptc,
- MHD_HTTP_GATEWAY_TIMEOUT,
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT)));
- return;
- }
- if (NULL == eh)
- {
- /* The request failed somehow */
- GNUNET_break_op (0);
- resume_transfer_with_response (
- ptc,
- MHD_HTTP_BAD_GATEWAY,
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
- TMH_pack_exchange_reply (hr)));
- return;
- }
-
- /* keep master key for later */
- {
- const struct TALER_EXCHANGE_Keys *keys;
-
- keys = TALER_EXCHANGE_get_keys (eh);
- if (NULL == keys)
- {
- GNUNET_break (0);
- resume_transfer_with_error (ptc,
- MHD_HTTP_BAD_GATEWAY,
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE,
- NULL);
- return;
- }
- ptc->master_pub = keys->master_pub;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Requesting transfer details from exchange\n");
- ptc->wdh = TALER_EXCHANGE_transfers_get (eh,
- &ptc->wtid,
- &wire_transfer_cb,
- ptc);
- if (NULL == ptc->wdh)
- {
- GNUNET_break (0);
- resume_transfer_with_error (ptc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_REQUEST_ERROR,
- "failed to run GET /transfers/ on exchange");
- }
-}
-
-
-/**
- * Now we want to double-check that any (Taler coin) deposit which is
- * accounted into _this_ wire transfer, does exist into _our_ database. This
- * is the rationale: if the exchange paid us for it, we must have received it
- * _beforehands_!
- *
- * @param cls a `struct PostTransfersContext`
- * @param current_offset at which offset in the exchange's reply are the @a ttd
- * @param ttd details about an aggregated transfer (to check)
- */
-static void
-verify_exchange_claim_cb (void *cls,
- unsigned int current_offset,
- const struct TALER_TrackTransferDetails *ttd)
-{
- struct PostTransfersContext *ptc = cls;
- enum GNUNET_DB_QueryStatus qs;
-
- if (0 != ptc->response_code)
- return; /* already encountered an error */
- if (ptc->soft_retry)
- return; /* already encountered an error */
- ptc->current_offset = current_offset;
- ptc->current_detail = ttd;
- /* Set the coin as "never seen" before. */
- ptc->check_transfer_result = GNUNET_NO;
- qs = TMH_db->lookup_deposits_by_contract_and_coin (
- TMH_db->cls,
- ptc->hc->instance->settings.id,
- &ttd->h_contract_terms,
- &ttd->coin_pub,
- &check_transfer,
- ptc);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SOFT_ERROR:
- ptc->soft_retry = true;
- return;
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- ptc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- ptc->response
- = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
- "deposit by contract and coin");
- return;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* The exchange says we made this deposit, but WE do not
- recall making it (corrupted / unreliable database?)!
- Well, let's say thanks and accept the money! */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to find payment data in DB\n");
- ptc->check_transfer_result = GNUNET_OK;
- break;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- switch (ptc->check_transfer_result)
- {
- case GNUNET_NO:
- /* Internal error: how can we have called #check_transfer()
- but still have no result? */
- GNUNET_break (0);
- ptc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- ptc->response =
- TALER_MHD_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "check_transfer_result must not be GNUNET_NO");
- return;
- case GNUNET_SYSERR:
- /* #check_transfer() failed, report conflict! */
- GNUNET_break_op (0);
- GNUNET_assert (NULL != ptc->response);
- return;
- case GNUNET_OK:
- break;
- }
-}
-
-
-/**
- * Represents an entry in the table used to sum up
- * individual deposits for each h_contract_terms/order_id
- * (as the exchange gives us per coin, and we return
- * per order).
- */
-struct Entry
-{
-
- /**
- * Order of the entry.
- */
- char *order_id;
-
- /**
- * Sum accumulator for deposited value.
- */
- struct TALER_Amount deposit_value;
-
- /**
- * Sum accumulator for deposit fee.
- */
- struct TALER_Amount deposit_fee;
-
-};
-
-
-/**
- * Function called with information about a wire transfer identifier.
- * Generate a response array based on the given information.
- *
- * @param cls closure, a hashmap to update
- * @param order_id the order to which the deposits belong
- * @param deposit_value the amount deposited under @a order_id
- * @param deposit_fee the fee charged for @a deposit_value
- */
-static void
-transfer_summary_cb (void *cls,
- const char *order_id,
- const struct TALER_Amount *deposit_value,
- const struct TALER_Amount *deposit_fee)
-{
- struct GNUNET_CONTAINER_MultiHashMap *map = cls;
- struct Entry *current_entry;
- struct GNUNET_HashCode h_key;
-
- GNUNET_CRYPTO_hash (order_id,
- strlen (order_id),
- &h_key);
- current_entry = GNUNET_CONTAINER_multihashmap_get (map,
- &h_key);
- if (NULL != current_entry)
- {
- /* The map already knows this order, do aggregation */
- GNUNET_assert ( (0 <=
- TALER_amount_add (&current_entry->deposit_value,
- &current_entry->deposit_value,
- deposit_value)) &&
- (0 <=
- TALER_amount_add (&current_entry->deposit_fee,
- &current_entry->deposit_fee,
- deposit_fee)) );
- }
- else
- {
- /* First time in the map for this h_contract_terms*/
- current_entry = GNUNET_new (struct Entry);
- current_entry->deposit_value = *deposit_value;
- current_entry->deposit_fee = *deposit_fee;
- current_entry->order_id = GNUNET_strdup (order_id);
- GNUNET_assert (GNUNET_SYSERR !=
- GNUNET_CONTAINER_multihashmap_put (map,
- &h_key,
- current_entry,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
- }
-}
-
-
-/**
- * Callback that frees all the elements in the hashmap, and @a cls
- * is non-NULL, appends them as JSON to the array
- *
- * @param cls closure, NULL or a `json_t *` array
- * @param key current key
- * @param value a `struct Entry`
- * @return #GNUNET_YES if the iteration should continue,
- * #GNUNET_NO otherwise.
- */
-static int
-hashmap_update_and_free (void *cls,
- const struct GNUNET_HashCode *key,
- void *value)
-{
- json_t *ja = cls;
- struct Entry *entry = value;
-
- (void) key;
- if (NULL != ja)
- {
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- ja,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("order_id",
- entry->order_id),
- TALER_JSON_pack_amount ("deposit_value",
- &entry->deposit_value),
- TALER_JSON_pack_amount ("deposit_fee",
- &entry->deposit_fee))));
- }
- GNUNET_free (entry->order_id);
- GNUNET_free (entry);
- return GNUNET_YES;
-}
-
-
-/**
- * Handle a timeout for the processing of the track transfer request.
- *
- * @param cls closure
- */
-static void
-handle_transfer_timeout (void *cls)
-{
- struct PostTransfersContext *ptc = cls;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Resuming POST /private/transfers with error after timeout\n");
- ptc->timeout_task = NULL;
- if (NULL != ptc->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (ptc->fo);
- ptc->fo = NULL;
- }
- if (NULL != ptc->wdh)
- {
- TALER_EXCHANGE_transfers_get_cancel (ptc->wdh);
- ptc->wdh = NULL;
- }
- resume_transfer_with_error (ptc,
- MHD_HTTP_GATEWAY_TIMEOUT,
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
- NULL);
-}
-
-
-/**
- * We are *done* processing the request, just queue the response (!)
- *
- * @param ptc request context
- */
-static MHD_RESULT
-queue (struct PostTransfersContext *ptc)
-{
- MHD_RESULT ret;
-
- GNUNET_assert (0 != ptc->response_code);
- if (UINT_MAX == ptc->response_code)
- {
- GNUNET_break (0);
- return MHD_NO; /* hard error */
- }
- ret = MHD_queue_response (ptc->connection,
- ptc->response_code,
- ptc->response);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Queueing response (%u) for POST /private/transfers (%s).\n",
- (unsigned int) ptc->response_code,
- ret ? "OK" : "FAILED");
- return ret;
-}
-
-
-/**
- * Download transfer data from the exchange.
- *
- * @param ptc request context
- */
-static void
-download (struct PostTransfersContext *ptc)
-{
- ptc->downloaded = true;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Suspending POST /private/transfers handling while working with exchange\n");
- MHD_suspend_connection (ptc->connection);
- ptc->suspended = GNUNET_YES;
- GNUNET_CONTAINER_DLL_insert (ptc_head,
- ptc_tail,
- ptc);
- ptc->fo = TMH_EXCHANGES_find_exchange (ptc->exchange_url,
- NULL,
- GNUNET_NO,
- &process_transfer_with_exchange,
- ptc);
- ptc->timeout_task
- = GNUNET_SCHEDULER_add_delayed (TRANSFER_GENERIC_TIMEOUT,
- &handle_transfer_timeout,
- ptc);
-}
-
MHD_RESULT
TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
- struct PostTransfersContext *ptc = hc->ctx;
+ const char *payto_uri;
+ const char *exchange_url;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct TALER_Amount amount;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("credit_amount",
+ &amount),
+ GNUNET_JSON_spec_fixed_auto ("wtid",
+ &wtid),
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &payto_uri),
+ TALER_JSON_spec_web_url ("exchange_url",
+ &exchange_url),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
enum GNUNET_DB_QueryStatus qs;
- if (NULL == ptc)
- {
- ptc = GNUNET_new (struct PostTransfersContext);
- ptc->connection = connection;
- ptc->hc = hc;
- hc->ctx = ptc;
- hc->cc = &transfer_cleanup;
- }
- if (GNUNET_SYSERR == ptc->suspended)
- return MHD_NO; /* we are in shutdown */
- /* resume logic: did we get resumed after a reply was built? */
- if (0 != ptc->response_code)
- return queue (ptc);
- if ( (NULL != ptc->fo) ||
- (NULL != ptc->wdh) )
- {
- /* likely old MHD version causing spurious wake-up */
- GNUNET_break (GNUNET_NO == ptc->suspended);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Not sure why we are here, should be suspended\n");
- return MHD_YES; /* still work in progress */
- }
- if (NULL == ptc->exchange_url)
- {
- /* First request, parse it! */
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("credit_amount",
- TMH_currency,
- &ptc->amount),
- GNUNET_JSON_spec_fixed_auto ("wtid",
- &ptc->wtid),
- GNUNET_JSON_spec_string ("payto_uri",
- &ptc->payto_uri),
- GNUNET_JSON_spec_string ("exchange_url",
- &ptc->exchange_url),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- hc->request_body,
- spec);
- if (GNUNET_OK != res)
- return (GNUNET_NO == res)
- ? MHD_YES
- : MHD_NO;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "New inbound wire transfer over %s to %s from %s\n",
- TALER_amount2s (&ptc->amount),
- ptc->payto_uri,
- ptc->exchange_url);
- }
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ if (GNUNET_OK != res)
+ return (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "New inbound wire transfer over %s to %s from %s\n",
+ TALER_amount2s (&amount),
+ payto_uri,
+ exchange_url);
/* Check if transfer data is in database, if not, add it. */
for (unsigned int retry = 0; retry<MAX_RETRIES; retry++)
{
- struct GNUNET_TIME_Timestamp execution_time;
- struct TALER_Amount total_amount;
- struct TALER_Amount exchange_amount;
- struct TALER_Amount wire_fee;
- bool verified;
- bool have_exchange_sig;
-
TMH_db->preflight (TMH_db->cls);
if (GNUNET_OK !=
TMH_db->start (TMH_db->cls,
@@ -992,16 +85,19 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
TALER_EC_GENERIC_DB_START_FAILED,
"transfer");
}
- qs = TMH_db->lookup_transfer (TMH_db->cls,
- ptc->hc->instance->settings.id,
- ptc->exchange_url,
- &ptc->wtid,
- &total_amount,
- &wire_fee,
- &exchange_amount,
- &execution_time,
- &have_exchange_sig,
- &verified);
+ qs = TMH_db->insert_transfer (TMH_db->cls,
+ hc->instance->settings.id,
+ exchange_url,
+ &wtid,
+ &amount,
+ payto_uri,
+ true /* confirmed! */);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ qs = TMH_db->set_transfer_status_to_confirmed (TMH_db->cls,
+ hc->instance->settings.id,
+ exchange_url,
+ &wtid,
+ &amount);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -1009,346 +105,58 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
TMH_db->rollback (TMH_db->cls);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "transfer");
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_transfer");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TMH_db->rollback (TMH_db->cls);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* Could not set to confirmed, must differ by amount! */
+ TMH_db->rollback (TMH_db->cls);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_SUBMISSION,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
+ }
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_MERCHANT_WIRE_TRANSFER_CONFIRMED)
+ };
+
+ TMH_db->event_notify (TMH_db->cls,
+ &es,
+ NULL,
+ 0);
+ }
+ qs = TMH_db->commit (TMH_db->cls);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TMH_db->rollback (TMH_db->cls);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ NULL);
case GNUNET_DB_STATUS_SOFT_ERROR:
TMH_db->rollback (TMH_db->cls);
continue;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* Transfer so far unknown; try to persist the wire transfer information
- we have received in the database (it is not yet present). Upon
- success, try to download the transfer details from the exchange. */
- {
- uint64_t account_serial;
-
- /* Make sure the bank account is configured. */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Transfer is not yet known\n");
- qs = TMH_db->lookup_account (TMH_db->cls,
- ptc->hc->instance->settings.id,
- ptc->payto_uri,
- &account_serial);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SOFT_ERROR:
- TMH_db->rollback (TMH_db->cls);
- continue;
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- TMH_db->rollback (TMH_db->cls);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_account");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Bank account `%s' not configured for instance `%s'\n",
- ptc->payto_uri,
- ptc->hc->instance->settings.id);
- TMH_db->rollback (TMH_db->cls);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_ACCOUNT_NOT_FOUND,
- ptc->payto_uri);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Bank account `%s' is configured at row %llu\n",
- ptc->payto_uri,
- (unsigned long long) account_serial);
- break;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Inserting new transfer\n");
- qs = TMH_db->insert_transfer (TMH_db->cls,
- ptc->hc->instance->settings.id,
- ptc->exchange_url,
- &ptc->wtid,
- &ptc->amount,
- ptc->payto_uri,
- true /* confirmed! */);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SOFT_ERROR:
- TMH_db->rollback (TMH_db->cls);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Soft error, retrying...\n");
- continue;
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- TMH_db->rollback (TMH_db->cls);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "transfer");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- TMH_db->rollback (TMH_db->cls);
- /* Should not happen: we checked earlier! */
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "not unique");
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
-
- qs = TMH_db->commit (TMH_db->cls);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SOFT_ERROR:
- TMH_db->rollback (TMH_db->cls);
- continue;
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- TMH_db->rollback (TMH_db->cls);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_COMMIT_FAILED,
- NULL);
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "post-transfer committed successfully\n");
- break;
- }
- download (ptc);
- return MHD_YES; /* download() always suspends */
- }
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* Transfer exists */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Transfer exists in DB (verified: %s, exchange signature: %s)\n",
- verified ? "true" : "false",
- have_exchange_sig ? "true" : "false");
- if (! verified)
- {
- if ( (! ptc->downloaded) &&
- (! have_exchange_sig) )
- {
- /* We may have previously attempted and failed to
- download the exchange data, do it again! */
- TMH_db->rollback (TMH_db->cls);
- download (ptc);
- return MHD_YES; /* download always suspends */
- }
- if (! have_exchange_sig)
- {
- /* We tried to download and still failed to get
- an exchange signture. Still, that should have
- been handled there. */
- TMH_db->rollback (TMH_db->cls);
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "download but no exchange signature and no error");
- }
- /* verify */
- if (GNUNET_SYSERR ==
- check_wire_fee (ptc,
- execution_time,
- &wire_fee))
- {
- TMH_db->rollback (TMH_db->cls);
- return queue (ptc); /* generate error */
- }
- if (ptc->soft_retry)
- {
- /* DB serialization failure */
- ptc->soft_retry = false;
- TMH_db->rollback (TMH_db->cls);
- continue;
- }
- qs = TMH_db->lookup_transfer_details (TMH_db->cls,
- ptc->exchange_url,
- &ptc->wtid,
- &verify_exchange_claim_cb,
- ptc);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- TMH_db->rollback (TMH_db->cls);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_transfer_details");
- case GNUNET_DB_STATUS_SOFT_ERROR:
- TMH_db->rollback (TMH_db->cls);
- continue;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- default:
- break;
- }
- if (0 != ptc->response_code)
- {
- TMH_db->rollback (TMH_db->cls);
- return queue (ptc); /* generate error */
- }
- if (ptc->soft_retry)
- {
- /* DB serialization failure */
- ptc->soft_retry = false;
- TMH_db->rollback (TMH_db->cls);
- continue;
- }
-
- {
- struct TALER_Amount delta;
-
- if (0 >
- TALER_amount_subtract (&delta,
- &total_amount,
- &wire_fee))
- {
- GNUNET_break (0);
- TMH_db->rollback (TMH_db->cls);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- NULL);
- }
- if (0 !=
- TALER_amount_cmp (&exchange_amount,
- &delta))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Amount of expected was %s\n",
- TALER_amount2s (&delta));
- TMH_db->rollback (TMH_db->cls);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_TRANSFERS,
- TALER_amount2s (&exchange_amount));
- }
- if ( (GNUNET_OK !=
- TALER_amount_cmp_currency (&ptc->amount,
- &delta)) ||
- (0 !=
- TALER_amount_cmp (&ptc->amount,
- &delta)) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Amount submitted was %s\n",
- TALER_amount2s (&ptc->amount));
- TMH_db->rollback (TMH_db->cls);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_SUBMISSION,
- TALER_amount2s (&exchange_amount));
- }
- }
- verified = true;
- qs = TMH_db->set_transfer_status_to_verified (TMH_db->cls,
- ptc->exchange_url,
- &ptc->wtid);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- TMH_db->rollback (TMH_db->cls);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "set_transfer_status_to_verified");
- case GNUNET_DB_STATUS_SOFT_ERROR:
- TMH_db->rollback (TMH_db->cls);
- continue;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_assert (0);
- break;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- } /* end of 'if (! verified)' */
-
- /* Short version: we verified that the exchange reply and
- our own accounting match; generate the summary response */
- GNUNET_assert (verified);
- {
- struct GNUNET_CONTAINER_MultiHashMap *map;
- json_t *deposit_sums;
-
- map = GNUNET_CONTAINER_multihashmap_create (16,
- GNUNET_NO);
- qs = TMH_db->lookup_transfer_summary (TMH_db->cls,
- ptc->exchange_url,
- &ptc->wtid,
- &transfer_summary_cb,
- map);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SOFT_ERROR:
- TMH_db->rollback (TMH_db->cls);
- continue;
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- TMH_db->rollback (TMH_db->cls);
- GNUNET_CONTAINER_multihashmap_iterate (map,
- &hashmap_update_and_free,
- NULL);
- GNUNET_CONTAINER_multihashmap_destroy (map);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "transfer summary");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- default:
- break;
- }
-
- qs = TMH_db->commit (TMH_db->cls);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SOFT_ERROR:
- TMH_db->rollback (TMH_db->cls);
- continue;
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- TMH_db->rollback (TMH_db->cls);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_COMMIT_FAILED,
- NULL);
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "post-transfer committed uselessly\n");
- break;
- }
-
- deposit_sums = json_array ();
- GNUNET_assert (NULL != deposit_sums);
- GNUNET_CONTAINER_multihashmap_iterate (map,
- &hashmap_update_and_free,
- deposit_sums);
- GNUNET_CONTAINER_multihashmap_destroy (map);
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- TALER_JSON_pack_amount ("total",
- &total_amount),
- TALER_JSON_pack_amount ("wire_fee",
- &wire_fee),
- GNUNET_JSON_pack_timestamp ("execution_time",
- execution_time),
- GNUNET_JSON_pack_array_steal ("deposit_sums",
- deposit_sums));
- } /* end of 'verified == true' (not an 'if'!) */
- } /* end of 'switch (qs)' */
- GNUNET_assert (0);
- } /* end of 'for(retries...) */
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_SOFT_FAILURE,
- "post-transfers");
+ "post-transfer committed successfully\n");
+ break;
+ }
+ }
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
}
diff --git a/src/backend/taler-merchant-httpd_private-post-transfers.h b/src/backend/taler-merchant-httpd_private-post-transfers.h
index a83a3449..8a411d2c 100644
--- a/src/backend/taler-merchant-httpd_private-post-transfers.h
+++ b/src/backend/taler-merchant-httpd_private-post-transfers.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2020 Taler Systems SA
+ (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -26,13 +26,6 @@
/**
- * We are shutting down, force resume of all POST /transfers requests.
- */
-void
-TMH_force_post_transfers_resume (void);
-
-
-/**
* Manages a POST /private/transfers call. It calls the GET /transfers/$WTID
* offered by the exchange in order to obtain the set of transfers
* (of coins) associated with a given wire transfer.
diff --git a/src/backend/taler-merchant-httpd_private-post-webhooks.c b/src/backend/taler-merchant-httpd_private-post-webhooks.c
index 48f6e7fb..391c7ea9 100644
--- a/src/backend/taler-merchant-httpd_private-post-webhooks.c
+++ b/src/backend/taler-merchant-httpd_private-post-webhooks.c
@@ -51,10 +51,18 @@ webhooks_equal (const struct TALER_MERCHANTDB_WebhookDetails *w1,
w2->url)) &&
(0 == strcmp (w1->http_method,
w2->http_method)) &&
- (0 == strcmp (w1->header_template,
- w2->header_template)) &&
- (0 == strcmp (w1->body_template,
- w2->body_template)));
+ ( ( (NULL == w1->header_template) &&
+ (NULL == w2->header_template) ) ||
+ ( (NULL != w1->header_template) &&
+ (NULL != w2->header_template) &&
+ (0 == strcmp (w1->header_template,
+ w2->header_template)) ) ) &&
+ ( ( (NULL == w1->body_template) &&
+ (NULL == w2->body_template) ) ||
+ ( (NULL != w1->body_template) &&
+ (NULL != w2->body_template) &&
+ (0 == strcmp (w1->body_template,
+ w2->body_template)) ) ) );
}
@@ -72,14 +80,18 @@ TMH_private_post_webhooks (const struct TMH_RequestHandler *rh,
&webhook_id),
GNUNET_JSON_spec_string ("event_type",
(const char **) &wb.event_type),
- GNUNET_JSON_spec_string ("url",
+ TALER_JSON_spec_web_url ("url",
(const char **) &wb.url),
GNUNET_JSON_spec_string ("http_method",
(const char **) &wb.http_method),
- GNUNET_JSON_spec_string ("header_template",
- (const char **) &wb.header_template),
- GNUNET_JSON_spec_string ("body_template",
- (const char **) &wb.body_template),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("header_template",
+ (const char **) &wb.header_template),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("body_template",
+ (const char **) &wb.body_template),
+ NULL),
GNUNET_JSON_spec_end ()
};
@@ -100,7 +112,6 @@ TMH_private_post_webhooks (const struct TMH_RequestHandler *rh,
}
-
/* finally, interact with DB until no serialization error */
for (unsigned int i = 0; i<MAX_RETRIES; i++)
{
diff --git a/src/backend/taler-merchant-httpd_reserves.c b/src/backend/taler-merchant-httpd_reserves.c
deleted file mode 100644
index 50af145f..00000000
--- a/src/backend/taler-merchant-httpd_reserves.c
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- This file is part of TALER
- (C) 2020, 2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-merchant-httpd_reserves.c
- * @brief logic for initially tracking a reserve's status
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <taler/taler_json_lib.h>
-#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_reserves.h"
-
-/**
- * How long do we keep the long-poller open?
- * Not very long here, as if the money has not
- * yet arrived, there is a fair chance that it'll
- * take much longer, and in that case we rather
- * enter into the delay created by try_later().
- */
-#define LONGPOLL_DELAY GNUNET_TIME_UNIT_MINUTES
-
-/**
- * Our representation of a reserve that we are (still) checking the status of.
- */
-struct Reserve
-{
-
- /**
- * Kept in a DLL.
- */
- struct Reserve *next;
-
- /**
- * Kept in a DLL.
- */
- struct Reserve *prev;
-
- /**
- * Reserve's public key.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Amount the merchant expects to see in the reserve initially.
- * We log a warning if there is a mismatch.
- */
- struct TALER_Amount expected_amount;
-
- /**
- * URL of the exchange hosting this reserve.
- */
- char *exchange_url;
-
- /**
- * Instance this reserve belongs with.
- */
- char *instance_id;
-
- /**
- * Active find operation for this reserve.
- */
- struct TMH_EXCHANGES_FindOperation *fo;
-
- /**
- * Task scheduled waiting for a timeout for this reserve.
- */
- struct GNUNET_SCHEDULER_Task *tt;
-
- /**
- * Get operation with the exchange.
- */
- struct TALER_EXCHANGE_ReservesGetHandle *gh;
-
- /**
- * How long do we wait before trying this reserve again?
- */
- struct GNUNET_TIME_Relative delay;
-
-};
-
-
-/**
- * Head of DLL of pending reserves.
- */
-static struct Reserve *reserves_head;
-
-/**
- * Tail of DLL of pending reserves.
- */
-static struct Reserve *reserves_tail;
-
-
-/**
- * Function called to probe a reserve now.
- *
- * @param cls a `struct Reserve` to query
- */
-static void
-try_now (void *cls);
-
-
-/**
- * Free reserve data structure.
- *
- * @param r reserve to free
- */
-static void
-free_reserve (struct Reserve *r)
-{
- GNUNET_CONTAINER_DLL_remove (reserves_head,
- reserves_tail,
- r);
- if (NULL != r->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (r->fo);
- r->fo = NULL;
- }
- if (NULL != r->gh)
- {
- TALER_EXCHANGE_reserves_get_cancel (r->gh);
- r->gh = NULL;
- }
- if (NULL != r->tt)
- {
- GNUNET_SCHEDULER_cancel (r->tt);
- r->tt = NULL;
- }
- GNUNET_free (r->exchange_url);
- GNUNET_free (r->instance_id);
- GNUNET_free (r);
-}
-
-
-/**
- * Schedule a job to probe a reserve later again.
- *
- * @param r reserve to try again later
- */
-static void
-try_later (struct Reserve *r)
-{
- /* minimum delay is the #LONGPOLL_DELAY */
- r->delay = GNUNET_TIME_relative_max (LONGPOLL_DELAY,
- r->delay);
- /* STD_BACKOFF has a maximum of 15 minutes */
- r->delay = GNUNET_TIME_STD_BACKOFF (r->delay);
- r->tt = GNUNET_SCHEDULER_add_delayed (r->delay,
- &try_now,
- r);
-}
-
-
-/**
- * Callbacks of this type are used to serve the result of submitting a
- * reserve status request to a exchange.
- *
- * @param cls closure with a `struct Reserve *`
- * @param rs HTTP response data
- */
-static void
-reserve_cb (void *cls,
- const struct TALER_EXCHANGE_ReserveSummary *rs)
-{
- struct Reserve *r = cls;
- enum GNUNET_DB_QueryStatus qs;
-
- r->gh = NULL;
- if (MHD_HTTP_OK != rs->hr.http_status)
- {
- try_later (r);
- return;
- }
- if (GNUNET_OK !=
- TALER_amount_cmp_currency (&r->expected_amount,
- &rs->details.ok.balance))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Reserve currency disagreement: exchange `%s' has %s, expected %s\n",
- r->exchange_url,
- rs->details.ok.balance.currency,
- r->expected_amount.currency);
- free_reserve (r);
- return;
- }
- if (0 !=
- TALER_amount_cmp (&r->expected_amount,
- &rs->details.ok.balance))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Reserve initial balance disagreement: exchange `%s' received `%s'\n",
- r->exchange_url,
- TALER_amount2s (&rs->details.ok.balance));
- }
- qs = TMH_db->activate_reserve (TMH_db->cls,
- r->instance_id,
- &r->reserve_pub,
- &rs->details.ok.balance);
- if (qs <= 0)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to commit reserve activation to database (%d)\n",
- (int) qs);
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Reserve activated with initial balance %s\n",
- TALER_amount2s (&rs->details.ok.balance));
- }
- free_reserve (r);
-}
-
-
-/**
- * Function called with the result of a #TMH_EXCHANGES_find_exchange()
- * operation.
- *
- * @param cls closure
- * @param hr HTTP response details
- * @param eh handle to the exchange context
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
- * @param exchange_trusted true if this exchange is trusted by config
- */
-static void
-find_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *eh,
- const char *payto_uri,
- const struct TALER_Amount *wire_fee,
- bool exchange_trusted)
-{
- struct Reserve *r = cls;
-
- r->fo = NULL;
- if (NULL == eh)
- {
- try_later (r);
- return;
- }
- r->gh = TALER_EXCHANGE_reserves_get (eh,
- &r->reserve_pub,
- LONGPOLL_DELAY,
- &reserve_cb,
- r);
- if (NULL == r->gh)
- {
- try_later (r);
- return;
- }
-}
-
-
-/**
- * Function called to probe a reserve now.
- *
- * @param cls a `struct Reserve` to query
- */
-static void
-try_now (void *cls)
-{
- struct Reserve *r = cls;
-
- r->tt = NULL;
- r->fo = TMH_EXCHANGES_find_exchange (r->exchange_url,
- NULL,
- GNUNET_NO,
- &find_cb,
- r);
- if (NULL == r->fo)
- {
- try_later (r);
- return;
- }
-}
-
-
-/**
- * Function called with information about a reserve that we need
- * to check the status from at the exchange to see if/when it has
- * been filled (and with what amount).
- *
- * @param cls closure, NULL
- * @param instance_id for which instance is this reserve
- * @param exchange_url base URL of the exchange at which the reserve lives
- * @param reserve_pub public key of the reserve
- * @param expected_amount how much do we expect to see in the reserve
- */
-static void
-add_reserve (void *cls,
- const char *instance_id,
- const char *exchange_url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_Amount *expected_amount)
-{
- struct Reserve *r;
-
- (void) cls;
- r = GNUNET_new (struct Reserve);
- r->exchange_url = GNUNET_strdup (exchange_url);
- r->instance_id = GNUNET_strdup (instance_id);
- r->reserve_pub = *reserve_pub;
- r->expected_amount = *expected_amount;
- GNUNET_CONTAINER_DLL_insert (reserves_head,
- reserves_tail,
- r);
- try_now (r);
-}
-
-
-void
-TMH_RESERVES_init (void)
-{
- TMH_db->lookup_pending_reserves (TMH_db->cls,
- &add_reserve,
- NULL);
-}
-
-
-void
-TMH_RESERVES_check (const char *instance_id,
- const char *exchange_url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_Amount *expected_amount)
-{
- add_reserve (NULL,
- instance_id,
- exchange_url,
- reserve_pub,
- expected_amount);
-}
-
-
-void
-TMH_RESERVES_done (void)
-{
- while (NULL != reserves_head)
- free_reserve (reserves_head);
-}
-
-
-/* end of taler-merchant-httpd_reserves.c */
diff --git a/src/backend/taler-merchant-httpd_reserves.h b/src/backend/taler-merchant-httpd_reserves.h
deleted file mode 100644
index af4133b1..00000000
--- a/src/backend/taler-merchant-httpd_reserves.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- This file is part of TALER
- (C) 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-merchant-httpd_reserves.h
- * @brief logic for initially tracking a reserve's status
- * @author Christian Grothoff
- */
-#ifndef TALER_MERCHANT_HTTPD_RESERVES_H
-#define TALER_MERCHANT_HTTPD_RESERVES_H
-
-#include <jansson.h>
-#include <gnunet/gnunet_util_lib.h>
-#include <taler/taler_util.h>
-#include <taler/taler_exchange_service.h>
-#include "taler-merchant-httpd.h"
-
-
-/**
- * Load information about reserves and start querying reserve status.
- * Must be called after the database is available.
- */
-void
-TMH_RESERVES_init (void);
-
-
-/**
- * Add a reserve to the list of reserves to check.
- *
- * @param instance_id which instance is the reserve for
- * @param exchange_url URL of the exchange with the reserve
- * @param reserve_pub public key of the reserve to check
- * @param expected_amount amount the merchant expects to see initially in the reserve
- */
-void
-TMH_RESERVES_check (const char *instance_id,
- const char *exchange_url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_Amount *expected_amount);
-
-
-/**
- * Stop checking reserve status.
- */
-void
-TMH_RESERVES_done (void);
-
-
-#endif
diff --git a/src/backend/taler-merchant-httpd_spa.c b/src/backend/taler-merchant-httpd_spa.c
index 248b8729..b12200b8 100644
--- a/src/backend/taler-merchant-httpd_spa.c
+++ b/src/backend/taler-merchant-httpd_spa.c
@@ -118,13 +118,7 @@ static enum GNUNET_GenericReturnValue
build_webui (void *cls,
const char *dn)
{
- int fd;
- struct stat sb;
- struct MHD_Response *zspa;
- struct MHD_Response *spa;
- const char *ext;
- const char *mime;
- struct
+ static struct
{
const char *ext;
const char *mime;
@@ -162,6 +156,12 @@ build_webui (void *cls,
.mime = NULL
},
};
+ int fd;
+ struct stat sb;
+ struct MHD_Response *zspa = NULL;
+ struct MHD_Response *spa;
+ const char *ext;
+ const char *mime;
(void) cls;
/* finally open template */
@@ -187,7 +187,11 @@ build_webui (void *cls,
mime = NULL;
ext = strrchr (dn, '.');
- GNUNET_assert (NULL != ext);
+ if (NULL == ext)
+ {
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
ext++;
for (unsigned int i = 0; NULL != mime_map[i].ext; i++)
if (0 == strcasecmp (ext,
diff --git a/src/backend/taler-merchant-webhook b/src/backend/taler-merchant-webhook
deleted file mode 100755
index 880c8c6d..00000000
--- a/src/backend/taler-merchant-webhook
+++ /dev/null
@@ -1,210 +0,0 @@
-#! /bin/bash
-
-# taler-merchant-webhook - temporary wrapper script for .libs/taler-merchant-webhook
-# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-15
-#
-# The taler-merchant-webhook program cannot be directly executed until all the libtool
-# libraries that it depends on are installed.
-#
-# This wrapper script should never be moved out of the build directory.
-# If it is, it will not operate correctly.
-
-# Sed substitution that helps us do robust quoting. It backslashifies
-# metacharacters that are still active within double-quoted strings.
-sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
-
-# Be Bourne compatible
-if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
- emulate sh
- NULLCMD=:
- # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
- # is contrary to our usage. Disable this feature.
- alias -g '${1+"$@"}'='"$@"'
- setopt NO_GLOB_SUBST
-else
- case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
-fi
-BIN_SH=xpg4; export BIN_SH # for Tru64
-DUALCASE=1; export DUALCASE # for MKS sh
-
-# The HP-UX ksh and POSIX shell print the target directory to stdout
-# if CDPATH is set.
-(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
-
-relink_command=""
-
-# This environment variable determines our operation mode.
-if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
- # install mode needs the following variables:
- generated_by_libtool_version='2.4.6'
- notinst_deplibs=' ../../src/backenddb/libtalermerchantdb.la'
-else
- # When we are sourced in execute mode, $file and $ECHO are already set.
- if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
- file="$0"
-
-# A function that is used when there is no print builtin or printf.
-func_fallback_echo ()
-{
- eval 'cat <<_LTECHO_EOF
-$1
-_LTECHO_EOF'
-}
- ECHO="printf %s\\n"
- fi
-
-# Very basic option parsing. These options are (a) specific to
-# the libtool wrapper, (b) are identical between the wrapper
-# /script/ and the wrapper /executable/ that is used only on
-# windows platforms, and (c) all begin with the string --lt-
-# (application programs are unlikely to have options that match
-# this pattern).
-#
-# There are only two supported options: --lt-debug and
-# --lt-dump-script. There is, deliberately, no --lt-help.
-#
-# The first argument to this parsing function should be the
-# script's ../../libtool value, followed by no.
-lt_option_debug=
-func_parse_lt_options ()
-{
- lt_script_arg0=$0
- shift
- for lt_opt
- do
- case "$lt_opt" in
- --lt-debug) lt_option_debug=1 ;;
- --lt-dump-script)
- lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
- test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
- lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
- cat "$lt_dump_D/$lt_dump_F"
- exit 0
- ;;
- --lt-*)
- $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
- exit 1
- ;;
- esac
- done
-
- # Print the debug banner immediately:
- if test -n "$lt_option_debug"; then
- echo "taler-merchant-webhook:taler-merchant-webhook:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-15" 1>&2
- fi
-}
-
-# Used when --lt-debug. Prints its arguments to stdout
-# (redirection is the responsibility of the caller)
-func_lt_dump_args ()
-{
- lt_dump_args_N=1;
- for lt_arg
- do
- $ECHO "taler-merchant-webhook:taler-merchant-webhook:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
- lt_dump_args_N=`expr $lt_dump_args_N + 1`
- done
-}
-
-# Core function for launching the target application
-func_exec_program_core ()
-{
-
- if test -n "$lt_option_debug"; then
- $ECHO "taler-merchant-webhook:taler-merchant-webhook:$LINENO: newargv[0]: $progdir/$program" 1>&2
- func_lt_dump_args ${1+"$@"} 1>&2
- fi
- exec "$progdir/$program" ${1+"$@"}
-
- $ECHO "$0: cannot exec $program $*" 1>&2
- exit 1
-}
-
-# A function to encapsulate launching the target application
-# Strips options in the --lt-* namespace from $@ and
-# launches target application with the remaining arguments.
-func_exec_program ()
-{
- case " $* " in
- *\ --lt-*)
- for lt_wr_arg
- do
- case $lt_wr_arg in
- --lt-*) ;;
- *) set x "$@" "$lt_wr_arg"; shift;;
- esac
- shift
- done ;;
- esac
- func_exec_program_core ${1+"$@"}
-}
-
- # Parse options
- func_parse_lt_options "$0" ${1+"$@"}
-
- # Find the directory that this script lives in.
- thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
- test "x$thisdir" = "x$file" && thisdir=.
-
- # Follow symbolic links until we get to the real thisdir.
- file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
- while test -n "$file"; do
- destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
-
- # If there was a directory component, then change thisdir.
- if test "x$destdir" != "x$file"; then
- case "$destdir" in
- [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
- *) thisdir="$thisdir/$destdir" ;;
- esac
- fi
-
- file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
- file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
- done
-
- # Usually 'no', except on cygwin/mingw when embedded into
- # the cwrapper.
- WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
- if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
- # special case for '.'
- if test "$thisdir" = "."; then
- thisdir=`pwd`
- fi
- # remove .libs from thisdir
- case "$thisdir" in
- *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
- .libs ) thisdir=. ;;
- esac
- fi
-
- # Try to get the absolute directory name.
- absdir=`cd "$thisdir" && pwd`
- test -n "$absdir" && thisdir="$absdir"
-
- program='taler-merchant-webhook'
- progdir="$thisdir/.libs"
-
-
- if test -f "$progdir/$program"; then
- # Add our own library path to LD_LIBRARY_PATH
- LD_LIBRARY_PATH="/home/priscilla/merchant/src/backenddb/.libs:/home/priscilla/install/lib:$LD_LIBRARY_PATH"
-
- # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
- # The second colon is a workaround for a bug in BeOS R4 sed
- LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
-
- export LD_LIBRARY_PATH
-
- if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
- # Run the actual program with our arguments.
- func_exec_program ${1+"$@"}
- fi
- else
- # The program doesn't exist.
- $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
- $ECHO "This script is just a wrapper for $program." 1>&2
- $ECHO "See the libtool documentation for more information." 1>&2
- exit 1
- fi
-fi
diff --git a/src/backend/taler-merchant-webhook.c b/src/backend/taler-merchant-webhook.c
index 36e0f1a1..80db78fd 100644
--- a/src/backend/taler-merchant-webhook.c
+++ b/src/backend/taler-merchant-webhook.c
@@ -16,7 +16,7 @@
/**
* @file taler-merchant-webhook.c
* @brief Process that runs webhooks triggered by the merchant backend
- * @author Christian Grothoff
+ * @author Priscilla HUANG
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
@@ -24,25 +24,28 @@
#include <pthread.h>
#include "taler_merchantdb_lib.h"
#include "taler_merchantdb_plugin.h"
+#include <taler/taler_dbevents.h>
-struct Work_response
+struct WorkResponse
{
- struct Work_response *next;
- struct Work_response *prev;
+ struct WorkResponse *next;
+ struct WorkResponse *prev;
struct GNUNET_CURL_Job *job;
- uint64_t webhook_serial;
+ uint64_t webhook_pending_serial;
char *body;
struct curl_slist *job_headers;
};
-static struct Work_response *w_head;
+static struct WorkResponse *w_head;
-static struct Work_response *w_tail;
+static struct WorkResponse *w_tail;
+
+static struct GNUNET_DB_EventHandler *eh;
/**
- * The exchange's configuration.
+ * The merchant's configuration.
*/
static const struct GNUNET_CONFIGURATION_Handle *cfg;
@@ -85,11 +88,16 @@ static int test_mode;
static void
shutdown_task (void *cls)
{
- struct Work_response *w;
+ struct WorkResponse *w;
(void) cls;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Running shutdown\n");
+ if (NULL != eh)
+ {
+ db_plugin->event_listen_cancel (eh);
+ eh = NULL;
+ }
if (NULL != task)
{
GNUNET_SCHEDULER_cancel (task);
@@ -121,8 +129,14 @@ shutdown_task (void *cls)
}
}
-/* The fuction is defined after */
-static void select_work (void *cls);
+
+/**
+ * Select webhook to process.
+ *
+ * @param cls NULL
+ */
+static void
+select_work (void *cls);
/**
@@ -140,47 +154,104 @@ handle_webhook_response (void *cls,
const void *body,
size_t body_size)
{
- struct Work_response *w = cls;
- struct GNUNET_TIME_Relative next_attempt;
+ struct WorkResponse *w = cls;
(void) body;
(void) body_size;
-
- if (2 == response_code / 100) /* any 2xx http status code is OK! */
- {
- db_plugin->delete_pending_webhook (db_plugin->cls,
- w->webhook_serial);
- }
- else
- {
- switch (response_code)
- {
- case MHD_HTTP_BAD_REQUEST:
- next_attempt = GNUNET_TIME_UNIT_FOREVER_REL; // never try again
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- next_attempt = GNUNET_TIME_UNIT_MINUTES;
- break;
- case MHD_HTTP_FORBIDDEN:
- next_attempt = GNUNET_TIME_UNIT_MINUTES;
- break;
- default:
- next_attempt = GNUNET_TIME_UNIT_HOURS;
- break;
- }
- db_plugin->update_pending_webhook (db_plugin->cls,
- w->webhook_serial,
- GNUNET_TIME_relative_to_absolute (next_attempt));
- }
+ w->job = NULL;
GNUNET_CONTAINER_DLL_remove (w_head,
w_tail,
w);
GNUNET_free (w->body);
curl_slist_free_all (w->job_headers);
- GNUNET_free (w);
if (NULL == w_head)
task = GNUNET_SCHEDULER_add_now (&select_work,
NULL);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Webhook %llu returned with status %ld\n",
+ (unsigned long long) w->webhook_pending_serial,
+ response_code);
+ if (2 == response_code / 100) /* any 2xx http status code is OK! */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db_plugin->delete_pending_webhook (db_plugin->cls,
+ w->webhook_pending_serial);
+ GNUNET_free (w);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to delete webhook, delete returned: %d\n",
+ qs);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Delete returned: %d\n",
+ qs);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Delete returned: %d\n",
+ qs);
+ return;
+ }
+ GNUNET_assert (0);
+ }
+
+ {
+ struct GNUNET_TIME_Relative next_attempt;
+ enum GNUNET_DB_QueryStatus qs;
+ switch (response_code)
+ {
+ case MHD_HTTP_BAD_REQUEST:
+ next_attempt = GNUNET_TIME_UNIT_FOREVER_REL; // never try again
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ next_attempt = GNUNET_TIME_UNIT_MINUTES;
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ next_attempt = GNUNET_TIME_UNIT_MINUTES;
+ break;
+ default:
+ next_attempt = GNUNET_TIME_UNIT_HOURS;
+ break;
+ }
+ qs = db_plugin->update_pending_webhook (db_plugin->cls,
+ w->webhook_pending_serial,
+ GNUNET_TIME_relative_to_absolute (
+ next_attempt));
+ GNUNET_free (w);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to update pending webhook to next in %s Rval: %d\n",
+ GNUNET_TIME_relative2s (next_attempt,
+ true),
+ qs);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Next in %s Rval: %d\n",
+ GNUNET_TIME_relative2s (next_attempt, true),
+ qs);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Next in %s Rval: %d\n",
+ GNUNET_TIME_relative2s (next_attempt, true),
+ qs);
+ return;
+ }
+ GNUNET_assert (0);
+ }
}
@@ -188,7 +259,7 @@ handle_webhook_response (void *cls,
* Typically called by `select_work`.
*
* @param cls a `json_t *` JSON array to build
- * @param webhook_serial reference to the configured webhook template.
+ * @param webhook_pending_serial reference to the configured webhook template.
* @param next_attempt is the time we should make the next request to the webhook.
* @param retries how often have we tried this request to the webhook.
* @param url to make request to
@@ -198,7 +269,7 @@ handle_webhook_response (void *cls,
*/
static void
pending_webhooks_cb (void *cls,
- uint64_t webhook_serial,
+ uint64_t webhook_pending_serial,
struct GNUNET_TIME_Absolute next_attempt,
uint32_t retries,
const char *url,
@@ -206,17 +277,17 @@ pending_webhooks_cb (void *cls,
const char *header,
const char *body)
{
- struct Work_response *w = GNUNET_new (struct Work_response);
+ struct WorkResponse *w = GNUNET_new (struct WorkResponse);
CURL *eh;
- struct curl_slist *job_headers = NULL;
(void) retries;
(void) next_attempt;
(void) cls;
+ struct curl_slist *job_headers = NULL;
GNUNET_CONTAINER_DLL_insert (w_head,
w_tail,
w);
- w->webhook_serial = webhook_serial;
+ w->webhook_pending_serial = webhook_pending_serial;
eh = curl_easy_init ();
GNUNET_assert (NULL != eh);
GNUNET_assert (CURLE_OK ==
@@ -227,25 +298,48 @@ pending_webhooks_cb (void *cls,
curl_easy_setopt (eh,
CURLOPT_URL,
url));
-
- /* conversion body data */
- w->body = GNUNET_strdup (body);
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
- CURLOPT_POSTFIELDS,
- w->body));
+ CURLOPT_VERBOSE,
+ 0L));
+ /* conversion body data */
+ if (NULL != body)
+ {
+ w->body = GNUNET_strdup (body);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDS,
+ w->body));
+ }
/* conversion header to job_headers data */
- char *header_copy = GNUNET_strdup (header);
+ if (NULL != header)
+ {
+ char *header_copy = GNUNET_strdup (header);
+
+ for (const char *tok = strtok (header_copy, "\n");
+ NULL != tok;
+ tok = strtok (NULL, "\n"))
+ {
+ // extract all Key: value from 'header_copy'!
+ job_headers = curl_slist_append (job_headers,
+ tok);
+ }
+ GNUNET_free (header_copy);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HTTPHEADER,
+ job_headers));
+ w->job_headers = job_headers;
+ }
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
- CURLOPT_POSTFIELDS,
- header_copy));
+ CURLOPT_MAXREDIRS,
+ 5));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
- CURLOPT_POSTFIELDS,
- job_headers));
- w->job_headers = job_headers;
+ CURLOPT_FOLLOWLOCATION,
+ 1));
w->job = GNUNET_CURL_job_add_raw (ctx,
eh,
@@ -255,8 +349,8 @@ pending_webhooks_cb (void *cls,
if (NULL == w->job)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to start the curl job for webhook #%llu\n",
- (unsigned long long) webhook_serial);
+ "Failed to start the curl job for pending webhook #%llu\n",
+ (unsigned long long) webhook_pending_serial);
curl_slist_free_all (w->job_headers);
GNUNET_free (w->body);
GNUNET_CONTAINER_DLL_remove (w_head,
@@ -269,13 +363,34 @@ pending_webhooks_cb (void *cls,
}
+/**
+ * Function called on events received from Postgres.
+ *
+ * @param cls closure, NULL
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+db_notify (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ (void) cls;
+ (void) extra;
+ (void) extra_size;
+
+ GNUNET_assert (NULL != task);
+ GNUNET_SCHEDULER_cancel (task);
+ task = GNUNET_SCHEDULER_add_now (&select_work,
+ NULL);
+}
/**
* Typically called by `select_work`.
*
* @param cls a `json_t *` JSON array to build
- * @param webhook_serial reference to the configured webhook template.
+ * @param webhook_pending_serial reference to the configured webhook template.
* @param next_attempt is the time we should make the next request to the webhook.
* @param retries how often have we tried this request to the webhook.
* @param url to make request to
@@ -285,7 +400,7 @@ pending_webhooks_cb (void *cls,
*/
static void
future_webhook_cb (void *cls,
- uint64_t webhook_serial,
+ uint64_t webhook_pending_serial,
struct GNUNET_TIME_Absolute next_attempt,
uint32_t retries,
const char *url,
@@ -293,7 +408,7 @@ future_webhook_cb (void *cls,
const char *header,
const char *body)
{
- (void) webhook_serial;
+ (void) webhook_pending_serial;
(void) retries;
(void) url;
(void) http_method;
@@ -306,11 +421,6 @@ future_webhook_cb (void *cls,
}
-/**
- * Select webhook to process.
- *
- * @param cls NULL
- */
static void
select_work (void *cls)
{
@@ -328,7 +438,7 @@ select_work (void *cls)
case GNUNET_DB_STATUS_HARD_ERROR:
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed!\n");
+ "Failed to lookup pending webhooks!\n");
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
@@ -341,11 +451,12 @@ select_work (void *cls)
qs = db_plugin->lookup_future_webhook (db_plugin->cls,
&future_webhook_cb,
NULL);
- switch (qs) {
+ switch (qs)
+ {
case GNUNET_DB_STATUS_HARD_ERROR:
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed!\n");
+ "Failed to lookup future webhook!\n");
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
@@ -353,6 +464,8 @@ select_work (void *cls)
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
/* wait 5 min */
+ /* Note: this should not even be necessary if all webhooks
+ use the events properly... */
rel = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 5);
task = GNUNET_SCHEDULER_add_delayed (rel,
&select_work,
@@ -366,7 +479,7 @@ select_work (void *cls)
}
-/*
+/**
* First task.
*
* @param cls closure, NULL
@@ -393,6 +506,7 @@ run (void *cls,
{
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NO_RESTART;
return;
}
if (NULL ==
@@ -401,6 +515,7 @@ run (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to initialize DB subsystem\n");
GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NOTCONFIGURED;
return;
}
if (GNUNET_OK !=
@@ -409,8 +524,21 @@ run (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to connect to database\n");
GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NO_RESTART;
return;
- }
+ }
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING)
+ };
+
+ eh = db_plugin->event_listen (db_plugin->cls,
+ &es,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &db_notify,
+ NULL);
+ }
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&select_work,
NULL);
@@ -432,6 +560,8 @@ main (int argc,
"test",
"run in test mode and exit when idle",
&test_mode),
+ GNUNET_GETOPT_option_timetravel ('T',
+ "timetravel"),
GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
GNUNET_GETOPT_OPTION_END
};
@@ -458,4 +588,4 @@ main (int argc,
}
-/* end of taler-exchange-transfer.c */
+/* end of taler-merchant-webhook.c */
diff --git a/src/backend/taler-merchant-wirewatch.c b/src/backend/taler-merchant-wirewatch.c
new file mode 100644
index 00000000..17eb7a0a
--- /dev/null
+++ b/src/backend/taler-merchant-wirewatch.c
@@ -0,0 +1,756 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-wirewatch.c
+ * @brief Process that imports information about incoming bank transfers into the merchant backend
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <pthread.h>
+#include <taler/taler_dbevents.h>
+#include "taler_merchant_bank_lib.h"
+#include "taler_merchantdb_lib.h"
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Timeout for the bank interaction. Rather long as we should do long-polling
+ * and do not want to wake up too often.
+ */
+#define BANK_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, \
+ 5)
+
+
+/**
+ * Information about a watch job.
+ */
+struct Watch
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct Watch *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct Watch *prev;
+
+ /**
+ * Next task to run, if any.
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * Dynamically adjusted long polling time-out.
+ */
+ struct GNUNET_TIME_Relative bank_timeout;
+
+ /**
+ * For which instance are we importing bank transfers?
+ */
+ char *instance_id;
+
+ /**
+ * For which account are we importing bank transfers?
+ */
+ char *payto_uri;
+
+ /**
+ * Bank history request.
+ */
+ struct TALER_MERCHANT_BANK_CreditHistoryHandle *hh;
+
+ /**
+ * Start row for the bank interaction. Exclusive.
+ */
+ uint64_t start_row;
+
+ /**
+ * Artificial delay to use between API calls. Used to
+ * throttle on failures.
+ */
+ struct GNUNET_TIME_Relative delay;
+
+ /**
+ * When did we start our last HTTP request?
+ */
+ struct GNUNET_TIME_Absolute start_time;
+
+ /**
+ * How long should long-polling take at least?
+ */
+ struct GNUNET_TIME_Absolute long_poll_timeout;
+
+ /**
+ * Login data for the bank.
+ */
+ struct TALER_MERCHANT_BANK_AuthenticationData ad;
+
+ /**
+ * Set to true if we found a transaction in the last iteration.
+ */
+ bool found;
+
+};
+
+
+/**
+ * Head of active watches.
+ */
+static struct Watch *w_head;
+
+/**
+ * Tail of active watches.
+ */
+static struct Watch *w_tail;
+
+/**
+ * The merchant's configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Our database plugin.
+ */
+static struct TALER_MERCHANTDB_Plugin *db_plugin;
+
+/**
+ * Handle to the context for interacting with the bank.
+ */
+static struct GNUNET_CURL_Context *ctx;
+
+/**
+ * Scheduler context for running the @e ctx.
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+/**
+ * Event handler to learn that the configuration changed
+ * and we should shutdown (to be restarted).
+ */
+static struct GNUNET_DB_EventHandler *eh;
+
+/**
+ * Value to return from main(). 0 on success, non-zero on errors.
+ */
+static int global_ret;
+
+/**
+ * How many transactions should we fetch at most per batch?
+ */
+static unsigned int batch_size = 32;
+
+/**
+ * #GNUNET_YES if we are in test mode and should exit when idle.
+ */
+static int test_mode;
+
+/**
+ * #GNUNET_YES if we are in persistent mode and do
+ * not exit on #config_changed.
+ */
+static int persist_mode;
+
+/**
+ * Set to true if we are shutting down due to a
+ * configuration change.
+ */
+static bool config_changed_flag;
+
+/**
+ * Save progress in DB.
+ */
+static void
+save (struct Watch *w)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db_plugin->update_wirewatch_progress (db_plugin->cls,
+ w->instance_id,
+ w->payto_uri,
+ w->start_row);
+ if (qs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to persist wirewatch progress for %s/%s (%d)\n",
+ w->instance_id,
+ w->payto_uri,
+ qs);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ }
+}
+
+
+/**
+ * Free resources of @a w.
+ *
+ * @param w watch job to terminate
+ */
+static void
+end_watch (struct Watch *w)
+{
+ if (NULL != w->task)
+ {
+ GNUNET_SCHEDULER_cancel (w->task);
+ w->task = NULL;
+ }
+ if (NULL != w->hh)
+ {
+ TALER_MERCHANT_BANK_credit_history_cancel (w->hh);
+ w->hh = NULL;
+ }
+ GNUNET_free (w->instance_id);
+ GNUNET_free (w->payto_uri);
+ TALER_MERCHANT_BANK_auth_free (&w->ad);
+ GNUNET_CONTAINER_DLL_remove (w_head,
+ w_tail,
+ w);
+ GNUNET_free (w);
+}
+
+
+/**
+ * We're being aborted with CTRL-C (or SIGTERM). Shut down.
+ *
+ * @param cls closure
+ */
+static void
+shutdown_task (void *cls)
+{
+ (void) cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running shutdown\n");
+ while (NULL != w_head)
+ {
+ struct Watch *w = w_head;
+
+ save (w);
+ end_watch (w);
+ }
+ if (NULL != eh)
+ {
+ db_plugin->event_listen_cancel (eh);
+ eh = NULL;
+ }
+ TALER_MERCHANTDB_plugin_unload (db_plugin);
+ db_plugin = NULL;
+ cfg = NULL;
+ if (NULL != ctx)
+ {
+ GNUNET_CURL_fini (ctx);
+ ctx = NULL;
+ }
+ if (NULL != rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (rc);
+ rc = NULL;
+ }
+}
+
+
+/**
+ * Parse @a subject from wire transfer into @a wtid and @a exchange_url.
+ *
+ * @param subject wire transfer subject to parse;
+ * format is "$WTID $URL"
+ * @param[out] wtid wire transfer ID to extract
+ * @param[out] exchange_url set to exchange URL
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_subject (const char *subject,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ char **exchange_url)
+{
+ const char *space;
+
+ space = strchr (subject, ' ');
+ if (NULL == space)
+ return GNUNET_NO;
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (subject,
+ space - subject,
+ wtid,
+ sizeof (*wtid)))
+ return GNUNET_NO;
+ space++;
+ if (! TALER_url_valid_charset (space))
+ return GNUNET_NO;
+ if ( (0 != strncasecmp ("http://",
+ space,
+ strlen ("http://"))) &&
+ (0 != strncasecmp ("https://",
+ space,
+ strlen ("https://"))) )
+ return GNUNET_NO;
+ *exchange_url = GNUNET_strdup (space);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Run next iteration.
+ *
+ * @param cls a `struct Watch *`
+ */
+static void
+do_work (void *cls);
+
+
+/**
+ * Callbacks of this type are used to serve the result of asking
+ * the bank for the credit transaction history.
+ *
+ * @param cls a `struct Watch *`
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
+ * 0 if the bank's reply is bogus (fails to follow the protocol),
+ * #MHD_HTTP_NO_CONTENT if there are no more results; on success the
+ * last callback is always of this status (even if `abs(num_results)` were
+ * already returned).
+ * @param ec detailed error code
+ * @param serial_id monotonically increasing counter corresponding to the transaction
+ * @param details details about the wire transfer
+ * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
+ */
+static enum GNUNET_GenericReturnValue
+credit_cb (
+ void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ uint64_t serial_id,
+ const struct TALER_MERCHANT_BANK_CreditDetails *details)
+{
+ struct Watch *w = cls;
+
+ switch (http_status)
+ {
+ case 0:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid HTTP response (HTTP status: 0, %d) from bank\n",
+ ec);
+ w->delay = GNUNET_TIME_STD_BACKOFF (w->delay);
+ break;
+ case MHD_HTTP_OK:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ char *exchange_url;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ char *credit_payto;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received wire transfer `%s' over %s\n",
+ details->wire_subject,
+ TALER_amount2s (&details->amount));
+ w->found = true;
+ if (GNUNET_OK !=
+ parse_subject (details->wire_subject,
+ &wtid,
+ &exchange_url))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Skipping transfer %llu (%s): not from exchange\n",
+ (unsigned long long) serial_id,
+ details->wire_subject);
+ w->start_row = serial_id;
+ return GNUNET_OK;
+ }
+ /* FIXME-Performance-Optimization: consider grouping multiple inserts
+ into one bigger transaction with just one notify. */
+ credit_payto = TALER_payto_normalize (details->credit_account_uri);
+ qs = db_plugin->insert_transfer (db_plugin->cls,
+ w->instance_id,
+ exchange_url,
+ &wtid,
+ &details->amount,
+ credit_payto,
+ true /* confirmed */);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ struct TALER_Amount total;
+ struct TALER_Amount wfee;
+ struct TALER_Amount eamount;
+ struct GNUNET_TIME_Timestamp timestamp;
+ bool have_esig;
+ bool verified;
+
+ qs = db_plugin->lookup_transfer (db_plugin->cls,
+ w->instance_id,
+ exchange_url,
+ &wtid,
+ &total,
+ &wfee,
+ &eamount,
+ &timestamp,
+ &have_esig,
+ &verified);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Inserting transfer for %s into database failed. Is the credit account %s configured correctly?\n",
+ w->instance_id,
+ credit_payto);
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ if (0 !=
+ TALER_amount_cmp (&total,
+ &details->amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Inserting transfer for %s into database failed. An entry exists for a different transfer amount (%s)!\n",
+ w->instance_id,
+ TALER_amount2s (&total));
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Inserting transfer for %s into database failed. An equivalent entry already exists.\n",
+ w->instance_id);
+ }
+ }
+ }
+ GNUNET_free (credit_payto);
+ GNUNET_free (exchange_url);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ w->hh = NULL;
+ return GNUNET_SYSERR;
+ }
+ /* Success => reset back-off timer! */
+ w->delay = GNUNET_TIME_UNIT_ZERO;
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_MERCHANT_WIRE_TRANSFER_CONFIRMED)
+ };
+
+ db_plugin->event_notify (db_plugin->cls,
+ &es,
+ NULL,
+ 0);
+ }
+ }
+ w->start_row = serial_id;
+ return GNUNET_OK;
+ case MHD_HTTP_NO_CONTENT:
+ save (w);
+ /* Delay artificially if server returned before long-poll timeout */
+ if (! w->found)
+ w->delay = GNUNET_TIME_absolute_get_remaining (w->long_poll_timeout);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* configuration likely wrong, wait at least 1 minute, backoff up to 15 minutes! */
+ w->delay = GNUNET_TIME_relative_max (GNUNET_TIME_UNIT_MINUTES,
+ GNUNET_TIME_STD_BACKOFF (w->delay));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Bank claims account is unknown, waiting for %s before trying again\n",
+ GNUNET_TIME_relative2s (w->delay,
+ true));
+ break;
+ case MHD_HTTP_GATEWAY_TIMEOUT:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Gateway timeout, adjusting long polling threshold\n");
+ /* Limit new timeout at request delay */
+ w->bank_timeout
+ = GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_duration (
+ w->start_time),
+ w->bank_timeout);
+ /* set the timeout a bit earlier */
+ w->bank_timeout
+ = GNUNET_TIME_relative_subtract (w->bank_timeout,
+ GNUNET_TIME_UNIT_SECONDS);
+ /* do not allow it to go to zero */
+ w->bank_timeout
+ = GNUNET_TIME_relative_max (w->bank_timeout,
+ GNUNET_TIME_UNIT_SECONDS);
+ w->delay = GNUNET_TIME_STD_BACKOFF (w->delay);
+ break;
+ default:
+ /* Something went wrong, try again, but with back-off */
+ w->delay = GNUNET_TIME_STD_BACKOFF (w->delay);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unexpected HTTP status code %u(%d) from bank\n",
+ http_status,
+ ec);
+ break;
+ }
+ w->hh = NULL;
+ if (test_mode && (! w->found))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No transactions found and in test mode. Ending watch!\n");
+ end_watch (w);
+ if (NULL == w_head)
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_OK;
+ }
+ w->task = GNUNET_SCHEDULER_add_delayed (w->delay,
+ &do_work,
+ w);
+ return GNUNET_OK;
+}
+
+
+static void
+do_work (void *cls)
+{
+ struct Watch *w = cls;
+
+ w->task = NULL;
+ w->found = false;
+ w->long_poll_timeout
+ = GNUNET_TIME_relative_to_absolute (w->bank_timeout);
+ w->start_time
+ = GNUNET_TIME_absolute_get ();
+ w->hh = TALER_MERCHANT_BANK_credit_history (ctx,
+ &w->ad,
+ w->start_row,
+ batch_size,
+ test_mode
+ ? GNUNET_TIME_UNIT_ZERO
+ : w->bank_timeout,
+ &credit_cb,
+ w);
+ if (NULL == w->hh)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+}
+
+
+/**
+ * Function called with information about a accounts
+ * the wirewatcher should monitor.
+ *
+ * @param cls closure (NULL)
+ * @param instance instance that owns the account
+ * @param payto_uri account URI
+ * @param credit_facade_url URL for the credit facade
+ * @param credit_facade_credentials account access credentials
+ * @param last_serial last transaction serial (inclusive) we have seen from this account
+ */
+static void
+start_watch (
+ void *cls,
+ const char *instance,
+ const char *payto_uri,
+ const char *credit_facade_url,
+ const json_t *credit_facade_credentials,
+ uint64_t last_serial)
+{
+ struct Watch *w = GNUNET_new (struct Watch);
+
+ (void) cls;
+ w->bank_timeout = BANK_TIMEOUT;
+ if (GNUNET_OK !=
+ TALER_MERCHANT_BANK_auth_parse_json (credit_facade_credentials,
+ credit_facade_url,
+ &w->ad))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse authentication data of `%s/%s'\n",
+ instance,
+ payto_uri);
+ GNUNET_free (w);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+
+ GNUNET_CONTAINER_DLL_insert (w_head,
+ w_tail,
+ w);
+ w->instance_id = GNUNET_strdup (instance);
+ w->payto_uri = TALER_payto_normalize (payto_uri);
+ w->start_row = last_serial;
+ w->task = GNUNET_SCHEDULER_add_now (&do_work,
+ w);
+}
+
+
+/**
+ * Function called on configuration change events received from Postgres. We
+ * shutdown (and systemd should restart us).
+ *
+ * @param cls closure (NULL)
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+config_changed (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ (void) cls;
+ (void) extra;
+ (void) extra_size;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Configuration changed, %s\n",
+ 0 == persist_mode
+ ? "restarting"
+ : "reinitializing");
+ config_changed_flag = true;
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/**
+ * First task.
+ *
+ * @param cls closure, NULL
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param c configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *c)
+{
+ (void) args;
+ (void) cfgfile;
+
+ cfg = c;
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ NULL);
+ ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &rc);
+ rc = GNUNET_CURL_gnunet_rc_create (ctx);
+ if (NULL == ctx)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NO_RESTART;
+ return;
+ }
+ if (NULL ==
+ (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_OK !=
+ db_plugin->connect (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to connect to database\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NO_RESTART;
+ return;
+ }
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_MERCHANT_ACCOUNTS_CHANGED)
+ };
+
+ eh = db_plugin->event_listen (db_plugin->cls,
+ &es,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &config_changed,
+ NULL);
+ }
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db_plugin->select_wirewatch_accounts (db_plugin->cls,
+ &start_watch,
+ NULL);
+ if (qs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to obtain wirewatch accounts from database\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NO_RESTART;
+ return;
+ }
+ if ( (NULL == w_head) &&
+ (GNUNET_YES == test_mode) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No active wirewatch accounts in database and in test mode. Exiting.\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_SUCCESS;
+ return;
+ }
+ }
+}
+
+
+/**
+ * The main function of taler-merchant-wirewatch
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_flag ('p',
+ "persist",
+ "run in persist mode and do not exit on configuration changes",
+ &persist_mode),
+ GNUNET_GETOPT_option_timetravel ('T',
+ "timetravel"),
+ GNUNET_GETOPT_option_flag ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
+ GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+ TALER_OS_init ();
+ do {
+ config_changed_flag = false;
+ ret = GNUNET_PROGRAM_run (
+ argc, argv,
+ "taler-merchant-wirewatch",
+ gettext_noop (
+ "background process that watches for incoming wire transfers to the merchant bank account"),
+ options,
+ &run, NULL);
+ } while ( (1 == persist_mode) &&
+ config_changed_flag);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
+}
+
+
+/* end of taler-exchange-wirewatch.c */
diff --git a/src/backend/test.conf b/src/backend/test.conf
index 502d807a..5f6528e6 100644
--- a/src/backend/test.conf
+++ b/src/backend/test.conf
@@ -119,7 +119,7 @@ ENABLE_DEBIT = YES
ENABLE_CREDIT = YES
[exchange-accountcredentials-exchange]
-WIRE_GATEWAY_URL = "http://localhost:8082/2/"
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
WIRE_GATEWAY_AUTH_METHOD = NONE
diff --git a/src/backenddb/.gitignore b/src/backenddb/.gitignore
new file mode 100644
index 00000000..e3c1e14d
--- /dev/null
+++ b/src/backenddb/.gitignore
@@ -0,0 +1 @@
+procedures.sql
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am
index a8fefb40..e7fb15cf 100644
--- a/src/backenddb/Makefile.am
+++ b/src/backenddb/Makefile.am
@@ -10,14 +10,29 @@ pkgcfg_DATA = \
sqldir = $(prefix)/share/taler/sql/merchant/
+sqlinputs = \
+ pg_*.sql \
+ procedures.sql.in
+
sql_DATA = \
versioning.sql \
+ procedures.sql \
merchant-0001.sql \
merchant-0002.sql \
merchant-0003.sql \
merchant-0004.sql \
+ merchant-0005.sql \
drop.sql
+BUILT_SOURCES = \
+ procedures.sql
+
+procedures.sql: procedures.sql.in pg_*.sql
+ chmod +w $@ || true
+ gcc -E -P -undef - < procedures.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@
+ chmod ugo-w $@
+
+
if HAVE_POSTGRESQL
if HAVE_GNUNETPQ
plugin_LTLIBRARIES = \
@@ -47,15 +62,124 @@ libtalermerchantdb_la_LIBADD = \
libtalermerchantdb_la_LDFLAGS = \
$(POSTGRESQL_LDFLAGS) \
- -version-info 2:0:0 \
+ -version-info 3:0:1 \
-no-undefined
libtaler_plugin_merchantdb_postgres_la_SOURCES = \
- plugin_merchantdb_postgres.c
+ pg_update_wirewatch_progress.h pg_update_wirewatch_progress.c \
+ pg_select_wirewatch_accounts.h pg_select_wirewatch_accounts.c \
+ pg_insert_account.h pg_insert_account.c \
+ pg_update_account.h pg_update_account.c \
+ pg_insert_deposit_to_transfer.h pg_insert_deposit_to_transfer.c \
+ pg_increase_refund.h pg_increase_refund.c \
+ pg_insert_transfer.h pg_insert_transfer.c \
+ pg_insert_transfer_details.h pg_insert_transfer_details.c \
+ pg_store_wire_fee_by_exchange.h pg_store_wire_fee_by_exchange.c \
+ pg_select_open_transfers.h pg_select_open_transfers.c \
+ pg_lookup_instances.h pg_lookup_instances.c \
+ pg_lookup_transfers.h pg_lookup_transfers.c \
+ pg_update_transfer_status.h pg_update_transfer_status.c \
+ pg_delete_exchange_accounts.h pg_delete_exchange_accounts.c \
+ pg_select_accounts_by_exchange.h pg_select_accounts_by_exchange.c \
+ pg_set_transfer_status_to_confirmed.h pg_set_transfer_status_to_confirmed.c \
+ pg_insert_exchange_account.h pg_insert_exchange_account.c \
+ pg_insert_login_token.h pg_insert_login_token.c \
+ pg_delete_login_token.h pg_delete_login_token.c \
+ pg_select_login_token.h pg_select_login_token.c \
+ pg_select_account_by_uri.h pg_select_account_by_uri.c \
+ pg_lookup_instance_auth.h pg_lookup_instance_auth.c \
+ pg_lookup_pending_deposits.h pg_lookup_pending_deposits.c \
+ pg_insert_instance.h pg_insert_instance.c \
+ pg_account_kyc_set_status.h pg_account_kyc_set_status.c \
+ pg_account_kyc_get_status.h pg_account_kyc_get_status.c \
+ pg_delete_instance_private_key.h pg_delete_instance_private_key.c \
+ pg_purge_instance.h pg_purge_instance.c \
+ pg_update_instance.h pg_update_instance.c \
+ pg_update_deposit_confirmation_status.h pg_update_deposit_confirmation_status.c \
+ pg_update_instance_auth.h pg_update_instance_auth.c \
+ pg_inactivate_account.h pg_inactivate_account.c \
+ pg_activate_account.h pg_activate_account.c \
+ pg_insert_otp.h pg_insert_otp.c \
+ pg_delete_otp.h pg_delete_otp.c \
+ pg_update_otp.h pg_update_otp.c \
+ pg_select_otp.h pg_select_otp.c \
+ pg_select_otp_serial.h pg_select_otp_serial.c \
+ pg_lookup_otp_devices.h pg_lookup_otp_devices.c \
+ pg_select_account.h pg_select_account.c \
+ pg_select_accounts.h pg_select_accounts.c \
+ pg_delete_template.h pg_delete_template.c \
+ pg_insert_template.h pg_insert_template.c \
+ pg_update_template.h pg_update_template.c \
+ pg_lookup_templates.h pg_lookup_templates.c \
+ pg_lookup_template.h pg_lookup_template.c \
+ pg_lookup_products.h pg_lookup_products.c \
+ pg_lookup_product.h pg_lookup_product.c \
+ pg_delete_product.h pg_delete_product.c \
+ pg_insert_product.h pg_insert_product.c \
+ pg_update_product.h pg_update_product.c \
+ pg_lock_product.h pg_lock_product.c \
+ pg_insert_exchange_keys.h pg_insert_exchange_keys.c \
+ pg_select_exchange_keys.h pg_select_exchange_keys.c \
+ pg_expire_locks.h pg_expire_locks.c \
+ pg_delete_order.h pg_delete_order.c \
+ pg_lookup_order.h pg_lookup_order.c \
+ pg_lookup_order_summary.h pg_lookup_order_summary.c \
+ pg_lookup_orders.h pg_lookup_orders.c \
+ pg_insert_order.h pg_insert_order.c \
+ pg_unlock_inventory.h pg_unlock_inventory.c \
+ pg_insert_order_lock.h pg_insert_order_lock.c \
+ pg_lookup_contract_terms3.h pg_lookup_contract_terms3.c \
+ pg_lookup_contract_terms2.h pg_lookup_contract_terms2.c \
+ pg_lookup_contract_terms.h pg_lookup_contract_terms.c \
+ pg_insert_contract_terms.h pg_insert_contract_terms.c \
+ pg_update_contract_terms.h pg_update_contract_terms.c \
+ pg_delete_contract_terms.h pg_delete_contract_terms.c \
+ pg_lookup_deposits.h pg_lookup_deposits.c \
+ pg_insert_exchange_signkey.h pg_insert_exchange_signkey.c \
+ pg_insert_deposit.h pg_insert_deposit.c \
+ pg_insert_deposit_confirmation.h pg_insert_deposit_confirmation.c \
+ pg_lookup_refunds.h pg_lookup_refunds.c \
+ pg_mark_contract_paid.h pg_mark_contract_paid.c \
+ pg_refund_coin.h pg_refund_coin.c \
+ pg_lookup_order_status.h pg_lookup_order_status.c \
+ pg_lookup_order_status_by_serial.h pg_lookup_order_status_by_serial.c \
+ pg_lookup_deposits_by_order.h pg_lookup_deposits_by_order.c \
+ pg_lookup_transfer_details_by_order.h pg_lookup_transfer_details_by_order.c \
+ pg_mark_order_wired.h pg_mark_order_wired.c \
+ pg_lookup_refunds_detailed.h pg_lookup_refunds_detailed.c \
+ pg_insert_refund_proof.h pg_insert_refund_proof.c \
+ pg_lookup_refund_proof.h pg_lookup_refund_proof.c \
+ pg_lookup_order_by_fulfillment.h pg_lookup_order_by_fulfillment.c \
+ pg_delete_transfer.h pg_delete_transfer.c \
+ pg_check_transfer_exists.h pg_check_transfer_exists.c \
+ pg_lookup_account.h pg_lookup_account.c \
+ pg_lookup_wire_fee.h pg_lookup_wire_fee.c \
+ pg_lookup_deposits_by_contract_and_coin.h pg_lookup_deposits_by_contract_and_coin.c \
+ pg_lookup_transfer.h pg_lookup_transfer.c \
+ pg_lookup_transfer_summary.h pg_lookup_transfer_summary.c \
+ pg_lookup_transfer_details.h pg_lookup_transfer_details.c \
+ pg_lookup_webhooks.h pg_lookup_webhooks.c \
+ pg_lookup_webhook.h pg_lookup_webhook.c \
+ pg_delete_webhook.h pg_delete_webhook.c \
+ pg_insert_webhook.h pg_insert_webhook.c \
+ pg_update_webhook.h pg_update_webhook.c \
+ pg_lookup_webhook_by_event.h pg_lookup_webhook_by_event.c \
+ pg_delete_pending_webhook.h pg_delete_pending_webhook.c \
+ pg_insert_pending_webhook.h pg_insert_pending_webhook.c \
+ pg_update_pending_webhook.h pg_update_pending_webhook.c \
+ pg_lookup_pending_webhooks.h pg_lookup_pending_webhooks.c \
+ pg_insert_token_family.h pg_insert_token_family.c \
+ pg_lookup_token_family.h pg_lookup_token_family.c \
+ pg_lookup_token_families.h pg_lookup_token_families.c \
+ pg_delete_token_family.h pg_delete_token_family.c \
+ pg_update_token_family.h pg_update_token_family.c \
+ plugin_merchantdb_postgres.c \
+ pg_helper.h pg_helper.c
libtaler_plugin_merchantdb_postgres_la_LIBADD = \
$(LTLIBINTL)
libtaler_plugin_merchantdb_postgres_la_LDFLAGS = \
$(TALER_PLUGIN_LDFLAGS) \
+ -ltalerexchange \
-ltalerpq \
-ltalerutil \
-ltalerjson \
@@ -93,4 +217,5 @@ TESTS = \
EXTRA_DIST = \
test-merchantdb-postgres.conf \
merchantdb-postgres.conf \
+ $(sqlinputs) \
$(sql_DATA)
diff --git a/src/backenddb/drop.sql b/src/backenddb/drop.sql
index fbfd9e6a..9005b6ed 100644
--- a/src/backenddb/drop.sql
+++ b/src/backenddb/drop.sql
@@ -17,17 +17,15 @@
-- Everything in one big transaction
BEGIN;
--- This script DROPs all of the tables we create, including the
--- versioning schema!
---
--- Unlike the other SQL files, it SHOULD be updated to reflect the
--- latest requirements for dropping tables.
+-- This script DROPs all of the tables we create.
--- Unregister patch (0003.sql)
-SELECT _v.unregister_patch('merchant-0004');
-SELECT _v.unregister_patch('merchant-0003');
-SELECT _v.unregister_patch('merchant-0002');
-SELECT _v.unregister_patch('merchant-0001');
+WITH xpatches AS (
+ SELECT patch_name
+ FROM _v.patches
+ WHERE starts_with(patch_name,'merchant-')
+)
+ SELECT _v.unregister_patch(xpatches.patch_name)
+ FROM xpatches;
DROP SCHEMA merchant CASCADE;
diff --git a/src/backenddb/merchant-0001.sql b/src/backenddb/merchant-0001.sql
index cd4ae9f3..2adb9996 100644
--- a/src/backenddb/merchant-0001.sql
+++ b/src/backenddb/merchant-0001.sql
@@ -1,6 +1,6 @@
--
-- This file is part of TALER
--- Copyright (C) 2014--2022 Taler Systems SA
+-- Copyright (C) 2014--2023 Taler Systems SA
--
-- TALER is free software; you can redistribute it and/or modify it under the
-- terms of the GNU General Public License as published by the Free Software
@@ -14,6 +14,11 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
+-- @file merchant-0001.sql
+-- @brief database schema for the merchant
+-- @author Christian Grothoff
+-- @author Priscilla Huang
+
-- Everything in one big transaction
BEGIN;
@@ -25,6 +30,15 @@ COMMENT ON SCHEMA merchant IS 'taler-merchant data';
SET search_path TO merchant;
+CREATE TYPE taler_amount_currency
+ AS
+ (val INT8
+ ,frac INT4
+ ,curr VARCHAR(12)
+ );
+COMMENT ON TYPE taler_amount_currency
+ IS 'Stores an amount, fraction is in units of 1/100000000 of the base value';
+
---------------- Exchange information ---------------------------
CREATE TABLE IF NOT EXISTS merchant_exchange_wire_fees
@@ -33,12 +47,8 @@ CREATE TABLE IF NOT EXISTS merchant_exchange_wire_fees
,h_wire_method BYTEA NOT NULL CHECK (LENGTH(h_wire_method)=64)
,start_date INT8 NOT NULL
,end_date INT8 NOT NULL
- ,wire_fee_val INT8 NOT NULL
- ,wire_fee_frac INT4 NOT NULL
- ,closing_fee_val INT8 NOT NULL
- ,closing_fee_frac INT4 NOT NULL
- ,wad_fee_val INT8 NOT NULL
- ,wad_fee_frac INT4 NOT NULL
+ ,wire_fee taler_amount_currency NOT NULL
+ ,closing_fee taler_amount_currency NOT NULL
,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
,UNIQUE (master_pub,h_wire_method,start_date)
);
@@ -70,17 +80,17 @@ CREATE TABLE IF NOT EXISTS merchant_instances
,merchant_pub BYTEA NOT NULL UNIQUE CHECK (LENGTH(merchant_pub)=32)
,auth_hash BYTEA CHECK(LENGTH(auth_hash)=64)
,auth_salt BYTEA CHECK(LENGTH(auth_salt)=32)
- ,merchant_id VARCHAR NOT NULL UNIQUE
- ,merchant_name VARCHAR NOT NULL
+ ,merchant_id TEXT NOT NULL UNIQUE
+ ,merchant_name TEXT NOT NULL
+ ,website TEXT
+ ,email TEXT
+ ,logo BYTEA
,address BYTEA NOT NULL
,jurisdiction BYTEA NOT NULL
- ,default_max_deposit_fee_val INT8 NOT NULL
- ,default_max_deposit_fee_frac INT4 NOT NULL
- ,default_max_wire_fee_val INT8 NOT NULL
- ,default_max_wire_fee_frac INT4 NOT NULL
- ,default_wire_fee_amortization INT4 NOT NULL
+ ,use_stefan BOOLEAN NOT NULL DEFAULT TRUE
,default_wire_transfer_delay INT8 NOT NULL
,default_pay_delay INT8 NOT NULL
+ ,user_type INT4
);
COMMENT ON TABLE merchant_instances
IS 'all the instances supported by this backend';
@@ -89,15 +99,50 @@ COMMENT ON COLUMN merchant_instances.merchant_id
COMMENT ON COLUMN merchant_instances.merchant_name
IS 'legal name of the merchant as a simple string (required)';
COMMENT ON COLUMN merchant_instances.address
- IS 'physical address of the merchant as a Location in JSON format (required)';
+ IS 'physical address of the merchant as a location in JSON format (required)';
COMMENT ON COLUMN merchant_instances.jurisdiction
- IS 'jurisdiction of the merchant as a Location in JSON format (required)';
+ IS 'jurisdiction of the merchant as a location in JSON format (required)';
+COMMENT ON COLUMN merchant_instances.website
+ IS 'merchant site URL';
+COMMENT ON COLUMN merchant_instances.use_stefan
+ IS 'use STEFAN curve of exchange to determine acceptable fees (unless given explicitly)';
+COMMENT ON COLUMN merchant_instances.email
+ IS 'email';
+COMMENT ON COLUMN merchant_instances.logo
+ IS 'data image url';
COMMENT ON COLUMN merchant_instances.auth_hash
- IS 'hash used for merchant back office Authorization, NULL for no check';
+ IS 'hash used for merchant back office authorization, NULL for no check';
COMMENT ON COLUMN merchant_instances.auth_salt
IS 'salt to use when hashing Authorization header before comparing with auth_hash';
+COMMENT ON COLUMN merchant_instances.user_type
+ IS 'what type of user is this (individual or business)';
+CREATE TABLE IF NOT EXISTS merchant_login_tokens
+ (token BYTEA NOT NULL UNIQUE CHECK (LENGTH(token)=32),
+ creation_time INT8 NOT NULL,
+ expiration_time INT8 NOT NULL,
+ validity_scope INT4 NOT NULL,
+ merchant_serial BIGINT
+ REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
+ );
+COMMENT ON TABLE merchant_login_tokens
+ IS 'login tokens that have been created for the given instance';
+COMMENT ON COLUMN merchant_login_tokens.token
+ IS 'binary value of the login token';
+COMMENT ON COLUMN merchant_login_tokens.creation_time
+ IS 'time when the token was created; currently not used, potentially useful in the future for a forced logout of all tokens issued before a certain date';
+COMMENT ON COLUMN merchant_login_tokens.expiration_time
+ IS 'determines when the token expires';
+COMMENT ON COLUMN merchant_login_tokens.validity_scope
+ IS 'identifies the operations for which the token is valid';
+COMMENT ON COLUMN merchant_login_tokens.merchant_serial
+ IS 'identifies the instance for which the token is valid';
+
+CREATE INDEX IF NOT EXISTS merchant_login_tokens_by_expiration
+ ON merchant_login_tokens
+ (expiration_time);
+
CREATE TABLE IF NOT EXISTS merchant_keys
(merchant_priv BYTEA NOT NULL UNIQUE CHECK (LENGTH(merchant_priv)=32),
@@ -113,7 +158,10 @@ CREATE TABLE IF NOT EXISTS merchant_accounts
REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
,h_wire BYTEA NOT NULL CHECK (LENGTH(h_wire)=64)
,salt BYTEA NOT NULL CHECK (LENGTH(salt)=16)
- ,payto_uri VARCHAR NOT NULL
+ ,credit_facade_url TEXT
+ ,credit_facade_credentials TEXT
+ ,last_bank_serial INT8 NOT NULL DEFAULT (0)
+ ,payto_uri TEXT NOT NULL
,active BOOLEAN NOT NULL
,UNIQUE (merchant_serial,payto_uri)
,UNIQUE (h_wire)
@@ -128,6 +176,12 @@ COMMENT ON COLUMN merchant_accounts.payto_uri
IS 'payto URI of a merchant bank account';
COMMENT ON COLUMN merchant_accounts.active
IS 'true if we actively use this bank account, false if it is just kept around for older contracts to refer to';
+COMMENT ON COLUMN merchant_accounts.credit_facade_url
+ IS 'Base URL of a facade where the merchant can inquire about incoming bank transactions into this account';
+COMMENT ON COLUMN merchant_accounts.credit_facade_credentials
+ IS 'JSON with credentials needed to access the credit facade';
+COMMENT ON COLUMN merchant_accounts.last_bank_serial
+ IS 'Serial number of the bank of the last transaction we successfully imported';
-------------------------- Inventory ---------------------------
@@ -136,14 +190,13 @@ CREATE TABLE IF NOT EXISTS merchant_inventory
(product_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
,merchant_serial BIGINT NOT NULL
REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
- ,product_id VARCHAR NOT NULL
- ,description VARCHAR NOT NULL
+ ,product_id TEXT NOT NULL
+ ,description TEXT NOT NULL
,description_i18n BYTEA NOT NULL
- ,unit VARCHAR NOT NULL
+ ,unit TEXT NOT NULL
,image BYTEA NOT NULL
,taxes BYTEA NOT NULL
- ,price_val INT8 NOT NULL
- ,price_frac INT4 NOT NULL
+ ,price taler_amount_currency NOT NULL
,total_stock BIGINT NOT NULL
,total_sold BIGINT NOT NULL DEFAULT 0
,total_lost BIGINT NOT NULL DEFAULT 0
@@ -164,7 +217,7 @@ COMMENT ON COLUMN merchant_inventory.image
IS 'NOT NULL, but can be 0 bytes; must contain an ImageDataUrl';
COMMENT ON COLUMN merchant_inventory.taxes
IS 'JSON array containing taxes the merchant pays, must be JSON, but can be just "[]"';
-COMMENT ON COLUMN merchant_inventory.price_val
+COMMENT ON COLUMN merchant_inventory.price
IS 'Current price of one unit of the product';
COMMENT ON COLUMN merchant_inventory.total_stock
IS 'A value of -1 is used for unlimited (electronic good), may never be lowered';
@@ -207,12 +260,14 @@ CREATE TABLE IF NOT EXISTS merchant_orders
(order_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
,merchant_serial BIGINT NOT NULL
REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
- ,order_id VARCHAR NOT NULL
+ ,order_id TEXT NOT NULL
,claim_token BYTEA NOT NULL CHECK (LENGTH(claim_token)=16)
,h_post_data BYTEA NOT NULL CHECK (LENGTH(h_post_data)=64)
,pay_deadline INT8 NOT NULL
,creation_time INT8 NOT NULL
,contract_terms BYTEA NOT NULL
+ ,pos_key TEXT DEFAULT NULL
+ ,pos_algorithm INT NOT NULL DEFAULT (0)
,UNIQUE (merchant_serial, order_id)
);
COMMENT ON TABLE merchant_orders
@@ -227,6 +282,12 @@ COMMENT ON COLUMN merchant_orders.merchant_serial
IS 'Identifies the instance offering the contract';
COMMENT ON COLUMN merchant_orders.pay_deadline
IS 'How long is the offer valid. After this time, the order can be garbage collected';
+COMMENT ON COLUMN merchant_orders.pos_key
+ IS 'encoded based key which is used for the verification of payment';
+COMMENT ON COLUMN merchant_orders.pos_algorithm
+ IS 'algorithm to used to generate the confirmation code. It is link with the pos_key';
+
+
CREATE INDEX IF NOT EXISTS merchant_orders_by_expiration
ON merchant_orders
(pay_deadline);
@@ -253,16 +314,19 @@ CREATE TABLE IF NOT EXISTS merchant_contract_terms
(order_serial BIGINT PRIMARY KEY
,merchant_serial BIGINT NOT NULL
REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
- ,order_id VARCHAR NOT NULL
+ ,order_id TEXT NOT NULL
,contract_terms BYTEA NOT NULL
+ ,wallet_data TEXT DEFAULT NULL
,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)
,creation_time INT8 NOT NULL
,pay_deadline INT8 NOT NULL
,refund_deadline INT8 NOT NULL
,paid BOOLEAN DEFAULT FALSE NOT NULL
,wired BOOLEAN DEFAULT FALSE NOT NULL
- ,fulfillment_url VARCHAR
- ,session_id VARCHAR DEFAULT '' NOT NULL
+ ,fulfillment_url TEXT
+ ,session_id TEXT DEFAULT '' NOT NULL
+ ,pos_key TEXT DEFAULT NULL
+ ,pos_algorithm INT NOT NULL DEFAULT (0)
,claim_token BYTEA NOT NULL CHECK (LENGTH(claim_token)=16)
,UNIQUE (merchant_serial, order_id)
,UNIQUE (merchant_serial, h_contract_terms)
@@ -275,8 +339,12 @@ COMMENT ON COLUMN merchant_contract_terms.merchant_serial
IS 'Identifies the instance offering the contract';
COMMENT ON COLUMN merchant_contract_terms.contract_terms
IS 'These contract terms include the wallet nonce';
+COMMENT ON COLUMN merchant_contract_terms.wallet_data
+ IS 'Data provided by the wallet when paying for the contract (subcontract selection, blinded tokens, etc.)';
COMMENT ON COLUMN merchant_contract_terms.h_contract_terms
IS 'Hash over contract_terms';
+COMMENT ON COLUMN merchant_contract_terms.pay_deadline
+ IS 'How long is the offer valid. After this time, the order can be garbage collected';
COMMENT ON COLUMN merchant_contract_terms.refund_deadline
IS 'By what times do refunds have to be approved (useful to reject refund requests)';
COMMENT ON COLUMN merchant_contract_terms.paid
@@ -289,6 +357,11 @@ COMMENT ON COLUMN merchant_contract_terms.session_id
IS 'last session_id from we confirmed the paying client to use, empty string for none';
COMMENT ON COLUMN merchant_contract_terms.pay_deadline
IS 'How long is the offer valid. After this time, the order can be garbage collected';
+COMMENT ON COLUMN merchant_contract_terms.pos_key
+ IS 'enconded based key which is used for the verification of payment';
+COMMENT ON COLUMN merchant_orders.pos_algorithm
+ IS 'algorithm to used to generate the confirmation code. It is link with the pos_key';
+
COMMENT ON COLUMN merchant_contract_terms.claim_token
IS 'Token optionally used to access the status of the order. All zeros (not NULL) if not used';
@@ -312,39 +385,61 @@ CREATE INDEX IF NOT EXISTS merchant_contract_terms_by_merchant_session_and_fulfi
---------------- Payment and refunds ---------------------------
-CREATE TABLE IF NOT EXISTS merchant_deposits
- (deposit_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+CREATE TABLE IF NOT EXISTS merchant_deposit_confirmations
+ (deposit_confirmation_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
,order_serial BIGINT
REFERENCES merchant_contract_terms (order_serial) ON DELETE CASCADE
,deposit_timestamp INT8 NOT NULL
- ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)
- ,exchange_url VARCHAR NOT NULL
- ,amount_with_fee_val INT8 NOT NULL
- ,amount_with_fee_frac INT4 NOT NULL
- ,deposit_fee_val INT8 NOT NULL
- ,deposit_fee_frac INT4 NOT NULL
- ,refund_fee_val INT8 NOT NULL
- ,refund_fee_frac INT4 NOT NULL
- ,wire_fee_val INT8 NOT NULL
- ,wire_fee_frac INT4 NOT NULL
+ ,exchange_url TEXT NOT NULL
+ ,total_without_fee taler_amount_currency NOT NULL
+ ,wire_fee taler_amount_currency NOT NULL
,signkey_serial BIGINT NOT NULL
REFERENCES merchant_exchange_signing_keys (signkey_serial) ON DELETE CASCADE
,exchange_sig BYTEA NOT NULL CHECK (LENGTH(exchange_sig)=64)
,account_serial BIGINT NOT NULL
REFERENCES merchant_accounts (account_serial) ON DELETE CASCADE
- ,UNIQUE (order_serial, coin_pub)
+ ,UNIQUE (order_serial, exchange_sig)
);
-COMMENT ON TABLE merchant_deposits
+COMMENT ON TABLE merchant_deposit_confirmations
IS 'Table with the deposit confirmations for each coin we deposited at the exchange';
-COMMENT ON COLUMN merchant_deposits.signkey_serial
+COMMENT ON COLUMN merchant_deposit_confirmations.signkey_serial
IS 'Online signing key of the exchange on the deposit confirmation';
-COMMENT ON COLUMN merchant_deposits.deposit_timestamp
+COMMENT ON COLUMN merchant_deposit_confirmations.deposit_timestamp
IS 'Time when the exchange generated the deposit confirmation';
-COMMENT ON COLUMN merchant_deposits.exchange_sig
+COMMENT ON COLUMN merchant_deposit_confirmations.exchange_sig
IS 'Signature of the exchange over the deposit confirmation';
-COMMENT ON COLUMN merchant_deposits.wire_fee_val
+COMMENT ON COLUMN merchant_deposit_confirmations.wire_fee
IS 'We MAY want to see if we should try to get this via merchant_exchange_wire_fees (not sure, may be too complicated with the date range, etc.)';
+
+CREATE TABLE IF NOT EXISTS merchant_deposits
+ (deposit_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+ ,coin_offset INT4 NOT NULL
+ ,deposit_confirmation_serial BIGINT NOT NULL
+ REFERENCES merchant_deposit_confirmations (deposit_confirmation_serial) ON DELETE CASCADE
+ ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)
+ ,coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64)
+ ,amount_with_fee taler_amount_currency NOT NULL
+ ,deposit_fee taler_amount_currency NOT NULL
+ ,refund_fee taler_amount_currency NOT NULL
+ ,UNIQUE (deposit_confirmation_serial, coin_pub)
+ );
+COMMENT ON TABLE merchant_deposits
+ IS 'Table with the deposit details for each coin we deposited at the exchange';
+COMMENT ON COLUMN merchant_deposits.coin_offset
+ IS 'Offset of this coin in the batch';
+COMMENT ON COLUMN merchant_deposits.deposit_confirmation_serial
+ IS 'Reference to the deposit confirmation of the exchange';
+COMMENT ON COLUMN merchant_deposits.coin_pub
+ IS 'Public key of the coin that was deposited';
+COMMENT ON COLUMN merchant_deposits.amount_with_fee
+ IS 'Total amount (incl. fee) of the coin that was deposited';
+COMMENT ON COLUMN merchant_deposits.deposit_fee
+ IS 'Deposit fee (for this coin) that was paid';
+COMMENT ON COLUMN merchant_deposits.refund_fee
+ IS 'How high would the refund fee be (for this coin)';
+
+
CREATE TABLE IF NOT EXISTS merchant_refunds
(refund_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
,order_serial BIGINT NOT NULL
@@ -352,9 +447,8 @@ CREATE TABLE IF NOT EXISTS merchant_refunds
,rtransaction_id BIGINT NOT NULL
,refund_timestamp INT8 NOT NULL
,coin_pub BYTEA NOT NULL
- ,reason VARCHAR NOT NULL
- ,refund_amount_val INT8 NOT NULL
- ,refund_amount_frac INT4 NOT NULL
+ ,reason TEXT NOT NULL
+ ,refund_amount taler_amount_currency NOT NULL
,UNIQUE (order_serial, coin_pub, rtransaction_id)
);
COMMENT ON TABLE merchant_deposits
@@ -380,13 +474,15 @@ COMMENT ON TABLE merchant_refund_proofs
-------------------- Wire transfers ---------------------------
CREATE TABLE IF NOT EXISTS merchant_transfers
- (credit_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
- ,exchange_url VARCHAR NOT NULL
+ (credit_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+ ,exchange_url TEXT NOT NULL
,wtid BYTEA CHECK (LENGTH(wtid)=32)
- ,credit_amount_val INT8 NOT NULL
- ,credit_amount_frac INT4 NOT NULL
- ,account_serial BIGINT NOT NULL
- REFERENCES merchant_accounts (account_serial) ON DELETE CASCADE
+ ,credit_amount taler_amount_currency NOT NULL
+ ,account_serial INT8 NOT NULL
+ REFERENCES merchant_accounts (account_serial) ON DELETE CASCADE
+ ,ready_time INT8 NOT NULL DEFAULT (0)
+ ,validation_status INT4 DEFAULT NULL
+ ,failed BOOLEAN NOT NULL DEFAULT FALSE
,verified BOOLEAN NOT NULL DEFAULT FALSE
,confirmed BOOLEAN NOT NULL DEFAULT FALSE
,UNIQUE (wtid, exchange_url, account_serial)
@@ -397,18 +493,28 @@ COMMENT ON COLUMN merchant_transfers.verified
IS 'true once we got an acceptable response from the exchange for this transfer';
COMMENT ON COLUMN merchant_transfers.confirmed
IS 'true once the merchant confirmed that this transfer was received';
-COMMENT ON COLUMN merchant_transfers.credit_amount_val
+COMMENT ON COLUMN merchant_transfers.credit_amount
IS 'actual value of the (aggregated) wire transfer, excluding the wire fee, according to the merchant';
+COMMENT ON COLUMN merchant_transfers.failed
+ IS 'set to true on permanent verification failures';
+COMMENT ON COLUMN merchant_transfers.validation_status
+ IS 'Taler error code describing the state of the validation';
+
+CREATE INDEX merchant_transfers_by_open
+ ON merchant_transfers
+ (ready_time ASC)
+ WHERE confirmed AND NOT (failed OR verified);
+COMMENT ON INDEX merchant_transfers_by_open
+ IS 'For select_open_transfers';
+
CREATE TABLE IF NOT EXISTS merchant_transfer_signatures
(credit_serial BIGINT PRIMARY KEY
REFERENCES merchant_transfers (credit_serial) ON DELETE CASCADE
,signkey_serial BIGINT NOT NULL
REFERENCES merchant_exchange_signing_keys (signkey_serial) ON DELETE CASCADE
- ,wire_fee_val INT8 NOT NULL
- ,wire_fee_frac INT4 NOT NULL
- ,credit_amount_val INT8 NOT NULL
- ,credit_amount_frac INT4 NOT NULL
+ ,wire_fee taler_amount_currency NOT NULL
+ ,credit_amount taler_amount_currency NOT NULL
,execution_time INT8 NOT NULL
,exchange_sig BYTEA NOT NULL CHECK (LENGTH(exchange_sig)=64)
);
@@ -416,7 +522,7 @@ COMMENT ON TABLE merchant_transfer_signatures
IS 'table represents the main information returned from the /transfer request to the exchange.';
COMMENT ON COLUMN merchant_transfer_signatures.execution_time
IS 'Execution time as claimed by the exchange, roughly matches time seen by merchant';
-COMMENT ON COLUMN merchant_transfer_signatures.credit_amount_val
+COMMENT ON COLUMN merchant_transfer_signatures.credit_amount
IS 'actual value of the (aggregated) wire transfer, excluding the wire fee, according to the exchange';
@@ -426,26 +532,23 @@ CREATE TABLE IF NOT EXISTS merchant_transfer_to_coin
,credit_serial BIGINT NOT NULL
REFERENCES merchant_transfers (credit_serial) ON DELETE CASCADE
,offset_in_exchange_list INT8 NOT NULL
- ,exchange_deposit_value_val INT8 NOT NULL
- ,exchange_deposit_value_frac INT4 NOT NULL
- ,exchange_deposit_fee_val INT8 NOT NULL
- ,exchange_deposit_fee_frac INT4 NOT NULL
+ ,exchange_deposit_value taler_amount_currency NOT NULL
+ ,exchange_deposit_fee taler_amount_currency NOT NULL
);
CREATE INDEX IF NOT EXISTS merchant_transfers_by_credit
ON merchant_transfer_to_coin
(credit_serial);
COMMENT ON TABLE merchant_transfer_to_coin
IS 'Mapping of (credit) transfers to (deposited) coins';
-COMMENT ON COLUMN merchant_transfer_to_coin.exchange_deposit_value_val
+COMMENT ON COLUMN merchant_transfer_to_coin.exchange_deposit_value
IS 'Deposit value as claimed by the exchange, should match our values in merchant_deposits minus refunds';
-COMMENT ON COLUMN merchant_transfer_to_coin.exchange_deposit_fee_val
+COMMENT ON COLUMN merchant_transfer_to_coin.exchange_deposit_fee
IS 'Deposit value as claimed by the exchange, should match our values in merchant_deposits';
CREATE TABLE IF NOT EXISTS merchant_deposit_to_transfer
(deposit_serial BIGINT NOT NULL
REFERENCES merchant_deposits (deposit_serial) ON DELETE CASCADE
- ,coin_contribution_value_val INT8 NOT NULL
- ,coin_contribution_value_frac INT4 NOT NULL
+ ,coin_contribution_value taler_amount_currency NOT NULL
,credit_serial BIGINT NOT NULL
REFERENCES merchant_transfers (credit_serial)
,execution_time INT8 NOT NULL
@@ -460,111 +563,105 @@ COMMENT ON COLUMN merchant_deposit_to_transfer.execution_time
IS 'Execution time as claimed by the exchange, roughly matches time seen by merchant';
--------------------------- Tipping ---------------------------
+-------------------------- Rewards ---------------------------
-CREATE TABLE IF NOT EXISTS merchant_tip_reserves
+CREATE TABLE IF NOT EXISTS merchant_reward_reserves
(reserve_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
,reserve_pub BYTEA NOT NULL UNIQUE CHECK (LENGTH(reserve_pub)=32)
,merchant_serial BIGINT NOT NULL
REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
,creation_time INT8 NOT NULL
,expiration INT8 NOT NULL
- ,merchant_initial_balance_val INT8 NOT NULL
- ,merchant_initial_balance_frac INT4 NOT NULL
- ,exchange_initial_balance_val INT8 NOT NULL DEFAULT 0
- ,exchange_initial_balance_frac INT4 NOT NULL DEFAULT 0
- ,tips_committed_val INT8 NOT NULL DEFAULT 0
- ,tips_committed_frac INT4 NOT NULL DEFAULT 0
- ,tips_picked_up_val INT8 NOT NULL DEFAULT 0
- ,tips_picked_up_frac INT4 NOT NULL DEFAULT 0
+ ,merchant_initial_balance taler_amount_currency NOT NULL
+ ,exchange_initial_balance taler_amount_currency NOT NULL
+ ,rewards_committed taler_amount_currency NOT NULL
+ ,rewards_picked_up taler_amount_currency NOT NULL
);
-COMMENT ON TABLE merchant_tip_reserves
- IS 'balances of the reserves available for tips';
-COMMENT ON COLUMN merchant_tip_reserves.expiration
+COMMENT ON TABLE merchant_reward_reserves
+ IS 'balances of the reserves available for rewards';
+COMMENT ON COLUMN merchant_reward_reserves.expiration
IS 'FIXME: EXCHANGE API needs to tell us when reserves close if we are to compute this';
-COMMENT ON COLUMN merchant_tip_reserves.merchant_initial_balance_val
+COMMENT ON COLUMN merchant_reward_reserves.merchant_initial_balance
IS 'Set to the initial balance the merchant told us when creating the reserve';
-COMMENT ON COLUMN merchant_tip_reserves.exchange_initial_balance_val
+COMMENT ON COLUMN merchant_reward_reserves.exchange_initial_balance
IS 'Set to the initial balance the exchange told us when we queried the reserve status';
-COMMENT ON COLUMN merchant_tip_reserves.tips_committed_val
- IS 'Amount of outstanding approved tips that have not been picked up';
-COMMENT ON COLUMN merchant_tip_reserves.tips_picked_up_val
- IS 'Total amount tips that have been picked up from this reserve';
-CREATE INDEX IF NOT EXISTS merchant_tip_reserves_by_reserve_pub_and_merchant_serial
- ON merchant_tip_reserves
+COMMENT ON COLUMN merchant_reward_reserves.rewards_committed
+ IS 'Amount of outstanding approved rewards that have not been picked up';
+COMMENT ON COLUMN merchant_reward_reserves.rewards_picked_up
+ IS 'Total amount rewards that have been picked up from this reserve';
+
+CREATE INDEX IF NOT EXISTS merchant_reward_reserves_by_reserve_pub_and_merchant_serial
+ ON merchant_reward_reserves
(reserve_pub,merchant_serial,creation_time);
-CREATE INDEX IF NOT EXISTS merchant_tip_reserves_by_merchant_serial_and_creation_time
- ON merchant_tip_reserves
+CREATE INDEX IF NOT EXISTS merchant_reward_reserves_by_merchant_serial_and_creation_time
+ ON merchant_reward_reserves
(merchant_serial,creation_time);
-CREATE INDEX IF NOT EXISTS merchant_tip_reserves_by_exchange_balance
- ON merchant_tip_reserves
- (exchange_initial_balance_val,exchange_initial_balance_frac);
+CREATE INDEX IF NOT EXISTS merchant_reward_reserves_by_exchange_balance
+ ON merchant_reward_reserves
+ (exchange_initial_balance);
-CREATE TABLE IF NOT EXISTS merchant_tip_reserve_keys
+CREATE TABLE IF NOT EXISTS merchant_reward_reserve_keys
(reserve_serial BIGINT NOT NULL UNIQUE
- REFERENCES merchant_tip_reserves (reserve_serial) ON DELETE CASCADE
+ REFERENCES merchant_reward_reserves (reserve_serial) ON DELETE CASCADE
,reserve_priv BYTEA NOT NULL UNIQUE CHECK (LENGTH(reserve_priv)=32)
- ,exchange_url VARCHAR NOT NULL
- ,payto_uri VARCHAR
+ ,exchange_url TEXT NOT NULL
+ ,master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)
);
-COMMENT ON TABLE merchant_tip_reserves
+COMMENT ON TABLE merchant_reward_reserves
IS 'private keys of reserves that have not been deleted';
-COMMENT ON COLUMN merchant_tip_reserve_keys.payto_uri
- IS 'payto:// URI used to fund the reserve, may be NULL once reserve is funded';
+COMMENT ON COLUMN merchant_reward_reserve_keys.master_pub
+ IS 'Master public key of the exchange to which the reserve belongs';
-CREATE TABLE IF NOT EXISTS merchant_tips
- (tip_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+CREATE TABLE IF NOT EXISTS merchant_rewards
+ (reward_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
,reserve_serial BIGINT NOT NULL
- REFERENCES merchant_tip_reserves (reserve_serial) ON DELETE CASCADE
- ,tip_id BYTEA NOT NULL UNIQUE CHECK (LENGTH(tip_id)=64)
- ,justification VARCHAR NOT NULL
- ,next_url VARCHAR NOT NULL
+ REFERENCES merchant_reward_reserves (reserve_serial) ON DELETE CASCADE
+ ,reward_id BYTEA NOT NULL UNIQUE CHECK (LENGTH(reward_id)=64)
+ ,justification TEXT NOT NULL
+ ,next_url TEXT NOT NULL
,expiration INT8 NOT NULL
- ,amount_val INT8 NOT NULL
- ,amount_frac INT4 NOT NULL
- ,picked_up_val INT8 NOT NULL DEFAULT 0
- ,picked_up_frac INT4 NOT NULL DEFAULT 0
+ ,amount taler_amount_currency NOT NULL
+ ,picked_up taler_amount_currency NOT NULL
,was_picked_up BOOLEAN NOT NULL DEFAULT FALSE
);
-CREATE INDEX IF NOT EXISTS merchant_tips_by_pickup_and_expiration
- ON merchant_tips
+CREATE INDEX IF NOT EXISTS merchant_rewards_by_pickup_and_expiration
+ ON merchant_rewards
(was_picked_up,expiration);
-COMMENT ON TABLE merchant_tips
- IS 'tips that have been authorized';
-COMMENT ON COLUMN merchant_tips.amount_val
- IS 'Overall tip amount';
-COMMENT ON COLUMN merchant_tips.picked_up_val
- IS 'Tip amount left to be picked up';
-COMMENT ON COLUMN merchant_tips.reserve_serial
- IS 'Reserve from which this tip is funded';
-COMMENT ON COLUMN merchant_tips.expiration
- IS 'by when does the client have to pick up the tip';
-
-CREATE TABLE IF NOT EXISTS merchant_tip_pickups
+COMMENT ON TABLE merchant_rewards
+ IS 'rewards that have been authorized';
+COMMENT ON COLUMN merchant_rewards.amount
+ IS 'Overall reward amount';
+COMMENT ON COLUMN merchant_rewards.picked_up
+ IS 'Reward amount left to be picked up';
+COMMENT ON COLUMN merchant_rewards.reserve_serial
+ IS 'Reserve from which this reward is funded';
+COMMENT ON COLUMN merchant_rewards.expiration
+ IS 'by when does the client have to pick up the reward';
+
+CREATE TABLE IF NOT EXISTS merchant_reward_pickups
(pickup_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL
- ,tip_serial BIGINT NOT NULL
- REFERENCES merchant_tips (tip_serial) ON DELETE CASCADE
+ ,reward_serial BIGINT NOT NULL
+ REFERENCES merchant_rewards (reward_serial) ON DELETE CASCADE
,pickup_id BYTEA NOT NULL UNIQUE CHECK (LENGTH(pickup_id)=64)
- ,amount_val INT8 NOT NULL
- ,amount_frac INT4 NOT NULL
+ ,amount taler_amount_currency NOT NULL
);
-COMMENT ON TABLE merchant_tip_pickups
- IS 'tips that have been picked up';
-COMMENT ON COLUMN merchant_tips.amount_val
+COMMENT ON TABLE merchant_reward_pickups
+ IS 'rewards that have been picked up';
+COMMENT ON COLUMN merchant_rewards.amount
IS 'total transaction cost for all coins including withdraw fees';
-CREATE TABLE IF NOT EXISTS merchant_tip_pickup_signatures
+CREATE TABLE IF NOT EXISTS merchant_reward_pickup_signatures
(pickup_serial INT8 NOT NULL
- REFERENCES merchant_tip_pickups (pickup_serial) ON DELETE CASCADE
+ REFERENCES merchant_reward_pickups (pickup_serial) ON DELETE CASCADE
,coin_offset INT4 NOT NULL
,blind_sig BYTEA NOT NULL
,PRIMARY KEY (pickup_serial, coin_offset)
);
-COMMENT ON TABLE merchant_tip_pickup_signatures
- IS 'blind signatures we got from the exchange during the tip pickup';
+COMMENT ON TABLE merchant_reward_pickup_signatures
+ IS 'blind signatures we got from the exchange during the reward pickup';
@@ -573,12 +670,13 @@ CREATE TABLE IF NOT EXISTS merchant_kyc
(kyc_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
,kyc_timestamp INT8 NOT NULL
,kyc_ok BOOLEAN NOT NULL DEFAULT (FALSE)
+,aml_decision INT4 NOT NULL DEFAULT (0)
,exchange_sig BYTEA CHECK(LENGTH(exchange_sig)=64)
,exchange_pub BYTEA CHECK(LENGTH(exchange_pub)=32)
,exchange_kyc_serial INT8 NOT NULL DEFAULT(0)
,account_serial INT8 NOT NULL
REFERENCES merchant_accounts (account_serial) ON DELETE CASCADE
-,exchange_url VARCHAR NOT NULL
+,exchange_url TEXT NOT NULL
,PRIMARY KEY (account_serial,exchange_url)
);
COMMENT ON TABLE merchant_kyc
@@ -593,11 +691,153 @@ COMMENT ON COLUMN merchant_kyc.exchange_sig
IS 'signature of the exchange affirming the KYC passed (or NULL if exchange does not require KYC or not kyc_ok)';
COMMENT ON COLUMN merchant_kyc.exchange_pub
IS 'public key used with exchange_sig (or NULL if exchange_sig is NULL)';
+COMMENT ON COLUMN merchant_kyc.aml_decision
+ IS 'current AML decision for our account at the exchange';
COMMENT ON COLUMN merchant_kyc.account_serial
IS 'Which bank account of the merchant is the KYC status for';
COMMENT ON COLUMN merchant_kyc.exchange_url
IS 'Which exchange base URL is this KYC status valid for';
+CREATE TABLE IF NOT EXISTS merchant_otp_devices
+ (otp_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+ ,merchant_serial BIGINT NOT NULL
+ REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
+ ,otp_id TEXT NOT NULL
+ ,otp_description TEXT NOT NULL
+ ,otp_key TEXT DEFAULT NULL
+ ,otp_algorithm INT NOT NULL DEFAULT (0)
+ ,otp_ctr INT8 NOT NULL DEFAULT (0)
+ ,UNIQUE (merchant_serial, otp_id)
+ );
+COMMENT ON TABLE merchant_otp_devices
+ IS 'OTP device owned by a merchant';
+COMMENT ON COLUMN merchant_otp_devices.otp_description
+ IS 'Human-readable OTP device description';
+COMMENT ON COLUMN merchant_otp_devices.otp_key
+ IS 'A base64-encoded key of the point-of-sale. It will be use by the OTP device';
+COMMENT ON COLUMN merchant_otp_devices.otp_algorithm
+ IS 'algorithm to used to generate the confirmation code. It is linked with the otp_key and otp_ctr';
+COMMENT ON COLUMN merchant_otp_devices.otp_ctr
+ IS 'counter for counter-based OTP generators';
+
+
+CREATE TABLE IF NOT EXISTS merchant_template
+ (template_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+ ,merchant_serial BIGINT NOT NULL
+ REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
+ ,template_id TEXT NOT NULL
+ ,template_description TEXT NOT NULL
+ ,otp_device_id BIGINT
+ REFERENCES merchant_otp_devices (otp_serial) ON DELETE SET NULL
+ ,template_contract TEXT NOT NULL -- in JSON format
+ ,UNIQUE (merchant_serial, template_id)
+ );
+COMMENT ON TABLE merchant_template
+ IS 'template used by the merchant (may be incomplete, frontend can override)';
+COMMENT ON COLUMN merchant_template.template_description
+ IS 'Human-readable template description';
+COMMENT ON COLUMN merchant_template.template_contract
+ IS 'The template contract will contains some additional information.';
+
+
+
+CREATE TABLE IF NOT EXISTS merchant_webhook
+ (webhook_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+ ,merchant_serial BIGINT NOT NULL
+ REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
+ ,webhook_id TEXT NOT NULL
+ ,event_type TEXT NOT NULL
+ ,url TEXT NOT NULL
+ ,http_method TEXT NOT NULL
+ ,header_template TEXT
+ ,body_template TEXT
+ ,UNIQUE (merchant_serial, webhook_id)
+ );
+COMMENT ON TABLE merchant_webhook
+ IS 'webhook used by the merchant (may be incomplete, frontend can override)';
+COMMENT ON COLUMN merchant_webhook.event_type
+ IS 'Event of the webhook';
+COMMENT ON COLUMN merchant_webhook.url
+ IS 'URL to make the request to';
+COMMENT ON COLUMN merchant_webhook.http_method
+ IS 'http method use by the merchant';
+COMMENT ON COLUMN merchant_webhook.header_template
+ IS 'Template for the header of the webhook, to be modified based on trigger data';
+COMMENT ON COLUMN merchant_webhook.body_template
+ IS 'Template for the body of the webhook, to be modified based on trigger data';
+
+
+CREATE TABLE IF NOT EXISTS merchant_pending_webhooks
+ (webhook_pending_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+ ,merchant_serial BIGINT NOT NULL
+ REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
+ ,webhook_serial BIGINT NOT NULL
+ REFERENCES merchant_webhook (webhook_serial) ON DELETE CASCADE
+ ,next_attempt INT8 NOT NULL DEFAULT(0)
+ ,retries INT4 NOT NULL DEFAULT(0)
+ ,url TEXT NOT NULL
+ ,http_method TEXT NOT NULL
+ ,header TEXT
+ ,body TEXT
+ ,UNIQUE (merchant_serial, webhook_pending_serial)
+ );
+COMMENT ON TABLE merchant_pending_webhooks
+ IS 'webhooks that still need to be executed by the merchant';
+COMMENT ON COLUMN merchant_pending_webhooks.url
+ IS 'URL to make the request to';
+COMMENT ON COLUMN merchant_pending_webhooks.webhook_serial
+ IS 'Reference to the configured webhook template';
+COMMENT ON COLUMN merchant_pending_webhooks.retries
+ IS 'How often have we tried this request so far';
+COMMENT ON COLUMN merchant_pending_webhooks.next_attempt
+ IS 'Time when we should make the next request to the webhook';
+COMMENT ON COLUMN merchant_pending_webhooks.http_method
+ IS 'http method use for the webhook';
+COMMENT ON COLUMN merchant_pending_webhooks.header
+ IS 'Header of the webhook';
+COMMENT ON COLUMN merchant_pending_webhooks.body
+ IS 'Body of the webhook';
+
+
+CREATE TABLE IF NOT EXISTS merchant_exchange_accounts
+ (mea_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+ ,master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)
+ ,payto_uri TEXT NOT NULL
+ ,conversion_url TEXT
+ ,debit_restrictions TEXT NOT NULL
+ ,credit_restrictions TEXT NOT NULL
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ );
+COMMENT ON TABLE merchant_exchange_accounts
+ IS 'Here we store which bank accounts the exchange uses and with which constraints';
+COMMENT ON COLUMN merchant_exchange_accounts.master_pub
+ IS 'Master public key of the exchange with these accounts';
+COMMENT ON COLUMN merchant_exchange_accounts.payto_uri
+ IS 'RFC 8905 URI of the exchange bank account';
+COMMENT ON COLUMN merchant_exchange_accounts.conversion_url
+ IS 'NULL if this account does not require currency conversion';
+COMMENT ON COLUMN merchant_exchange_accounts.debit_restrictions
+ IS 'JSON array with account restrictions';
+COMMENT ON COLUMN merchant_exchange_accounts.credit_restrictions
+ IS 'JSON array with account restrictions';
+
+
+CREATE TABLE IF NOT EXISTS merchant_exchange_keys
+ (mek_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,exchange_url TEXT PRIMARY KEY
+ ,keys_json TEXT NOT NULL
+ ,expiration_time INT8 NOT NULL
+ );
+COMMENT ON TABLE merchant_exchange_keys
+ IS 'Here we store the cached /keys response from an exchange in JSON format';
+COMMENT ON COLUMN merchant_exchange_keys.exchange_url
+ IS 'Base URL of the exchange with these keys';
+COMMENT ON COLUMN merchant_exchange_keys.keys_json
+ IS 'JSON string of the /keys as generated by libtalerexchange';
+COMMENT ON COLUMN merchant_exchange_keys.expiration_time
+ IS 'When should this /keys object be deleted';
+
+
-- Complete transaction
COMMIT;
diff --git a/src/backenddb/merchant-0002.sql b/src/backenddb/merchant-0002.sql
index 29dd0115..00053cf3 100644
--- a/src/backenddb/merchant-0002.sql
+++ b/src/backenddb/merchant-0002.sql
@@ -1,6 +1,6 @@
--
-- This file is part of TALER
--- Copyright (C) 2021 Taler Systems SA
+-- Copyright (C) 2023 Taler Systems SA
--
-- TALER is free software; you can redistribute it and/or modify it under the
-- terms of the GNU General Public License as published by the Free Software
@@ -14,6 +14,10 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
+-- @file merchant-0002.sql
+-- @brief database schema for the merchant
+-- @author Christian Blättler
+
-- Everything in one big transaction
BEGIN;
@@ -22,17 +26,146 @@ SELECT _v.register_patch('merchant-0002', NULL, NULL);
SET search_path TO merchant;
-ALTER TABLE merchant_instances
- ADD COLUMN website VARCHAR,
- ADD COLUMN email VARCHAR,
- ADD COLUMN logo BYTEA;
-
-COMMENT ON COLUMN merchant_instances.website
- IS 'merchant site URL';
-COMMENT ON COLUMN merchant_instances.email
- IS 'email';
-COMMENT ON COLUMN merchant_instances.logo
- IS 'data image url';
+ALTER TABLE merchant_orders
+ ADD COLUMN fulfillment_url TEXT DEFAULT NULL
+ ,ADD COLUMN session_id TEXT DEFAULT '' NOT NULL;
+
+COMMENT ON COLUMN merchant_orders.fulfillment_url
+ IS 'URL where the wallet will redirect the user upon payment';
+COMMENT ON COLUMN merchant_orders.session_id
+ IS 'session_id to which the payment will be bound';
+
+
+CREATE INDEX IF NOT EXISTS merchant_orders_by_merchant_and_session
+ ON merchant_orders
+ (merchant_serial,session_id);
+
+CREATE INDEX IF NOT EXISTS merchant_orders_by_merchant_and_fullfilment_and_session
+ ON merchant_orders
+ (merchant_serial,fulfillment_url,session_id);
+
+CREATE INDEX IF NOT EXISTS merchant_contract_terms_by_merchant_and_session
+ ON merchant_contract_terms
+ (merchant_serial,session_id);
+
+
+
+ALTER TABLE merchant_deposit_confirmations
+ ADD COLUMN wire_transfer_deadline INT8 DEFAULT (0) NOT NULL,
+ ADD COLUMN wire_pending BOOL DEFAULT (TRUE) NOT NULL,
+ ADD COLUMN exchange_failure TEXT DEFAULT NULL;
+
+COMMENT ON COLUMN merchant_deposit_confirmations.wire_transfer_deadline
+ IS 'when should the exchange make the wire transfer at the latest';
+COMMENT ON COLUMN merchant_deposit_confirmations.wire_pending
+ IS 'true if we are awaiting wire details for a deposit of this purchase (and are not blocked on KYC); false once the exchange says that the wire transfer has happened (does not mean that we confirmed it happened though)';
+COMMENT ON COLUMN merchant_deposit_confirmations.exchange_failure
+ IS 'Text describing exchange failures in making timely wire transfers for this deposit confirmation';
+
+CREATE INDEX IF NOT EXISTS merchant_deposit_confirmations_by_pending_wire
+ ON merchant_deposit_confirmations
+ (exchange_url,wire_transfer_deadline)
+ WHERE wire_pending;
+
+CREATE INDEX IF NOT EXISTS merchant_deposits_by_deposit_confirmation_serial
+ ON merchant_deposits
+ (deposit_confirmation_serial);
+
+-------------------------- Tokens -----------------------------
+
+CREATE TABLE IF NOT EXISTS merchant_token_families
+ (token_family_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+ ,merchant_serial BIGINT NOT NULL REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
+ ,slug TEXT NOT NULL UNIQUE
+ ,name TEXT NOT NULL
+ ,description TEXT
+ ,description_i18n BYTEA NOT NULL
+ ,valid_after BIGINT NOT NULL
+ ,valid_before BIGINT NOT NULL
+ ,duration BIGINT NOT NULL
+ ,kind TEXT NOT NULL CHECK (kind IN ('subscription', 'discount'))
+ ,issued BIGINT DEFAULT 0
+ ,redeemed BIGINT DEFAULT 0
+ );
+COMMENT ON TABLE merchant_token_families
+ IS 'Token families configured by the merchant.';
+COMMENT ON COLUMN merchant_token_families.merchant_serial
+ IS 'Instance where the token family is configured.';
+COMMENT ON COLUMN merchant_token_families.slug
+ IS 'Unique slug for the token family.';
+COMMENT ON COLUMN merchant_token_families.name
+ IS 'Name of the token family.';
+COMMENT ON COLUMN merchant_token_families.description
+ IS 'Human-readable description or details about the token family.';
+COMMENT ON COLUMN merchant_token_families.description_i18n
+ IS 'JSON map from IETF BCP 47 language tags to localized descriptions';
+COMMENT ON COLUMN merchant_token_families.valid_after
+ IS 'Start time of the token family''s validity period.';
+COMMENT ON COLUMN merchant_token_families.valid_before
+ IS 'End time of the token family''s validity period.';
+COMMENT ON COLUMN merchant_token_families.duration
+ IS 'Duration of the token.';
+COMMENT ON COLUMN merchant_token_families.kind
+ IS 'Kind of the token (e.g., subscription, discount).';
+COMMENT ON COLUMN merchant_token_families.issued
+ IS 'Counter for the number of tokens issued for this token family.';
+COMMENT ON COLUMN merchant_token_families.redeemed
+ IS 'Counter for the number of tokens redeemed for this token family.';
+
+
+CREATE TABLE IF NOT EXISTS merchant_token_family_keys
+ (token_family_key_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+ ,token_family_serial BIGINT REFERENCES merchant_token_families(token_family_serial) ON DELETE CASCADE
+ ,valid_after BIGINT NOT NULL
+ ,valid_before BIGINT NOT NULL
+ ,pub BYTEA NOT NULL
+ ,h_pub BYTEA NOT NULL UNIQUE CHECK (LENGTH(h_pub)=32)
+ ,priv BYTEA
+ ,cipher TEXT NOT NULL CHECK (cipher IN ('rsa', 'cs'))
+ ,UNIQUE (token_family_serial, valid_after)
+ );
+
+COMMENT ON TABLE merchant_token_family_keys
+ IS 'Keys for token families.';
+COMMENT ON COLUMN merchant_token_family_keys.token_family_serial
+ IS 'Token family to which the key belongs.';
+COMMENT ON COLUMN merchant_token_family_keys.valid_after
+ IS 'Start time for the validity of the token key.';
+COMMENT ON COLUMN merchant_token_family_keys.valid_before
+ IS 'Expiration time for the validity of the token key.';
+COMMENT ON COLUMN merchant_token_family_keys.pub
+ IS 'Public key of the token family.';
+COMMENT ON COLUMN merchant_token_family_keys.h_pub
+ IS 'Hash of the public key for quick lookup.';
+COMMENT ON COLUMN merchant_token_family_keys.priv
+ IS 'Private key of the token family; can be NULL if no more tokens of this family should be issued.';
+COMMENT ON COLUMN merchant_token_family_keys.cipher
+ IS 'Cipher used (rsa or cs).';
+
+
+CREATE TABLE IF NOT EXISTS merchant_spent_tokens
+ (spent_token_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+ ,merchant_serial BIGINT NOT NULL REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
+ ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)
+ ,token_family_key_serial BIGINT REFERENCES merchant_token_family_keys(token_family_key_serial) ON DELETE CASCADE
+ ,token_pub BYTEA NOT NULL UNIQUE CHECK (LENGTH(token_pub)=32)
+ ,token_sig BYTEA NOT NULL CHECK (LENGTH(token_sig)=64)
+ ,blind_sig BYTEA NOT NULL
+ );
+COMMENT ON TABLE merchant_spent_tokens
+ IS 'Tokens that have been spent by customers.';
+COMMENT ON COLUMN merchant_spent_tokens.merchant_serial
+ IS 'Merchant serial where the token was spent.';
+COMMENT ON COLUMN merchant_spent_tokens.h_contract_terms
+ IS 'This is no foreign key by design.';
+COMMENT ON COLUMN merchant_spent_tokens.token_family_key_serial
+ IS 'Token family to which the spent token belongs.';
+COMMENT ON COLUMN merchant_spent_tokens.token_pub
+ IS 'Public key of the spent token.';
+COMMENT ON COLUMN merchant_spent_tokens.token_sig
+ IS 'Signature that the token was spent on specified order.';
+COMMENT ON COLUMN merchant_spent_tokens.blind_sig
+ IS 'Blind signature for the spent token to prove validity of token.';
-- Complete transaction
COMMIT;
diff --git a/src/backenddb/merchant-0003.sql b/src/backenddb/merchant-0003.sql
index a3c8b484..f524218d 100644
--- a/src/backenddb/merchant-0003.sql
+++ b/src/backenddb/merchant-0003.sql
@@ -1,6 +1,6 @@
--
-- This file is part of TALER
--- Copyright (C) 2021 Taler Systems SA
+-- Copyright (C) 2024 Taler Systems SA
--
-- TALER is free software; you can redistribute it and/or modify it under the
-- terms of the GNU General Public License as published by the Free Software
@@ -22,9 +22,29 @@ SELECT _v.register_patch('merchant-0003', NULL, NULL);
SET search_path TO merchant;
-ALTER TABLE merchant_exchange_wire_fees
- DROP COLUMN wad_fee_val,
- DROP COLUMN wad_fee_frac;
+ALTER TABLE merchant_deposit_to_transfer
+ ADD COLUMN wtid BYTEA CHECK (LENGTH(wtid)=32) DEFAULT NULL;
+
+UPDATE merchant_deposit_to_transfer dst
+ SET wtid=src.wtid
+ FROM merchant_transfers src
+ WHERE (src.credit_serial = dst.credit_serial);
+
+ALTER TABLE merchant_deposit_to_transfer
+ DROP COLUMN credit_serial,
+ ALTER COLUMN wtid SET NOT NULL,
+ ADD UNIQUE (deposit_serial,wtid);
+
+COMMENT ON COLUMN merchant_deposit_to_transfer.wtid
+ IS 'wire transfer identifier of the transfer the exchange claims to have done';
+
+
+ALTER TABLE merchant_deposit_confirmations
+ ADD COLUMN retry_backoff INT8 DEFAULT (0) NOT NULL;
+
+COMMENT ON COLUMN merchant_deposit_confirmations.retry_backoff
+ IS 'exponentially increasing value we add to the wire_transfer_deadline on each failure to confirm the wire transfer';
+
-- Complete transaction
COMMIT;
diff --git a/src/backenddb/merchant-0004.sql b/src/backenddb/merchant-0004.sql
index 55cfa2fc..711026a2 100644
--- a/src/backenddb/merchant-0004.sql
+++ b/src/backenddb/merchant-0004.sql
@@ -1,6 +1,6 @@
--
-- This file is part of TALER
--- Copyright (C) 2022-2023 Taler Systems SA
+-- Copyright (C) 2024 Taler Systems SA
--
-- TALER is free software; you can redistribute it and/or modify it under the
-- terms of the GNU General Public License as published by the Free Software
@@ -14,98 +14,17 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
-
--- @file merchant-0004.sql
--- @brief database helper functions for postgres used by the merchant and function for plugin_merchantdb_postgres.c
--- @author Priscilla Huang
-
-
BEGIN;
-- Check patch versioning is in place.
SELECT _v.register_patch('merchant-0004', NULL, NULL);
SET search_path TO merchant;
--- create table here!
-
-CREATE TABLE IF NOT EXISTS merchant_template
- (template_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
- ,merchant_serial BIGINT NOT NULL
- REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
- ,template_id VARCHAR NOT NULL
- ,template_description VARCHAR NOT NULL
- ,image BYTEA
- ,template_contract VARCHAR NOT NULL -- in JSON format
- ,UNIQUE (merchant_serial, template_id)
- );
-COMMENT ON TABLE merchant_template
- IS 'template used by the merchant (may be incomplete, frontend can override)';
-COMMENT ON COLUMN merchant_template.template_description
- IS 'Human-readable template description';
-COMMENT ON COLUMN merchant_template.image
- IS 'NOT NULL, but can be 0 bytes; must contain an ImageDataUrl';
-COMMENT ON COLUMN merchant_template.template_contract
- IS 'The template contract will contains some additional information.';
-
-
-CREATE TABLE IF NOT EXISTS merchant_webhook
- (webhook_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
- ,merchant_serial BIGINT NOT NULL
- REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
- ,webhook_id VARCHAR NOT NULL
- ,event_type VARCHAR NOT NULL
- ,url VARCHAR NOT NULL
- ,http_method VARCHAR NOT NULL
- ,header_template VARCHAR
- ,body_template VARCHAR
- ,UNIQUE (merchant_serial, webhook_id)
- );
-COMMENT ON TABLE merchant_webhook
- IS 'webhook used by the merchant (may be incomplete, frontend can override)';
-COMMENT ON COLUMN merchant_webhook.event_type
- IS 'Event of the webhook';
-COMMENT ON COLUMN merchant_webhook.url
- IS 'URL to make the request to';
-COMMENT ON COLUMN merchant_webhook.http_method
- IS 'http method use by the merchant';
-COMMENT ON COLUMN merchant_webhook.header_template
- IS 'Template for the header of the webhook, to be modified based on trigger data';
-COMMENT ON COLUMN merchant_webhook.body_template
- IS 'Template for the body of the webhook, to be modified based on trigger data';
-
-
-CREATE TABLE IF NOT EXISTS merchant_pending_webhooks
- (webhook_pending_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
- ,merchant_serial BIGINT NOT NULL
- REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
- ,webhook_serial BIGINT NOT NULL
- REFERENCES merchant_webhook (webhook_serial) ON DELETE CASCADE
- ,next_attempt INT8 NOT NULL DEFAULT(0)
- ,retries INT4 NOT NULL DEFAULT(0)
- ,url VARCHAR NOT NULL
- ,http_method VARCHAR NOT NULL
- ,header VARCHAR
- ,body VARCHAR
- ,UNIQUE (merchant_serial, webhook_pending_serial)
- );
-COMMENT ON TABLE merchant_pending_webhooks
- IS 'webhooks that still need to be executed by the merchant';
-COMMENT ON COLUMN merchant_pending_webhooks.url
- IS 'URL to make the request to';
-COMMENT ON COLUMN merchant_pending_webhooks.webhook_serial
- IS 'Reference to the configured webhook template';
-COMMENT ON COLUMN merchant_pending_webhooks.retries
- IS 'How often have we tried this request so far';
-COMMENT ON COLUMN merchant_pending_webhooks.next_attempt
- IS 'Time when we should make the next request to the webhook';
-COMMENT ON COLUMN merchant_pending_webhooks.http_method
- IS 'http method use for the webhook';
-COMMENT ON COLUMN merchant_pending_webhooks.header
- IS 'Header of the webhook';
-COMMENT ON COLUMN merchant_pending_webhooks.body
- IS 'Body of the webhook';
+DROP TABLE merchant_reward_pickup_signatures;
+DROP TABLE merchant_reward_pickups;
+DROP TABLE merchant_rewards;
+DROP TABLE merchant_reward_reserve_keys;
+DROP TABLE merchant_reward_reserves;
COMMIT;
-
-
diff --git a/src/backenddb/merchant-0005.sql b/src/backenddb/merchant-0005.sql
new file mode 100644
index 00000000..a356f6da
--- /dev/null
+++ b/src/backenddb/merchant-0005.sql
@@ -0,0 +1,36 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+-- Everything in one big transaction
+BEGIN;
+
+-- Check patch versioning is in place.
+SELECT _v.register_patch('merchant-0005', NULL, NULL);
+
+SET search_path TO merchant;
+
+ALTER TABLE merchant_template
+ ADD COLUMN required_currency VARCHAR(12) DEFAULT NULL,
+ ADD COLUMN editable_defaults TEXT DEFAULT NULL;
+
+COMMENT ON COLUMN merchant_template.required_currency
+ IS 'currency that the amount to be paid entered by the user must be in; if not given and the amount is not fixed in the template contract, the user may edit the currency';
+COMMENT ON COLUMN merchant_template.editable_defaults
+ IS 'JSON object with fields matching the template contract, just with default values that are editable by the user';
+
+
+-- Complete transaction
+COMMIT;
diff --git a/src/backenddb/merchantdb-postgres.conf b/src/backenddb/merchantdb-postgres.conf
index db4763b9..33b4b838 100644
--- a/src/backenddb/merchantdb-postgres.conf
+++ b/src/backenddb/merchantdb-postgres.conf
@@ -3,4 +3,4 @@ CONFIG = "postgres:///talermerchant"
# Where are the SQL files to setup our tables?
# Important: this MUST end with a "/"!
-SQL_DIR = $DATADIR/sql/merchant/
+SQL_DIR = ${DATADIR}sql/merchant/
diff --git a/src/backenddb/merchantdb_helper.c b/src/backenddb/merchantdb_helper.c
index 0a49f5d6..5894525c 100644
--- a/src/backenddb/merchantdb_helper.c
+++ b/src/backenddb/merchantdb_helper.c
@@ -37,13 +37,14 @@ TALER_MERCHANTDB_product_details_free (
}
-
void
TALER_MERCHANTDB_template_details_free (
struct TALER_MERCHANTDB_TemplateDetails *tp)
{
GNUNET_free (tp->template_description);
- GNUNET_free (tp->image);
+ GNUNET_free (tp->otp_id);
+ GNUNET_free (tp->required_currency);
+ json_decref (tp->editable_defaults);
json_decref (tp->template_contract);
}
@@ -59,6 +60,7 @@ TALER_MERCHANTDB_webhook_details_free (
GNUNET_free (wb->body_template);
}
+
void
TALER_MERCHANTDB_pending_webhook_details_free (
struct TALER_MERCHANTDB_PendingWebhookDetails *pwb)
@@ -70,5 +72,15 @@ TALER_MERCHANTDB_pending_webhook_details_free (
}
+void
+TALER_MERCHANTDB_token_family_details_free (
+ struct TALER_MERCHANTDB_TokenFamilyDetails *tf)
+{
+ GNUNET_free (tf->slug);
+ GNUNET_free (tf->name);
+ GNUNET_free (tf->description);
+ json_decref (tf->description_i18n);
+}
+
/* end of merchantdb_helper.c */
diff --git a/src/backenddb/pg_account_kyc_get_status.c b/src/backenddb/pg_account_kyc_get_status.c
new file mode 100644
index 00000000..1c7c5792
--- /dev/null
+++ b/src/backenddb/pg_account_kyc_get_status.c
@@ -0,0 +1,195 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_account_kyc_get_status.c
+ * @brief Implementation of the account_kyc_get_status function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_account_kyc_get_status.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for kyc_status_cb().
+ */
+struct KycStatusContext
+{
+ /**
+ * Function to call with results.
+ */
+ TALER_MERCHANTDB_KycCallback kyc_cb;
+
+ /**
+ * Closure for @e kyc_cb.
+ */
+ void *kyc_cb_cls;
+
+ /**
+ * Filter, NULL to not filter.
+ */
+ const struct TALER_MerchantWireHashP *h_wire;
+
+ /**
+ * Filter, NULL to not filter.
+ */
+ const char *exchange_url;
+
+ /**
+ * Number of results found.
+ */
+ unsigned int count;
+
+ /**
+ * Set to true on failure(s).
+ */
+ bool failure;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about accounts.
+ *
+ * @param[in,out] cls of type `struct KycStatusContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+kyc_status_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct KycStatusContext *ksc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct TALER_MerchantWireHashP h_wire;
+ uint64_t kyc_serial;
+ char *exchange_url;
+ char *payto_uri;
+ struct GNUNET_TIME_Timestamp last_check;
+ bool kyc_ok;
+ uint32_t aml_decision;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+ &h_wire),
+ GNUNET_PQ_result_spec_uint64 ("exchange_kyc_serial",
+ &kyc_serial),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_PQ_result_spec_string ("exchange_url",
+ &exchange_url),
+ GNUNET_PQ_result_spec_timestamp ("kyc_timestamp",
+ &last_check),
+ GNUNET_PQ_result_spec_bool ("kyc_ok",
+ &kyc_ok),
+ GNUNET_PQ_result_spec_uint32 ("aml_decision",
+ &aml_decision),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ksc->failure = true;
+ return;
+ }
+ if ( (NULL != ksc->exchange_url) &&
+ (0 != strcmp (ksc->exchange_url,
+ exchange_url)) )
+ {
+ GNUNET_PQ_cleanup_result (rs);
+ continue;
+ }
+ if ( (NULL != ksc->h_wire) &&
+ (0 != GNUNET_memcmp (ksc->h_wire,
+ &h_wire)) )
+ {
+ GNUNET_PQ_cleanup_result (rs);
+ continue;
+ }
+ ksc->count++;
+ ksc->kyc_cb (ksc->kyc_cb_cls,
+ &h_wire,
+ kyc_serial,
+ payto_uri,
+ exchange_url,
+ last_check,
+ kyc_ok,
+ (enum TALER_AmlDecisionState) aml_decision);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_account_kyc_get_status (void *cls,
+ const char *merchant_id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *exchange_url,
+ TALER_MERCHANTDB_KycCallback kyc_cb,
+ void *kyc_cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct KycStatusContext ksc = {
+ .kyc_cb = kyc_cb,
+ .kyc_cb_cls = kyc_cb_cls,
+ .exchange_url = exchange_url,
+ .h_wire = h_wire
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (merchant_id),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_kyc_status",
+ "SELECT"
+ " h_wire"
+ ",exchange_kyc_serial"
+ ",payto_uri"
+ ",exchange_url"
+ ",kyc_timestamp"
+ ",kyc_ok"
+ ",aml_decision"
+ " FROM merchant_instances"
+ " JOIN merchant_accounts"
+ " USING (merchant_serial)"
+ " JOIN merchant_kyc"
+ " USING (account_serial)"
+ " WHERE merchant_instances.merchant_id=$1");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_kyc_status",
+ params,
+ &kyc_status_cb,
+ &ksc);
+ if (ksc.failure)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (0 > qs)
+ return qs;
+ return ksc.count;
+}
diff --git a/src/backenddb/pg_account_kyc_get_status.h b/src/backenddb/pg_account_kyc_get_status.h
new file mode 100644
index 00000000..41d5c05d
--- /dev/null
+++ b/src/backenddb/pg_account_kyc_get_status.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_account_kyc_get_status.h
+ * @brief implementation of the account_kyc_get_status function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ACCOUNT_KYC_GET_STATUS_H
+#define PG_ACCOUNT_KYC_GET_STATUS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Check an instance's account's KYC status.
+ *
+ * @param cls closure
+ * @param merchant_id merchant backend instance ID
+ * @param h_wire hash of the wire account to check,
+ * NULL to check all accounts of the merchant
+ * @param exchange_url base URL of the exchange to check,
+ * NULL to check all exchanges
+ * @param kyc_cb KYC status callback to invoke
+ * @param kyc_cb_cls closure for @a kyc_cb
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_account_kyc_get_status (void *cls,
+ const char *merchant_id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *exchange_url,
+ TALER_MERCHANTDB_KycCallback kyc_cb,
+ void *kyc_cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_account_kyc_set_status.c b/src/backenddb/pg_account_kyc_set_status.c
new file mode 100644
index 00000000..6c69c448
--- /dev/null
+++ b/src/backenddb/pg_account_kyc_set_status.c
@@ -0,0 +1,88 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_account_kyc_set_status.c
+ * @brief Implementation of the account_kyc_set_status function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_account_kyc_set_status.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_account_kyc_set_status (
+ void *cls,
+ const char *merchant_id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *exchange_url,
+ uint64_t exchange_kyc_serial,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct GNUNET_TIME_Timestamp timestamp,
+ bool kyc_ok,
+ enum TALER_AmlDecisionState aml_decision)
+{
+ struct PostgresClosure *pg = cls;
+ uint32_t aml32 = (uint32_t) aml_decision;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (merchant_id),
+ GNUNET_PQ_query_param_auto_from_type (h_wire),
+ GNUNET_PQ_query_param_string (exchange_url),
+ GNUNET_PQ_query_param_uint64 (&exchange_kyc_serial),
+ GNUNET_PQ_query_param_timestamp (&timestamp),
+ GNUNET_PQ_query_param_bool (kyc_ok),
+ exchange_pub
+ ? GNUNET_PQ_query_param_auto_from_type (exchange_pub)
+ : GNUNET_PQ_query_param_null (),
+ exchange_sig
+ ? GNUNET_PQ_query_param_auto_from_type (exchange_sig)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_uint32 (&aml32),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "upsert_account_kyc",
+ "INSERT INTO merchant_kyc"
+ "(kyc_timestamp"
+ ",kyc_ok"
+ ",exchange_kyc_serial"
+ ",account_serial"
+ ",exchange_url"
+ ",exchange_pub"
+ ",exchange_sig"
+ ",aml_decision)"
+ " SELECT $5, $6, $4, account_serial, $3, $7, $8, $9"
+ " FROM merchant_instances"
+ " JOIN merchant_accounts USING (merchant_serial)"
+ " WHERE merchant_id=$1"
+ " AND h_wire=$2"
+ " ON CONFLICT(account_serial,exchange_url) DO "
+ "UPDATE"
+ " SET exchange_kyc_serial=$4"
+ " ,kyc_timestamp=$5"
+ " ,kyc_ok=$6"
+ " ,exchange_pub=$7"
+ " ,exchange_sig=$8"
+ " ,aml_decision=$9");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "upsert_account_kyc",
+ params);
+}
diff --git a/src/backenddb/pg_account_kyc_set_status.h b/src/backenddb/pg_account_kyc_set_status.h
new file mode 100644
index 00000000..c9869242
--- /dev/null
+++ b/src/backenddb/pg_account_kyc_set_status.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_account_kyc_set_status.h
+ * @brief implementation of the account_kyc_set_status function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ACCOUNT_KYC_SET_STATUS_H
+#define PG_ACCOUNT_KYC_SET_STATUS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Update an instance's account's KYC status.
+ *
+ * @param cls closure
+ * @param merchant_id merchant backend instance ID
+ * @param h_wire hash of the wire account to check
+ * @param exchange_url base URL of the exchange to check
+ * @param exchange_kyc_serial serial number for our account at the exchange (0 if unknown)
+ * @param exchange_sig signature of the exchange, or NULL for none
+ * @param exchange_pub public key of the exchange, or NULL for none
+ * @param timestamp timestamp to store
+ * @param kyc_ok current KYC status (true for satisfied)
+ * @param aml_decision current AML decision state at the exchange
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_account_kyc_set_status (void *cls,
+ const char *merchant_id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *exchange_url,
+ uint64_t exchange_kyc_serial,
+ const struct
+ TALER_ExchangeSignatureP *exchange_sig,
+ const struct
+ TALER_ExchangePublicKeyP *exchange_pub,
+ struct GNUNET_TIME_Timestamp timestamp,
+ bool kyc_ok,
+ enum TALER_AmlDecisionState aml_decision);
+
+#endif
diff --git a/src/backenddb/pg_activate_account.c b/src/backenddb/pg_activate_account.c
new file mode 100644
index 00000000..21c23752
--- /dev/null
+++ b/src/backenddb/pg_activate_account.c
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_activate_account.c
+ * @brief Implementation of the activate_account function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_activate_account.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_activate_account (void *cls,
+ const char *merchant_id,
+ const struct TALER_MerchantWireHashP *h_wire)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (merchant_id),
+ GNUNET_PQ_query_param_auto_from_type (h_wire),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "activate_account",
+ "UPDATE merchant_accounts SET"
+ " active=TRUE"
+ " WHERE h_wire=$2 AND"
+ " merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "activate_account",
+ params);
+}
diff --git a/src/backenddb/pg_activate_account.h b/src/backenddb/pg_activate_account.h
new file mode 100644
index 00000000..5edb7d1a
--- /dev/null
+++ b/src/backenddb/pg_activate_account.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_activate_account.h
+ * @brief implementation of the activate_account function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_ACTIVATE_ACCOUNT_H
+#define PG_ACTIVATE_ACCOUNT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Set an instance's account in our database to "active".
+ *
+ * @param cls closure
+ * @param merchant_id merchant backend instance ID
+ * @param h_wire hash of the wire account to set to active
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_activate_account (void *cls,
+ const char *merchant_id,
+ const struct TALER_MerchantWireHashP *h_wire);
+
+#endif
diff --git a/src/backenddb/pg_check_transfer_exists.c b/src/backenddb/pg_check_transfer_exists.c
new file mode 100644
index 00000000..2ab15ef5
--- /dev/null
+++ b/src/backenddb/pg_check_transfer_exists.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_check_transfer_exists.c
+ * @brief Implementation of the check_transfer_exists function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_check_transfer_exists.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_check_transfer_exists (void *cls,
+ const char *instance_id,
+ uint64_t transfer_serial_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_uint64 (&transfer_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "check_transfer_exists",
+ "SELECT"
+ " 1"
+ " FROM merchant_transfers"
+ " JOIN merchant_accounts"
+ " USING (account_serial)"
+ " WHERE"
+ " credit_serial=$2"
+ " AND"
+ " merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "check_transfer_exists",
+ params,
+ rs);
+}
diff --git a/src/backenddb/pg_check_transfer_exists.h b/src/backenddb/pg_check_transfer_exists.h
new file mode 100644
index 00000000..0e75e6cb
--- /dev/null
+++ b/src/backenddb/pg_check_transfer_exists.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_check_transfer_exists.h
+ * @brief implementation of the check_transfer_exists function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_CHECK_TRANSFER_EXISTS_H
+#define PG_CHECK_TRANSFER_EXISTS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Check if information about a transfer exists with the
+ * backend. Returns no data, only the query status.
+ *
+ * @param cls closure
+ * @param instance_id instance to delete transfer of
+ * @param transfer_serial_id transfer to delete
+ * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
+ * if the transfer record exists
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_check_transfer_exists (void *cls,
+ const char *instance_id,
+ uint64_t transfer_serial_id);
+
+#endif
diff --git a/src/backenddb/pg_delete_contract_terms.c b/src/backenddb/pg_delete_contract_terms.c
new file mode 100644
index 00000000..708a724a
--- /dev/null
+++ b/src/backenddb/pg_delete_contract_terms.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_contract_terms.c
+ * @brief Implementation of the delete_contract_terms function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_delete_contract_terms.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_contract_terms (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ struct GNUNET_TIME_Relative legal_expiration)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_relative_time (&legal_expiration),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "delete_contract_terms",
+ "DELETE FROM merchant_contract_terms"
+ " WHERE order_id=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND ( ( (pay_deadline < $4) AND"
+ " (NOT paid) ) OR"
+ " (creation_time + $3 < $4) )");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_contract_terms",
+ params);
+}
diff --git a/src/backenddb/pg_delete_contract_terms.h b/src/backenddb/pg_delete_contract_terms.h
new file mode 100644
index 00000000..ce74a3c5
--- /dev/null
+++ b/src/backenddb/pg_delete_contract_terms.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_contract_terms.h
+ * @brief implementation of the delete_contract_terms function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_DELETE_CONTRACT_TERMS_H
+#define PG_DELETE_CONTRACT_TERMS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Delete information about a contract. Note that the transaction must
+ * enforce that the contract is not awaiting payment anymore AND was not
+ * paid, or is past the legal expiration.
+ *
+ * @param cls closure
+ * @param instance_id instance to delete order of
+ * @param order_id order to delete
+ * @param legal_expiration how long do we need to keep (paid) contracts on
+ * file for legal reasons (i.e. taxation)
+ * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ * if locks prevent deletion OR order unknown
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_contract_terms (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ struct GNUNET_TIME_Relative legal_expiration);
+
+#endif
diff --git a/src/backenddb/pg_delete_exchange_accounts.c b/src/backenddb/pg_delete_exchange_accounts.c
new file mode 100644
index 00000000..7d8a5e48
--- /dev/null
+++ b/src/backenddb/pg_delete_exchange_accounts.c
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_exchange_accounts.c
+ * @brief Implementation of the delete_exchange_accounts function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_delete_exchange_accounts.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_exchange_accounts (
+ void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (master_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "delete_exchange_accounts",
+ "DELETE FROM merchant_exchange_accounts"
+ " WHERE master_pub=$1;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_exchange_accounts",
+ params);
+}
diff --git a/src/backenddb/pg_delete_exchange_accounts.h b/src/backenddb/pg_delete_exchange_accounts.h
new file mode 100644
index 00000000..da9013d3
--- /dev/null
+++ b/src/backenddb/pg_delete_exchange_accounts.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_exchange_accounts.h
+ * @brief implementation of the delete_exchange_accounts function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DELETE_EXCHANGE_ACCOUNTS_H
+#define PG_DELETE_EXCHANGE_ACCOUNTS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Delete information about wire accounts of an exchange. (Used when we got new account data.)
+ *
+ * @param cls closure
+ * @param master_pub public key of the exchange
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_exchange_accounts (
+ void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub);
+
+
+#endif
diff --git a/src/backenddb/pg_delete_instance_private_key.c b/src/backenddb/pg_delete_instance_private_key.c
new file mode 100644
index 00000000..5c7356ad
--- /dev/null
+++ b/src/backenddb/pg_delete_instance_private_key.c
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_instance_private_key.c
+ * @brief Implementation of the delete_instance_private_key function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_delete_instance_private_key.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_instance_private_key (
+ void *cls,
+ const char *merchant_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (merchant_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "delete_key",
+ "DELETE FROM merchant_keys"
+ " USING merchant_instances"
+ " WHERE merchant_keys.merchant_serial"
+ " = merchant_instances.merchant_serial"
+ " AND merchant_instances.merchant_id = $1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_key",
+ params);
+}
diff --git a/src/backenddb/pg_delete_instance_private_key.h b/src/backenddb/pg_delete_instance_private_key.h
new file mode 100644
index 00000000..9a8b3be6
--- /dev/null
+++ b/src/backenddb/pg_delete_instance_private_key.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_instance_private_key.h
+ * @brief implementation of the delete_instance_private_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DELETE_INSTANCE_PRIVATE_KEY_H
+#define PG_DELETE_INSTANCE_PRIVATE_KEY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Delete private key of an instance from our database.
+ *
+ * @param cls closure
+ * @param merchant_id identifier of the instance
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_instance_private_key (void *cls,
+ const char *merchant_id);
+
+#endif
diff --git a/src/backenddb/pg_delete_login_token.c b/src/backenddb/pg_delete_login_token.c
new file mode 100644
index 00000000..d23e541e
--- /dev/null
+++ b/src/backenddb/pg_delete_login_token.c
@@ -0,0 +1,55 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_login_token.c
+ * @brief Implementation of the delete_login_token function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_delete_login_token.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_login_token (
+ void *cls,
+ const char *id,
+ const struct TALER_MERCHANTDB_LoginTokenP *token)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (id),
+ GNUNET_PQ_query_param_auto_from_type (token),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "delete_login_token",
+ "DELETE FROM merchant_login_tokens"
+ " WHERE token=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_login_token",
+ params);
+}
+
diff --git a/src/backenddb/pg_delete_login_token.h b/src/backenddb/pg_delete_login_token.h
new file mode 100644
index 00000000..0ae9f56b
--- /dev/null
+++ b/src/backenddb/pg_delete_login_token.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_login_token.h
+ * @brief implementation of the delete_login_token function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DELETE_LOGIN_TOKEN_H
+#define PG_DELETE_LOGIN_TOKEN_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Delete login token from database.
+ *
+ * @param cls closure
+ * @param id identifier of the instance
+ * @param token value of the token
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_login_token (
+ void *cls,
+ const char *id,
+ const struct TALER_MERCHANTDB_LoginTokenP *token);
+
+
+#endif
diff --git a/src/backenddb/pg_delete_order.c b/src/backenddb/pg_delete_order.c
new file mode 100644
index 00000000..778f4ddd
--- /dev/null
+++ b/src/backenddb/pg_delete_order.c
@@ -0,0 +1,93 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_order.c
+ * @brief Implementation of the delete_order function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_delete_order.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_order (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ bool force)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_bool (force),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_QueryParam params2[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ enum GNUNET_DB_QueryStatus qs2;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "delete_order",
+ "WITH ms AS"
+ "(SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ ", mc AS"
+ "(SELECT paid"
+ " FROM merchant_contract_terms"
+ " JOIN ms USING (merchant_serial)"
+ " WHERE order_id=$2) "
+ "DELETE"
+ " FROM merchant_orders mo"
+ " WHERE order_id=$2"
+ " AND merchant_serial=(SELECT merchant_serial FROM ms)"
+ " AND ( (pay_deadline < $3)"
+ " OR (NOT EXISTS (SELECT paid FROM mc))"
+ " OR ($4 AND (FALSE=(SELECT paid FROM mc))) );");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_order",
+ params);
+ if ( (qs < 0) || (! force) )
+ return qs;
+ PREPARE (pg,
+ "delete_contract",
+ "DELETE"
+ " FROM merchant_contract_terms"
+ " WHERE order_id=$2 AND"
+ " merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND NOT paid;");
+ qs2 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_contract",
+ params2);
+ if (qs2 < 0)
+ return qs2;
+ if (qs2 > 0)
+ return qs2;
+ return qs;
+}
diff --git a/src/backenddb/pg_delete_order.h b/src/backenddb/pg_delete_order.h
new file mode 100644
index 00000000..58f3e5bb
--- /dev/null
+++ b/src/backenddb/pg_delete_order.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_order.h
+ * @brief implementation of the delete_order function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_DELETE_ORDER_H
+#define PG_DELETE_ORDER_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Delete information about an order. Note that the transaction must
+ * enforce that the order is not awaiting payment anymore.
+ *
+ * @param cls closure
+ * @param instance_id instance to delete order of
+ * @param order_id order to delete
+ * @param force delete claimed but unpaid orders as well
+ * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ * if pending payment prevents deletion OR order unknown
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_order (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ bool force);
+
+#endif
diff --git a/src/backenddb/pg_delete_otp.c b/src/backenddb/pg_delete_otp.c
new file mode 100644
index 00000000..5f011a4b
--- /dev/null
+++ b/src/backenddb/pg_delete_otp.c
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_otp.c
+ * @brief Implementation of the delete_otp function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_delete_otp.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_otp (void *cls,
+ const char *instance_id,
+ const char *otp_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (otp_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "delete_otp",
+ "DELETE"
+ " FROM merchant_otp_devices"
+ " WHERE merchant_otp_devices.merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND merchant_otp_devices.otp_id=$2");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_otp",
+ params);
+}
diff --git a/src/backenddb/pg_delete_otp.h b/src/backenddb/pg_delete_otp.h
new file mode 100644
index 00000000..40e67e8a
--- /dev/null
+++ b/src/backenddb/pg_delete_otp.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_otp.h
+ * @brief implementation of the delete_otp function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DELETE_OTP_H
+#define PG_DELETE_OTP_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Delete information about an OTP device.
+ *
+ * @param cls closure
+ * @param instance_id instance to delete OTP device of
+ * @param otp_id otp device to delete
+ * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ * if template unknown.
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_otp (void *cls,
+ const char *instance_id,
+ const char *otp_id);
+
+#endif
diff --git a/src/backenddb/pg_delete_pending_webhook.c b/src/backenddb/pg_delete_pending_webhook.c
new file mode 100644
index 00000000..9121fe41
--- /dev/null
+++ b/src/backenddb/pg_delete_pending_webhook.c
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_pending_webhook.c
+ * @brief Implementation of the delete_pending_webhook function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_delete_pending_webhook.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_pending_webhook (void *cls,
+ uint64_t webhook_pending_serial)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&webhook_pending_serial),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "delete_pending_webhook",
+ "DELETE"
+ " FROM merchant_pending_webhooks"
+ " WHERE webhook_pending_serial=$1");
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_pending_webhook",
+ params);
+}
diff --git a/src/backenddb/pg_delete_pending_webhook.h b/src/backenddb/pg_delete_pending_webhook.h
new file mode 100644
index 00000000..1247cf4e
--- /dev/null
+++ b/src/backenddb/pg_delete_pending_webhook.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_pending_webhook.h
+ * @brief implementation of the delete_pending_webhook function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_DELETE_PENDING_WEBHOOK_H
+#define PG_DELETE_PENDING_WEBHOOK_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Delete a webhook in the pending webhook after the
+ * webhook was completed successfully.
+ *
+ * @param cls closure
+ * @param webhook_pending_serial identifies the row that needs to be deleted in the pending webhook table
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_pending_webhook (void *cls,
+ uint64_t webhook_pending_serial);
+
+#endif
diff --git a/src/backenddb/pg_delete_product.c b/src/backenddb/pg_delete_product.c
new file mode 100644
index 00000000..2d70c9b8
--- /dev/null
+++ b/src/backenddb/pg_delete_product.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_product.c
+ * @brief Implementation of the delete_product function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_delete_product.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_product (void *cls,
+ const char *instance_id,
+ const char *product_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (product_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "delete_product",
+ "DELETE"
+ " FROM merchant_inventory"
+ " WHERE merchant_inventory.merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND merchant_inventory.product_id=$2"
+ " AND product_serial NOT IN "
+ " (SELECT product_serial FROM merchant_order_locks)"
+ " AND product_serial NOT IN "
+ " (SELECT product_serial FROM merchant_inventory_locks)");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_product",
+ params);
+}
diff --git a/src/backenddb/pg_delete_product.h b/src/backenddb/pg_delete_product.h
new file mode 100644
index 00000000..c88e46f4
--- /dev/null
+++ b/src/backenddb/pg_delete_product.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_product.h
+ * @brief implementation of the delete_product function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_DELETE_PRODUCT_H
+#define PG_DELETE_PRODUCT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Delete information about a product. Note that the transaction must
+ * enforce that no stocks are currently locked.
+ *
+ * @param cls closure
+ * @param instance_id instance to delete product of
+ * @param product_id product to delete
+ * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ * if locks prevent deletion OR product unknown
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_product (void *cls,
+ const char *instance_id,
+ const char *product_id);
+
+#endif
diff --git a/src/backenddb/pg_delete_template.c b/src/backenddb/pg_delete_template.c
new file mode 100644
index 00000000..15531c6b
--- /dev/null
+++ b/src/backenddb/pg_delete_template.c
@@ -0,0 +1,55 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_template.c
+ * @brief Implementation of the delete_template function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_delete_template.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_template (void *cls,
+ const char *instance_id,
+ const char *template_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (template_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "delete_template",
+ "DELETE"
+ " FROM merchant_template"
+ " WHERE merchant_template.merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND merchant_template.template_id=$2");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_template",
+ params);
+}
+
diff --git a/src/backenddb/pg_delete_template.h b/src/backenddb/pg_delete_template.h
new file mode 100644
index 00000000..ed0e0cf0
--- /dev/null
+++ b/src/backenddb/pg_delete_template.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_template.h
+ * @brief implementation of the delete_template function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DELETE_TEMPLATE_H
+#define PG_DELETE_TEMPLATE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Delete information about a template.
+ *
+ * @param cls closure
+ * @param instance_id instance to delete template of
+ * @param template_id template to delete
+ * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ * if template unknown.
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_template (void *cls,
+ const char *instance_id,
+ const char *template_id);
+
+
+#endif
diff --git a/src/backenddb/pg_delete_token_family.c b/src/backenddb/pg_delete_token_family.c
new file mode 100644
index 00000000..46a4c01f
--- /dev/null
+++ b/src/backenddb/pg_delete_token_family.c
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_token_family.c
+ * @brief Implementation of the delete_token_family function for Postgres
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_delete_token_family.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_token_family (void *cls,
+ const char *instance_id,
+ const char *token_family_slug)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (token_family_slug),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "delete_token_family",
+ "DELETE"
+ " FROM merchant_token_families"
+ " WHERE merchant_token_families.merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND slug=$2");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_token_family",
+ params);
+} \ No newline at end of file
diff --git a/src/backenddb/pg_delete_token_family.h b/src/backenddb/pg_delete_token_family.h
new file mode 100644
index 00000000..ed380998
--- /dev/null
+++ b/src/backenddb/pg_delete_token_family.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_token_family.h
+ * @brief implementation of the delete_token_family function for Postgres
+ * @author Christian Blättler
+ */
+#ifndef PG_DELETE_TOKEN_FAMILY_H
+#define PG_DELETE_TOKEN_FAMILY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Delete information about a token family.
+ *
+ * @param cls closure
+ * @param instance_id instance to delete token family of
+ * @param token_family_slug slug of token family to delete
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_token_family (void *cls,
+ const char *instance_id,
+ const char *token_family_slug);
+
+#endif
diff --git a/src/backenddb/pg_delete_transfer.c b/src/backenddb/pg_delete_transfer.c
new file mode 100644
index 00000000..f23d97fa
--- /dev/null
+++ b/src/backenddb/pg_delete_transfer.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_transfer.c
+ * @brief Implementation of the delete_transfer function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_delete_transfer.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_transfer (void *cls,
+ const char *instance_id,
+ uint64_t transfer_serial_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_uint64 (&transfer_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "delete_transfer",
+ "DELETE FROM merchant_transfers"
+ " WHERE"
+ " credit_serial=$2"
+ " AND account_serial IN "
+ " (SELECT account_serial "
+ " FROM merchant_accounts"
+ " WHERE merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1))");
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_transfer",
+ params);
+}
diff --git a/src/backenddb/pg_delete_transfer.h b/src/backenddb/pg_delete_transfer.h
new file mode 100644
index 00000000..2be7125b
--- /dev/null
+++ b/src/backenddb/pg_delete_transfer.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_transfer.h
+ * @brief implementation of the delete_transfer function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_DELETE_TRANSFER_H
+#define PG_DELETE_TRANSFER_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Delete information about a transfer. Note that transfers
+ * confirmed by the exchange cannot be deleted anymore.
+ *
+ * @param cls closure
+ * @param instance_id instance to delete transfer of
+ * @param transfer_serial_id transfer to delete
+ * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ * if deletion is prohibited OR transfer is unknown
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_transfer (void *cls,
+ const char *instance_id,
+ uint64_t transfer_serial_id);
+
+#endif
diff --git a/src/backenddb/pg_delete_webhook.c b/src/backenddb/pg_delete_webhook.c
new file mode 100644
index 00000000..ba2173cb
--- /dev/null
+++ b/src/backenddb/pg_delete_webhook.c
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_webhook.c
+ * @brief Implementation of the delete_webhook function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_delete_webhook.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_webhook (void *cls,
+ const char *instance_id,
+ const char *webhook_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (webhook_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "delete_webhook",
+ "DELETE"
+ " FROM merchant_webhook"
+ " WHERE merchant_webhook.merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND merchant_webhook.webhook_id=$2");
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_webhook",
+ params);
+}
diff --git a/src/backenddb/pg_delete_webhook.h b/src/backenddb/pg_delete_webhook.h
new file mode 100644
index 00000000..02f021ab
--- /dev/null
+++ b/src/backenddb/pg_delete_webhook.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_webhook.h
+ * @brief implementation of the delete_webhook function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_DELETE_WEBHOOK_H
+#define PG_DELETE_WEBHOOK_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Delete information about a webhook.
+ *
+ * @param cls closure
+ * @param instance_id instance to delete webhook of
+ * @param webhook_id webhook to delete
+ * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ * if webhook unknown.
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_webhook (void *cls,
+ const char *instance_id,
+ const char *webhook_id);
+
+#endif
diff --git a/src/backenddb/pg_expire_locks.c b/src/backenddb/pg_expire_locks.c
new file mode 100644
index 00000000..7a3c3bac
--- /dev/null
+++ b/src/backenddb/pg_expire_locks.c
@@ -0,0 +1,86 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_expire_locks.c
+ * @brief Implementation of the expire_locks function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_expire_locks.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_expire_locks (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs1;
+ enum GNUNET_DB_QueryStatus qs2;
+ enum GNUNET_DB_QueryStatus qs3;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "unlock_products",
+ "DELETE FROM merchant_inventory_locks"
+ " WHERE expiration < $1");
+ qs1 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "unlock_products",
+ params);
+ if (qs1 < 0)
+ {
+ GNUNET_break (0);
+ return qs1;
+ }
+ PREPARE (pg,
+ "unlock_orders",
+ "DELETE FROM merchant_orders"
+ " WHERE pay_deadline < $1");
+ qs2 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "unlock_orders",
+ params);
+ if (qs2 < 0)
+ {
+ GNUNET_break (0);
+ return qs2;
+ }
+ PREPARE (pg,
+ "unlock_contracts",
+ "DELETE FROM merchant_contract_terms"
+ " WHERE NOT paid"
+ " AND pay_deadline < $1");
+ qs3 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "unlock_contracts",
+ params);
+ if (qs3 < 0)
+ {
+ GNUNET_break (0);
+ return qs3;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Released %d+%d+%d locks\n",
+ qs1,
+ qs2,
+ qs3);
+ return qs1 + qs2 + qs3;
+}
diff --git a/src/backenddb/pg_expire_locks.h b/src/backenddb/pg_expire_locks.h
new file mode 100644
index 00000000..55f1ff48
--- /dev/null
+++ b/src/backenddb/pg_expire_locks.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_expire_locks.h
+ * @brief implementation of the expire_locks function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_EXPIRE_LOCKS_H
+#define PG_EXPIRE_LOCKS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Release all expired product locks, including
+ * those from expired offers -- across all
+ * instances.
+ *
+ * @param cls closure
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_expire_locks (void *cls);
+
+#endif
diff --git a/src/backenddb/pg_helper.c b/src/backenddb/pg_helper.c
new file mode 100644
index 00000000..802abc21
--- /dev/null
+++ b/src/backenddb/pg_helper.c
@@ -0,0 +1,138 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_helper.c
+ * @brief shared internal definitions for postgres DB plugin
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "pg_helper.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_pq_lib.h>
+#include <taler/taler_util.h>
+#include <taler/taler_pq_lib.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_mhd_lib.h>
+
+
+enum GNUNET_GenericReturnValue
+TMH_PG_start (void *cls,
+ const char *name)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ GNUNET_assert (NULL != name);
+ check_connection (pg);
+ postgres_preflight (pg);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting merchant DB transaction `%s'\n",
+ name);
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ TALER_LOG_ERROR ("Failed to start transaction\n");
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ pg->transaction_name = name;
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TMH_PG_start_read_committed (void *cls,
+ const char *name)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL READ COMMITTED"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ GNUNET_assert (NULL != name);
+ check_connection (pg);
+ postgres_preflight (pg);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting merchant DB transaction %s (READ COMMITTED)\n",
+ name);
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ TALER_LOG_ERROR ("Failed to start transaction\n");
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ pg->transaction_name = name;
+ return GNUNET_OK;
+}
+
+
+void
+TMH_PG_rollback (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("ROLLBACK"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ if (NULL == pg->transaction_name)
+ return;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Rolling back merchant DB transaction `%s'\n",
+ pg->transaction_name);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_PQ_exec_statements (pg->conn,
+ es));
+ pg->transaction_name = NULL;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_commit (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Committing merchant DB transaction %s\n",
+ pg->transaction_name);
+ check_connection (pg);
+ PREPARE (pg,
+ "merchant_commit",
+ "COMMIT");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "merchant_commit",
+ params);
+ if (qs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to commit transaction\n");
+ TMH_PG_rollback (pg);
+ return qs;
+ }
+ pg->transaction_name = NULL;
+ return qs;
+}
diff --git a/src/backenddb/pg_helper.h b/src/backenddb/pg_helper.h
new file mode 100644
index 00000000..5f677fc5
--- /dev/null
+++ b/src/backenddb/pg_helper.h
@@ -0,0 +1,158 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_helper.h
+ * @brief shared internal definitions for postgres DB plugin
+ * @author Christian Grothoff
+ */
+#ifndef PG_HELPER_H
+#define PG_HELPER_H
+
+#include <gnunet/gnunet_db_lib.h>
+
+/**
+ * Type of the "cls" argument given to each of the functions in
+ * our API.
+ */
+struct PostgresClosure
+{
+
+ /**
+ * Postgres connection handle.
+ */
+ struct GNUNET_PQ_Context *conn;
+
+ /**
+ * Directory with SQL statements to run to create tables.
+ */
+ char *sql_dir;
+
+ /**
+ * Underlying configuration.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Name of the currently active transaction, NULL if none is active.
+ */
+ const char *transaction_name;
+
+ /**
+ * How many times have we connected to the DB.
+ */
+ uint64_t prep_gen;
+
+};
+
+
+/**
+ * Prepares SQL statement @a sql under @a name for
+ * connection @a pg once.
+ * Returns with #GNUNET_DB_STATUS_HARD_ERROR on failure.
+ *
+ * @param pg a `struct PostgresClosure`
+ * @param name name to prepare the statement under
+ * @param sql actual SQL text
+ */
+#define PREPARE(pg,name,sql) \
+ do { \
+ static unsigned long long gen; \
+ \
+ if (gen < pg->prep_gen) \
+ { \
+ struct GNUNET_PQ_PreparedStatement ps[] = { \
+ GNUNET_PQ_make_prepare (name, sql), \
+ GNUNET_PQ_PREPARED_STATEMENT_END \
+ }; \
+ \
+ if (GNUNET_OK != \
+ GNUNET_PQ_prepare_statements (pg->conn, \
+ ps)) \
+ { \
+ GNUNET_break (0); \
+ return GNUNET_DB_STATUS_HARD_ERROR; \
+ } \
+ gen = pg->prep_gen; \
+ } \
+ } while (0)
+
+
+/**
+ * Check that the database connection is still up
+ * and automatically reconnects unless we are
+ * already inside of a transaction.
+ *
+ * @param pg connection to check
+ */
+void
+check_connection (struct PostgresClosure *pg);
+
+
+/**
+ * Do a pre-flight check that we are not in an uncommitted transaction.
+ * If we are, die.
+ * Does not return anything, as we will continue regardless of the outcome.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ */
+void
+postgres_preflight (void *cls);
+
+/**
+ * Start a transaction.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging),
+ * must point to a constant
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TMH_PG_start (void *cls,
+ const char *name);
+
+
+/**
+ * Start a transaction in 'read committed' mode.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging),
+ * must point to a constant
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TMH_PG_start_read_committed (void *cls,
+ const char *name);
+
+/**
+ * Roll back the current transaction of a database connection.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ */
+void
+TMH_PG_rollback (void *cls);
+
+
+/**
+ * Commit the current transaction of a database connection.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_commit (void *cls);
+
+
+#endif
diff --git a/src/backenddb/pg_inactivate_account.c b/src/backenddb/pg_inactivate_account.c
new file mode 100644
index 00000000..7e0d10ae
--- /dev/null
+++ b/src/backenddb/pg_inactivate_account.c
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_inactivate_account.c
+ * @brief Implementation of the inactivate_account function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_inactivate_account.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_inactivate_account (void *cls,
+ const char *merchant_id,
+ const struct TALER_MerchantWireHashP *h_wire)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (merchant_id),
+ GNUNET_PQ_query_param_auto_from_type (h_wire),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "inactivate_account",
+ "UPDATE merchant_accounts SET"
+ " active=FALSE"
+ " WHERE h_wire=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "inactivate_account",
+ params);
+}
diff --git a/src/backenddb/pg_inactivate_account.h b/src/backenddb/pg_inactivate_account.h
new file mode 100644
index 00000000..5146faca
--- /dev/null
+++ b/src/backenddb/pg_inactivate_account.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_inactivate_account.h
+ * @brief implementation of the inactivate_account function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_INACTIVATE_ACCOUNT_H
+#define PG_INACTIVATE_ACCOUNT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Set an instance's account in our database to "inactive".
+ *
+ * @param cls closure
+ * @param merchant_id merchant backend instance ID
+ * @param h_wire hash of the wire account to set to inactive
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_inactivate_account (void *cls,
+ const char *merchant_id,
+ const struct TALER_MerchantWireHashP *h_wire);
+
+
+#endif
diff --git a/src/backenddb/pg_increase_refund.c b/src/backenddb/pg_increase_refund.c
new file mode 100644
index 00000000..eef7adc6
--- /dev/null
+++ b/src/backenddb/pg_increase_refund.c
@@ -0,0 +1,507 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_increase_refund.c
+ * @brief Implementation of the increase_refund function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_increase_refund.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #process_refund_cb().
+ */
+struct FindRefundContext
+{
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Updated to reflect total amount refunded so far.
+ */
+ struct TALER_Amount refunded_amount;
+
+ /**
+ * Set to the largest refund transaction ID encountered.
+ */
+ uint64_t max_rtransaction_id;
+
+ /**
+ * Set to true on hard errors.
+ */
+ bool err;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure, our `struct FindRefundContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+process_refund_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct FindRefundContext *ictx = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ /* Sum up existing refunds */
+ struct TALER_Amount acc;
+ uint64_t rtransaction_id;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount_with_currency ("refund_amount",
+ &acc),
+ GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
+ &rtransaction_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ictx->err = true;
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&ictx->refunded_amount,
+ &acc))
+ {
+ GNUNET_break (0);
+ ictx->err = true;
+ return;
+ }
+ if (0 >
+ TALER_amount_add (&ictx->refunded_amount,
+ &ictx->refunded_amount,
+ &acc))
+ {
+ GNUNET_break (0);
+ ictx->err = true;
+ return;
+ }
+ ictx->max_rtransaction_id = GNUNET_MAX (ictx->max_rtransaction_id,
+ rtransaction_id);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Found refund of %s\n",
+ TALER_amount2s (&acc));
+ }
+}
+
+
+/**
+ * Closure for #process_deposits_for_refund_cb().
+ */
+struct InsertRefundContext
+{
+ /**
+ * Used to provide a connection to the db
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Amount to which increase the refund for this contract
+ */
+ const struct TALER_Amount *refund;
+
+ /**
+ * Human-readable reason behind this refund
+ */
+ const char *reason;
+
+ /**
+ * Transaction status code.
+ */
+ enum TALER_MERCHANTDB_RefundStatus rs;
+};
+
+
+/**
+ * Data extracted per coin.
+ */
+struct RefundCoinData
+{
+
+ /**
+ * Public key of a coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Amount deposited for this coin.
+ */
+ struct TALER_Amount deposited_with_fee;
+
+ /**
+ * Amount refunded already for this coin.
+ */
+ struct TALER_Amount refund_amount;
+
+ /**
+ * Order serial (actually not really per-coin).
+ */
+ uint64_t order_serial;
+
+ /**
+ * Maximum rtransaction_id for this coin so far.
+ */
+ uint64_t max_rtransaction_id;
+
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure, our `struct InsertRefundContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+process_deposits_for_refund_cb (
+ void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct InsertRefundContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_Amount current_refund;
+ struct RefundCoinData rcd[GNUNET_NZL (num_results)];
+ struct GNUNET_TIME_Timestamp now;
+
+ now = GNUNET_TIME_timestamp_get ();
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (ctx->refund->currency,
+ &current_refund));
+ memset (rcd, 0, sizeof (rcd));
+ /* Pass 1: Collect amount of existing refunds into current_refund.
+ * Also store existing refunded amount for each deposit in deposit_refund. */
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &rcd[i].coin_pub),
+ GNUNET_PQ_result_spec_uint64 ("order_serial",
+ &rcd[i].order_serial),
+ TALER_PQ_result_spec_amount_with_currency ("amount_with_fee",
+ &rcd[i].deposited_with_fee),
+ GNUNET_PQ_result_spec_end
+ };
+ struct FindRefundContext ictx = {
+ .pg = pg
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
+ return;
+ }
+
+ if (0 != strcmp (rcd[i].deposited_with_fee.currency,
+ ctx->refund->currency))
+ {
+ GNUNET_break_op (0);
+ ctx->rs = TALER_MERCHANTDB_RS_BAD_CURRENCY;
+ return;
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus ires;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&rcd[i].coin_pub),
+ GNUNET_PQ_query_param_uint64 (&rcd[i].order_serial),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (ctx->refund->currency,
+ &ictx.refunded_amount));
+ ires = GNUNET_PQ_eval_prepared_multi_select (ctx->pg->conn,
+ "find_refunds_by_coin",
+ params,
+ &process_refund_cb,
+ &ictx);
+ if ( (ictx.err) ||
+ (GNUNET_DB_STATUS_HARD_ERROR == ires) )
+ {
+ GNUNET_break (0);
+ ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
+ return;
+ }
+ if (GNUNET_DB_STATUS_SOFT_ERROR == ires)
+ {
+ ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
+ return;
+ }
+ }
+ if (0 >
+ TALER_amount_add (&current_refund,
+ &current_refund,
+ &ictx.refunded_amount))
+ {
+ GNUNET_break (0);
+ ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
+ return;
+ }
+ rcd[i].refund_amount = ictx.refunded_amount;
+ rcd[i].max_rtransaction_id = ictx.max_rtransaction_id;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Existing refund for coin %s is %s\n",
+ TALER_B2S (&rcd[i].coin_pub),
+ TALER_amount2s (&ictx.refunded_amount));
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Total existing refund is %s\n",
+ TALER_amount2s (&current_refund));
+
+ /* stop immediately if we are 'done' === amount already
+ * refunded. */
+ if (0 >= TALER_amount_cmp (ctx->refund,
+ &current_refund))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Existing refund of %s at or above requested refund. Finished early.\n",
+ TALER_amount2s (&current_refund));
+ ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
+ return;
+ }
+
+ /* Phase 2: Try to increase current refund until it matches desired refund */
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ const struct TALER_Amount *increment;
+ struct TALER_Amount left;
+ struct TALER_Amount remaining_refund;
+
+ /* How much of the coin is left after the existing refunds? */
+ if (0 >
+ TALER_amount_subtract (&left,
+ &rcd[i].deposited_with_fee,
+ &rcd[i].refund_amount))
+ {
+ GNUNET_break (0);
+ ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
+ return;
+ }
+
+ if ( (0 == left.value) &&
+ (0 == left.fraction) )
+ {
+ /* coin was fully refunded, move to next coin */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Coin %s fully refunded, moving to next coin\n",
+ TALER_B2S (&rcd[i].coin_pub));
+ continue;
+ }
+
+ rcd[i].max_rtransaction_id++;
+ /* How much of the refund is still to be paid back? */
+ if (0 >
+ TALER_amount_subtract (&remaining_refund,
+ ctx->refund,
+ &current_refund))
+ {
+ GNUNET_break (0);
+ ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
+ return;
+ }
+
+ /* By how much will we increase the refund for this coin? */
+ if (0 >= TALER_amount_cmp (&remaining_refund,
+ &left))
+ {
+ /* remaining_refund <= left */
+ increment = &remaining_refund;
+ }
+ else
+ {
+ increment = &left;
+ }
+
+ if (0 >
+ TALER_amount_add (&current_refund,
+ &current_refund,
+ increment))
+ {
+ GNUNET_break (0);
+ ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
+ return;
+ }
+
+ /* actually run the refund */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Coin %s deposit amount is %s\n",
+ TALER_B2S (&rcd[i].coin_pub),
+ TALER_amount2s (&rcd[i].deposited_with_fee));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Coin %s refund will be incremented by %s\n",
+ TALER_B2S (&rcd[i].coin_pub),
+ TALER_amount2s (increment));
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&rcd[i].order_serial),
+ GNUNET_PQ_query_param_uint64 (&rcd[i].max_rtransaction_id), /* already inc'ed */
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_auto_from_type (&rcd[i].coin_pub),
+ GNUNET_PQ_query_param_string (ctx->reason),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ increment),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_refund",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
+ return;
+ default:
+ ctx->rs = (enum TALER_MERCHANTDB_RefundStatus) qs;
+ break;
+ }
+ }
+
+ /* stop immediately if we are done */
+ if (0 == TALER_amount_cmp (ctx->refund,
+ &current_refund))
+ {
+ ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
+ return;
+ }
+ }
+
+ /**
+ * We end up here if not all of the refund has been covered.
+ * Although this should be checked as the business should never
+ * issue a refund bigger than the contract's actual price, we cannot
+ * rely upon the frontend being correct.
+ */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "The refund of %s is bigger than the order's value\n",
+ TALER_amount2s (ctx->refund));
+ ctx->rs = TALER_MERCHANTDB_RS_TOO_HIGH;
+}
+
+
+enum TALER_MERCHANTDB_RefundStatus
+TMH_PG_increase_refund (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const struct TALER_Amount *refund,
+ const char *reason)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct InsertRefundContext ctx = {
+ .pg = pg,
+ .refund = refund,
+ .reason = reason
+ };
+
+ PREPARE (pg,
+ "insert_refund",
+ "INSERT INTO merchant_refunds"
+ "(order_serial"
+ ",rtransaction_id"
+ ",refund_timestamp"
+ ",coin_pub"
+ ",reason"
+ ",refund_amount"
+ ") VALUES"
+ "($1, $2, $3, $4, $5, $6)");
+ PREPARE (pg,
+ "find_refunds_by_coin",
+ "SELECT"
+ " refund_amount"
+ ",rtransaction_id"
+ " FROM merchant_refunds"
+ " WHERE coin_pub=$1"
+ " AND order_serial=$2");
+ PREPARE (pg,
+ "find_deposits_for_refund",
+ "SELECT"
+ " dep.coin_pub"
+ ",dco.order_serial"
+ ",dep.amount_with_fee"
+ " FROM merchant_deposits dep"
+ " JOIN merchant_deposit_confirmations dco"
+ " USING (deposit_confirmation_serial)"
+ " WHERE order_serial="
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE order_id=$2"
+ " AND paid"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1))");
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Asked to refund %s on order %s\n",
+ TALER_amount2s (refund),
+ order_id);
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "find_deposits_for_refund",
+ params,
+ &process_deposits_for_refund_cb,
+ &ctx);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* never paid, means we clearly cannot refund anything */
+ return TALER_MERCHANTDB_RS_NO_SUCH_ORDER;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return TALER_MERCHANTDB_RS_SOFT_ERROR;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ return TALER_MERCHANTDB_RS_HARD_ERROR;
+ default:
+ /* Got one or more deposits */
+ return ctx.rs;
+ }
+}
diff --git a/src/backenddb/pg_increase_refund.h b/src/backenddb/pg_increase_refund.h
new file mode 100644
index 00000000..0fe8a470
--- /dev/null
+++ b/src/backenddb/pg_increase_refund.h
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_increase_refund.h
+ * @brief implementation of the increase_refund function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INCREASE_REFUND_H
+#define PG_INCREASE_REFUND_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Function called when some backoffice staff decides to award or
+ * increase the refund on an existing contract. This function
+ * MUST be called from within a transaction scope setup by the
+ * caller as it executes mulrewardle SQL statements.
+ *
+ * @param cls closure
+ * @param instance_id instance identifier
+ * @param order_id the order to increase the refund for
+ * @param refund maximum refund to return to the customer for this contract
+ * @param reason 0-terminated UTF-8 string giving the reason why the customer
+ * got a refund (free form, business-specific)
+ * @return transaction status
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a refund is ABOVE the amount we
+ * were originally paid and thus the transaction failed;
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid,
+ * regardless of whether it actually increased the refund beyond
+ * what was already refunded (idempotency!)
+ */
+enum TALER_MERCHANTDB_RefundStatus
+TMH_PG_increase_refund (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const struct TALER_Amount *refund,
+ const char *reason);
+
+
+#endif
diff --git a/src/backenddb/pg_insert_account.c b/src/backenddb/pg_insert_account.c
new file mode 100644
index 00000000..3b57b0ba
--- /dev/null
+++ b/src/backenddb/pg_insert_account.c
@@ -0,0 +1,68 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_account.c
+ * @brief Implementation of the insert_account function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_account.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_account (
+ void *cls,
+ const char *id,
+ const struct TALER_MERCHANTDB_AccountDetails *account_details)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (id),
+ GNUNET_PQ_query_param_auto_from_type (&account_details->h_wire),
+ GNUNET_PQ_query_param_auto_from_type (&account_details->salt),
+ GNUNET_PQ_query_param_string (account_details->payto_uri),
+ NULL ==account_details->credit_facade_url
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (account_details->credit_facade_url),
+ NULL == account_details->credit_facade_credentials
+ ? GNUNET_PQ_query_param_null ()
+ : TALER_PQ_query_param_json (account_details->credit_facade_credentials),
+ GNUNET_PQ_query_param_bool (account_details->active),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_account",
+ "INSERT INTO merchant_accounts"
+ "(merchant_serial"
+ ",h_wire"
+ ",salt"
+ ",payto_uri"
+ ",credit_facade_url"
+ ",credit_facade_credentials"
+ ",active)"
+ " SELECT merchant_serial, $2, $3, $4, $5, $6, $7"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_account",
+ params);
+}
diff --git a/src/backenddb/pg_insert_account.h b/src/backenddb/pg_insert_account.h
new file mode 100644
index 00000000..463bc527
--- /dev/null
+++ b/src/backenddb/pg_insert_account.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_account.h
+ * @brief implementation of the insert_account function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_ACCOUNT_H
+#define PG_INSERT_ACCOUNT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Insert information about an instance's account into our database.
+ *
+ * @param cls closure
+ * @param id identifier of the instance
+ * @param account_details details about the account
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_account (
+ void *cls,
+ const char *id,
+ const struct TALER_MERCHANTDB_AccountDetails *account_details);
+
+
+#endif
diff --git a/src/backenddb/pg_insert_contract_terms.c b/src/backenddb/pg_insert_contract_terms.c
new file mode 100644
index 00000000..2bc6ab86
--- /dev/null
+++ b/src/backenddb/pg_insert_contract_terms.c
@@ -0,0 +1,132 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_contract_terms.c
+ * @brief Implementation of the insert_contract_terms function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_contract_terms.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_contract_terms (
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ json_t *contract_terms,
+ uint64_t *order_serial)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp pay_deadline;
+ struct GNUNET_TIME_Timestamp refund_deadline;
+ const char *fulfillment_url;
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ if (GNUNET_OK !=
+ TALER_JSON_contract_hash (contract_terms,
+ &h_contract_terms))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("pay_deadline",
+ &pay_deadline),
+ GNUNET_JSON_spec_timestamp ("refund_deadline",
+ &refund_deadline),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (NULL,
+ contract_terms,
+ spec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+
+ fulfillment_url =
+ json_string_value (json_object_get (contract_terms,
+ "fulfillment_url"));
+ check_connection (pg);
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (order_id),
+ TALER_PQ_query_param_json (contract_terms),
+ GNUNET_PQ_query_param_auto_from_type (&h_contract_terms),
+ GNUNET_PQ_query_param_timestamp (&pay_deadline),
+ GNUNET_PQ_query_param_timestamp (&refund_deadline),
+ (NULL == fulfillment_url)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (fulfillment_url),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("order_serial",
+ order_serial),
+ GNUNET_PQ_result_spec_end
+ };
+ PREPARE (pg,
+ "insert_contract_terms",
+ "INSERT INTO merchant_contract_terms"
+ "(order_serial"
+ ",merchant_serial"
+ ",order_id"
+ ",contract_terms"
+ ",h_contract_terms"
+ ",creation_time"
+ ",pay_deadline"
+ ",refund_deadline"
+ ",fulfillment_url"
+ ",claim_token"
+ ",pos_key"
+ ",pos_algorithm)"
+ "SELECT"
+ " mo.order_serial"
+ ",mo.merchant_serial"
+ ",mo.order_id"
+ ",$3" /* contract_terms */
+ ",$4" /* h_contract_terms */
+ ",mo.creation_time"
+ ",$5" /* pay_deadline */
+ ",$6" /* refund_deadline */
+ ",$7" /* fulfillment_url */
+ ",mo.claim_token"
+ ",mo.pos_key"
+ ",mo.pos_algorithm"
+ " FROM merchant_orders mo"
+ " WHERE order_id=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " RETURNING order_serial");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "insert_contract_terms",
+ params,
+ rs);
+ }
+}
diff --git a/src/backenddb/pg_insert_contract_terms.h b/src/backenddb/pg_insert_contract_terms.h
new file mode 100644
index 00000000..8f22f5b8
--- /dev/null
+++ b/src/backenddb/pg_insert_contract_terms.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_contract_terms.h
+ * @brief implementation of the insert_contract_terms function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_INSERT_CONTRACT_TERMS_H
+#define PG_INSERT_CONTRACT_TERMS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Store contract terms given its @a order_id. Note that some attributes are
+ * expected to be calculated inside of the function, like the hash of the
+ * contract terms (to be hashed), the creation_time and pay_deadline (to be
+ * obtained from the merchant_orders table). The "session_id" should be
+ * initially set to the empty string. The "fulfillment_url" and "refund_deadline"
+ * must be extracted from @a contract_terms.
+ *
+ * @param cls closure
+ * @param instance_id instance's identifier
+ * @param order_id order_id used to store
+ * @param contract_terms contract terms to store
+ * @param[out] order_serial set to the serial of the order
+ * @return transaction status, #GNUNET_DB_STATUS_HARD_ERROR if @a contract_terms
+ * is malformed
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_contract_terms (
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ json_t *contract_terms,
+ uint64_t *order_serial);
+
+#endif
diff --git a/src/backenddb/pg_insert_deposit.c b/src/backenddb/pg_insert_deposit.c
new file mode 100644
index 00000000..8e77e24a
--- /dev/null
+++ b/src/backenddb/pg_insert_deposit.c
@@ -0,0 +1,76 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_deposit.c
+ * @brief Implementation of the insert_deposit function for Postgres
+ * @author Christian Grothoff
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_deposit.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_deposit (
+ void *cls,
+ uint32_t offset,
+ uint64_t deposit_confirmation_serial,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_Amount *refund_fee)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&deposit_confirmation_serial),
+ GNUNET_PQ_query_param_uint32 (&offset),
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (coin_sig),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ amount_with_fee),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ deposit_fee),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ refund_fee),
+ GNUNET_PQ_query_param_end
+ };
+
+ /* no preflight check here, run in transaction by caller! */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Storing deposit for coin_pub: `%s', amount_with_fee: %s\n",
+ TALER_B2S (coin_pub),
+ TALER_amount2s (amount_with_fee));
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_deposit",
+ "INSERT INTO merchant_deposits"
+ "(deposit_confirmation_serial"
+ ",coin_offset"
+ ",coin_pub"
+ ",coin_sig"
+ ",amount_with_fee"
+ ",deposit_fee"
+ ",refund_fee"
+ ") VALUES ($1, $2, $3, $4, $5, $6, $7)");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_deposit",
+ params);
+}
diff --git a/src/backenddb/pg_insert_deposit.h b/src/backenddb/pg_insert_deposit.h
new file mode 100644
index 00000000..1d08e39e
--- /dev/null
+++ b/src/backenddb/pg_insert_deposit.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_deposit.h
+ * @brief implementation of the insert_deposit function for Postgres
+ * @author IvánAvalos
+ */
+#ifndef PG_INSERT_DEPOSIT_H
+#define PG_INSERT_DEPOSIT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Insert payment confirmation from the exchange into the database.
+ *
+ * @param cls closure
+ * @param offset offset of the coin in the batch
+ * @param deposit_confirmation_serial which deposit confirmation is this coin part of
+ * @param coin_pub public key of the coin
+ * @param coin_sig signature of the coin
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param refund_fee fee the exchange charges to refund this coin
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_deposit (
+ void *cls,
+ uint32_t offset,
+ uint64_t deposit_confirmation_serial,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_Amount *refund_fee);
+
+#endif
diff --git a/src/backenddb/pg_insert_deposit_confirmation.c b/src/backenddb/pg_insert_deposit_confirmation.c
new file mode 100644
index 00000000..f23bf252
--- /dev/null
+++ b/src/backenddb/pg_insert_deposit_confirmation.c
@@ -0,0 +1,134 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023, 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_deposit_confirmation.c
+ * @brief Implementation of the insert_deposit_confirmation function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_deposit.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_deposit_confirmation (
+ void *cls,
+ const char *instance_id,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const char *exchange_url,
+ struct GNUNET_TIME_Timestamp wire_transfer_deadline,
+ const struct TALER_Amount *total_without_fees,
+ const struct TALER_Amount *wire_fee,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ uint64_t *deposit_confirmation_serial_id)
+{
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE)
+ };
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_timestamp (&deposit_timestamp),
+ GNUNET_PQ_query_param_string (exchange_url),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ total_without_fees),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ wire_fee),
+ GNUNET_PQ_query_param_auto_from_type (h_wire), /* 7 */
+ GNUNET_PQ_query_param_auto_from_type (exchange_sig),
+ GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+ GNUNET_PQ_query_param_timestamp (&wire_transfer_deadline),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("deposit_confirmation_serial",
+ deposit_confirmation_serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* no preflight check here, run in transaction by caller! */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Storing deposit confirmation for instance `%s' h_contract_terms `%s', total_without_fees: %s and wire transfer deadline in %s\n",
+ instance_id,
+ GNUNET_h2s (&h_contract_terms->hash),
+ TALER_amount2s (total_without_fees),
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_remaining (
+ wire_transfer_deadline.abs_time),
+ true));
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_deposit_confirmation",
+ "WITH md AS"
+ " (SELECT account_serial, merchant_serial"
+ " FROM merchant_accounts"
+ " WHERE h_wire=$7"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1))"
+ ", ed AS"
+ " (SELECT signkey_serial"
+ " FROM merchant_exchange_signing_keys"
+ " WHERE exchange_pub=$9"
+ " ORDER BY start_date DESC"
+ " LIMIT 1)"
+ "INSERT INTO merchant_deposit_confirmations"
+ "(order_serial"
+ ",deposit_timestamp"
+ ",exchange_url"
+ ",total_without_fee"
+ ",wire_fee"
+ ",exchange_sig"
+ ",wire_transfer_deadline"
+ ",signkey_serial"
+ ",account_serial)"
+ " SELECT "
+ " order_serial"
+ " ,$3, $4, $5, $6, $8, $10"
+ " ,ed.signkey_serial"
+ " ,md.account_serial"
+ " FROM merchant_contract_terms"
+ " JOIN md USING (merchant_serial)"
+ " FULL OUTER JOIN ed ON TRUE"
+ " WHERE h_contract_terms=$2"
+ " RETURNING deposit_confirmation_serial");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "insert_deposit_confirmation",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ /* inform taler-merchant-depositcheck about new deadline */
+ struct GNUNET_TIME_AbsoluteNBO nbo;
+
+ nbo = GNUNET_TIME_absolute_hton (wire_transfer_deadline.abs_time);
+ GNUNET_PQ_event_notify (pg->conn,
+ &es,
+ &nbo,
+ sizeof (nbo));
+ }
+ return qs;
+}
diff --git a/src/backenddb/pg_insert_deposit_confirmation.h b/src/backenddb/pg_insert_deposit_confirmation.h
new file mode 100644
index 00000000..f033e237
--- /dev/null
+++ b/src/backenddb/pg_insert_deposit_confirmation.h
@@ -0,0 +1,60 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_deposit_confirmation.h
+ * @brief implementation of the insert_deposit_confirmation function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_DEPOSIT_CONFIRMATION_H
+#define PG_INSERT_DEPOSIT_CONFIRMATION_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Insert deposit confirmation from the exchange into the database.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup deposits for
+ * @param deposit_timestamp time when the exchange generated the deposit confirmation
+ * @param h_contract_terms proposal data's hashcode
+ * @param exchange_url URL of the exchange that issued @a coin_pub
+ * @param wire_transfer_deadline when do we expect the wire transfer from the exchange
+ * @param total_without_fees deposited total in the batch without fees
+ * @param wire_fee wire fee the exchange charges
+ * @param h_wire hash of the wire details of the target account of the merchant
+ * @param exchange_sig signature from exchange that coin was accepted
+ * @param exchange_pub signing key that was used for @a exchange_sig
+ * @param[out] deposit_confirmation_serial_id set to the table row
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_deposit_confirmation (
+ void *cls,
+ const char *instance_id,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const char *exchange_url,
+ struct GNUNET_TIME_Timestamp wire_transfer_deadline,
+ const struct TALER_Amount *total_without_fees,
+ const struct TALER_Amount *wire_fee,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ uint64_t *deposit_confirmation_serial_id);
+
+#endif
diff --git a/src/backenddb/pg_insert_deposit_to_transfer.c b/src/backenddb/pg_insert_deposit_to_transfer.c
new file mode 100644
index 00000000..7b6e6a79
--- /dev/null
+++ b/src/backenddb/pg_insert_deposit_to_transfer.c
@@ -0,0 +1,83 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_deposit_to_transfer.c
+ * @brief Implementation of the insert_deposit_to_transfer function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_deposit_to_transfer.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_deposit_to_transfer (
+ void *cls,
+ uint64_t deposit_serial,
+ const struct TALER_EXCHANGE_DepositData *dd)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&deposit_serial),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ &dd->coin_contribution),
+ GNUNET_PQ_query_param_timestamp (&dd->execution_time),
+ GNUNET_PQ_query_param_auto_from_type (&dd->exchange_sig),
+ GNUNET_PQ_query_param_auto_from_type (&dd->exchange_pub),
+ GNUNET_PQ_query_param_auto_from_type (&dd->wtid),
+ GNUNET_PQ_query_param_end
+ };
+ bool wpc;
+ bool conflict;
+ bool no_exchange_pub;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("out_wire_pending_cleared",
+ &wpc),
+ GNUNET_PQ_result_spec_bool ("out_conflict",
+ &conflict),
+ GNUNET_PQ_result_spec_bool ("out_no_exchange_pub",
+ &no_exchange_pub),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "insert_deposit_to_transfer",
+ "SELECT"
+ " out_wire_pending_cleared"
+ " ,out_conflict"
+ " ,out_no_exchange_pub"
+ " FROM merchant_insert_deposit_to_transfer"
+ " ($1,$2,$3,$4,$5,$6);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "insert_deposit_to_transfer",
+ params,
+ rs);
+ if (qs <= 0)
+ return qs;
+ if (no_exchange_pub)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Exchange public key unknown (bug!?)\n");
+ if (wpc)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Wire pending flag cleared (good!)\n");
+ if (conflict)
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ return qs;
+}
diff --git a/src/backenddb/pg_insert_deposit_to_transfer.h b/src/backenddb/pg_insert_deposit_to_transfer.h
new file mode 100644
index 00000000..049cbcac
--- /dev/null
+++ b/src/backenddb/pg_insert_deposit_to_transfer.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_deposit_to_transfer.h
+ * @brief implementation of the insert_deposit_to_transfer function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_DEPOSIT_TO_TRANSFER_H
+#define PG_INSERT_DEPOSIT_TO_TRANSFER_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Insert wire transfer details for a deposit.
+ *
+ * @param cls closure
+ * @param deposit_serial serial number of the deposit
+ * @param dd deposit transfer data from the exchange to store
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_deposit_to_transfer (
+ void *cls,
+ uint64_t deposit_serial,
+ const struct TALER_EXCHANGE_DepositData *dd);
+
+
+#endif
diff --git a/src/backenddb/pg_insert_deposit_to_transfer.sql b/src/backenddb/pg_insert_deposit_to_transfer.sql
new file mode 100644
index 00000000..b2e587f1
--- /dev/null
+++ b/src/backenddb/pg_insert_deposit_to_transfer.sql
@@ -0,0 +1,160 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE OR REPLACE FUNCTION merchant_insert_deposit_to_transfer (
+ IN in_deposit_serial INT8,
+ IN in_amount_with_fee taler_amount_currency,
+ IN in_execution_time INT8,
+ IN in_exchange_sig BYTEA,
+ IN in_exchange_pub BYTEA,
+ IN in_wtid BYTEA,
+ OUT out_wire_pending_cleared BOOL,
+ OUT out_conflict BOOL,
+ OUT out_no_exchange_pub BOOL)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_signkey_serial INT8;
+DECLARE
+ my_confirmed BOOL;
+DECLARE
+ my_decose INT8;
+DECLARE
+ my_order_serial INT8;
+BEGIN
+
+-- Find exchange sign key
+SELECT signkey_serial
+ INTO my_signkey_serial
+ FROM merchant_exchange_signing_keys
+ WHERE exchange_pub=in_exchange_pub
+ ORDER BY start_date DESC
+ LIMIT 1;
+
+IF NOT FOUND
+THEN
+ out_no_exchange_pub=TRUE;
+ out_conflict=FALSE;
+ out_wire_pending_cleared=FALSE;
+ RETURN;
+END IF;
+out_no_exchange_pub=FALSE;
+
+
+-- Try to insert new wire transfer
+INSERT INTO merchant_deposit_to_transfer
+ (deposit_serial
+ ,coin_contribution_value
+ ,wtid
+ ,execution_time
+ ,signkey_serial
+ ,exchange_sig
+ )
+ VALUES
+ (in_deposit_serial
+ ,in_amount_with_fee
+ ,in_wtid
+ ,in_execution_time
+ ,my_signkey_serial
+ ,in_exchange_sig
+ )
+ ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Same or conflicting wire transfer existed in the table already
+ -- Note: we don't distinguish here between
+ -- conflict and duplicate. Do we need to?
+ out_conflict=TRUE;
+ out_wire_pending_cleared=FALSE;
+ return;
+END IF;
+out_conflict=FALSE;
+
+
+-- Check if we already imported the (confirmed)
+-- wire transfer *and* if it is mapped to this deposit.
+PERFORM
+ FROM merchant_transfers mt
+ JOIN merchant_transfer_to_coin mtc
+ USING (credit_serial)
+ WHERE mt.wtid=in_wtid
+ AND mt.confirmed
+ AND mtc.deposit_serial=in_deposit_serial;
+
+IF NOT FOUND
+THEN
+ out_wire_pending_cleared=FALSE;
+ RETURN;
+END IF;
+
+
+RAISE NOTICE 'checking affected deposit confirmation for completion';
+
+SELECT deposit_confirmation_serial
+ INTO my_decose
+ FROM merchant_deposits
+ WHERE deposit_serial=in_deposit_serial;
+
+-- we made a change, check about clearing wire_pending
+-- for the entire deposit confirmation
+UPDATE merchant_deposit_confirmations
+ SET wire_pending=FALSE
+ WHERE (deposit_confirmation_serial=decose)
+ AND NOT EXISTS
+ (SELECT 1
+ FROM merchant_deposits md
+ LEFT JOIN merchant_deposit_to_transfer mdtt
+ USING (wtid)
+ WHERE md.deposit_confirmation_serial=my_decose
+ AND mdtt.credit_serial IS NULL);
+-- credit_serial will be NULL due to LEFT JOIN
+-- if we do not have an entry in mdtt for the deposit
+-- and thus some entry in md was not yet wired.
+
+IF NOT FOUND
+THEN
+ out_wire_pending_cleared=FALSE;
+ RETURN;
+END IF;
+out_wire_pending_cleared=TRUE;
+
+
+RAISE NOTICE 'checking affected contracts for completion';
+
+-- Check if all deposit confirmations of the same
+-- contract are now wired.
+SELECT deposit_confirmation_serial
+ INTO my_order_serial
+ FROM merchant_deposit_confirmations
+ WHERE deposit_confirmation_serial=my_decose;
+-- The above MUST succeed by invariants.
+
+-- Check about setting 'wired' for the contract term.
+-- Note: the same contract may be paid from
+-- multiple exchanges, so we need to check if
+-- payments were wired from all of them!
+UPDATE merchant_contract_terms
+ SET wired=TRUE
+ WHERE (order_serial=my_order_serial)
+ AND NOT EXISTS
+ (SELECT 1
+ FROM merchant_deposit_confirmations mdc
+ WHERE mdc.wire_pending
+ AND mdc.order_serial=my_order_serial);
+
+END $$;
diff --git a/src/backenddb/pg_insert_exchange_account.c b/src/backenddb/pg_insert_exchange_account.c
new file mode 100644
index 00000000..4495ccc0
--- /dev/null
+++ b/src/backenddb/pg_insert_exchange_account.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_exchange_account.c
+ * @brief Implementation of the insert_exchange_account function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_exchange_account.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_exchange_account (
+ void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (master_pub),
+ GNUNET_PQ_query_param_string (payto_uri),
+ NULL == conversion_url
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (conversion_url),
+ TALER_PQ_query_param_json (debit_restrictions),
+ TALER_PQ_query_param_json (credit_restrictions),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_exchange_account",
+ "INSERT INTO merchant_exchange_accounts"
+ "(master_pub"
+ ",payto_uri"
+ ",conversion_url"
+ ",debit_restrictions"
+ ",credit_restrictions"
+ ",master_sig)"
+ " VALUES ($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_exchange_account",
+ params);
+}
diff --git a/src/backenddb/pg_insert_exchange_account.h b/src/backenddb/pg_insert_exchange_account.h
new file mode 100644
index 00000000..acfcdba2
--- /dev/null
+++ b/src/backenddb/pg_insert_exchange_account.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_exchange_account.h
+ * @brief implementation of the insert_exchange_account function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_EXCHANGE_ACCOUNT_H
+#define PG_INSERT_EXCHANGE_ACCOUNT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Insert information about a wire account of an exchange.
+ *
+ * @param cls closure
+ * @param master_pub public key of the exchange
+ * @param payto_uri URI of the bank account
+ * @param conversion_url conversion service, NULL if there is no conversion required
+ * @param debit_restrictions JSON array of debit restrictions on the account
+ * @param credit_restrictions JSON array of debit restrictions on the account
+ * @param master_sig signature affirming the account of the exchange
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_exchange_account (
+ void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+#endif
diff --git a/src/backenddb/pg_insert_exchange_keys.c b/src/backenddb/pg_insert_exchange_keys.c
new file mode 100644
index 00000000..e3e9e84a
--- /dev/null
+++ b/src/backenddb/pg_insert_exchange_keys.c
@@ -0,0 +1,67 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_exchange_keys.c
+ * @brief Implementation of the insert_exchange_keys function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_exchange_keys.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_exchange_keys (void *cls,
+ const struct TALER_EXCHANGE_Keys *keys)
+{
+ struct PostgresClosure *pg = cls;
+ json_t *jkeys = TALER_EXCHANGE_keys_to_json (keys);
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_json (jkeys),
+ GNUNET_PQ_query_param_timestamp (&keys->last_denom_issue_date),
+ GNUNET_PQ_query_param_string (keys->exchange_url),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_exchange_keys",
+ "INSERT INTO merchant_exchange_keys"
+ "(keys_json"
+ ",expiration_time"
+ ",exchange_url"
+ ") VALUES ($1, $2, $3);");
+ PREPARE (pg,
+ "update_exchange_keys",
+ "UPDATE merchant_exchange_keys SET"
+ " keys_json=$1"
+ ",expiration_time=$2"
+ " WHERE"
+ " exchange_url=$3;");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_exchange_keys",
+ params);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_exchange_keys",
+ params);
+ json_decref (jkeys);
+ return qs;
+}
diff --git a/src/backenddb/pg_insert_exchange_keys.h b/src/backenddb/pg_insert_exchange_keys.h
new file mode 100644
index 00000000..6a658c88
--- /dev/null
+++ b/src/backenddb/pg_insert_exchange_keys.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_exchange_keys.h
+ * @brief implementation of the insert_exchange_keys function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_EXCHANGE_KEYS_H
+#define PG_INSERT_EXCHANGE_KEYS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Insert or update @a keys into the database.
+ *
+ * @param cls plugin closure
+ * @param keys data to store
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_exchange_keys (void *cls,
+ const struct TALER_EXCHANGE_Keys *keys);
+
+#endif
diff --git a/src/backenddb/pg_insert_exchange_signkey.c b/src/backenddb/pg_insert_exchange_signkey.c
new file mode 100644
index 00000000..b32a22b0
--- /dev/null
+++ b/src/backenddb/pg_insert_exchange_signkey.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_exchange_signkey.c
+ * @brief Implementation of the insert_exchange_signkey function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_exchange_signkey.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_exchange_signkey (
+ void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp expire_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (master_pub),
+ GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+ GNUNET_PQ_query_param_timestamp (&start_date),
+ GNUNET_PQ_query_param_timestamp (&expire_date),
+ GNUNET_PQ_query_param_timestamp (&end_date),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_exchange_signkey",
+ "INSERT INTO merchant_exchange_signing_keys"
+ "(master_pub"
+ ",exchange_pub"
+ ",start_date"
+ ",expire_date"
+ ",end_date"
+ ",master_sig)"
+ "VALUES"
+ "($1, $2, $3, $4, $5, $6)"
+ " ON CONFLICT DO NOTHING;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_exchange_signkey",
+ params);
+
+}
diff --git a/src/backenddb/pg_insert_exchange_signkey.h b/src/backenddb/pg_insert_exchange_signkey.h
new file mode 100644
index 00000000..47c7b996
--- /dev/null
+++ b/src/backenddb/pg_insert_exchange_signkey.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_exchange_signkey.h
+ * @brief implementation of the insert_exchange_signkey function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_INSERT_EXCHANGE_SIGNKEY_H
+#define PG_INSERT_EXCHANGE_SIGNKEY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Insert an exchange signing key into our database.
+ *
+ * @param cls closure
+ * @param master_pub exchange master public key used for @a master_sig
+ * @param exchange_pub exchange signing key to insert
+ * @param start_date when does the signing key become valid
+ * @param expire_date when does the signing key stop being used
+ * @param end_date when does the signing key become void as proof
+ * @param master_sig signature of @a master_pub over the @a exchange_pub and the dates
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_exchange_signkey (void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct
+ TALER_ExchangePublicKeyP *exchange_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp expire_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct
+ TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/backenddb/pg_insert_instance.c b/src/backenddb/pg_insert_instance.c
new file mode 100644
index 00000000..6928a094
--- /dev/null
+++ b/src/backenddb/pg_insert_instance.c
@@ -0,0 +1,106 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_instance.c
+ * @brief Implementation of the insert_instance function for Postgres
+ * @author Christian Grothoff
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_instance.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_instance (
+ void *cls,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ const struct TALER_MERCHANTDB_InstanceSettings *is,
+ const struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
+{
+ struct PostgresClosure *pg = cls;
+ uint32_t ut32 = (uint32_t) is->ut;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (&ias->auth_hash),
+ GNUNET_PQ_query_param_auto_from_type (&ias->auth_salt),
+ GNUNET_PQ_query_param_string (is->id),
+ GNUNET_PQ_query_param_string (is->name),
+ GNUNET_PQ_query_param_uint32 (&ut32),
+ TALER_PQ_query_param_json (is->address),
+ TALER_PQ_query_param_json (is->jurisdiction),
+ GNUNET_PQ_query_param_bool (is->use_stefan),
+ GNUNET_PQ_query_param_relative_time (
+ &is->default_wire_transfer_delay),
+ GNUNET_PQ_query_param_relative_time (&is->default_pay_delay),
+ (NULL == is->website)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (is->website),
+ (NULL == is->email)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (is->email),
+ (NULL == is->logo)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (is->logo),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_QueryParam params_priv[] = {
+ GNUNET_PQ_query_param_auto_from_type (merchant_priv),
+ GNUNET_PQ_query_param_string (is->id),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_instance",
+ "INSERT INTO merchant_instances"
+ "(merchant_pub"
+ ",auth_hash"
+ ",auth_salt"
+ ",merchant_id"
+ ",merchant_name"
+ ",user_type"
+ ",address"
+ ",jurisdiction"
+ ",use_stefan"
+ ",default_wire_transfer_delay"
+ ",default_pay_delay"
+ ",website"
+ ",email"
+ ",logo)"
+ "VALUES"
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_instance",
+ params);
+ if (qs <= 0)
+ return qs;
+ PREPARE (pg,
+ "insert_keys",
+ "INSERT INTO merchant_keys"
+ "(merchant_priv"
+ ",merchant_serial)"
+ " SELECT $1, merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$2");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_keys",
+ params_priv);
+}
diff --git a/src/backenddb/pg_insert_instance.h b/src/backenddb/pg_insert_instance.h
new file mode 100644
index 00000000..27ba8581
--- /dev/null
+++ b/src/backenddb/pg_insert_instance.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_instance.h
+ * @brief implementation of the insert_instance function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_INSERT_INSTANCE_H
+#define PG_INSERT_INSTANCE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Insert information about an instance into our database.
+ *
+ * @param cls closure
+ * @param merchant_pub public key of the instance
+ * @param merchant_priv private key of the instance
+ * @param is details about the instance
+ * @param ias authentication settings for the instance
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_instance (void *cls,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ const struct TALER_MERCHANTDB_InstanceSettings *is,
+ const struct
+ TALER_MERCHANTDB_InstanceAuthSettings *ias);
+
+#endif
diff --git a/src/backenddb/pg_insert_login_token.c b/src/backenddb/pg_insert_login_token.c
new file mode 100644
index 00000000..faeaeec8
--- /dev/null
+++ b/src/backenddb/pg_insert_login_token.c
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_login_token.c
+ * @brief Implementation of the insert_login_token function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_login_token.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_login_token (
+ void *cls,
+ const char *id,
+ const struct TALER_MERCHANTDB_LoginTokenP *token,
+ struct GNUNET_TIME_Timestamp creation_time,
+ struct GNUNET_TIME_Timestamp expiration_time,
+ uint32_t validity_scope)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (id),
+ GNUNET_PQ_query_param_auto_from_type (token),
+ GNUNET_PQ_query_param_timestamp (&creation_time),
+ GNUNET_PQ_query_param_timestamp (&expiration_time),
+ GNUNET_PQ_query_param_uint32 (&validity_scope),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_login_token",
+ "INSERT INTO merchant_login_tokens"
+ "(token"
+ ",creation_time"
+ ",expiration_time"
+ ",validity_scope"
+ ",merchant_serial"
+ ")"
+ "SELECT $2, $3, $4, $5, merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_login_token",
+ params);
+}
diff --git a/src/backenddb/pg_insert_login_token.h b/src/backenddb/pg_insert_login_token.h
new file mode 100644
index 00000000..c411b038
--- /dev/null
+++ b/src/backenddb/pg_insert_login_token.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_login_token.h
+ * @brief implementation of the insert_login_token function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_LOGIN_TOKEN_H
+#define PG_INSERT_LOGIN_TOKEN_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Insert instance login token into our database.
+ *
+ * @param cls closure
+ * @param id identifier of the instance
+ * @param token value of the token
+ * @param creation_time the current time
+ * @param expiration_time when does the token expire
+ * @param validity_scope scope of the token
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_login_token (
+ void *cls,
+ const char *id,
+ const struct TALER_MERCHANTDB_LoginTokenP *token,
+ struct GNUNET_TIME_Timestamp creation_time,
+ struct GNUNET_TIME_Timestamp expiration_time,
+ uint32_t validity_scope);
+
+
+#endif
diff --git a/src/backenddb/pg_insert_order.c b/src/backenddb/pg_insert_order.c
new file mode 100644
index 00000000..867fdec8
--- /dev/null
+++ b/src/backenddb/pg_insert_order.c
@@ -0,0 +1,95 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_order.c
+ * @brief Implementation of the insert_order function for Postgres
+ * @author Iván Ãvalos
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_order.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_order (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const char *session_id,
+ const struct TALER_MerchantPostDataHashP *h_post_data,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ const struct TALER_ClaimTokenP *claim_token,
+ const json_t *contract_terms,
+ const char *pos_key,
+ enum TALER_MerchantConfirmationAlgorithm pos_algorithm)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp now;
+ uint32_t pos32 = (uint32_t) pos_algorithm;
+ const char *fulfillment_url
+ = json_string_value (json_object_get (contract_terms,
+ "fulfillment_url"));
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_timestamp (&pay_deadline),
+ GNUNET_PQ_query_param_auto_from_type (claim_token),
+ GNUNET_PQ_query_param_auto_from_type (h_post_data),
+ GNUNET_PQ_query_param_timestamp (&now),
+ TALER_PQ_query_param_json (contract_terms),
+ (NULL == pos_key)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (pos_key),
+ GNUNET_PQ_query_param_uint32 (&pos32),
+ (NULL == session_id)
+ ? GNUNET_PQ_query_param_string ("")
+ : GNUNET_PQ_query_param_string (session_id),
+ (NULL == fulfillment_url)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (fulfillment_url),
+ GNUNET_PQ_query_param_end
+ };
+
+ now = GNUNET_TIME_timestamp_get ();
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "inserting order: order_id: %s, instance_id: %s.\n",
+ order_id,
+ instance_id);
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_order",
+ "INSERT INTO merchant_orders"
+ "(merchant_serial"
+ ",order_id"
+ ",pay_deadline"
+ ",claim_token"
+ ",h_post_data"
+ ",creation_time"
+ ",contract_terms"
+ ",pos_key"
+ ",pos_algorithm"
+ ",session_id"
+ ",fulfillment_url)"
+ " SELECT merchant_serial,"
+ " $2, $3, $4, $5, $6, $7, $8, $9, $10, $11"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_order",
+ params);
+}
diff --git a/src/backenddb/pg_insert_order.h b/src/backenddb/pg_insert_order.h
new file mode 100644
index 00000000..d8c41533
--- /dev/null
+++ b/src/backenddb/pg_insert_order.h
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_order.h
+ * @brief implementation of the insert_order function for Postgres
+ * @author Iván Ãvalos
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_ORDER_H
+#define PG_INSERT_ORDER_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Insert order into the DB.
+ *
+ * @param cls closure
+ * @param instance_id identifies the instance responsible for the order
+ * @param order_id alphanumeric string that uniquely identifies the proposal
+ * @param session_id session ID associated with the order, can be NULL
+ * @param h_post_data hash of the POST data for idempotency checks
+ * @param pay_deadline how long does the customer have to pay for the order
+ * @param claim_token token to use for access control
+ * @param contract_terms proposal data to store
+ * @param pos_key encoded key for payment verification
+ * @param pos_algorithm algorithm to compute the payment verification
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_order (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const char *session_id,
+ const struct TALER_MerchantPostDataHashP *h_post_data,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ const struct TALER_ClaimTokenP *claim_token,
+ const json_t *contract_terms,
+ const char *pos_key,
+ enum TALER_MerchantConfirmationAlgorithm pos_algorithm);
+
+#endif
diff --git a/src/backenddb/pg_insert_order_lock.c b/src/backenddb/pg_insert_order_lock.c
new file mode 100644
index 00000000..862a6320
--- /dev/null
+++ b/src/backenddb/pg_insert_order_lock.c
@@ -0,0 +1,78 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_order_lock.c
+ * @brief Implementation of the insert_order_lock function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_order_lock.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_order_lock (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const char *product_id,
+ uint64_t quantity)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_string (product_id),
+ GNUNET_PQ_query_param_uint64 (&quantity),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_order_lock",
+ "WITH tmp AS"
+ " (SELECT "
+ " product_serial"
+ " ,merchant_serial"
+ " ,total_stock"
+ " ,total_sold"
+ " ,total_lost"
+ " FROM merchant_inventory"
+ " WHERE product_id=$3"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1))"
+ " INSERT INTO merchant_order_locks"
+ " (product_serial"
+ " ,total_locked"
+ " ,order_serial)"
+ " SELECT tmp.product_serial, $4, order_serial"
+ " FROM merchant_orders"
+ " JOIN tmp USING(merchant_serial)"
+ " WHERE order_id=$2 AND"
+ " tmp.total_stock - tmp.total_sold - tmp.total_lost - $4 >= "
+ " (SELECT COALESCE(SUM(total_locked), 0)"
+ " FROM merchant_inventory_locks"
+ " WHERE product_serial=tmp.product_serial) + "
+ " (SELECT COALESCE(SUM(total_locked), 0)"
+ " FROM merchant_order_locks"
+ " WHERE product_serial=tmp.product_serial)");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_order_lock",
+ params);
+}
diff --git a/src/backenddb/pg_insert_order_lock.h b/src/backenddb/pg_insert_order_lock.h
new file mode 100644
index 00000000..c2cc9908
--- /dev/null
+++ b/src/backenddb/pg_insert_order_lock.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_order_lock.h
+ * @brief implementation of the insert_order_lock function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_INSERT_ORDER_LOCK_H
+#define PG_INSERT_ORDER_LOCK_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lock inventory stock to a particular order.
+ *
+ * @param cls closure
+ * @param instance_id identifies the instance responsible for the order
+ * @param order_id alphanumeric string that uniquely identifies the order
+ * @param product_id uniquely identifies the product to be locked
+ * @param quantity how many units should be locked to the @a order_id
+ * @return transaction status,
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are insufficient stocks
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_order_lock (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const char *product_id,
+ uint64_t quantity);
+
+#endif
diff --git a/src/backenddb/pg_insert_otp.c b/src/backenddb/pg_insert_otp.c
new file mode 100644
index 00000000..feae2230
--- /dev/null
+++ b/src/backenddb/pg_insert_otp.c
@@ -0,0 +1,74 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_otp.c
+ * @brief Implementation of the insert_otp function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_otp.h"
+#include "pg_helper.h"
+
+
+/**
+ * Insert details about a particular OTP device.
+ *
+ * @param cls closure
+ * @param instance_id instance to insert OTP device for
+ * @param otp_id otp identifier of OTP device to insert
+ * @param td the OTP device details to insert
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_otp (void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ const struct TALER_MERCHANTDB_OtpDeviceDetails *td)
+{
+ struct PostgresClosure *pg = cls;
+ uint32_t pos32 = (uint32_t) td->otp_algorithm;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (otp_id),
+ GNUNET_PQ_query_param_string (td->otp_description),
+ GNUNET_PQ_query_param_string (td->otp_key),
+ GNUNET_PQ_query_param_uint32 (&pos32),
+ GNUNET_PQ_query_param_uint64 (&td->otp_ctr),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_otp",
+ "INSERT INTO merchant_otp_devices"
+ "(merchant_serial"
+ ",otp_id"
+ ",otp_description"
+ ",otp_key"
+ ",otp_algorithm"
+ ",otp_ctr"
+ ")"
+ " SELECT merchant_serial,"
+ " $2, $3, $4, $5, $6"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_otp",
+ params);
+}
diff --git a/src/backenddb/pg_insert_otp.h b/src/backenddb/pg_insert_otp.h
new file mode 100644
index 00000000..70267d37
--- /dev/null
+++ b/src/backenddb/pg_insert_otp.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_otp.h
+ * @brief implementation of the insert_otp function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_OTP_H
+#define PG_INSERT_OTP_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Insert details about a particular OTP device.
+ *
+ * @param cls closure
+ * @param instance_id instance to insert OTP device for
+ * @param otp_id otp identifier of OTP device to insert
+ * @param td the OTP device details to insert
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_otp (void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ const struct TALER_MERCHANTDB_OtpDeviceDetails *td);
+
+
+#endif
diff --git a/src/backenddb/pg_insert_pending_webhook.c b/src/backenddb/pg_insert_pending_webhook.c
new file mode 100644
index 00000000..abb3234f
--- /dev/null
+++ b/src/backenddb/pg_insert_pending_webhook.c
@@ -0,0 +1,70 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_pending_webhook.c
+ * @brief Implementation of the insert_pending_webhook function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_pending_webhook.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_pending_webhook (void *cls,
+ const char *instance_id,
+ uint64_t webhook_serial,
+ const char *url,
+ const char *http_method,
+ const char *header,
+ const char *body)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_uint64 (&webhook_serial),
+ GNUNET_PQ_query_param_string (url),
+ GNUNET_PQ_query_param_string (http_method),
+ NULL == header
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (header),
+ NULL == body
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (body),
+ GNUNET_PQ_query_param_end
+ };
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_pending_webhook",
+ "INSERT INTO merchant_pending_webhooks"
+ "(merchant_serial"
+ ",webhook_serial"
+ ",url"
+ ",http_method"
+ ",header"
+ ",body"
+ ")"
+ " SELECT mi.merchant_serial,"
+ " $2, $3, $4, $5, $6"
+ " FROM merchant_instances mi"
+ " WHERE mi.merchant_id=$1");
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_pending_webhook",
+ params);
+}
diff --git a/src/backenddb/pg_insert_pending_webhook.h b/src/backenddb/pg_insert_pending_webhook.h
new file mode 100644
index 00000000..0107fd2c
--- /dev/null
+++ b/src/backenddb/pg_insert_pending_webhook.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_pending_webhook.h
+ * @brief implementation of the insert_pending_webhook function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_INSERT_PENDING_WEBHOOK_H
+#define PG_INSERT_PENDING_WEBHOOK_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Insert webhook in the pending webhook.
+ *
+ * @param cls closure
+ * @param instance_id instance to insert webhook for
+ * @param webhook_serial webhook to insert in the pending webhook
+ * @param url to make the request to
+ * @param http_method for the webhook
+ * @param header of the webhook
+ * @param body of the webhook
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_pending_webhook (void *cls,
+ const char *instance_id,
+ uint64_t webhook_serial,
+ const char *url,
+ const char *http_method,
+ const char *header,
+ const char *body);
+
+#endif
diff --git a/src/backenddb/pg_insert_product.c b/src/backenddb/pg_insert_product.c
new file mode 100644
index 00000000..55db57e9
--- /dev/null
+++ b/src/backenddb/pg_insert_product.c
@@ -0,0 +1,77 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_product.c
+ * @brief Implementation of the insert_product function for Postgres
+ * @author Christian Grothoff
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_product.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_product (void *cls,
+ const char *instance_id,
+ const char *product_id,
+ const struct TALER_MERCHANTDB_ProductDetails *pd)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (product_id),
+ GNUNET_PQ_query_param_string (pd->description),
+ TALER_PQ_query_param_json (pd->description_i18n),
+ GNUNET_PQ_query_param_string (pd->unit),
+ GNUNET_PQ_query_param_string (pd->image),
+ TALER_PQ_query_param_json (pd->taxes),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ &pd->price),
+ GNUNET_PQ_query_param_uint64 (&pd->total_stock),
+ TALER_PQ_query_param_json (pd->address),
+ GNUNET_PQ_query_param_timestamp (&pd->next_restock),
+ GNUNET_PQ_query_param_uint32 (&pd->minimum_age),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_product",
+ "INSERT INTO merchant_inventory"
+ "(merchant_serial"
+ ",product_id"
+ ",description"
+ ",description_i18n"
+ ",unit"
+ ",image"
+ ",taxes"
+ ",price"
+ ",total_stock"
+ ",address"
+ ",next_restock"
+ ",minimum_age"
+ ")"
+ " SELECT merchant_serial,"
+ " $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_product",
+ params);
+}
diff --git a/src/backenddb/pg_insert_product.h b/src/backenddb/pg_insert_product.h
new file mode 100644
index 00000000..169bd150
--- /dev/null
+++ b/src/backenddb/pg_insert_product.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_product.h
+ * @brief implementation of the insert_product function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_INSERT_PRODUCT_H
+#define PG_INSERT_PRODUCT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Insert details about a particular product.
+ *
+ * @param cls closure
+ * @param instance_id instance to insert product for
+ * @param product_id product identifier of product to insert
+ * @param pd the product details to insert
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_product (void *cls,
+ const char *instance_id,
+ const char *product_id,
+ const struct TALER_MERCHANTDB_ProductDetails *pd);
+
+#endif
diff --git a/src/backenddb/pg_insert_refund_proof.c b/src/backenddb/pg_insert_refund_proof.c
new file mode 100644
index 00000000..05d0d9cd
--- /dev/null
+++ b/src/backenddb/pg_insert_refund_proof.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_refund_proof.c
+ * @brief Implementation of the insert_refund_proof function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_refund_proof.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_refund_proof (void *cls,
+ uint64_t refund_serial,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&refund_serial),
+ GNUNET_PQ_query_param_auto_from_type (exchange_sig),
+ GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_refund_proof",
+ "INSERT INTO merchant_refund_proofs"
+ "(refund_serial"
+ ",exchange_sig"
+ ",signkey_serial)"
+ "SELECT $1, $2, signkey_serial"
+ " FROM merchant_exchange_signing_keys"
+ " WHERE exchange_pub=$3"
+ " ORDER BY start_date DESC"
+ " LIMIT 1");
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_refund_proof",
+ params);
+}
diff --git a/src/backenddb/pg_insert_refund_proof.h b/src/backenddb/pg_insert_refund_proof.h
new file mode 100644
index 00000000..906de077
--- /dev/null
+++ b/src/backenddb/pg_insert_refund_proof.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_refund_proof.h
+ * @brief implementation of the insert_refund_proof function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_INSERT_REFUND_PROOF_H
+#define PG_INSERT_REFUND_PROOF_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Insert refund proof data from the exchange into the database.
+ *
+ * @param cls closure
+ * @param refund_serial serial number of the refund
+ * @param exchange_sig signature from exchange that coin was refunded
+ * @param exchange_pub signing key that was used for @a exchange_sig
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_refund_proof (void *cls,
+ uint64_t refund_serial,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_ExchangePublicKeyP *exchange_pub);
+
+#endif
diff --git a/src/backenddb/pg_insert_template.c b/src/backenddb/pg_insert_template.c
new file mode 100644
index 00000000..67cae495
--- /dev/null
+++ b/src/backenddb/pg_insert_template.c
@@ -0,0 +1,73 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_template.c
+ * @brief Implementation of the insert_template function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_template.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_template (void *cls,
+ const char *instance_id,
+ const char *template_id,
+ uint64_t otp_serial_id,
+ const struct TALER_MERCHANTDB_TemplateDetails *td)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (template_id),
+ GNUNET_PQ_query_param_string (td->template_description),
+ (0 == otp_serial_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (&otp_serial_id),
+ TALER_PQ_query_param_json (td->template_contract),
+ (NULL == td->editable_defaults)
+ ? GNUNET_PQ_query_param_null ()
+ : TALER_PQ_query_param_json (td->editable_defaults),
+ (NULL == td->required_currency)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (td->required_currency),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_template",
+ "INSERT INTO merchant_template"
+ "(merchant_serial"
+ ",template_id"
+ ",template_description"
+ ",otp_device_id"
+ ",template_contract"
+ ",editable_defaults"
+ ",required_currency"
+ ")"
+ " SELECT merchant_serial,"
+ " $2, $3, $4, $5, $6, $7"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_template",
+ params);
+}
diff --git a/src/backenddb/pg_insert_template.h b/src/backenddb/pg_insert_template.h
new file mode 100644
index 00000000..fb9c3700
--- /dev/null
+++ b/src/backenddb/pg_insert_template.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_template.h
+ * @brief implementation of the insert_template function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_TEMPLATE_H
+#define PG_INSERT_TEMPLATE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Insert details about a particular template.
+ *
+ * @param cls closure
+ * @param instance_id instance to insert template for
+ * @param template_id template identifier of template to insert
+ * @param otp_serial_id 0 if no OTP device is associated
+ * @param td the template details to insert
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_template (void *cls,
+ const char *instance_id,
+ const char *template_id,
+ uint64_t otp_serial_id,
+ const struct TALER_MERCHANTDB_TemplateDetails *td);
+
+#endif
diff --git a/src/backenddb/pg_insert_token_family.c b/src/backenddb/pg_insert_token_family.c
new file mode 100644
index 00000000..bf7159b8
--- /dev/null
+++ b/src/backenddb/pg_insert_token_family.c
@@ -0,0 +1,82 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_token_family.c
+ * @brief Implementation of the insert_token_family function for Postgres
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_token_family.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_token_family (void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ const struct TALER_MERCHANTDB_TokenFamilyDetails *details)
+{
+ struct PostgresClosure *pg = cls;
+
+ const char *kind;
+ switch (details->kind)
+ {
+ case TALER_MERCHANTDB_TFK_Discount:
+ kind = "discount";
+ break;
+ case TALER_MERCHANTDB_TFK_Subscription:
+ kind = "subscription";
+ break;
+ default:
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (token_family_slug),
+ GNUNET_PQ_query_param_string (details->name),
+ GNUNET_PQ_query_param_string (details->description),
+ TALER_PQ_query_param_json (details->description_i18n),
+ GNUNET_PQ_query_param_timestamp (&details->valid_after),
+ GNUNET_PQ_query_param_timestamp (&details->valid_before),
+ GNUNET_PQ_query_param_relative_time (&details->duration),
+ GNUNET_PQ_query_param_string (kind),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_token_family",
+ "INSERT INTO merchant_token_families"
+ "(merchant_serial"
+ ",slug"
+ ",name"
+ ",description"
+ ",description_i18n"
+ ",valid_after"
+ ",valid_before"
+ ",duration"
+ ",kind)"
+ " SELECT merchant_serial, $2, $3, $4, $5, $6, $7, $8, $9"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_token_family",
+ params);
+}
diff --git a/src/backenddb/pg_insert_token_family.h b/src/backenddb/pg_insert_token_family.h
new file mode 100644
index 00000000..e05755a6
--- /dev/null
+++ b/src/backenddb/pg_insert_token_family.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_token_family.h
+ * @brief implementation of the insert_token_family function for Postgres
+ * @author Christian Blättler
+ */
+#ifndef PG_INSERT_TOKEN_FAMILY_H
+#define PG_INSERT_TOKEN_FAMILY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * @param cls closure
+ * @param instance_id instance to insert token family for TODO: Is this needed?
+ * @param token_family_slug slug of the token family to insert
+ * @param details the token family details to insert
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_token_family (void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ const struct TALER_MERCHANTDB_TokenFamilyDetails *details);
+
+#endif
+
diff --git a/src/backenddb/pg_insert_transfer.c b/src/backenddb/pg_insert_transfer.c
new file mode 100644
index 00000000..45a4fa70
--- /dev/null
+++ b/src/backenddb/pg_insert_transfer.c
@@ -0,0 +1,73 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_transfer.c
+ * @brief Implementation of the insert_transfer function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_transfer.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_transfer (
+ void *cls,
+ const char *instance_id,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *credit_amount,
+ const char *payto_uri,
+ bool confirmed)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (exchange_url),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ credit_amount),
+ GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_bool (confirmed),
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_transfer",
+ "INSERT INTO merchant_transfers"
+ "(exchange_url"
+ ",wtid"
+ ",credit_amount"
+ ",account_serial"
+ ",confirmed)"
+ "SELECT"
+ " $1, $2, $3, account_serial, $5"
+ " FROM merchant_accounts"
+ " WHERE REGEXP_REPLACE(payto_uri,'\\?.*','')"
+ " =REGEXP_REPLACE($4,'\\?.*','')"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$6)"
+ " ON CONFLICT DO NOTHING;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_transfer",
+ params);
+}
diff --git a/src/backenddb/pg_insert_transfer.h b/src/backenddb/pg_insert_transfer.h
new file mode 100644
index 00000000..8385c330
--- /dev/null
+++ b/src/backenddb/pg_insert_transfer.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_transfer.h
+ * @brief implementation of the postgres_insert_transfer function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_TRANSFER_H
+#define PG_INSERT_TRANSFER_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Insert information about a wire transfer the merchant has received.
+ *
+ * @param cls closure
+ * @param instance_id the instance that received the transfer
+ * @param exchange_url which exchange made the transfer
+ * @param wtid identifier of the wire transfer
+ * @param credit_amount how much did we receive
+ * @param payto_uri what is the merchant's bank account that received the transfer
+ * @param confirmed whether the transfer was confirmed by the merchant or
+ * was merely claimed by the exchange at this point
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_transfer (
+ void *cls,
+ const char *instance_id,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *credit_amount,
+ const char *payto_uri,
+ bool confirmed);
+
+
+#endif
diff --git a/src/backenddb/pg_insert_transfer_details.c b/src/backenddb/pg_insert_transfer_details.c
new file mode 100644
index 00000000..aa0ccc53
--- /dev/null
+++ b/src/backenddb/pg_insert_transfer_details.c
@@ -0,0 +1,171 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_transfer_details.c
+ * @brief Implementation of the insert_transfer_details function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_transfer_details.h"
+#include "pg_helper.h"
+
+
+/**
+ * How often do we re-try if we run into a DB serialization error?
+ */
+#define MAX_RETRIES 3
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_transfer_details (
+ void *cls,
+ const char *instance_id,
+ const char *exchange_url,
+ const char *payto_uri,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_EXCHANGE_TransferData *td)
+{
+ struct PostgresClosure *pg = cls;
+ unsigned int len = td->details_length;
+ struct TALER_Amount coin_values[GNUNET_NZL (len)];
+ struct TALER_Amount deposit_fees[GNUNET_NZL (len)];
+ const struct TALER_CoinSpendPublicKeyP *coin_pubs[GNUNET_NZL (len)];
+ const struct TALER_PrivateContractHashP *contract_terms[GNUNET_NZL (len)];
+ enum GNUNET_DB_QueryStatus qs;
+
+ for (unsigned int i = 0; i<len; i++)
+ {
+ const struct TALER_TrackTransferDetails *tdd = &td->details[i];
+
+ coin_values[i] = tdd->coin_value;
+ deposit_fees[i] = tdd->coin_fee;
+ coin_pubs[i] = &tdd->coin_pub;
+ contract_terms[i] = &tdd->h_contract_terms;
+ }
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_transfer_details",
+ "SELECT"
+ " out_no_instance"
+ ",out_no_account"
+ ",out_no_exchange"
+ ",out_duplicate"
+ ",out_conflict"
+ " FROM merchant_do_insert_transfer_details"
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13);");
+
+ for (unsigned int retries = 0;
+ retries < MAX_RETRIES;
+ retries++)
+ {
+ if (GNUNET_OK !=
+ TMH_PG_start (pg,
+ "insert transfer details"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (exchange_url),
+ GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_timestamp (&td->execution_time),
+ GNUNET_PQ_query_param_auto_from_type (&td->exchange_pub),
+ GNUNET_PQ_query_param_auto_from_type (&td->exchange_sig),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ &td->total_amount),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ &td->wire_fee),
+ TALER_PQ_query_param_array_amount_with_currency (
+ len,
+ coin_values,
+ pg->conn),
+ TALER_PQ_query_param_array_amount_with_currency (
+ len,
+ deposit_fees,
+ pg->conn),
+ GNUNET_PQ_query_param_array_ptrs_auto_from_type (
+ len,
+ coin_pubs,
+ pg->conn),
+ GNUNET_PQ_query_param_array_ptrs_auto_from_type (
+ len,
+ contract_terms,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ bool no_instance;
+ bool no_account;
+ bool no_exchange;
+ bool duplicate;
+ bool conflict;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("out_no_instance",
+ &no_instance),
+ GNUNET_PQ_result_spec_bool ("out_no_account",
+ &no_account),
+ GNUNET_PQ_result_spec_bool ("out_no_exchange",
+ &no_exchange),
+ GNUNET_PQ_result_spec_bool ("out_duplicate",
+ &duplicate),
+ GNUNET_PQ_result_spec_bool ("out_conflict",
+ &conflict),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "insert_transfer_details",
+ params,
+ rs);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ if (0 >= qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ TMH_PG_rollback (pg);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ continue;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "'insert_transfer_details' failed with status %d\n",
+ qs);
+ return qs;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Transfer details inserted: %s%s%s%s%s\n",
+ no_instance ? "no instance " : "",
+ no_account ? "no account " : "",
+ no_exchange ? "no exchange ": "",
+ duplicate ? "duplicate ": "",
+ conflict ? "conflict" : "");
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Committing transaction...\n");
+ qs = TMH_PG_commit (pg);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+ break;
+ }
+ return qs;
+}
diff --git a/src/backenddb/pg_insert_transfer_details.h b/src/backenddb/pg_insert_transfer_details.h
new file mode 100644
index 00000000..8980024e
--- /dev/null
+++ b/src/backenddb/pg_insert_transfer_details.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_transfer_details.h
+ * @brief implementation of the insert_transfer_details function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_TRANSFER_DETAILS_H
+#define PG_INSERT_TRANSFER_DETAILS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Insert information about a wire transfer the merchant has received.
+ *
+ * @param cls closure
+ * @param instance_id instance to provide transfer details for
+ * @param exchange_url which exchange made the transfer
+ * @param payto_uri what is the merchant's bank account that received the transfer
+ * @param wtid identifier of the wire transfer
+ * @param td transfer details to store
+ * @return transaction status,
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the @a wtid and @a exchange_uri are not known for this @a instance_id
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT on success
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_transfer_details (
+ void *cls,
+ const char *instance_id,
+ const char *exchange_url,
+ const char *payto_uri,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_EXCHANGE_TransferData *td);
+
+#endif
diff --git a/src/backenddb/pg_insert_transfer_details.sql b/src/backenddb/pg_insert_transfer_details.sql
new file mode 100644
index 00000000..1650d157
--- /dev/null
+++ b/src/backenddb/pg_insert_transfer_details.sql
@@ -0,0 +1,229 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE OR REPLACE FUNCTION merchant_do_insert_transfer_details (
+ IN in_instance_id TEXT,
+ IN in_exchange_url TEXT,
+ IN in_payto_uri TEXT,
+ IN in_wtid BYTEA,
+ IN in_execution_time INT8,
+ IN in_exchange_pub BYTEA,
+ IN in_exchange_sig BYTEA,
+ IN in_total_amount taler_amount_currency,
+ IN in_wire_fee taler_amount_currency,
+ IN ina_coin_values taler_amount_currency[],
+ IN ina_deposit_fees taler_amount_currency[],
+ IN ina_coin_pubs BYTEA[],
+ IN ina_contract_terms BYTEA[],
+ OUT out_no_instance BOOL,
+ OUT out_no_account BOOL,
+ OUT out_no_exchange BOOL,
+ OUT out_duplicate BOOL,
+ OUT out_conflict BOOL)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_merchant_id INT8;
+ my_signkey_serial INT8;
+ my_credit_serial INT8;
+ my_affected_orders RECORD;
+ i INT8;
+ curs CURSOR (arg_coin_pub BYTEA) FOR
+ SELECT mcon.deposit_confirmation_serial,
+ mcon.order_serial
+ FROM merchant_deposits dep
+ JOIN merchant_deposit_confirmations mcon
+ USING (deposit_confirmation_serial)
+ WHERE dep.coin_pub=arg_coin_pub;
+ ini_coin_pub BYTEA;
+ ini_contract_term BYTEA;
+ ini_coin_value taler_amount_currency;
+ ini_deposit_fee taler_amount_currency;
+BEGIN
+
+-- Which instance are we using?
+SELECT merchant_serial
+ INTO my_merchant_id
+ FROM merchant_instances
+ WHERE merchant_id=in_instance_id;
+
+IF NOT FOUND
+THEN
+ out_no_instance=TRUE;
+ out_no_account=FALSE;
+ out_no_exchange=FALSE;
+ out_duplicate=FALSE;
+ out_conflict=FALSE;
+ RETURN;
+END IF;
+out_no_instance=FALSE;
+
+-- Determine account that was credited.
+SELECT credit_serial
+ INTO my_credit_serial
+ FROM merchant_transfers
+ WHERE exchange_url=in_exchange_url
+ AND wtid=in_wtid
+ AND account_serial=
+ (SELECT account_serial
+ FROM merchant_accounts
+ WHERE payto_uri=in_payto_uri
+ AND exchange_url=in_exchange_url
+ AND merchant_serial=my_merchant_id);
+
+IF NOT FOUND
+THEN
+ out_no_account=TRUE;
+ out_no_exchange=FALSE;
+ out_duplicate=FALSE;
+ out_conflict=FALSE;
+ RETURN;
+END IF;
+out_no_account=FALSE;
+
+-- Find exchange sign key
+SELECT signkey_serial
+ INTO my_signkey_serial
+ FROM merchant_exchange_signing_keys
+ WHERE exchange_pub=in_exchange_pub
+ ORDER BY start_date DESC
+ LIMIT 1;
+
+IF NOT FOUND
+THEN
+ out_no_exchange=TRUE;
+ out_conflict=FALSE;
+ out_duplicate=FALSE;
+ RETURN;
+END IF;
+out_no_exchange=FALSE;
+
+-- Add signature first, check for idempotent request
+INSERT INTO merchant_transfer_signatures
+ (credit_serial
+ ,signkey_serial
+ ,credit_amount
+ ,wire_fee
+ ,execution_time
+ ,exchange_sig)
+ VALUES
+ (my_credit_serial
+ ,my_signkey_serial
+ ,in_total_amount
+ ,in_wire_fee
+ ,in_execution_time
+ ,in_exchange_sig)
+ ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ PERFORM 1
+ FROM merchant_transfer_signatures
+ WHERE credit_serial=my_credit_serial
+ AND signkey_serial=my_signkey_serial
+ AND credit_amount=in_credit_amount
+ AND wire_fee=in_wire_fee
+ AND execution_time=in_execution_time
+ AND exchange_sig=in_exchange_sig;
+ IF FOUND
+ THEN
+ -- duplicate case
+ out_duplicate=TRUE;
+ out_conflict=FALSE;
+ RETURN;
+ END IF;
+ -- conflict case
+ out_duplicate=FALSE;
+ out_conflict=TRUE;
+ RETURN;
+END IF;
+
+out_duplicate=FALSE;
+out_conflict=FALSE;
+
+
+FOR i IN 1..array_length(ina_coin_pubs,1)
+LOOP
+ ini_coin_value=ina_coin_values[i];
+ ini_deposit_fee=ina_deposit_fees[i];
+ ini_coin_pub=ina_coin_pubs[i];
+ ini_contract_term=ina_contract_terms[i];
+
+ INSERT INTO merchant_transfer_to_coin
+ (deposit_serial
+ ,credit_serial
+ ,offset_in_exchange_list
+ ,exchange_deposit_value
+ ,exchange_deposit_fee)
+ SELECT
+ dep.deposit_serial
+ ,my_credit_serial
+ ,i
+ ,ini_coin_value
+ ,ini_deposit_fee
+ FROM merchant_deposits dep
+ JOIN merchant_deposit_confirmations dcon
+ USING (deposit_confirmation_serial)
+ JOIN merchant_contract_terms cterm
+ USING (order_serial)
+ WHERE dep.coin_pub=ini_coin_pub
+ AND cterm.h_contract_terms=ini_contract_term
+ AND cterm.merchant_serial=my_merchant_id;
+
+ RAISE NOTICE 'iterating over affected orders';
+ OPEN curs (arg_coin_pub:=ini_coin_pub);
+ LOOP
+ FETCH NEXT FROM curs INTO my_affected_orders;
+ EXIT WHEN NOT FOUND;
+
+ RAISE NOTICE 'checking affected order for completion';
+
+ -- First, check if deposit confirmation is done.
+ UPDATE merchant_deposit_confirmations
+ SET wire_pending=FALSE
+ WHERE (deposit_confirmation_serial=my_affected_orders.deposit_confirmation_serial)
+ AND NOT EXISTS
+ (SELECT 1
+ FROM merchant_deposits md
+ LEFT JOIN merchant_deposit_to_transfer mdtt
+ USING (deposit_serial)
+ WHERE md.deposit_confirmation_serial=my_affected_orders.deposit_confirmation_serial
+ AND mdtt.wtid IS NULL);
+ -- wtid will be NULL due to LEFT JOIN
+ -- if we do not have an entry in mdtt for the deposit
+ -- and thus some entry in md was not yet wired.
+
+ IF FOUND
+ THEN
+ -- Also update contract terms, if all (other) associated
+ -- deposit_confirmations are also done.
+
+ UPDATE merchant_contract_terms
+ SET wired=TRUE
+ WHERE (order_serial=my_affected_orders.order_serial)
+ AND NOT EXISTS
+ (SELECT 1
+ FROM merchant_deposit_confirmations mdc
+ WHERE mdc.wire_pending
+ AND mdc.order_serial=my_affected_orders.order_serial);
+ END IF;
+
+ END LOOP; -- END curs LOOP
+ CLOSE curs;
+END LOOP; -- END FOR loop
+
+END $$;
diff --git a/src/backenddb/pg_insert_webhook.c b/src/backenddb/pg_insert_webhook.c
new file mode 100644
index 00000000..53049a43
--- /dev/null
+++ b/src/backenddb/pg_insert_webhook.c
@@ -0,0 +1,70 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_webhook.c
+ * @brief Implementation of the insert_webhook function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_webhook.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_webhook (void *cls,
+ const char *instance_id,
+ const char *webhook_id,
+ const struct TALER_MERCHANTDB_WebhookDetails *wb)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (webhook_id),
+ GNUNET_PQ_query_param_string (wb->event_type),
+ GNUNET_PQ_query_param_string (wb->url),
+ GNUNET_PQ_query_param_string (wb->http_method),
+ (NULL == wb->header_template)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (wb->header_template),
+ (NULL == wb->body_template)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (wb->body_template),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_webhook",
+ "INSERT INTO merchant_webhook"
+ "(merchant_serial"
+ ",webhook_id"
+ ",event_type"
+ ",url"
+ ",http_method"
+ ",header_template"
+ ",body_template"
+ ")"
+ " SELECT merchant_serial,"
+ " $2, $3, $4, $5, $6, $7"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1");
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_webhook",
+ params);
+}
diff --git a/src/backenddb/pg_insert_webhook.h b/src/backenddb/pg_insert_webhook.h
new file mode 100644
index 00000000..fe89d113
--- /dev/null
+++ b/src/backenddb/pg_insert_webhook.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_webhook.h
+ * @brief implementation of the insert_webhook function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_INSERT_WEBHOOK_H
+#define PG_INSERT_WEBHOOK_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Insert details about a particular webhook.
+ *
+ * @param cls closure
+ * @param instance_id instance to insert webhook for
+ * @param webhook_id webhook identifier of webhook to insert
+ * @param wb the webhook details to insert
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_webhook (void *cls,
+ const char *instance_id,
+ const char *webhook_id,
+ const struct TALER_MERCHANTDB_WebhookDetails *wb);
+
+#endif
diff --git a/src/backenddb/pg_lock_product.c b/src/backenddb/pg_lock_product.c
new file mode 100644
index 00000000..205f0b67
--- /dev/null
+++ b/src/backenddb/pg_lock_product.c
@@ -0,0 +1,76 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lock_product.c
+ * @brief Implementation of the lock_product function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lock_product.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lock_product (void *cls,
+ const char *instance_id,
+ const char *product_id,
+ const struct GNUNET_Uuid *uuid,
+ uint64_t quantity,
+ struct GNUNET_TIME_Timestamp expiration_time)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (product_id),
+ GNUNET_PQ_query_param_auto_from_type (uuid),
+ GNUNET_PQ_query_param_uint64 (&quantity),
+ GNUNET_PQ_query_param_timestamp (&expiration_time),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lock_product",
+ "WITH ps AS"
+ " (SELECT product_serial"
+ " FROM merchant_inventory"
+ " WHERE product_id=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1))"
+ "INSERT INTO merchant_inventory_locks"
+ "(product_serial"
+ ",lock_uuid"
+ ",total_locked"
+ ",expiration)"
+ " SELECT product_serial, $3, $4, $5"
+ " FROM merchant_inventory"
+ " JOIN ps USING (product_serial)"
+ " WHERE "
+ " total_stock - total_sold - total_lost - $4 >= "
+ " (SELECT COALESCE(SUM(total_locked), 0)"
+ " FROM merchant_inventory_locks"
+ " WHERE product_serial=ps.product_serial) + "
+ " (SELECT COALESCE(SUM(total_locked), 0)"
+ " FROM merchant_order_locks"
+ " WHERE product_serial=ps.product_serial)");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "lock_product",
+ params);
+}
diff --git a/src/backenddb/pg_lock_product.h b/src/backenddb/pg_lock_product.h
new file mode 100644
index 00000000..d0e13d41
--- /dev/null
+++ b/src/backenddb/pg_lock_product.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lock_product.h
+ * @brief implementation of the lock_product function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOCK_PRODUCT_H
+#define PG_LOCK_PRODUCT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lock stocks of a particular product. Note that the transaction must
+ * enforce that the "stocked-sold-lost >= locked" constraint holds.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup products for
+ * @param product_id product to lookup
+ * @param uuid the UUID that holds the lock
+ * @param quantity how many units should be locked
+ * @param expiration_time when should the lock expire
+ * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the
+ * product is unknown OR if there insufficient stocks remaining
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lock_product (void *cls,
+ const char *instance_id,
+ const char *product_id,
+ const struct GNUNET_Uuid *uuid,
+ uint64_t quantity,
+ struct GNUNET_TIME_Timestamp expiration_time);
+
+#endif
diff --git a/src/backenddb/pg_lookup_account.c b/src/backenddb/pg_lookup_account.c
new file mode 100644
index 00000000..992b7408
--- /dev/null
+++ b/src/backenddb/pg_lookup_account.c
@@ -0,0 +1,62 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_account.c
+ * @brief Implementation of the lookup_account function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_account.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_account (void *cls,
+ const char *instance_id,
+ const char *payto_uri,
+ uint64_t *account_serial)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("account_serial",
+ account_serial),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_account",
+ "SELECT"
+ " account_serial"
+ " FROM merchant_accounts"
+ " WHERE REGEXP_REPLACE(payto_uri,'\\?.*','')"
+ " =REGEXP_REPLACE($2,'\\?.*','')"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_account",
+ params,
+ rs);
+}
diff --git a/src/backenddb/pg_lookup_account.h b/src/backenddb/pg_lookup_account.h
new file mode 100644
index 00000000..63b0aa73
--- /dev/null
+++ b/src/backenddb/pg_lookup_account.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_account.h
+ * @brief implementation of the lookup_account function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_ACCOUNT_H
+#define PG_LOOKUP_ACCOUNT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup account serial by payto URI.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup the account from
+ * @param payto_uri what is the merchant's bank account to lookup
+ * @param[out] account_serial serial number of the account
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_account (void *cls,
+ const char *instance_id,
+ const char *payto_uri,
+ uint64_t *account_serial);
+
+#endif
diff --git a/src/backenddb/pg_lookup_contract_terms.c b/src/backenddb/pg_lookup_contract_terms.c
new file mode 100644
index 00000000..9588eef4
--- /dev/null
+++ b/src/backenddb/pg_lookup_contract_terms.c
@@ -0,0 +1,80 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_contract_terms.c
+ * @brief Implementation of the lookup_contract_terms function for Postgres
+ * @author Iván Ãvalos
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_contract_terms.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_contract_terms (
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ json_t **contract_terms,
+ uint64_t *order_serial,
+ struct TALER_ClaimTokenP *claim_token)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_ClaimTokenP ct;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ /* contract_terms must be first! */
+ TALER_PQ_result_spec_json ("contract_terms",
+ contract_terms),
+ GNUNET_PQ_result_spec_uint64 ("order_serial",
+ order_serial),
+ GNUNET_PQ_result_spec_auto_from_type ("claim_token",
+ &ct),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_contract_terms",
+ "SELECT"
+ " contract_terms"
+ ",order_serial"
+ ",claim_token"
+ " FROM merchant_contract_terms"
+ " WHERE order_id=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_contract_terms",
+ params,
+ (NULL != contract_terms)
+ ? rs
+ : &rs[1]);
+ if (NULL != claim_token)
+ *claim_token = ct;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_contract_terms.h b/src/backenddb/pg_lookup_contract_terms.h
new file mode 100644
index 00000000..fa757ed1
--- /dev/null
+++ b/src/backenddb/pg_lookup_contract_terms.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_contract_terms.h
+ * @brief implementation of the lookup_contract_terms function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_CONTRACT_TERMS_H
+#define PG_LOOKUP_CONTRACT_TERMS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Retrieve contract terms given its @a order_id
+ *
+ * @param cls closure
+ * @param instance_id instance's identifier
+ * @param order_id order_id used to lookup.
+ * @param[out] contract_terms where to store the result, NULL to only check for existence
+ * @param[out] order_serial set to the order's serial number
+ * @param[out] claim_token set to token to use for access control
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_contract_terms (
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ json_t **contract_terms,
+ uint64_t *order_serial,
+ struct TALER_ClaimTokenP *claim_token);
+
+#endif
diff --git a/src/backenddb/pg_lookup_contract_terms2.c b/src/backenddb/pg_lookup_contract_terms2.c
new file mode 100644
index 00000000..1fbb02ea
--- /dev/null
+++ b/src/backenddb/pg_lookup_contract_terms2.c
@@ -0,0 +1,96 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_contract_terms2.c
+ * @brief Implementation of the lookup_contract_terms2 function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_contract_terms2.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_contract_terms2 (
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ json_t **contract_terms,
+ uint64_t *order_serial,
+ bool *paid,
+ struct TALER_ClaimTokenP *claim_token,
+ char **pos_key,
+ enum TALER_MerchantConfirmationAlgorithm *pos_algorithm)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_ClaimTokenP ct;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_end
+ };
+ uint32_t pos32;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ /* contract_terms must be first! */
+ TALER_PQ_result_spec_json ("contract_terms",
+ contract_terms),
+ GNUNET_PQ_result_spec_uint64 ("order_serial",
+ order_serial),
+ GNUNET_PQ_result_spec_bool ("paid",
+ paid),
+ GNUNET_PQ_result_spec_auto_from_type ("claim_token",
+ &ct),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("pos_key",
+ pos_key),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint32 ("pos_algorithm",
+ &pos32),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_contract_terms2",
+ "SELECT"
+ " contract_terms"
+ ",order_serial"
+ ",claim_token"
+ ",paid"
+ ",pos_key"
+ ",pos_algorithm"
+ " FROM merchant_contract_terms"
+ " WHERE order_id=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_contract_terms2",
+ params,
+ (NULL != contract_terms)
+ ? rs
+ : &rs[1]);
+ *pos_algorithm = (enum TALER_MerchantConfirmationAlgorithm) pos32;
+ if (NULL != claim_token)
+ *claim_token = ct;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_contract_terms2.h b/src/backenddb/pg_lookup_contract_terms2.h
new file mode 100644
index 00000000..6c1c8514
--- /dev/null
+++ b/src/backenddb/pg_lookup_contract_terms2.h
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_contract_terms2.h
+ * @brief implementation of the lookup_contract_terms2 function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_CONTRACT_TERMS2_H
+#define PG_LOOKUP_CONTRACT_TERMS2_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Retrieve contract terms given its @a order_id
+ *
+ * @param cls closure
+ * @param instance_id instance's identifier
+ * @param order_id order_id used to lookup.
+ * @param[out] contract_terms where to store the result, NULL to only check for existence
+ * @param[out] order_serial set to the order's serial number
+ * @param[out] paid set to true if the order is fully paid
+ * @param[out] claim_token set to the claim token, NULL to only check for existence
+ * @param[out] pos_key encoded key for payment verification
+ * @param[out] pos_algorithm algorithm to compute the payment verification
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_contract_terms2 (
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ json_t **contract_terms,
+ uint64_t *order_serial,
+ bool *paid,
+ struct TALER_ClaimTokenP *claim_token,
+ char **pos_key,
+ enum TALER_MerchantConfirmationAlgorithm *pos_algorithm);
+
+#endif
diff --git a/src/backenddb/pg_lookup_contract_terms3.c b/src/backenddb/pg_lookup_contract_terms3.c
new file mode 100644
index 00000000..ef955a51
--- /dev/null
+++ b/src/backenddb/pg_lookup_contract_terms3.c
@@ -0,0 +1,99 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_contract_terms3.c
+ * @brief Implementation of the lookup_contract_terms3 function for Postgres
+ * @author Iván Ãvalos
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_contract_terms3.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_contract_terms3 (
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const char *session_id,
+ json_t **contract_terms,
+ uint64_t *order_serial,
+ bool *paid,
+ bool *wired,
+ bool *session_matches,
+ struct TALER_ClaimTokenP *claim_token)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_ClaimTokenP ct;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (order_id),
+ NULL == session_id
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (session_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ /* contract_terms must be first! */
+ TALER_PQ_result_spec_json ("contract_terms",
+ contract_terms),
+ GNUNET_PQ_result_spec_uint64 ("order_serial",
+ order_serial),
+ GNUNET_PQ_result_spec_bool ("paid",
+ paid),
+ GNUNET_PQ_result_spec_bool ("wired",
+ wired),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_bool ("session_matches",
+ session_matches),
+ NULL),
+ GNUNET_PQ_result_spec_auto_from_type ("claim_token",
+ &ct),
+ GNUNET_PQ_result_spec_end
+ };
+
+ *session_matches = false;
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_contract_terms3",
+ "SELECT"
+ " contract_terms"
+ ",order_serial"
+ ",claim_token"
+ ",paid"
+ ",wired"
+ ",(session_id=$3) AS session_matches"
+ " FROM merchant_contract_terms"
+ " WHERE order_id=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_contract_terms3",
+ params,
+ (NULL != contract_terms)
+ ? rs
+ : &rs[1]);
+ if (NULL != claim_token)
+ *claim_token = ct;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_contract_terms3.h b/src/backenddb/pg_lookup_contract_terms3.h
new file mode 100644
index 00000000..d1cc78a2
--- /dev/null
+++ b/src/backenddb/pg_lookup_contract_terms3.h
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_contract_terms3.h
+ * @brief implementation of the lookup_contract_terms3 function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_CONTRACT_TERMS3_H
+#define PG_LOOKUP_CONTRACT_TERMS3_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Retrieve contract terms given its @a order_id
+ *
+ * @param cls closure
+ * @param instance_id instance's identifier
+ * @param order_id order_id used to lookup.
+ * @param session_id session_id to compare, can be NULL
+ * @param[out] contract_terms where to store the result, NULL to only check for existence
+ * @param[out] order_serial set to the order's serial number
+ * @param[out] paid set to true if the order is fully paid
+ * @param[out] wired set to true if the exchange wired the funds
+ * @param[out] session_matches set to true if @a session_id matches session stored for this contract
+ * @param[out] claim_token set to token to use for access control
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_contract_terms3 (
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const char *session_id,
+ json_t **contract_terms,
+ uint64_t *order_serial,
+ bool *paid,
+ bool *wired,
+ bool *session_matches,
+ struct TALER_ClaimTokenP *claim_token);
+
+#endif
diff --git a/src/backenddb/pg_lookup_deposits.c b/src/backenddb/pg_lookup_deposits.c
new file mode 100644
index 00000000..2440f62c
--- /dev/null
+++ b/src/backenddb/pg_lookup_deposits.c
@@ -0,0 +1,165 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_deposits.c
+ * @brief Implementation of the lookup_deposits function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_deposits.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #lookup_deposits_cb().
+ */
+struct LookupDepositsContext
+{
+ /**
+ * Function to call with results.
+ */
+ TALER_MERCHANTDB_DepositsCallback cb;
+
+ /**
+ * Closure for @e cls.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Transaction status (set).
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param[in,out] cls of type `struct LookupDepositsContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_deposits_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupDepositsContext *ldc = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount deposit_fee;
+ struct TALER_Amount refund_fee;
+ char *exchange_url;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("exchange_url",
+ &exchange_url),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &coin_pub),
+ TALER_PQ_result_spec_amount_with_currency ("amount_with_fee",
+ &amount_with_fee),
+ TALER_PQ_result_spec_amount_with_currency ("deposit_fee",
+ &deposit_fee),
+ TALER_PQ_result_spec_amount_with_currency ("refund_fee",
+ &refund_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ldc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ ldc->cb (ldc->cb_cls,
+ exchange_url,
+ &coin_pub,
+ &amount_with_fee,
+ &deposit_fee,
+ &refund_fee);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+ ldc->qs = num_results;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_deposits (
+ void *cls,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ TALER_MERCHANTDB_DepositsCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_end
+ };
+ struct LookupDepositsContext ldc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* no preflight check here, run in its own transaction by the caller! */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Finding deposits for h_contract_terms '%s'\n",
+ GNUNET_h2s (&h_contract_terms->hash));
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_deposits",
+ "SELECT"
+ " dcom.exchange_url"
+ ",dep.coin_pub"
+ ",dep.amount_with_fee"
+ ",dep.deposit_fee"
+ ",dep.refund_fee"
+ " FROM merchant_deposits dep"
+ " JOIN merchant_deposit_confirmations dcom"
+ " USING (deposit_confirmation_serial)"
+ " WHERE dcom.order_serial="
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE h_contract_terms=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1))");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_deposits",
+ params,
+ &lookup_deposits_cb,
+ &ldc);
+ if (qs <= 0)
+ return qs;
+ return ldc.qs;
+}
diff --git a/src/backenddb/pg_lookup_deposits.h b/src/backenddb/pg_lookup_deposits.h
new file mode 100644
index 00000000..33c7165a
--- /dev/null
+++ b/src/backenddb/pg_lookup_deposits.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_deposits.h
+ * @brief implementation of the lookup_deposits function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_DEPOSITS_H
+#define PG_LOOKUP_DEPOSITS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup information about coins that were successfully deposited for a
+ * given contract.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup deposits for
+ * @param h_contract_terms proposal data's hashcode
+ * @param cb function to call with payment data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_deposits (void *cls,
+ const char *instance_id,
+ const struct
+ TALER_PrivateContractHashP *h_contract_terms,
+ TALER_MERCHANTDB_DepositsCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_deposits_by_contract_and_coin.c b/src/backenddb/pg_lookup_deposits_by_contract_and_coin.c
new file mode 100644
index 00000000..9bf46d0c
--- /dev/null
+++ b/src/backenddb/pg_lookup_deposits_by_contract_and_coin.c
@@ -0,0 +1,192 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_deposits_by_contract_and_coin.c
+ * @brief Implementation of the lookup_deposits_by_contract_and_coin function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_deposits_by_contract_and_coin.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #lookup_deposits_by_contract_and_coin_cb().
+ */
+struct LookupDepositsByCnCContext
+{
+ /**
+ * Function to call for each deposit.
+ */
+ TALER_MERCHANTDB_CoinDepositCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Transaction result.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct LookupDepositsByCnCContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_deposits_by_contract_and_coin_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupDepositsByCnCContext *ldcc = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ char *exchange_url;
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount deposit_fee;
+ struct TALER_Amount refund_fee;
+ struct TALER_Amount wire_fee;
+ struct TALER_MerchantWireHashP h_wire;
+ struct GNUNET_TIME_Timestamp deposit_timestamp;
+ struct GNUNET_TIME_Timestamp refund_deadline;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("exchange_url",
+ &exchange_url),
+ TALER_PQ_result_spec_amount_with_currency ("amount_with_fee",
+ &amount_with_fee),
+ TALER_PQ_result_spec_amount_with_currency ("deposit_fee",
+ &deposit_fee),
+ TALER_PQ_result_spec_amount_with_currency ("refund_fee",
+ &refund_fee),
+ TALER_PQ_result_spec_amount_with_currency ("wire_fee",
+ &wire_fee),
+ GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+ &h_wire),
+ GNUNET_PQ_result_spec_timestamp ("deposit_timestamp",
+ &deposit_timestamp),
+ GNUNET_PQ_result_spec_timestamp ("refund_deadline",
+ &refund_deadline),
+ GNUNET_PQ_result_spec_auto_from_type ("exchange_sig",
+ &exchange_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
+ &exchange_pub),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ ldcc->cb (ldcc->cb_cls,
+ exchange_url,
+ &amount_with_fee,
+ &deposit_fee,
+ &refund_fee,
+ &wire_fee,
+ &h_wire,
+ deposit_timestamp,
+ refund_deadline,
+ &exchange_sig,
+ &exchange_pub);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+ ldcc->qs = num_results;
+}
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_deposits_by_contract_and_coin (void *cls,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ TALER_MERCHANTDB_CoinDepositCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct LookupDepositsByCnCContext ldcc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_deposits_by_contract_and_coin",
+ "SELECT"
+ " mcon.exchange_url"
+ ",dep.amount_with_fee"
+ ",dep.deposit_fee"
+ ",dep.refund_fee"
+ ",mcon.wire_fee"
+ ",acc.h_wire"
+ ",mcon.deposit_timestamp"
+ ",mct.refund_deadline"
+ ",mcon.exchange_sig"
+ ",msig.exchange_pub"
+ " FROM merchant_contract_terms mct"
+ " JOIN merchant_deposit_confirmations mcon"
+ " USING (order_serial)"
+ " JOIN merchant_deposits dep"
+ " USING (deposit_confirmation_serial)"
+ " JOIN merchant_exchange_signing_keys msig"
+ " USING (signkey_serial)"
+ " JOIN merchant_accounts acc"
+ " USING (account_serial)"
+ " WHERE h_contract_terms=$2"
+ " AND dep.coin_pub=$3"
+ " AND mct.merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "lookup_deposits_by_contract_and_coin",
+ params,
+ &lookup_deposits_by_contract_and_coin_cb,
+ &ldcc);
+ if (0 >= qs)
+ return qs;
+ return ldcc.qs;
+}
diff --git a/src/backenddb/pg_lookup_deposits_by_contract_and_coin.h b/src/backenddb/pg_lookup_deposits_by_contract_and_coin.h
new file mode 100644
index 00000000..250e3dfc
--- /dev/null
+++ b/src/backenddb/pg_lookup_deposits_by_contract_and_coin.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_deposits_by_contract_and_coin.h
+ * @brief implementation of the lookup_deposits_by_contract_and_coin function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_DEPOSITS_BY_CONTRACT_AND_COIN_H
+#define PG_LOOKUP_DEPOSITS_BY_CONTRACT_AND_COIN_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup information about coin payments by @a h_contract_terms and
+ * @a coin_pub.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup payments for
+ * @param h_contract_terms proposal data's hashcode
+ * @param coin_pub public key to use for the search
+ * @param cb function to call with payment data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_deposits_by_contract_and_coin (void *cls,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ TALER_MERCHANTDB_CoinDepositCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_deposits_by_order.c b/src/backenddb/pg_lookup_deposits_by_order.c
new file mode 100644
index 00000000..fdaf1dfc
--- /dev/null
+++ b/src/backenddb/pg_lookup_deposits_by_order.c
@@ -0,0 +1,161 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_deposits_by_order.c
+ * @brief Implementation of the lookup_deposits_by_order function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_deposits_by_order.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for lookup_deposits_by_order_cb().
+ */
+struct LookupDepositsByOrderContext
+{
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Function to call with all results.
+ */
+ TALER_MERCHANTDB_DepositedCoinsCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Set to the query result.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct LookupDepositsByOrderContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_deposits_by_order_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupDepositsByOrderContext *ldoc = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t deposit_serial;
+ char *exchange_url;
+ struct TALER_MerchantWireHashP h_wire;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount deposit_fee;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("deposit_serial",
+ &deposit_serial),
+ GNUNET_PQ_result_spec_string ("exchange_url",
+ &exchange_url),
+ GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+ &h_wire),
+ TALER_PQ_result_spec_amount_with_currency ("amount_with_fee",
+ &amount_with_fee),
+ TALER_PQ_result_spec_amount_with_currency ("deposit_fee",
+ &deposit_fee),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &coin_pub),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ldoc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ ldoc->cb (ldoc->cb_cls,
+ deposit_serial,
+ exchange_url,
+ &h_wire,
+ &amount_with_fee,
+ &deposit_fee,
+ &coin_pub);
+ GNUNET_PQ_cleanup_result (rs); /* technically useless here */
+ }
+ ldoc->qs = num_results;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_deposits_by_order (void *cls,
+ uint64_t order_serial,
+ TALER_MERCHANTDB_DepositedCoinsCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupDepositsByOrderContext ldoc = {
+ .pg = pg,
+ .cb = cb,
+ .cb_cls = cb_cls
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&order_serial),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_deposits_by_order",
+ "SELECT"
+ " dep.deposit_serial"
+ ",mcon.exchange_url"
+ ",acc.h_wire"
+ ",dep.amount_with_fee"
+ ",dep.deposit_fee"
+ ",dep.coin_pub"
+ " FROM merchant_deposits dep"
+ " JOIN merchant_deposit_confirmations mcon"
+ " USING(deposit_confirmation_serial)"
+ " JOIN merchant_accounts acc"
+ " USING (account_serial)"
+ " WHERE mcon.order_serial=$1");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_deposits_by_order",
+ params,
+ &lookup_deposits_by_order_cb,
+ &ldoc);
+
+ if (qs < 0)
+ return qs;
+ return ldoc.qs;
+}
diff --git a/src/backenddb/pg_lookup_deposits_by_order.h b/src/backenddb/pg_lookup_deposits_by_order.h
new file mode 100644
index 00000000..ecf2d367
--- /dev/null
+++ b/src/backenddb/pg_lookup_deposits_by_order.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_deposits_by_order.h
+ * @brief implementation of the lookup_deposits_by_order function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_DEPOSITS_BY_ORDER_H
+#define PG_LOOKUP_DEPOSITS_BY_ORDER_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Retrieve details about coins that were deposited for an order.
+ *
+ * @param cls closure
+ * @param order_serial identifies the order
+ * @param cb function to call for each deposited coin
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_deposits_by_order (void *cls,
+ uint64_t order_serial,
+ TALER_MERCHANTDB_DepositedCoinsCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_instance_auth.c b/src/backenddb/pg_lookup_instance_auth.c
new file mode 100644
index 00000000..1360cce9
--- /dev/null
+++ b/src/backenddb/pg_lookup_instance_auth.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_instance_auth.c
+ * @brief Implementation of the lookup_instance_auth function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_instance_auth.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_instance_auth (
+ void *cls,
+ const char *instance_id,
+ struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("auth_hash",
+ &ias->auth_hash),
+ GNUNET_PQ_result_spec_auto_from_type ("auth_salt",
+ &ias->auth_salt),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_instance_auth",
+ "SELECT"
+ " auth_hash"
+ ",auth_salt"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_instance_auth",
+ params,
+ rs);
+}
diff --git a/src/backenddb/pg_lookup_instance_auth.h b/src/backenddb/pg_lookup_instance_auth.h
new file mode 100644
index 00000000..ff788a79
--- /dev/null
+++ b/src/backenddb/pg_lookup_instance_auth.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_instance_auth.h
+ * @brief implementation of the lookup_instance_auth function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_INSTANCE_AUTH_H
+#define PG_LOOKUP_INSTANCE_AUTH_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup authentication data of an instance.
+ *
+ * @param cls closure
+ * @param instance_id instance to query
+ * @param[out] ias where to store the auth data
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_instance_auth (void *cls,
+ const char *instance_id,
+ struct TALER_MERCHANTDB_InstanceAuthSettings *ias);
+
+#endif
diff --git a/src/backenddb/pg_lookup_instances.c b/src/backenddb/pg_lookup_instances.c
new file mode 100644
index 00000000..323b1957
--- /dev/null
+++ b/src/backenddb/pg_lookup_instances.c
@@ -0,0 +1,343 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_instances.c
+ * @brief Implementation of the lookup_instances function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_instances.h"
+#include "pg_helper.h"
+
+
+/**
+ * Context for lookup_instances().
+ */
+struct LookupInstancesContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_InstanceCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Database context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Instance settings, valid only during find_instances_cb().
+ */
+ struct TALER_MERCHANTDB_InstanceSettings is;
+
+ /**
+ * Instance authentication settings, valid only during find_instances_cb().
+ */
+ struct TALER_MERCHANTDB_InstanceAuthSettings ias;
+
+ /**
+ * Instance serial number, valid only during find_instances_cb().
+ */
+ uint64_t instance_serial;
+
+ /**
+ * Public key of the current instance, valid only during find_instances_cb().
+ */
+ struct TALER_MerchantPublicKeyP merchant_pub;
+
+ /**
+ * Set to the return value on errors.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+
+ /**
+ * true if we only are interested in instances for which we have the private key.
+ */
+ bool active_only;
+};
+
+
+/**
+ * Helper function to run PREPARE() macro.
+ *
+ * @param pg closure to pass
+ * @return status of the preparation
+ */
+static enum GNUNET_DB_QueryStatus
+prepare (struct PostgresClosure *pg)
+{
+ PREPARE (pg,
+ "lookup_instance_private_key",
+ "SELECT"
+ " merchant_priv"
+ " FROM merchant_keys"
+ " WHERE merchant_serial=$1");
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+}
+
+
+/**
+ * We are processing an instances lookup and have the @a accounts.
+ * Find the private key if possible, and invoke the callback.
+ *
+ * @param lic context we are handling
+ */
+static void
+call_cb (struct LookupInstancesContext *lic)
+{
+ struct PostgresClosure *pg = lic->pg;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&lic->instance_serial),
+ GNUNET_PQ_query_param_end
+ };
+ struct TALER_MerchantPrivateKeyP merchant_priv;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_priv",
+ &merchant_priv),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = prepare (pg);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ lic->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_instance_private_key",
+ params,
+ rs);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ lic->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ if ( (0 == qs) &&
+ (lic->active_only) )
+ return; /* skip, not interesting */
+ lic->cb (lic->cb_cls,
+ &lic->merchant_pub,
+ (0 == qs) ? NULL : &merchant_priv,
+ &lic->is,
+ &lic->ias);
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about instances.
+ *
+ * @param cls of type `struct FindInstancesContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_instances_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupInstancesContext *lic = cls;
+ struct PostgresClosure *pg = lic->pg;
+
+ lic->qs = prepare (pg);
+ if (lic->qs < 0)
+ {
+ GNUNET_break (0);
+ return;
+ }
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ bool no_auth;
+ bool no_salt;
+ uint32_t ut32;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("merchant_serial",
+ &lic->instance_serial),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &lic->merchant_pub),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("auth_hash",
+ &lic->ias.auth_hash),
+ &no_auth),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("auth_salt",
+ &lic->ias.auth_salt),
+ &no_salt),
+ GNUNET_PQ_result_spec_string ("merchant_id",
+ &lic->is.id),
+ GNUNET_PQ_result_spec_string ("merchant_name",
+ &lic->is.name),
+ GNUNET_PQ_result_spec_uint32 ("user_type",
+ &ut32),
+ TALER_PQ_result_spec_json ("address",
+ &lic->is.address),
+ TALER_PQ_result_spec_json ("jurisdiction",
+ &lic->is.jurisdiction),
+ GNUNET_PQ_result_spec_bool ("use_stefan",
+ &lic->is.use_stefan),
+ GNUNET_PQ_result_spec_relative_time ("default_wire_transfer_delay",
+ &lic->is.default_wire_transfer_delay),
+ GNUNET_PQ_result_spec_relative_time ("default_pay_delay",
+ &lic->is.default_pay_delay),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("website",
+ &lic->is.website),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("email",
+ &lic->is.email),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("logo",
+ &lic->is.logo),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ memset (&lic->ias.auth_salt,
+ 0,
+ sizeof (lic->ias.auth_salt));
+ memset (&lic->ias.auth_hash,
+ 0,
+ sizeof (lic->ias.auth_hash));
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ lic->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ lic->is.ut = (enum TALER_KYCLOGIC_KycUserType) ut32;
+ call_cb (lic);
+ GNUNET_PQ_cleanup_result (rs);
+ if (0 > lic->qs)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_instances (void *cls,
+ bool active_only,
+ TALER_MERCHANTDB_InstanceCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupInstancesContext lic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .active_only = active_only,
+ .pg = pg
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_instances",
+ "SELECT"
+ " merchant_serial"
+ ",merchant_pub"
+ ",auth_hash"
+ ",auth_salt"
+ ",merchant_id"
+ ",merchant_name"
+ ",user_type"
+ ",address"
+ ",jurisdiction"
+ ",use_stefan"
+ ",default_wire_transfer_delay"
+ ",default_pay_delay"
+ ",website"
+ ",email"
+ ",logo"
+ " FROM merchant_instances");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_instances",
+ params,
+ &lookup_instances_cb,
+ &lic);
+ if (0 > lic.qs)
+ return lic.qs;
+ return qs;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_instance (void *cls,
+ const char *id,
+ bool active_only,
+ TALER_MERCHANTDB_InstanceCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupInstancesContext lic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .active_only = active_only,
+ .pg = pg
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (id),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_instance",
+ "SELECT"
+ " merchant_serial"
+ ",merchant_pub"
+ ",auth_hash"
+ ",auth_salt"
+ ",merchant_id"
+ ",merchant_name"
+ ",user_type"
+ ",address"
+ ",jurisdiction"
+ ",use_stefan"
+ ",default_wire_transfer_delay"
+ ",default_pay_delay"
+ ",website"
+ ",email"
+ ",logo"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_instance",
+ params,
+ &lookup_instances_cb,
+ &lic);
+ if (0 > lic.qs)
+ return lic.qs;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_instances.h b/src/backenddb/pg_lookup_instances.h
new file mode 100644
index 00000000..e3b20ff1
--- /dev/null
+++ b/src/backenddb/pg_lookup_instances.h
@@ -0,0 +1,60 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_instances.h
+ * @brief implementation of the lookup_instances function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_INSTANCES_H
+#define PG_LOOKUP_INSTANCES_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Lookup all of the instances this backend has configured.
+ *
+ * @param cls closure
+ * @param active_only only find 'active' instances
+ * @param cb function to call on all instances found
+ * @param cb_cls closure for @a cb
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_instances (void *cls,
+ bool active_only,
+ TALER_MERCHANTDB_InstanceCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Lookup one of the instances this backend has configured.
+ *
+ * @param cls closure
+ * @param id instance ID to resolve
+ * @param active_only only find 'active' instances
+ * @param cb function to call on all instances found
+ * @param cb_cls closure for @a cb
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_instance (void *cls,
+ const char *id,
+ bool active_only,
+ TALER_MERCHANTDB_InstanceCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_order.c b/src/backenddb/pg_lookup_order.c
new file mode 100644
index 00000000..6df7456c
--- /dev/null
+++ b/src/backenddb/pg_lookup_order.c
@@ -0,0 +1,96 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_order.c
+ * @brief Implementation of the lookup_order function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_order.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_order (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ struct TALER_ClaimTokenP *claim_token,
+ struct TALER_MerchantPostDataHashP *h_post_data,
+ json_t **contract_terms)
+{
+ struct PostgresClosure *pg = cls;
+ json_t *j;
+ struct TALER_ClaimTokenP ct;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_json ("contract_terms",
+ &j),
+ GNUNET_PQ_result_spec_auto_from_type ("claim_token",
+ &ct),
+ GNUNET_PQ_result_spec_auto_from_type ("h_post_data",
+ h_post_data),
+ GNUNET_PQ_result_spec_end
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Finding contract term, order_id: '%s', instance_id: '%s'.\n",
+ order_id,
+ instance_id);
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_order",
+ "SELECT"
+ " contract_terms"
+ ",claim_token"
+ ",h_post_data"
+ ",pos_key"
+ " FROM merchant_orders"
+ " WHERE merchant_orders.merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND merchant_orders.order_id=$2");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_order",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ if (NULL != contract_terms)
+ *contract_terms = j;
+ else
+ json_decref (j);
+ if (NULL != claim_token)
+ *claim_token = ct;
+ }
+ else
+ {
+ /* just to be safe: NULL it */
+ if (NULL != contract_terms)
+ *contract_terms = NULL;
+ if (NULL != claim_token)
+ *claim_token = (struct TALER_ClaimTokenP) { 0 }
+ ;
+ }
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_order.h b/src/backenddb/pg_lookup_order.h
new file mode 100644
index 00000000..7ca60869
--- /dev/null
+++ b/src/backenddb/pg_lookup_order.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_order.h
+ * @brief implementation of the lookup_order function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_ORDER_H
+#define PG_LOOKUP_ORDER_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Retrieve order given its @a order_id and the @a instance_id.
+ *
+ * @param cls closure
+ * @param instance_id instance to obtain order of
+ * @param order_id order id used to perform the lookup
+ * @param[out] claim_token the claim token generated for the order,
+ * NULL to only test if the order exists
+ * @param[out] h_post_data set to the hash of the POST data that created the order
+ * @param[out] contract_terms where to store the retrieved contract terms,
+ * NULL to only test if the order exists
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_order (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ struct TALER_ClaimTokenP *claim_token,
+ struct TALER_MerchantPostDataHashP *h_post_data,
+ json_t **contract_terms);
+
+#endif
diff --git a/src/backenddb/pg_lookup_order_by_fulfillment.c b/src/backenddb/pg_lookup_order_by_fulfillment.c
new file mode 100644
index 00000000..e600df32
--- /dev/null
+++ b/src/backenddb/pg_lookup_order_by_fulfillment.c
@@ -0,0 +1,80 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_order_by_fulfillment.c
+ * @brief Implementation of the lookup_order_by_fulfillment function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_order_by_fulfillment.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_order_by_fulfillment (
+ void *cls,
+ const char *instance_id,
+ const char *fulfillment_url,
+ const char *session_id,
+ bool allow_refunded_for_repurchase,
+ char **order_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (fulfillment_url),
+ GNUNET_PQ_query_param_string (session_id),
+ GNUNET_PQ_query_param_bool (allow_refunded_for_repurchase),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("order_id",
+ order_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_order_by_fulfillment",
+ "SELECT"
+ " mct.order_id"
+ " FROM merchant_contract_terms mct"
+ " LEFT JOIN merchant_refunds mref"
+ " USING (order_serial)"
+ " WHERE fulfillment_url=$2"
+ " AND session_id=$3"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND ((CAST($4 AS BOOL)) OR"
+ " mref.refund_serial IS NULL)"
+ /* Theoretically, multiple paid orders
+ for the same fulfillment URL could
+ exist for this session_id -- if a
+ wallet was broken and did multiple
+ payments without repurchase detection.
+ So we need to limit to 1 when returning! */
+ " ORDER BY order_id DESC"
+ " LIMIT 1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_order_by_fulfillment",
+ params,
+ rs);
+}
diff --git a/src/backenddb/pg_lookup_order_by_fulfillment.h b/src/backenddb/pg_lookup_order_by_fulfillment.h
new file mode 100644
index 00000000..44e96756
--- /dev/null
+++ b/src/backenddb/pg_lookup_order_by_fulfillment.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_order_by_fulfillment.h
+ * @brief implementation of the lookup_order_by_fulfillment function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_ORDER_BY_FULFILLMENT_H
+#define PG_LOOKUP_ORDER_BY_FULFILLMENT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Retrieve the order ID that was used to pay for a resource within a session.
+ *
+ * @param cls closure
+ * @param instance_id identifying the instance
+ * @param fulfillment_url URL that canonically identifies the resource
+ * being paid for
+ * @param session_id session id
+ * @param allow_refunded_for_repurchase true to include refunded orders in repurchase detection
+ * @param[out] order_id where to store the order ID that was used when
+ * paying for the resource URL
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_order_by_fulfillment (void *cls,
+ const char *instance_id,
+ const char *fulfillment_url,
+ const char *session_id,
+ bool allow_refunded_for_repurchase,
+ char **order_id);
+
+#endif
diff --git a/src/backenddb/pg_lookup_order_status.c b/src/backenddb/pg_lookup_order_status.c
new file mode 100644
index 00000000..be6f0a15
--- /dev/null
+++ b/src/backenddb/pg_lookup_order_status.c
@@ -0,0 +1,73 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_order_status.c
+ * @brief Implementation of the lookup_order_status function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_order_status.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_order_status (
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ bool *paid)
+{
+ struct PostgresClosure *pg = cls;
+ uint8_t paid8;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("paid",
+ &paid8),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_order_status",
+ "SELECT"
+ " h_contract_terms"
+ ",paid"
+ " FROM merchant_contract_terms"
+ " WHERE merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND order_id=$2");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_order_status",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ *paid = (0 != paid8);
+ else
+ *paid = false; /* just to be safe(r) */
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_order_status.h b/src/backenddb/pg_lookup_order_status.h
new file mode 100644
index 00000000..acf8fe31
--- /dev/null
+++ b/src/backenddb/pg_lookup_order_status.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_order_status.h
+ * @brief implementation of the lookup_order_status function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_ORDER_STATUS_H
+#define PG_LOOKUP_ORDER_STATUS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Retrieve contract terms given its @a order_id
+ *
+ * @param cls closure
+ * @param instance_id instance's identifier
+ * @param order_id order to lookup contract for
+ * @param[out] h_contract_terms set to the hash of the contract.
+ * @param[out] paid set to the payment status of the contract
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_order_status (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ bool *paid);
+
+#endif
diff --git a/src/backenddb/pg_lookup_order_status_by_serial.c b/src/backenddb/pg_lookup_order_status_by_serial.c
new file mode 100644
index 00000000..b6a4ebb0
--- /dev/null
+++ b/src/backenddb/pg_lookup_order_status_by_serial.c
@@ -0,0 +1,77 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_order_status_by_serial.c
+ * @brief Implementation of the lookup_order_status_by_serial function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_order_status_by_serial.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_order_status_by_serial (void *cls,
+ const char *instance_id,
+ uint64_t order_serial,
+ char **order_id,
+ struct TALER_PrivateContractHashP *
+ h_contract_terms,
+ bool *paid)
+{
+ struct PostgresClosure *pg = cls;
+ uint8_t paid8;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_uint64 (&order_serial),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("paid",
+ &paid8),
+ GNUNET_PQ_result_spec_string ("order_id",
+ order_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_order_status_by_serial",
+ "SELECT"
+ " h_contract_terms"
+ ",order_id"
+ ",paid"
+ " FROM merchant_contract_terms"
+ " WHERE merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND order_serial=$2");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_order_status_by_serial",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ *paid = (0 != paid8);
+ else
+ *paid = false; /* just to be safe(r) */
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_order_status_by_serial.h b/src/backenddb/pg_lookup_order_status_by_serial.h
new file mode 100644
index 00000000..eb60e97f
--- /dev/null
+++ b/src/backenddb/pg_lookup_order_status_by_serial.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_order_status_by_serial.h
+ * @brief implementation of the lookup_order_status_by_serial function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_ORDER_STATUS_BY_SERIAL_H
+#define PG_LOOKUP_ORDER_STATUS_BY_SERIAL_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Retrieve contract terms given its @a order_serial
+ *
+ * @param cls closure
+ * @param instance_id instance's identifier
+ * @param order_serial serial ID of the order to look up
+ * @param[out] order_id set to ID of the order
+ * @param[out] h_contract_terms set to the hash of the contract.
+ * @param[out] paid set to the payment status of the contract
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_order_status_by_serial (void *cls,
+ const char *instance_id,
+ uint64_t order_serial,
+ char **order_id,
+ struct TALER_PrivateContractHashP *
+ h_contract_terms,
+ bool *paid);
+
+#endif
diff --git a/src/backenddb/pg_lookup_order_summary.c b/src/backenddb/pg_lookup_order_summary.c
new file mode 100644
index 00000000..3a2a4e16
--- /dev/null
+++ b/src/backenddb/pg_lookup_order_summary.c
@@ -0,0 +1,75 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_order_summary.c
+ * @brief Implementation of the lookup_order_summary function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_order_summary.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_order_summary (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ struct GNUNET_TIME_Timestamp *timestamp,
+ uint64_t *order_serial)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("order_serial",
+ order_serial),
+ GNUNET_PQ_result_spec_timestamp ("creation_time",
+ timestamp),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_order_summary",
+ "(SELECT"
+ " creation_time"
+ ",order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE merchant_contract_terms.merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND merchant_contract_terms.order_id=$2)"
+ "UNION"
+ "(SELECT"
+ " creation_time"
+ ",order_serial"
+ " FROM merchant_orders"
+ " WHERE merchant_orders.merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND merchant_orders.order_id=$2)");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_order_summary",
+ params,
+ rs);
+}
diff --git a/src/backenddb/pg_lookup_order_summary.h b/src/backenddb/pg_lookup_order_summary.h
new file mode 100644
index 00000000..5827aa66
--- /dev/null
+++ b/src/backenddb/pg_lookup_order_summary.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_order_summary.h
+ * @brief implementation of the lookup_order_summary function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_ORDER_SUMMARY_H
+#define PG_LOOKUP_ORDER_SUMMARY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Retrieve order summary given its @a order_id and the @a instance_id.
+ *
+ * @param cls closure
+ * @param instance_id instance to obtain order of
+ * @param order_id order id used to perform the lookup
+ * @param[out] timestamp when was the order created
+ * @param[out] order_serial under which serial do we keep this order
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_order_summary (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ struct GNUNET_TIME_Timestamp *timestamp,
+ uint64_t *order_serial);
+
+#endif
diff --git a/src/backenddb/pg_lookup_orders.c b/src/backenddb/pg_lookup_orders.c
new file mode 100644
index 00000000..972db6d8
--- /dev/null
+++ b/src/backenddb/pg_lookup_orders.c
@@ -0,0 +1,280 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022,2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_orders.c
+ * @brief Implementation of the lookup_orders function for Postgres
+ * @author Iván Ãvalos
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_orders.h"
+#include "pg_helper.h"
+
+/**
+ * Context used for TMH_PG_lookup_orders().
+ */
+struct LookupOrdersContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_OrdersCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about orders.
+ *
+ * @param[in,out] cls of type `struct LookupOrdersContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_orders_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupOrdersContext *plc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *order_id;
+ uint64_t order_serial;
+ struct GNUNET_TIME_Timestamp ts;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("order_id",
+ &order_id),
+ GNUNET_PQ_result_spec_uint64 ("order_serial",
+ &order_serial),
+ GNUNET_PQ_result_spec_timestamp ("creation_time",
+ &ts),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ plc->extract_failed = true;
+ return;
+ }
+ plc->cb (plc->cb_cls,
+ order_id,
+ order_serial,
+ ts);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_orders (void *cls,
+ const char *instance_id,
+ const struct TALER_MERCHANTDB_OrderFilter *of,
+ TALER_MERCHANTDB_OrdersCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupOrdersContext plc = {
+ .cb = cb,
+ .cb_cls = cb_cls
+ };
+ uint64_t limit = (of->delta > 0) ? of->delta : -of->delta;
+ struct GNUNET_PQ_QueryParam params[] = {
+ /* $1 */
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_uint64 (&limit),
+ GNUNET_PQ_query_param_uint64 (&of->start_row),
+ GNUNET_PQ_query_param_timestamp (&of->date),
+ GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_ALL == of->paid)),
+ /* $6 */
+ GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES == of->paid)),
+ GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_ALL == of->refunded)),
+ GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES == of->refunded)),
+ GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_ALL == of->wired)),
+ GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES == of->wired)),
+ /* $11 */
+ GNUNET_PQ_query_param_bool (NULL == of->session_id),
+ NULL == of->session_id
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (of->session_id),
+ GNUNET_PQ_query_param_bool (NULL == of->fulfillment_url),
+ NULL == of->fulfillment_url
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (of->fulfillment_url),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ char stmt[128];
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Looking up orders, using filter paid:%d, refunded: %d, wired: %d\n",
+ of->paid,
+ of->refunded,
+ of->wired);
+ GNUNET_snprintf (stmt,
+ sizeof (stmt),
+ "lookup_orders_%s",
+ (of->delta > 0) ? "inc" : "dec");
+ PREPARE (pg,
+ "lookup_orders_dec",
+ "(SELECT"
+ " order_id"
+ ",order_serial"
+ ",creation_time"
+ " FROM merchant_orders"
+ " WHERE merchant_orders.merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND"
+ " order_serial < $3"
+ " AND"
+ " creation_time < $4"
+ " AND ($5 OR "
+ " NOT CAST($6 AS BOOL))" /* unclaimed orders are never paid */
+ " AND ($7 OR "
+ " NOT CAST($8 AS BOOL))"/* unclaimed orders are never refunded */
+ " AND ($9 OR "
+ " NOT CAST($10 AS BOOL))" /* unclaimed orders are never wired */
+ " AND"
+ " order_serial NOT IN"
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms)" /* only select unclaimed orders */
+ " AND ($11 OR "
+ " ($12 = session_id))"
+ " AND ($13 OR "
+ " ($14 = fulfillment_url))"
+ " ORDER BY order_serial DESC"
+ " LIMIT $2)"
+ "UNION " /* union ensures elements are distinct! */
+ "(SELECT"
+ " order_id"
+ ",order_serial"
+ ",creation_time"
+ " FROM merchant_contract_terms"
+ " WHERE merchant_contract_terms.merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND"
+ " order_serial < $3"
+ " AND"
+ " creation_time < $4"
+ " AND ($5 OR "
+ " (CAST($6 AS BOOL) = paid))"
+ " AND ($7 OR "
+ " (CAST($8 AS BOOL) = (order_serial IN"
+ " (SELECT order_serial "
+ " FROM merchant_refunds))))"
+ " AND ($9 OR"
+ " (CAST($10 AS BOOL) = wired))"
+ " AND ($11 OR "
+ " ($12 = session_id))"
+ " AND ($13 OR "
+ " ($14 = fulfillment_url))"
+ " ORDER BY order_serial DESC"
+ " LIMIT $2)"
+ " ORDER BY order_serial DESC"
+ " LIMIT $2");
+
+ PREPARE (pg,
+ "lookup_orders_inc",
+ "(SELECT"
+ " order_id"
+ ",order_serial"
+ ",creation_time"
+ " FROM merchant_orders"
+ " WHERE merchant_orders.merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND"
+ " order_serial > $3"
+ " AND"
+ " creation_time > $4"
+ " AND ($5 OR "
+ " NOT CAST($6 AS BOOL))" /* unclaimed orders are never paid */
+ " AND ($7 OR "
+ " NOT CAST($8 AS BOOL))"/* unclaimed orders are never refunded */
+ " AND ($9 OR "
+ " NOT CAST($10 AS BOOL))" /* unclaimed orders are never wired */
+ " AND"
+ " (order_serial NOT IN"
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms))" /* only select unclaimed orders */
+ " AND ($11 OR "
+ " ($12 = session_id))"
+ " AND ($13 OR "
+ " ($14 = fulfillment_url))"
+ " ORDER BY order_serial ASC"
+ " LIMIT $2)"
+ "UNION " /* union ensures elements are distinct! */
+ "(SELECT"
+ " order_id"
+ ",order_serial"
+ ",creation_time"
+ " FROM merchant_contract_terms"
+ " WHERE merchant_contract_terms.merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND"
+ " order_serial > $3"
+ " AND"
+ " creation_time > $4"
+ " AND ($5 OR "
+ " (CAST($6 AS BOOL) = paid))"
+ " AND ($7 OR "
+ " (CAST($8 AS BOOL) = (order_serial IN"
+ " (SELECT order_serial "
+ " FROM merchant_refunds))))"
+ " AND ($9 OR"
+ " (CAST($10 AS BOOL) = wired))"
+ " AND ($11 OR "
+ " ($12 = session_id))"
+ " AND ($13 OR "
+ " ($14 = fulfillment_url))"
+ " ORDER BY order_serial ASC"
+ " LIMIT $2)"
+ " ORDER BY order_serial ASC"
+ " LIMIT $2");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ stmt,
+ params,
+ &lookup_orders_cb,
+ &plc);
+ if (plc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_orders.h b/src/backenddb/pg_lookup_orders.h
new file mode 100644
index 00000000..4b00f18b
--- /dev/null
+++ b/src/backenddb/pg_lookup_orders.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_orders.h
+ * @brief implementation of the lookup_orders function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_ORDERS_H
+#define PG_LOOKUP_ORDERS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Retrieve orders given the @a instance_id.
+ *
+ * @param cls closure
+ * @param instance_id instance to obtain order of
+ * @param of filter to apply when looking up orders
+ * @param cb callback to pass all the orders that are found
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_orders (void *cls,
+ const char *instance_id,
+ const struct TALER_MERCHANTDB_OrderFilter *of,
+ TALER_MERCHANTDB_OrdersCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_otp_devices.c b/src/backenddb/pg_lookup_otp_devices.c
new file mode 100644
index 00000000..b4a2b569
--- /dev/null
+++ b/src/backenddb/pg_lookup_otp_devices.c
@@ -0,0 +1,133 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_otp_devices.c
+ * @brief Implementation of the lookup_otp_devices function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_otp_devices.h"
+#include "pg_helper.h"
+
+
+/**
+ * Context used for TMH_PG_lookup_otp_devices().
+ */
+struct LookupOtpDeviceContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_OtpDeviceCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about otp_device.
+ *
+ * @param[in,out] cls of type `struct LookupOtpDeviceContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_otp_devices_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupOtpDeviceContext *tlc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *otp_device_id;
+ char *otp_device_description;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("otp_id",
+ &otp_device_id),
+ GNUNET_PQ_result_spec_string ("otp_description",
+ &otp_device_description),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ tlc->extract_failed = true;
+ return;
+ }
+ tlc->cb (tlc->cb_cls,
+ otp_device_id,
+ otp_device_description);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_otp_devices (void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_OtpDeviceCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupOtpDeviceContext tlc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ /* Can be overwritten by the lookup_otp_device_cb */
+ .extract_failed = false,
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_otp_devices",
+ "SELECT"
+ " otp_id"
+ ",otp_description"
+ " FROM merchant_otp_devices"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_otp_devices",
+ params,
+ &lookup_otp_devices_cb,
+ &tlc);
+ /* If there was an error inside lookup_otp_device_cb, return a hard error. */
+ if (tlc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_otp_devices.h b/src/backenddb/pg_lookup_otp_devices.h
new file mode 100644
index 00000000..bb58b70f
--- /dev/null
+++ b/src/backenddb/pg_lookup_otp_devices.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_otp_devices.h
+ * @brief implementation of the lookup_otp_devices function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_OTP_DEVICES_H
+#define PG_LOOKUP_OTP_DEVICES_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Lookup all of the OTP devices the given instance has configured.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup OTP devices for
+ * @param cb function to call on all OTP devices found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_otp_devices (void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_OtpDeviceCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/backenddb/pg_lookup_pending_deposits.c b/src/backenddb/pg_lookup_pending_deposits.c
new file mode 100644
index 00000000..ab8981a5
--- /dev/null
+++ b/src/backenddb/pg_lookup_pending_deposits.c
@@ -0,0 +1,201 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_pending_deposits.c
+ * @brief Implementation of the lookup_pending_deposits function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_pending_deposits.h"
+#include "pg_helper.h"
+
+
+/**
+ * Context for lookup_pending_deposits().
+ */
+struct LookupDepositsContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_PendingDepositsCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Database context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to the return value on errors.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about instances.
+ *
+ * @param cls of type `struct LookupDepositsContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_deposits_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupDepositsContext *ldc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t deposit_serial;
+ struct GNUNET_TIME_Absolute wire_deadline;
+ struct GNUNET_TIME_Relative retry_backoff;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct TALER_MerchantPrivateKeyP merchant_priv;
+ char *instance_id;
+ struct TALER_MerchantWireHashP h_wire;
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount deposit_fee;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("deposit_serial",
+ &deposit_serial),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_priv",
+ &merchant_priv),
+ GNUNET_PQ_result_spec_string ("merchant_id",
+ &instance_id),
+ GNUNET_PQ_result_spec_absolute_time ("wire_transfer_deadline",
+ &wire_deadline),
+ GNUNET_PQ_result_spec_relative_time ("retry_backoff",
+ &retry_backoff),
+ GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+ &h_wire),
+ TALER_PQ_result_spec_amount_with_currency ("amount_with_fee",
+ &amount_with_fee),
+ TALER_PQ_result_spec_amount_with_currency ("deposit_fee",
+ &deposit_fee),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &coin_pub),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ldc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ ldc->cb (ldc->cb_cls,
+ deposit_serial,
+ wire_deadline,
+ retry_backoff,
+ &h_contract_terms,
+ &merchant_priv,
+ instance_id,
+ &h_wire,
+ &amount_with_fee,
+ &deposit_fee,
+ &coin_pub);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_pending_deposits (
+ void *cls,
+ const char *exchange_url,
+ uint64_t limit,
+ bool allow_future,
+ TALER_MERCHANTDB_PendingDepositsCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupDepositsContext ldc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+ struct GNUNET_TIME_Absolute now
+ = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (exchange_url),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_uint64 (&limit),
+ GNUNET_PQ_query_param_bool (allow_future),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_pending_deposits",
+ "SELECT"
+ " md.deposit_serial"
+ ",mct.h_contract_terms"
+ ",mk.merchant_priv"
+ ",mi.merchant_id"
+ ",mdc.wire_transfer_deadline"
+ ",ma.h_wire"
+ ",md.amount_with_fee"
+ ",md.deposit_fee"
+ ",md.coin_pub"
+ ",mdc.retry_backoff"
+ " FROM merchant_deposit_confirmations mdc"
+ " JOIN merchant_contract_terms mct"
+ " USING (order_serial)"
+ " JOIN merchant_accounts ma"
+ " USING (account_serial)"
+ " LEFT JOIN merchant_kyc kyc"
+ " ON (ma.account_serial=kyc.account_serial)"
+ " JOIN merchant_instances mi"
+ " ON (mct.merchant_serial=mi.merchant_serial)"
+ " JOIN merchant_keys mk"
+ " ON (mi.merchant_serial=mk.merchant_serial)"
+ " JOIN merchant_deposits md"
+ " USING (deposit_confirmation_serial)"
+ " WHERE mdc.wire_pending"
+ " AND (mdc.exchange_url=$1)"
+ " AND ($4 OR (mdc.wire_transfer_deadline < $2))"
+ " AND ( (kyc.kyc_ok IS NULL) OR kyc.kyc_ok)"
+ " AND ( (kyc.aml_decision IS NULL) OR (0=kyc.aml_decision) )"
+ " ORDER BY mdc.wire_transfer_deadline ASC"
+ " LIMIT $3");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_pending_deposits",
+ params,
+ &lookup_deposits_cb,
+ &ldc);
+ if (0 > ldc.qs)
+ return ldc.qs;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_pending_deposits.h b/src/backenddb/pg_lookup_pending_deposits.h
new file mode 100644
index 00000000..65bcbb9e
--- /dev/null
+++ b/src/backenddb/pg_lookup_pending_deposits.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_pending_deposits.h
+ * @brief implementation of the lookup_pending_deposits function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_PENDING_DEPOSITS_H
+#define PG_LOOKUP_PENDING_DEPOSITS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Lookup deposits that are finished and awaiting a wire transfer.
+ *
+ * @param cls closure
+ * @param exchange_url exchange to filter deposits by
+ * @param limit maximum number of deposits to return
+ * @param allow_future true to allow deposits with wire deadline in the future
+ * @param cb function to call with deposit data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_pending_deposits (
+ void *cls,
+ const char *exchange_url,
+ uint64_t limit,
+ bool allow_future,
+ TALER_MERCHANTDB_PendingDepositsCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/backenddb/pg_lookup_pending_webhooks.c b/src/backenddb/pg_lookup_pending_webhooks.c
new file mode 100644
index 00000000..d1d3eda9
--- /dev/null
+++ b/src/backenddb/pg_lookup_pending_webhooks.c
@@ -0,0 +1,261 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_pending_webhooks.c
+ * @brief Implementation of the lookup_pending_webhooks function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_pending_webhooks.h"
+#include "pg_helper.h"
+
+/**
+ * Context used for lookup_pending_webhooks_cb().
+ */
+struct LookupPendingWebhookContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_PendingWebhooksCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about webhook.
+ *
+ * @param[in,out] cls of type `struct LookupPendingWebhookContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_pending_webhooks_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupPendingWebhookContext *pwlc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t webhook_pending_serial;
+ struct GNUNET_TIME_Absolute next_attempt;
+ uint32_t retries;
+ char *url;
+ char *http_method;
+ char *header = NULL;
+ char *body = NULL;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("webhook_pending_serial",
+ &webhook_pending_serial),
+ GNUNET_PQ_result_spec_absolute_time ("next_attempt",
+ &next_attempt),
+ GNUNET_PQ_result_spec_uint32 ("retries",
+ &retries),
+ GNUNET_PQ_result_spec_string ("url",
+ &url),
+ GNUNET_PQ_result_spec_string ("http_method",
+ &http_method),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("header",
+ &header),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("body",
+ &body),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ pwlc->extract_failed = true;
+ return;
+ }
+ pwlc->cb (pwlc->cb_cls,
+ webhook_pending_serial,
+ next_attempt,
+ retries,
+ url,
+ http_method,
+ header,
+ body);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_pending_webhooks (void *cls,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupPendingWebhookContext pwlc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .extract_failed = false,
+ };
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params_null[] = {
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_pending_webhooks",
+ "SELECT"
+ " webhook_pending_serial"
+ ",next_attempt"
+ ",retries"
+ ",url"
+ ",http_method"
+ ",header"
+ ",body"
+ " FROM merchant_pending_webhooks"
+ " WHERE next_attempt <= $1"
+ " ORDER BY next_attempt ASC"
+ );
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_pending_webhooks",
+ params_null,
+ &lookup_pending_webhooks_cb,
+ &pwlc);
+
+ if (pwlc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_future_webhook (void *cls,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupPendingWebhookContext pwlc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .extract_failed = false,
+ };
+ struct GNUNET_PQ_QueryParam params_null[] = {
+ GNUNET_PQ_query_param_end
+ };
+
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_future_webhook",
+ "SELECT"
+ " webhook_pending_serial"
+ ",next_attempt"
+ ",retries"
+ ",url"
+ ",http_method"
+ ",header"
+ ",body"
+ " FROM merchant_pending_webhooks"
+ " ORDER BY next_attempt ASC LIMIT 1"
+ );
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_future_webhook",
+ params_null,
+ &lookup_pending_webhooks_cb,
+ &pwlc);
+
+ if (pwlc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_all_webhooks (void *cls,
+ const char *instance_id,
+ uint64_t min_row,
+ uint32_t max_results,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupPendingWebhookContext pwlc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .extract_failed = false,
+ };
+ uint64_t max_results64 = max_results;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_uint64 (&min_row),
+ GNUNET_PQ_query_param_uint64 (&max_results64),
+ GNUNET_PQ_query_param_end
+ };
+
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_all_webhooks",
+ " SELECT"
+ " webhook_pending_serial"
+ ",next_attempt"
+ ",retries"
+ ",url"
+ ",http_method"
+ ",header"
+ ",body"
+ " FROM merchant_pending_webhooks"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"
+ " AND webhook_pending_serial > $2"
+ " ORDER BY webhook_pending_serial"
+ " ASC LIMIT $3");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_all_webhooks",
+ params,
+ &lookup_pending_webhooks_cb,
+ &pwlc);
+
+ if (pwlc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_pending_webhooks.h b/src/backenddb/pg_lookup_pending_webhooks.h
new file mode 100644
index 00000000..d7322f56
--- /dev/null
+++ b/src/backenddb/pg_lookup_pending_webhooks.h
@@ -0,0 +1,78 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_pending_webhooks.h
+ * @brief implementation of the lookup_pending_webhooks function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_PENDING_WEBHOOKS_H
+#define PG_LOOKUP_PENDING_WEBHOOKS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup the webhook that need to be send in priority.
+ * send.
+ *
+ * @param cls closure
+ * @param cb pending webhook callback
+ * @param cb_cls callback closure
+ */
+// WHERE next_attempt <= now ORDER BY next_attempt ASC
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_pending_webhooks (void *cls,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Lookup future webhook in the pending webhook that need to be send.
+ * With that we can know how long the system can 'sleep'.
+ *
+ * @param cls closure
+ * @param cb pending webhook callback
+ * @param cb_cls callback closure
+ */
+// ORDER BY next_attempt ASC LIMIT 1
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_future_webhook (void *cls,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Lookup all the webhooks in the pending webhook.
+ * Use by the administrator
+ *
+ * @param cls closure
+ * @param instance_id to lookup webhooks for this instance particularly
+ * @param min_row to see the list of the pending webhook that it is started with this minimum row.
+ * @param max_results to see the list of the pending webhook that it is end with this max results.
+ * @param cb pending webhook callback
+ * @param cb_cls callback closure
+ */
+// WHERE webhook_pending_serial > min_row ORDER BY webhook_pending_serial ASC LIMIT max_results
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_all_webhooks (void *cls,
+ const char *instance_id,
+ uint64_t min_row,
+ uint32_t max_results,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_product.c b/src/backenddb/pg_lookup_product.c
new file mode 100644
index 00000000..65a3c0e5
--- /dev/null
+++ b/src/backenddb/pg_lookup_product.c
@@ -0,0 +1,109 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_product.c
+ * @brief Implementation of the lookup_product function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_product.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_product (void *cls,
+ const char *instance_id,
+ const char *product_id,
+ struct TALER_MERCHANTDB_ProductDetails *pd)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (product_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ if (NULL == pd)
+ {
+ struct GNUNET_PQ_ResultSpec rs_null[] = {
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_product",
+ params,
+ rs_null);
+ }
+ else
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("description",
+ &pd->description),
+ TALER_PQ_result_spec_json ("description_i18n",
+ &pd->description_i18n),
+ GNUNET_PQ_result_spec_string ("unit",
+ &pd->unit),
+ TALER_PQ_result_spec_amount_with_currency ("price",
+ &pd->price),
+ TALER_PQ_result_spec_json ("taxes",
+ &pd->taxes),
+ GNUNET_PQ_result_spec_uint64 ("total_stock",
+ &pd->total_stock),
+ GNUNET_PQ_result_spec_uint64 ("total_sold",
+ &pd->total_sold),
+ GNUNET_PQ_result_spec_uint64 ("total_lost",
+ &pd->total_lost),
+ GNUNET_PQ_result_spec_string ("image",
+ &pd->image),
+ TALER_PQ_result_spec_json ("address",
+ &pd->address),
+ GNUNET_PQ_result_spec_timestamp ("next_restock",
+ &pd->next_restock),
+ GNUNET_PQ_result_spec_uint32 ("minimum_age",
+ &pd->minimum_age),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_product",
+ "SELECT"
+ " description"
+ ",description_i18n"
+ ",unit"
+ ",price"
+ ",taxes"
+ ",total_stock"
+ ",total_sold"
+ ",total_lost"
+ ",image"
+ ",merchant_inventory.address"
+ ",next_restock"
+ ",minimum_age"
+ " FROM merchant_inventory"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"
+ " AND merchant_inventory.product_id=$2");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_product",
+ params,
+ rs);
+ }
+}
diff --git a/src/backenddb/pg_lookup_product.h b/src/backenddb/pg_lookup_product.h
new file mode 100644
index 00000000..a6add4cb
--- /dev/null
+++ b/src/backenddb/pg_lookup_product.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_product.h
+ * @brief implementation of the lookup_product function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_PRODUCT_H
+#define PG_LOOKUP_PRODUCT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup details about a particular product.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup products for
+ * @param product_id product to lookup
+ * @param[out] pd set to the product details on success, can be NULL
+ * (in that case we only want to check if the product exists)
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_product (void *cls,
+ const char *instance_id,
+ const char *product_id,
+ struct TALER_MERCHANTDB_ProductDetails *pd);
+
+#endif
diff --git a/src/backenddb/pg_lookup_products.c b/src/backenddb/pg_lookup_products.c
new file mode 100644
index 00000000..fa2887a8
--- /dev/null
+++ b/src/backenddb/pg_lookup_products.c
@@ -0,0 +1,155 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_products.c
+ * @brief Implementation of the lookup_products function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_products.h"
+#include "pg_helper.h"
+
+/**
+ * Context used for TMH_PG_lookup_products().
+ */
+struct LookupProductsContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_ProductsCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about products.
+ *
+ * @param[in,out] cls of type `struct LookupProductsContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_products_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupProductsContext *plc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *product_id;
+ uint64_t product_serial;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("product_id",
+ &product_id),
+ GNUNET_PQ_result_spec_uint64 ("product_serial",
+ &product_serial),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ plc->extract_failed = true;
+ return;
+ }
+ plc->cb (plc->cb_cls,
+ product_serial,
+ product_id);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_products (void *cls,
+ const char *instance_id,
+ uint64_t offset,
+ int64_t limit,
+ TALER_MERCHANTDB_ProductsCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
+ struct LookupProductsContext plc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ /* Can be overwritten by the lookup_products_cb */
+ .extract_failed = false,
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_uint64 (&offset),
+ GNUNET_PQ_query_param_uint64 (&plimit),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_products_asc",
+ "SELECT"
+ " product_id"
+ " ,product_serial"
+ " FROM merchant_inventory"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"
+ " AND product_serial > $2"
+ " ORDER BY product_serial ASC"
+ " LIMIT $3");
+ PREPARE (pg,
+ "lookup_products_desc",
+ "SELECT"
+ " product_id"
+ " ,product_serial"
+ " FROM merchant_inventory"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"
+ " AND product_serial < $2"
+ " ORDER BY product_serial DESC"
+ " LIMIT $3");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ (limit > 0)
+ ? "lookup_products_asc"
+ : "lookup_products_desc",
+ params,
+ &lookup_products_cb,
+ &plc);
+ /* If there was an error inside lookup_products_cb, return a hard error. */
+ if (plc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_products.h b/src/backenddb/pg_lookup_products.h
new file mode 100644
index 00000000..d96328c8
--- /dev/null
+++ b/src/backenddb/pg_lookup_products.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_products.h
+ * @brief implementation of the lookup_products function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_PRODUCTS_H
+#define PG_LOOKUP_PRODUCTS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup all of the products the given instance has configured.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup products for
+ * @param offset transfer_serial number of the transfer we want to offset from
+ * @param limit number of entries to return, negative for descending,
+ * positive for ascending
+ * @param cb function to call on all products found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_products (void *cls,
+ const char *instance_id,
+ uint64_t offset,
+ int64_t limit,
+ TALER_MERCHANTDB_ProductsCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_refund_proof.c b/src/backenddb/pg_lookup_refund_proof.c
new file mode 100644
index 00000000..caa3827f
--- /dev/null
+++ b/src/backenddb/pg_lookup_refund_proof.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_refund_proof.c
+ * @brief Implementation of the lookup_refund_proof function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_refund_proof.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_refund_proof (void *cls,
+ uint64_t refund_serial,
+ struct TALER_ExchangeSignatureP *exchange_sig,
+ struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&refund_serial),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("exchange_sig",
+ exchange_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
+ exchange_pub),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_refund_proof",
+ "SELECT"
+ " merchant_exchange_signing_keys.exchange_pub"
+ ",exchange_sig"
+ " FROM merchant_refund_proofs"
+ " JOIN merchant_exchange_signing_keys"
+ " USING (signkey_serial)"
+ " WHERE"
+ " refund_serial=$1");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_refund_proof",
+ params,
+ rs);
+}
diff --git a/src/backenddb/pg_lookup_refund_proof.h b/src/backenddb/pg_lookup_refund_proof.h
new file mode 100644
index 00000000..56d8ad97
--- /dev/null
+++ b/src/backenddb/pg_lookup_refund_proof.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_refund_proof.h
+ * @brief implementation of the lookup_refund_proof function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_REFUND_PROOF_H
+#define PG_LOOKUP_REFUND_PROOF_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup refund proof data.
+ *
+ * @param cls closure
+ * @param refund_serial serial number of the refund
+ * @param[out] exchange_sig set to signature from exchange
+ * @param[out] exchange_pub signing key that was used for @a exchange_sig
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_refund_proof (void *cls,
+ uint64_t refund_serial,
+ struct TALER_ExchangeSignatureP *exchange_sig,
+ struct TALER_ExchangePublicKeyP *exchange_pub);
+
+#endif
diff --git a/src/backenddb/pg_lookup_refunds.c b/src/backenddb/pg_lookup_refunds.c
new file mode 100644
index 00000000..2facb73f
--- /dev/null
+++ b/src/backenddb/pg_lookup_refunds.c
@@ -0,0 +1,148 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_refunds.c
+ * @brief Implementation of the lookup_refunds function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_refunds.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #lookup_refunds_cb().
+ */
+struct LookupRefundsContext
+{
+ /**
+ * Function to call for each refund.
+ */
+ TALER_MERCHANTDB_RefundCallback rc;
+
+ /**
+ * Closure for @e rc.
+ */
+ void *rc_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Transaction result.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct LookupRefundsContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_refunds_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRefundsContext *lrc = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_Amount refund_amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &coin_pub),
+ TALER_PQ_result_spec_amount_with_currency ("refund_amount",
+ &refund_amount),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ lrc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ lrc->rc (lrc->rc_cls,
+ &coin_pub,
+ &refund_amount);
+ GNUNET_PQ_cleanup_result (rs); /* technically useless here */
+ }
+ lrc->qs = num_results;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_refunds (
+ void *cls,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ TALER_MERCHANTDB_RefundCallback rc,
+ void *rc_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_end
+ };
+ struct LookupRefundsContext lrc = {
+ .rc = rc,
+ .rc_cls = rc_cls,
+ .pg = pg
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* no preflight check here, run in transaction by caller! */
+ TALER_LOG_DEBUG ("Looking for refund of h_contract_terms %s at `%s'\n",
+ GNUNET_h2s (&h_contract_terms->hash),
+ instance_id);
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_refunds",
+ "SELECT"
+ " coin_pub"
+ ",refund_amount"
+ " FROM merchant_refunds"
+ " WHERE order_serial="
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE h_contract_terms=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1))");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_refunds",
+ params,
+ &lookup_refunds_cb,
+ &lrc);
+ if (0 >= qs)
+ return qs;
+ return lrc.qs;
+}
diff --git a/src/backenddb/pg_lookup_refunds.h b/src/backenddb/pg_lookup_refunds.h
new file mode 100644
index 00000000..2b047019
--- /dev/null
+++ b/src/backenddb/pg_lookup_refunds.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_refunds.h
+ * @brief implementation of the lookup_refunds function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_REFUNDS_H
+#define PG_LOOKUP_REFUNDS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Obtain refunds associated with a contract.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id instance to lookup refunds for
+ * @param h_contract_terms hash code of the contract
+ * @param rc function to call for each coin on which there is a refund
+ * @param rc_cls closure for @a rc
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_refunds (void *cls,
+ const char *instance_id,
+ const struct
+ TALER_PrivateContractHashP *h_contract_terms,
+ TALER_MERCHANTDB_RefundCallback rc,
+ void *rc_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_refunds_detailed.c b/src/backenddb/pg_lookup_refunds_detailed.c
new file mode 100644
index 00000000..1b01f4d6
--- /dev/null
+++ b/src/backenddb/pg_lookup_refunds_detailed.c
@@ -0,0 +1,185 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_refunds_detailed.c
+ * @brief Implementation of the lookup_refunds_detailed function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_refunds_detailed.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #lookup_refunds_detailed_cb().
+ */
+struct LookupRefundsDetailedContext
+{
+ /**
+ * Function to call for each refund.
+ */
+ TALER_MERCHANTDB_RefundDetailCallback rc;
+
+ /**
+ * Closure for @e rc.
+ */
+ void *rc_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Transaction result.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct GetRefundsContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_refunds_detailed_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRefundsDetailedContext *lrdc = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t refund_serial;
+ struct GNUNET_TIME_Timestamp timestamp;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ uint64_t rtransaction_id;
+ struct TALER_Amount refund_amount;
+ char *reason;
+ char *exchange_url;
+ uint8_t pending8;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("refund_serial",
+ &refund_serial),
+ GNUNET_PQ_result_spec_timestamp ("refund_timestamp",
+ &timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &coin_pub),
+ GNUNET_PQ_result_spec_string ("exchange_url",
+ &exchange_url),
+ GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
+ &rtransaction_id),
+ GNUNET_PQ_result_spec_string ("reason",
+ &reason),
+ TALER_PQ_result_spec_amount_with_currency ("refund_amount",
+ &refund_amount),
+ GNUNET_PQ_result_spec_auto_from_type ("pending",
+ &pending8),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ lrdc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ lrdc->rc (lrdc->rc_cls,
+ refund_serial,
+ timestamp,
+ &coin_pub,
+ exchange_url,
+ rtransaction_id,
+ reason,
+ &refund_amount,
+ 0 != pending8);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+ lrdc->qs = num_results;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_refunds_detailed (void *cls,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ TALER_MERCHANTDB_RefundDetailCallback rc,
+ void *rc_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_end
+ };
+ struct LookupRefundsDetailedContext lrdc = {
+ .rc = rc,
+ .rc_cls = rc_cls,
+ .pg = pg
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* no preflight check here, run in transaction by caller! */
+ TALER_LOG_DEBUG ("Looking for refund %s + %s\n",
+ GNUNET_h2s (&h_contract_terms->hash),
+ instance_id);
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_refunds_detailed",
+ "SELECT"
+ " ref.refund_serial"
+ ",ref.refund_timestamp"
+ ",dep.coin_pub"
+ ",mcon.exchange_url"
+ ",ref.rtransaction_id"
+ ",ref.reason"
+ ",ref.refund_amount"
+ ",merchant_refund_proofs.exchange_sig IS NULL AS pending"
+ " FROM merchant_deposit_confirmations mcon"
+ " JOIN merchant_deposits dep"
+ " USING (deposit_confirmation_serial)"
+ " JOIN merchant_refunds ref"
+ " USING (order_serial, coin_pub)"
+ " LEFT JOIN merchant_refund_proofs"
+ " USING (refund_serial)"
+ " WHERE mcon.order_serial="
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE h_contract_terms=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1))");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_refunds_detailed",
+ params,
+ &lookup_refunds_detailed_cb,
+ &lrdc);
+ if (0 >= qs)
+ return qs;
+ return lrdc.qs;
+}
diff --git a/src/backenddb/pg_lookup_refunds_detailed.h b/src/backenddb/pg_lookup_refunds_detailed.h
new file mode 100644
index 00000000..c2531446
--- /dev/null
+++ b/src/backenddb/pg_lookup_refunds_detailed.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_refunds_detailed.h
+ * @brief implementation of the lookup_refunds_detailed function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_REFUNDS_DETAILED_H
+#define PG_LOOKUP_REFUNDS_DETAILED_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Obtain detailed refund data associated with a contract.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id instance to lookup refunds for
+ * @param h_contract_terms hash code of the contract
+ * @param rc function to call for each coin on which there is a refund
+ * @param rc_cls closure for @a rc
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_refunds_detailed (void *cls,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ TALER_MERCHANTDB_RefundDetailCallback rc,
+ void *rc_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_template.c b/src/backenddb/pg_lookup_template.c
new file mode 100644
index 00000000..6e9d3681
--- /dev/null
+++ b/src/backenddb/pg_lookup_template.c
@@ -0,0 +1,109 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_template.c
+ * @brief Implementation of the lookup_template function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_template.h"
+#include "pg_helper.h"
+
+
+/**
+ * Lookup details about a particular template.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup template for
+ * @param template_id template to lookup
+ * @param[out] td set to the template details on success, can be NULL
+ * (in that case we only want to check if the template exists)
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_template (void *cls,
+ const char *instance_id,
+ const char *template_id,
+ struct TALER_MERCHANTDB_TemplateDetails *td)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (template_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_template",
+ "SELECT"
+ " mt.template_description"
+ ",mod.otp_id"
+ ",mt.template_contract"
+ ",mt.required_currency"
+ ",mt.editable_defaults"
+ " FROM merchant_template mt"
+ " JOIN merchant_instances mi"
+ " ON (mi.merchant_serial = mt.merchant_serial)"
+ " LEFT JOIN merchant_otp_devices mod"
+ " ON (mod.otp_serial = mt.otp_device_id)"
+ " WHERE mi.merchant_id=$1"
+ " AND mt.template_id=$2");
+ if (NULL == td)
+ {
+ struct GNUNET_PQ_ResultSpec rs_null[] = {
+ GNUNET_PQ_result_spec_end
+ };
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_template",
+ params,
+ rs_null);
+ }
+ else
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("template_description",
+ &td->template_description),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("otp_id",
+ &td->otp_id),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("required_currency",
+ &td->required_currency),
+ NULL),
+ TALER_PQ_result_spec_json ("template_contract",
+ &td->template_contract),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_json ("editable_defaults",
+ &td->editable_defaults),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ memset (td,
+ 0,
+ sizeof (*td));
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_template",
+ params,
+ rs);
+ }
+}
diff --git a/src/backenddb/pg_lookup_template.h b/src/backenddb/pg_lookup_template.h
new file mode 100644
index 00000000..44e01b08
--- /dev/null
+++ b/src/backenddb/pg_lookup_template.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_template.h
+ * @brief implementation of the lookup_template function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_TEMPLATE_H
+#define PG_LOOKUP_TEMPLATE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Lookup details about a particular template.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup template for
+ * @param template_id template to lookup
+ * @param[out] td set to the template details on success, can be NULL
+ * (in that case we only want to check if the template exists)
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_template (void *cls,
+ const char *instance_id,
+ const char *template_id,
+ struct TALER_MERCHANTDB_TemplateDetails *td);
+
+
+#endif
diff --git a/src/backenddb/pg_lookup_templates.c b/src/backenddb/pg_lookup_templates.c
new file mode 100644
index 00000000..7938d575
--- /dev/null
+++ b/src/backenddb/pg_lookup_templates.c
@@ -0,0 +1,133 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_templates.c
+ * @brief Implementation of the lookup_templates function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_templates.h"
+#include "pg_helper.h"
+
+
+/**
+ * Context used for TMH_PG_lookup_templates().
+ */
+struct LookupTemplateContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_TemplatesCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about template.
+ *
+ * @param[in,out] cls of type `struct LookupTemplateContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_templates_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupTemplateContext *tlc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *template_id;
+ char *template_description;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("template_id",
+ &template_id),
+ GNUNET_PQ_result_spec_string ("template_description",
+ &template_description),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ tlc->extract_failed = true;
+ return;
+ }
+ tlc->cb (tlc->cb_cls,
+ template_id,
+ template_description);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_templates (void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_TemplatesCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupTemplateContext tlc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ /* Can be overwritten by the lookup_template_cb */
+ .extract_failed = false,
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_templates",
+ "SELECT"
+ " template_id"
+ ",template_description"
+ " FROM merchant_template"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_templates",
+ params,
+ &lookup_templates_cb,
+ &tlc);
+ /* If there was an error inside lookup_templates_cb, return a hard error. */
+ if (tlc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_templates.h b/src/backenddb/pg_lookup_templates.h
new file mode 100644
index 00000000..eee5be7e
--- /dev/null
+++ b/src/backenddb/pg_lookup_templates.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_templates.h
+ * @brief implementation of the lookup_templates function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_TEMPLATES_H
+#define PG_LOOKUP_TEMPLATES_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Lookup all of the templates the given instance has configured.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup template for
+ * @param cb function to call on all template found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_templates (void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_TemplatesCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_token_families.c b/src/backenddb/pg_lookup_token_families.c
new file mode 100644
index 00000000..0ebe3b53
--- /dev/null
+++ b/src/backenddb/pg_lookup_token_families.c
@@ -0,0 +1,150 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_token_families.c
+ * @brief Implementation of the lookup_token_families function for Postgres
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_token_families.h"
+#include "pg_helper.h"
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Context used for TMH_PG_lookup_token_families().
+ */
+struct LookupTokenFamiliesContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_TokenFamiliesCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about token families.
+ *
+ * @param[in,out] cls of type `struct LookupTokenFamiliesContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_token_families_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupTokenFamiliesContext *tflc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *slug;
+ char *name;
+ char *kind;
+ struct GNUNET_TIME_Timestamp valid_after;
+ struct GNUNET_TIME_Timestamp valid_before;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("slug",
+ &slug),
+ GNUNET_PQ_result_spec_string ("name",
+ &name),
+ GNUNET_PQ_result_spec_timestamp ("valid_after",
+ &valid_after),
+ GNUNET_PQ_result_spec_timestamp ("valid_before",
+ &valid_before),
+ GNUNET_PQ_result_spec_string ("kind",
+ &kind),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ tflc->extract_failed = true;
+ return;
+ }
+
+ tflc->cb (tflc->cb_cls,
+ slug,
+ name,
+ valid_after,
+ valid_before,
+ kind);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_token_families (void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_TokenFamiliesCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupTokenFamiliesContext context = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ /* Can be overwritten by the lookup_token_families_cb */
+ .extract_failed = false,
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_token_families",
+ "SELECT"
+ " slug"
+ ",name"
+ ",valid_after"
+ ",valid_before"
+ ",kind"
+ " FROM merchant_token_families"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_token_families",
+ params,
+ &lookup_token_families_cb,
+ &context);
+ /* If there was an error inside lookup_token_families_cb, return a hard error. */
+ if (context.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_token_families.h b/src/backenddb/pg_lookup_token_families.h
new file mode 100644
index 00000000..0c9f80fe
--- /dev/null
+++ b/src/backenddb/pg_lookup_token_families.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_token_families.h
+ * @brief implementation of the lookup_token_families function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_TOKEN_FAMILIES_H
+#define PG_LOOKUP_TOKEN_FAMILIES_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup all of the token families the given instance has configured.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup token families for
+ * @param cb function to call on all token families found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_token_families (void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_TokenFamiliesCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_token_family.c b/src/backenddb/pg_lookup_token_family.c
new file mode 100644
index 00000000..848b79a9
--- /dev/null
+++ b/src/backenddb/pg_lookup_token_family.c
@@ -0,0 +1,121 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_token_family.c
+ * @brief Implementation of the lookup_token_family function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_token_family.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_token_family (void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ struct TALER_MERCHANTDB_TokenFamilyDetails *details)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (token_family_slug),
+ GNUNET_PQ_query_param_end
+ };
+
+ if (NULL == details)
+ {
+ struct GNUNET_PQ_ResultSpec rs_null[] = {
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_token_family",
+ params,
+ rs_null);
+ }
+ else
+ {
+ char *kind;
+
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("slug",
+ &details->slug),
+ GNUNET_PQ_result_spec_string ("name",
+ &details->name),
+ GNUNET_PQ_result_spec_string ("description",
+ &details->description),
+ TALER_PQ_result_spec_json ("description_i18n",
+ &details->description_i18n),
+ GNUNET_PQ_result_spec_timestamp ("valid_after",
+ &details->valid_after),
+ GNUNET_PQ_result_spec_timestamp ("valid_before",
+ &details->valid_before),
+ GNUNET_PQ_result_spec_relative_time ("duration",
+ &details->duration),
+ GNUNET_PQ_result_spec_string ("kind",
+ &kind),
+ GNUNET_PQ_result_spec_uint64 ("issued",
+ &details->issued),
+ GNUNET_PQ_result_spec_uint64 ("redeemed",
+ &details->redeemed),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_token_family",
+ "SELECT"
+ " slug"
+ ",name"
+ ",description"
+ ",description_i18n"
+ ",valid_after"
+ ",valid_before"
+ ",duration"
+ ",kind"
+ ",issued"
+ ",redeemed"
+ " FROM merchant_token_families"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"
+ " AND merchant_token_families.slug=$2");
+ enum GNUNET_DB_QueryStatus qs;
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_token_family",
+ params,
+ rs);
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ if (strcmp(kind, "discount") == 0)
+ details->kind = TALER_MERCHANTDB_TFK_Discount;
+ else if (strcmp(kind, "subscription") == 0)
+ details->kind = TALER_MERCHANTDB_TFK_Subscription;
+ else
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+
+ return qs;
+ }
+}
diff --git a/src/backenddb/pg_lookup_token_family.h b/src/backenddb/pg_lookup_token_family.h
new file mode 100644
index 00000000..4a1b1872
--- /dev/null
+++ b/src/backenddb/pg_lookup_token_family.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_token_family.h
+ * @brief implementation of the lookup_token_family function for Postgres
+ * @author Christian Blättler
+ */
+#ifndef PG_LOOKUP_TOKEN_FAMILY_H
+#define PG_LOOKUP_TOKEN_FAMILY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup details about a particular token family.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup token family for
+ * @param token_family_slug token family to lookup
+ * @param[out] details set to the token family details on success, can be NULL
+ * (in that case we only want to check if the token family exists)
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_token_family (void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ struct TALER_MERCHANTDB_TokenFamilyDetails *details);
+
+#endif
diff --git a/src/backenddb/pg_lookup_transfer.c b/src/backenddb/pg_lookup_transfer.c
new file mode 100644
index 00000000..03c2ccce
--- /dev/null
+++ b/src/backenddb/pg_lookup_transfer.c
@@ -0,0 +1,125 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_transfer.c
+ * @brief Implementation of the lookup_transfer function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_transfer.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_transfer (void *cls,
+ const char *instance_id,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_Amount *total_amount,
+ struct TALER_Amount *wire_fee,
+ struct TALER_Amount *exchange_amount,
+ struct GNUNET_TIME_Timestamp *execution_time,
+ bool *have_exchange_sig,
+ bool *verified)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (exchange_url),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_end
+ };
+ uint8_t verified8;
+ /** Amount we got actually credited, _excludes_ the wire fee */
+ bool no_sig;
+ struct TALER_Amount credit_amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount_with_currency ("credit_amount",
+ &credit_amount),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_amount_with_currency ("wire_fee",
+ wire_fee),
+ &no_sig),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_amount_with_currency ("exchange_amount",
+ exchange_amount),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("execution_time",
+ execution_time),
+ NULL),
+ GNUNET_PQ_result_spec_auto_from_type ("verified",
+ &verified8),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ *execution_time = GNUNET_TIME_UNIT_ZERO_TS;
+
+ PREPARE (pg,
+ "lookup_transfer",
+ "SELECT"
+ " mt.credit_amount AS credit_amount"
+ ",mts.credit_amount AS exchange_amount"
+ ",wire_fee"
+ ",execution_time"
+ ",verified"
+ " FROM merchant_transfers mt"
+ " JOIN merchant_accounts USING (account_serial)"
+ " JOIN merchant_instances USING (merchant_serial)"
+ " LEFT JOIN merchant_transfer_signatures mts USING (credit_serial)"
+ " WHERE wtid=$2"
+ " AND exchange_url=$1"
+ " AND merchant_id=$3;");
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_transfer",
+ params,
+ rs);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Lookup transfer returned %d\n",
+ qs);
+ if (qs > 0)
+ {
+ *have_exchange_sig = ! no_sig;
+ *verified = (0 != verified8);
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&credit_amount,
+ wire_fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (! no_sig) &&
+ (0 >
+ TALER_amount_add (total_amount,
+ &credit_amount,
+ wire_fee)) )
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ else
+ {
+ *verified = false;
+ *have_exchange_sig = false;
+ }
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_transfer.h b/src/backenddb/pg_lookup_transfer.h
new file mode 100644
index 00000000..4dd9c3d7
--- /dev/null
+++ b/src/backenddb/pg_lookup_transfer.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_transfer.h
+ * @brief implementation of the lookup_transfer function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_TRANSFER_H
+#define PG_LOOKUP_TRANSFER_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup transfer status.
+ *
+ * @param cls closure
+ * @param instance_id at which instance should we resolve the transfer
+ * @param exchange_url the exchange that made the transfer
+ * @param wtid wire transfer subject
+ * @param[out] total_amount amount that was debited from our
+ * aggregate balance at the exchange (in total, sum of
+ * the wire transfer amount and the @a wire_fee)
+ * @param[out] wire_fee the wire fee the exchange charged (only set if @a have_exchange_sig is true)
+ * @param[out] exchange_amount the amount the exchange claims was transferred (only set if @a have_exchange_sig is true)
+ * @param[out] execution_time when the transfer was executed by the exchange (only set if @a have_exchange_sig is true)
+ * @param[out] have_exchange_sig do we have a response from the exchange about this transfer
+ * @param[out] verified did we confirm the transfer was OK
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_transfer (void *cls,
+ const char *instance_id,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_Amount *total_amount,
+ struct TALER_Amount *wire_fee,
+ struct TALER_Amount *exchange_amount,
+ struct GNUNET_TIME_Timestamp *execution_time,
+ bool *have_exchange_sig,
+ bool *verified);
+
+#endif
diff --git a/src/backenddb/pg_lookup_transfer_details.c b/src/backenddb/pg_lookup_transfer_details.c
new file mode 100644
index 00000000..cd497ece
--- /dev/null
+++ b/src/backenddb/pg_lookup_transfer_details.c
@@ -0,0 +1,155 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_transfer_details.c
+ * @brief Implementation of the lookup_transfer_details function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_transfer_details.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #lookup_transfer_details_cb().
+ */
+struct LookupTransferDetailsContext
+{
+ /**
+ * Function to call for each order that was aggregated.
+ */
+ TALER_MERCHANTDB_TransferDetailsCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Transaction result.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct LookupTransferDetailsContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_transfer_details_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupTransferDetailsContext *ltdc = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t current_offset;
+ struct TALER_TrackTransferDetails ttd;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("offset_in_exchange_list",
+ &current_offset),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &ttd.h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &ttd.coin_pub),
+ TALER_PQ_result_spec_amount_with_currency ("exchange_deposit_value",
+ &ttd.coin_value),
+ TALER_PQ_result_spec_amount_with_currency ("exchange_deposit_fee",
+ &ttd.coin_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ltdc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ ltdc->cb (ltdc->cb_cls,
+ (unsigned int) current_offset,
+ &ttd);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+ ltdc->qs = num_results;
+}
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_transfer_details (void *cls,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ TALER_MERCHANTDB_TransferDetailsCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (exchange_url),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_end
+ };
+ struct LookupTransferDetailsContext ltdc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_transfer_details",
+ "SELECT"
+ " mterm.h_contract_terms"
+ ",mtcoin.offset_in_exchange_list"
+ ",dep.coin_pub"
+ ",mtcoin.exchange_deposit_value"
+ ",mtcoin.exchange_deposit_fee"
+ " FROM merchant_transfer_to_coin mtcoin"
+ " JOIN merchant_deposits dep"
+ " USING (deposit_serial)"
+ " JOIN merchant_deposit_confirmations mcon"
+ " USING (deposit_confirmation_serial)"
+ " JOIN merchant_contract_terms mterm"
+ " USING (order_serial)"
+ " JOIN merchant_transfers mtr"
+ " USING (credit_serial)"
+ " WHERE mtr.wtid=$2"
+ " AND mtr.exchange_url=$1");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "lookup_transfer_details",
+ params,
+ &lookup_transfer_details_cb,
+ &ltdc);
+ if (0 >= qs)
+ return qs;
+ return ltdc.qs;
+}
diff --git a/src/backenddb/pg_lookup_transfer_details.h b/src/backenddb/pg_lookup_transfer_details.h
new file mode 100644
index 00000000..3f99fc67
--- /dev/null
+++ b/src/backenddb/pg_lookup_transfer_details.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_transfer_details.h
+ * @brief implementation of the lookup_transfer_details function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_TRANSFER_DETAILS_H
+#define PG_LOOKUP_TRANSFER_DETAILS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup transfer details.
+ *
+ * @param cls closure
+ * @param exchange_url the exchange that made the transfer
+ * @param wtid wire transfer subject
+ * @param cb function to call with detailed transfer data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_transfer_details (void *cls,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ TALER_MERCHANTDB_TransferDetailsCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_transfer_details_by_order.c b/src/backenddb/pg_lookup_transfer_details_by_order.c
new file mode 100644
index 00000000..6ad07f4a
--- /dev/null
+++ b/src/backenddb/pg_lookup_transfer_details_by_order.c
@@ -0,0 +1,172 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_transfer_details_by_order.c
+ * @brief Implementation of the lookup_transfer_details_by_order function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_transfer_details_by_order.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for lookup_transfer_details_by_order_cb().
+ */
+struct LookupTransferDetailsByOrderContext
+{
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Function to call with all results.
+ */
+ TALER_MERCHANTDB_OrderTransferDetailsCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Set to the query result.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct LookupTransferDetailsByOrderContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_transfer_details_by_order_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupTransferDetailsByOrderContext *ltdo = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_WireTransferIdentifierRawP wtid;
+ char *exchange_url;
+ uint64_t deposit_serial;
+ struct GNUNET_TIME_Timestamp execution_time;
+ struct TALER_Amount deposit_value;
+ struct TALER_Amount deposit_fee;
+ uint8_t transfer_confirmed;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("deposit_serial",
+ &deposit_serial),
+ GNUNET_PQ_result_spec_timestamp ("deposit_timestamp",
+ &execution_time),
+ GNUNET_PQ_result_spec_string ("exchange_url",
+ &exchange_url),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid",
+ &wtid),
+ TALER_PQ_result_spec_amount_with_currency ("exchange_deposit_value",
+ &deposit_value),
+ TALER_PQ_result_spec_amount_with_currency ("exchange_deposit_fee",
+ &deposit_fee),
+ GNUNET_PQ_result_spec_auto_from_type ("transfer_confirmed",
+ &transfer_confirmed),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ltdo->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ ltdo->cb (ltdo->cb_cls,
+ &wtid,
+ exchange_url,
+ execution_time,
+ &deposit_value,
+ &deposit_fee,
+ (0 != transfer_confirmed));
+ GNUNET_PQ_cleanup_result (rs); /* technically useless here */
+ }
+ ltdo->qs = num_results;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_transfer_details_by_order (void *cls,
+ uint64_t order_serial,
+ TALER_MERCHANTDB_OrderTransferDetailsCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupTransferDetailsByOrderContext ltdo = {
+ .pg = pg,
+ .cb = cb,
+ .cb_cls = cb_cls
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&order_serial),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_transfer_details_by_order",
+ "SELECT"
+ " md.deposit_serial"
+ ",mcon.exchange_url"
+ ",mt.wtid"
+ ",mtc.exchange_deposit_value"
+ ",mtc.exchange_deposit_fee"
+ ",mcon.deposit_timestamp"
+ ",mt.confirmed AS transfer_confirmed"
+ " FROM merchant_transfer_to_coin mtc"
+ " JOIN merchant_deposits md"
+ " USING (deposit_serial)"
+ " JOIN merchant_deposit_confirmations mcon"
+ " USING (deposit_confirmation_serial)"
+ " JOIN merchant_transfers mt"
+ " USING (credit_serial)"
+ " JOIN merchant_accounts acc"
+ " ON (acc.account_serial = mt.account_serial)"
+ /* Check that all this is for the same instance */
+ " JOIN merchant_contract_terms contracts"
+ " USING (merchant_serial, order_serial)"
+ " WHERE mcon.order_serial=$1");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "lookup_transfer_details_by_order",
+ params,
+ &lookup_transfer_details_by_order_cb,
+ &ltdo);
+ if (qs < 0)
+ return qs;
+ return ltdo.qs;
+}
diff --git a/src/backenddb/pg_lookup_transfer_details_by_order.h b/src/backenddb/pg_lookup_transfer_details_by_order.h
new file mode 100644
index 00000000..3194888b
--- /dev/null
+++ b/src/backenddb/pg_lookup_transfer_details_by_order.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_transfer_details_by_order.h
+ * @brief implementation of the lookup_transfer_details_by_order function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_TRANSFER_DETAILS_BY_ORDER_H
+#define PG_LOOKUP_TRANSFER_DETAILS_BY_ORDER_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Retrieve wire transfer details for all deposits associated with
+ * a given @a order_serial.
+ *
+ * @param cls closure
+ * @param order_serial identifies the order
+ * @param cb function called with the wire transfer details
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_transfer_details_by_order (void *cls,
+ uint64_t order_serial,
+ TALER_MERCHANTDB_OrderTransferDetailsCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_transfer_summary.c b/src/backenddb/pg_lookup_transfer_summary.c
new file mode 100644
index 00000000..b282d3bf
--- /dev/null
+++ b/src/backenddb/pg_lookup_transfer_summary.c
@@ -0,0 +1,152 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_transfer_summary.c
+ * @brief Implementation of the lookup_transfer_summary function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_transfer_summary.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #lookup_transfer_summary_cb().
+ */
+struct LookupTransferSummaryContext
+{
+ /**
+ * Function to call for each order that was aggregated.
+ */
+ TALER_MERCHANTDB_TransferSummaryCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Transaction result.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct LookupTransferSummaryContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_transfer_summary_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupTransferSummaryContext *ltdc = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ char *order_id;
+ struct TALER_Amount deposit_value;
+ struct TALER_Amount deposit_fee;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("order_id",
+ &order_id),
+ TALER_PQ_result_spec_amount_with_currency ("exchange_deposit_value",
+ &deposit_value),
+ TALER_PQ_result_spec_amount_with_currency ("exchange_deposit_fee",
+ &deposit_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ltdc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ ltdc->cb (ltdc->cb_cls,
+ order_id,
+ &deposit_value,
+ &deposit_fee);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+ ltdc->qs = num_results;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_transfer_summary (void *cls,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ TALER_MERCHANTDB_TransferSummaryCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (exchange_url),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_end
+ };
+ struct LookupTransferSummaryContext ltdc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_transfer_summary",
+ "SELECT"
+ " mct.order_id"
+ ",mtc.exchange_deposit_value"
+ ",mtc.exchange_deposit_fee"
+ " FROM merchant_transfers mtr"
+ " JOIN merchant_transfer_to_coin mtc"
+ " USING (credit_serial)"
+ " JOIN merchant_deposits dep"
+ " USING (deposit_serial)"
+ " JOIN merchant_deposit_confirmations mcon"
+ " USING (deposit_confirmation_serial)"
+ " JOIN merchant_contract_terms mct"
+ " USING (order_serial)"
+ " WHERE mtr.wtid=$2"
+ " AND mtr.exchange_url=$1");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "lookup_transfer_summary",
+ params,
+ &lookup_transfer_summary_cb,
+ &ltdc);
+ if (0 >= qs)
+ return qs;
+ return ltdc.qs;
+}
diff --git a/src/backenddb/pg_lookup_transfer_summary.h b/src/backenddb/pg_lookup_transfer_summary.h
new file mode 100644
index 00000000..affcbcdc
--- /dev/null
+++ b/src/backenddb/pg_lookup_transfer_summary.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_transfer_summary.h
+ * @brief implementation of the lookup_transfer_summary function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_TRANSFER_SUMMARY_H
+#define PG_LOOKUP_TRANSFER_SUMMARY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup transfer summary.
+ *
+ * @param cls closure
+ * @param exchange_url the exchange that made the transfer
+ * @param wtid wire transfer subject
+ * @param cb function to call with detailed transfer data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_transfer_summary (void *cls,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ TALER_MERCHANTDB_TransferSummaryCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_transfers.c b/src/backenddb/pg_lookup_transfers.c
new file mode 100644
index 00000000..a2d22719
--- /dev/null
+++ b/src/backenddb/pg_lookup_transfers.c
@@ -0,0 +1,241 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_transfers.c
+ * @brief Implementation of the lookup_transfers function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_transfers.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #lookup_transfers_cb().
+ */
+struct LookupTransfersContext
+{
+ /**
+ * Function to call on results.
+ */
+ TALER_MERCHANTDB_TransferCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Postgres context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Transaction status (set).
+ */
+ enum GNUNET_DB_QueryStatus qs;
+
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct LookupTransfersContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_transfers_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupTransfersContext *ltc = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_Amount credit_amount;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ char *payto_uri;
+ char *exchange_url;
+ uint64_t transfer_serial_id;
+ struct GNUNET_TIME_Timestamp execution_time = GNUNET_TIME_UNIT_FOREVER_TS;
+ bool verified;
+ bool confirmed;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount_with_currency ("credit_amount",
+ &credit_amount),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid",
+ &wtid),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_PQ_result_spec_string ("exchange_url",
+ &exchange_url),
+ GNUNET_PQ_result_spec_uint64 ("credit_serial",
+ &transfer_serial_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("execution_time",
+ &execution_time),
+ NULL),
+ GNUNET_PQ_result_spec_bool ("verified",
+ &verified),
+ GNUNET_PQ_result_spec_bool ("confirmed",
+ &confirmed),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ltc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ ltc->cb (ltc->cb_cls,
+ &credit_amount,
+ &wtid,
+ payto_uri,
+ exchange_url,
+ transfer_serial_id,
+ execution_time,
+ verified,
+ confirmed);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+ ltc->qs = num_results;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_transfers (void *cls,
+ const char *instance_id,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp before,
+ struct GNUNET_TIME_Timestamp after,
+ int64_t limit,
+ uint64_t offset,
+ enum TALER_EXCHANGE_YesNoAll verified,
+ TALER_MERCHANTDB_TransferCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
+ bool by_time = ( (! GNUNET_TIME_absolute_is_never (before.abs_time)) ||
+ (! GNUNET_TIME_absolute_is_zero (after.abs_time)) );
+ struct LookupTransfersContext ltc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_timestamp (&before),
+ GNUNET_PQ_query_param_timestamp (&after),
+ GNUNET_PQ_query_param_uint64 (&offset),
+ GNUNET_PQ_query_param_uint64 (&plimit),
+ NULL == payto_uri
+ ? GNUNET_PQ_query_param_null () /* NULL: do not filter by payto URI */
+ : GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_bool (! by_time), /* $7: filter by time? */
+ GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_ALL == verified), /* filter by verified? */
+ GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_YES == verified),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_transfers_asc",
+ "SELECT"
+ " mt.credit_amount"
+ ",wtid"
+ ",mac.payto_uri"
+ ",exchange_url"
+ ",credit_serial"
+ ",mts.execution_time"
+ ",verified"
+ ",confirmed"
+ " FROM merchant_transfers mt"
+ " JOIN merchant_accounts mac"
+ " USING (account_serial)"
+ " LEFT JOIN merchant_transfer_signatures mts"
+ " USING (credit_serial)"
+ " WHERE ( $7 OR "
+ " (mts.execution_time IS NOT NULL AND"
+ " mts.execution_time < $2 AND"
+ " mts.execution_time >= $3) )"
+ " AND ( $8 OR "
+ " (verified = $9) )"
+ " AND ( (CAST($6 AS TEXT) IS NULL) OR "
+ " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')"
+ " =REGEXP_REPLACE($6,'\\?.*','')) )"
+ " AND merchant_serial ="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND (credit_serial > $4)"
+ " ORDER BY credit_serial ASC"
+ " LIMIT $5");
+ PREPARE (pg,
+ "lookup_transfers_desc",
+ "SELECT"
+ " mt.credit_amount"
+ ",wtid"
+ ",mac.payto_uri"
+ ",exchange_url"
+ ",credit_serial"
+ ",mts.execution_time"
+ ",verified"
+ ",confirmed"
+ " FROM merchant_transfers mt"
+ " JOIN merchant_accounts mac"
+ " USING (account_serial)"
+ " LEFT JOIN merchant_transfer_signatures mts"
+ " USING (credit_serial)"
+ " WHERE ( $7 OR "
+ " (mts.execution_time IS NOT NULL AND"
+ " mts.execution_time < $2 AND"
+ " mts.execution_time >= $3) )"
+ " AND ( $8 OR "
+ " (verified = $9) )"
+ " AND ( (CAST($6 AS TEXT) IS NULL) OR "
+ " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')"
+ " =REGEXP_REPLACE($6,'\\?.*','')) )"
+ " AND merchant_serial ="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND (credit_serial < $4)"
+ " ORDER BY credit_serial DESC"
+ " LIMIT $5");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ (limit > 0)
+ ? "lookup_transfers_asc"
+ : "lookup_transfers_desc",
+ params,
+ &lookup_transfers_cb,
+ &ltc);
+ if (0 >= qs)
+ return qs;
+ return ltc.qs;
+}
diff --git a/src/backenddb/pg_lookup_transfers.h b/src/backenddb/pg_lookup_transfers.h
new file mode 100644
index 00000000..5a522256
--- /dev/null
+++ b/src/backenddb/pg_lookup_transfers.h
@@ -0,0 +1,60 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_transfers.h
+ * @brief implementation of the lookup_transfers function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_TRANSFERS_H
+#define PG_LOOKUP_TRANSFERS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup transfers. Note that filtering by @a verified status is done
+ * outside of SQL, as we already have 8 prepared statements and adding
+ * a filter on verified would further double the number of statements for
+ * a likely rather ineffective filter. So we apply that filter in
+ * #lookup_transfers_cb().
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup payments for
+ * @param payto_uri account that we are interested in transfers to
+ * @param before timestamp for the earliest transfer we care about
+ * @param after timestamp for the last transfer we care about
+ * @param limit number of entries to return, negative for descending in execution time,
+ * positive for ascending in execution time
+ * @param offset transfer_serial number of the transfer we want to offset from
+ * @param verified filter transfers by verification status
+ * @param cb function to call with detailed transfer data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_transfers (void *cls,
+ const char *instance_id,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp before,
+ struct GNUNET_TIME_Timestamp after,
+ int64_t limit,
+ uint64_t offset,
+ enum TALER_EXCHANGE_YesNoAll verified,
+ TALER_MERCHANTDB_TransferCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_webhook.c b/src/backenddb/pg_lookup_webhook.c
new file mode 100644
index 00000000..3dcf396c
--- /dev/null
+++ b/src/backenddb/pg_lookup_webhook.c
@@ -0,0 +1,92 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_webhook.c
+ * @brief Implementation of the lookup_webhook function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_webhook.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_webhook (void *cls,
+ const char *instance_id,
+ const char *webhook_id,
+ struct TALER_MERCHANTDB_WebhookDetails *wb)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (webhook_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_webhook",
+ "SELECT"
+ " event_type"
+ ",url"
+ ",http_method"
+ ",header_template"
+ ",body_template"
+ " FROM merchant_webhook"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"
+ " AND merchant_webhook.webhook_id=$2");
+
+ if (NULL == wb)
+ {
+ struct GNUNET_PQ_ResultSpec rs_null[] = {
+ GNUNET_PQ_result_spec_end
+ };
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_webhook",
+ params,
+ rs_null);
+ }
+ else
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("event_type",
+ &wb->event_type),
+ GNUNET_PQ_result_spec_string ("url",
+ &wb->url),
+ GNUNET_PQ_result_spec_string ("http_method",
+ &wb->http_method),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("header_template",
+ &wb->header_template),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("body_template",
+ &wb->body_template),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_webhook",
+ params,
+ rs);
+ }
+}
diff --git a/src/backenddb/pg_lookup_webhook.h b/src/backenddb/pg_lookup_webhook.h
new file mode 100644
index 00000000..d39828f1
--- /dev/null
+++ b/src/backenddb/pg_lookup_webhook.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_webhook.h
+ * @brief implementation of the lookup_webhook function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_WEBHOOK_H
+#define PG_LOOKUP_WEBHOOK_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup details about a particular webhook.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup webhook for
+ * @param webhook_id webhook to lookup
+ * @param[out] wb set to the webhook details on success, can be NULL
+ * (in that case we only want to check if the webhook exists)
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_webhook (void *cls,
+ const char *instance_id,
+ const char *webhook_id,
+ struct TALER_MERCHANTDB_WebhookDetails *wb);
+
+#endif
diff --git a/src/backenddb/pg_lookup_webhook_by_event.c b/src/backenddb/pg_lookup_webhook_by_event.c
new file mode 100644
index 00000000..97960bbe
--- /dev/null
+++ b/src/backenddb/pg_lookup_webhook_by_event.c
@@ -0,0 +1,158 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_webhook_by_event.c
+ * @brief Implementation of the lookup_webhook_by_event function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_webhook_by_event.h"
+#include "pg_helper.h"
+
+/**
+ * Context used for lookup_webhook_by_event_cb().
+ */
+struct LookupWebhookDetailContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_WebhookDetailCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about webhook.
+ *
+ * @param[in,out] cls of type `struct LookupPendingWebhookContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_webhook_by_event_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupWebhookDetailContext *wlc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t webhook_serial;
+ char *event_type;
+ char *url;
+ char *http_method;
+ char *header_template;
+ char *body_template;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("webhook_serial",
+ &webhook_serial),
+ GNUNET_PQ_result_spec_string ("event_type",
+ &event_type),
+ GNUNET_PQ_result_spec_string ("url",
+ &url),
+ GNUNET_PQ_result_spec_string ("http_method",
+ &http_method),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("header_template",
+ &header_template),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("body_template",
+ &body_template),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ wlc->extract_failed = true;
+ return;
+ }
+ wlc->cb (wlc->cb_cls,
+ webhook_serial,
+ event_type,
+ url,
+ http_method,
+ header_template,
+ body_template);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_webhook_by_event (void *cls,
+ const char *instance_id,
+ const char *event_type,
+ TALER_MERCHANTDB_WebhookDetailCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupWebhookDetailContext wlc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .extract_failed = false,
+ };
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (event_type),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_webhook_by_event",
+ "SELECT"
+ " webhook_serial"
+ ",event_type"
+ ",url"
+ ",http_method"
+ ",header_template"
+ ",body_template"
+ " FROM merchant_webhook"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"
+ " AND event_type=$2");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_webhook_by_event",
+ params,
+ &lookup_webhook_by_event_cb,
+ &wlc);
+
+ if (wlc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_webhook_by_event.h b/src/backenddb/pg_lookup_webhook_by_event.h
new file mode 100644
index 00000000..ccabc1ec
--- /dev/null
+++ b/src/backenddb/pg_lookup_webhook_by_event.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_webhook_by_event.h
+ * @brief implementation of the lookup_webhook_by_event function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_WEBHOOK_BY_EVENT_H
+#define PG_LOOKUP_WEBHOOK_BY_EVENT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup webhook by event
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup webhook for
+ * @param event_type event that we need to put in the pending webhook
+ * @param[out] cb set to the webhook details on success
+ * @param cb_cls callback closure
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_webhook_by_event (void *cls,
+ const char *instance_id,
+ const char *event_type,
+ TALER_MERCHANTDB_WebhookDetailCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_webhooks.c b/src/backenddb/pg_lookup_webhooks.c
new file mode 100644
index 00000000..0a85d527
--- /dev/null
+++ b/src/backenddb/pg_lookup_webhooks.c
@@ -0,0 +1,133 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_webhooks.c
+ * @brief Implementation of the lookup_webhooks function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_webhooks.h"
+#include "pg_helper.h"
+
+/**
+ * Context used for postgres_lookup_webhooks().
+ */
+struct LookupWebhookContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_WebhooksCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about webhook.
+ *
+ * @param[in,out] cls of type `struct LookupWebhookContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_webhooks_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupWebhookContext *wlc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *webhook_id;
+ char *event_type;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("webhook_id",
+ &webhook_id),
+ GNUNET_PQ_result_spec_string ("event_type",
+ &event_type),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ wlc->extract_failed = true;
+ return;
+ }
+ wlc->cb (wlc->cb_cls,
+ webhook_id,
+ event_type);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_webhooks (void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_WebhooksCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupWebhookContext wlc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ /* Can be overwritten by the lookup_webhook_cb */
+ .extract_failed = false,
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_webhooks",
+ "SELECT"
+ " webhook_id"
+ ",event_type"
+ " FROM merchant_webhook"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_webhooks",
+ params,
+ &lookup_webhooks_cb,
+ &wlc);
+ /* If there was an error inside lookup_webhook_cb, return a hard error. */
+ if (wlc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_webhooks.h b/src/backenddb/pg_lookup_webhooks.h
new file mode 100644
index 00000000..84509267
--- /dev/null
+++ b/src/backenddb/pg_lookup_webhooks.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_webhooks.h
+ * @brief implementation of the lookup_webhooks function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_WEBHOOKS_H
+#define PG_LOOKUP_WEBHOOKS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup all of the webhooks the given instance has configured.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup webhook for
+ * @param cb function to call on all webhook found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_webhooks (void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_WebhooksCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_wire_fee.c b/src/backenddb/pg_lookup_wire_fee.c
new file mode 100644
index 00000000..9ef680a5
--- /dev/null
+++ b/src/backenddb/pg_lookup_wire_fee.c
@@ -0,0 +1,83 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_wire_fee.c
+ * @brief Implementation of the lookup_wire_fee function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_wire_fee.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_wire_fee (void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const char *wire_method,
+ struct GNUNET_TIME_Timestamp contract_date,
+ struct TALER_WireFeeSet *fees,
+ struct GNUNET_TIME_Timestamp *start_date,
+ struct GNUNET_TIME_Timestamp *end_date,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_HashCode h_wire_method;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (master_pub),
+ GNUNET_PQ_query_param_auto_from_type (&h_wire_method),
+ GNUNET_PQ_query_param_timestamp (&contract_date),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount_with_currency ("wire_fee",
+ &fees->wire),
+ TALER_PQ_result_spec_amount_with_currency ("closing_fee",
+ &fees->closing),
+ GNUNET_PQ_result_spec_timestamp ("start_date",
+ start_date),
+ GNUNET_PQ_result_spec_timestamp ("end_date",
+ end_date),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ GNUNET_CRYPTO_hash (wire_method,
+ strlen (wire_method) + 1,
+ &h_wire_method);
+
+ PREPARE (pg,
+ "lookup_wire_fee",
+ "SELECT"
+ " wire_fee"
+ ",closing_fee"
+ ",start_date"
+ ",end_date"
+ ",master_sig"
+ " FROM merchant_exchange_wire_fees"
+ " WHERE master_pub=$1"
+ " AND h_wire_method=$2"
+ " AND start_date <= $3"
+ " AND end_date > $3");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_wire_fee",
+ params,
+ rs);
+}
diff --git a/src/backenddb/pg_lookup_wire_fee.h b/src/backenddb/pg_lookup_wire_fee.h
new file mode 100644
index 00000000..12a88050
--- /dev/null
+++ b/src/backenddb/pg_lookup_wire_fee.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_wire_fee.h
+ * @brief implementation of the lookup_wire_fee function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_LOOKUP_WIRE_FEE_H
+#define PG_LOOKUP_WIRE_FEE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Obtain information about wire fees charged by an exchange,
+ * including signature (so we have proof).
+ *
+ * @param cls closure
+ * @param master_pub public key of the exchange
+ * @param wire_method the wire method
+ * @param contract_date date of the contract to use for the lookup
+ * @param[out] fees wire fees charged
+ * @param[out] start_date start of fee being used
+ * @param[out] end_date end of fee being used
+ * @param[out] master_sig signature of exchange over fee structure
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_wire_fee (void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const char *wire_method,
+ struct GNUNET_TIME_Timestamp contract_date,
+ struct TALER_WireFeeSet *fees,
+ struct GNUNET_TIME_Timestamp *start_date,
+ struct GNUNET_TIME_Timestamp *end_date,
+ struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/backenddb/pg_mark_contract_paid.c b/src/backenddb/pg_mark_contract_paid.c
new file mode 100644
index 00000000..1119dd76
--- /dev/null
+++ b/src/backenddb/pg_mark_contract_paid.c
@@ -0,0 +1,112 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_mark_contract_paid.c
+ * @brief Implementation of the mark_contract_paid function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_mark_contract_paid.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_mark_contract_paid (
+ void *cls,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const char *session_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_string (session_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_QueryParam uparams[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Session ID must always be given by the caller. */
+ GNUNET_assert (NULL != session_id);
+
+ /* no preflight check here, run in transaction by caller! */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Marking h_contract_terms '%s' of %s as paid for session `%s'\n",
+ GNUNET_h2s (&h_contract_terms->hash),
+ instance_id,
+ session_id);
+ PREPARE (pg,
+ "mark_contract_paid",
+ "UPDATE merchant_contract_terms SET"
+ " paid=TRUE"
+ ",session_id=$3"
+ " WHERE h_contract_terms=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "mark_contract_paid",
+ params);
+ if (qs <= 0)
+ return qs;
+ PREPARE (pg,
+ "mark_inventory_sold",
+ "UPDATE merchant_inventory SET"
+ " total_sold=total_sold + order_locks.total_locked"
+ " FROM (SELECT total_locked,product_serial"
+ " FROM merchant_order_locks"
+ " WHERE order_serial="
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE h_contract_terms=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1))"
+ " ) AS order_locks"
+ " WHERE merchant_inventory.product_serial"
+ " =order_locks.product_serial");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "mark_inventory_sold",
+ uparams);
+ if (qs < 0)
+ return qs; /* 0: no inventory management, that's OK! */
+ /* ON DELETE CASCADE deletes from merchant_order_locks */
+ PREPARE (pg,
+ "delete_completed_order",
+ "WITH md AS"
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1) "
+ "DELETE"
+ " FROM merchant_orders"
+ " WHERE order_serial="
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms"
+ " JOIN md USING (merchant_serial)"
+ " WHERE h_contract_terms=$2)");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_completed_order",
+ uparams);
+}
diff --git a/src/backenddb/pg_mark_contract_paid.h b/src/backenddb/pg_mark_contract_paid.h
new file mode 100644
index 00000000..591e440e
--- /dev/null
+++ b/src/backenddb/pg_mark_contract_paid.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_mark_contract_paid.h
+ * @brief implementation of the mark_contract_paid function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_MARK_CONTRACT_PAID_H
+#define PG_MARK_CONTRACT_PAID_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Mark contract as paid and store the current @a session_id
+ * for which the contract was paid. Deletes the underlying order
+ * and marks the locked stocks of the order as sold.
+ *
+ * @param cls closure
+ * @param instance_id instance to mark contract as paid for
+ * @param h_contract_terms hash of the contract that is now paid
+ * @param session_id the session that paid the contract
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_mark_contract_paid (void *cls,
+ const char *instance_id,
+ const struct
+ TALER_PrivateContractHashP *h_contract_terms,
+ const char *session_id);
+
+#endif
diff --git a/src/backenddb/pg_mark_order_wired.c b/src/backenddb/pg_mark_order_wired.c
new file mode 100644
index 00000000..fde1ecc7
--- /dev/null
+++ b/src/backenddb/pg_mark_order_wired.c
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_mark_order_wired.c
+ * @brief Implementation of the mark_order_wired function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_mark_order_wired.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_mark_order_wired (void *cls,
+ uint64_t order_serial)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&order_serial),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "mark_order_wired",
+ "UPDATE merchant_contract_terms SET"
+ " wired=TRUE"
+ " WHERE order_serial=$1");
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "mark_order_wired",
+ params);
+}
diff --git a/src/backenddb/pg_mark_order_wired.h b/src/backenddb/pg_mark_order_wired.h
new file mode 100644
index 00000000..dd7cc97f
--- /dev/null
+++ b/src/backenddb/pg_mark_order_wired.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_mark_order_wired.h
+ * @brief implementation of the mark_order_wired function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_MARK_ORDER_WIRED_H
+#define PG_MARK_ORDER_WIRED_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Set 'wired' status for an order to 'true'.
+ *
+ * @param cls closure
+ * @param order_serial serial number of the order
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_mark_order_wired (void *cls,
+ uint64_t order_serial);
+
+#endif
diff --git a/src/backenddb/pg_purge_instance.c b/src/backenddb/pg_purge_instance.c
new file mode 100644
index 00000000..74ca5613
--- /dev/null
+++ b/src/backenddb/pg_purge_instance.c
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_purge_instance.c
+ * @brief Implementation of the purge_instance function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_purge_instance.h"
+#include "pg_helper.h"
+
+/**
+ * Purge an instance and all associated information from our database.
+ * Highly likely to cause undesired data loss. Use with caution.
+ *
+ * @param cls closure
+ * @param merchant_id identifier of the instance
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_purge_instance (void *cls,
+ const char *merchant_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (merchant_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "purge_instance",
+ "DELETE FROM merchant_instances"
+ " WHERE merchant_instances.merchant_id = $1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "purge_instance",
+ params);
+}
diff --git a/src/backenddb/pg_purge_instance.h b/src/backenddb/pg_purge_instance.h
new file mode 100644
index 00000000..3df05bd8
--- /dev/null
+++ b/src/backenddb/pg_purge_instance.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_purge_instance.h
+ * @brief implementation of the purge_instance function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_PURGE_INSTANCE_H
+#define PG_PURGE_INSTANCE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Purge an instance and all associated information from our database.
+ * Highly likely to cause undesired data loss. Use with caution.
+ *
+ * @param cls closure
+ * @param merchant_id identifier of the instance
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_purge_instance (void *cls,
+ const char *merchant_id);
+
+#endif
diff --git a/src/backenddb/pg_refund_coin.c b/src/backenddb/pg_refund_coin.c
new file mode 100644
index 00000000..1bde2974
--- /dev/null
+++ b/src/backenddb/pg_refund_coin.c
@@ -0,0 +1,77 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_refund_coin.c
+ * @brief Implementation of the refund_coin function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_refund_coin.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_refund_coin (void *cls,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp refund_timestamp,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const char *reason)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_timestamp (&refund_timestamp),
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_string (reason),
+ GNUNET_PQ_query_param_end
+ };
+ PREPARE (pg,
+ "refund_coin",
+ "INSERT INTO merchant_refunds"
+ "(order_serial"
+ ",rtransaction_id"
+ ",refund_timestamp"
+ ",coin_pub"
+ ",reason"
+ ",refund_amount"
+ ") "
+ "SELECT "
+ " dcon.order_serial"
+ ",0" /* rtransaction_id always 0 for /abort */
+ ",$3"
+ ",dep.coin_pub"
+ ",$5"
+ ",dep.amount_with_fee"
+ " FROM merchant_deposits dep"
+ " JOIN merchant_deposit_confirmations dcon"
+ " USING (deposit_confirmation_serial)"
+ " WHERE dep.coin_pub=$4"
+ " AND dcon.order_serial="
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE h_contract_terms=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1))");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "refund_coin",
+ params);
+}
diff --git a/src/backenddb/pg_refund_coin.h b/src/backenddb/pg_refund_coin.h
new file mode 100644
index 00000000..6b5c5d30
--- /dev/null
+++ b/src/backenddb/pg_refund_coin.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_refund_coin.h
+ * @brief implementation of the refund_coin function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_REFUND_COIN_H
+#define PG_REFUND_COIN_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Function called during aborts to refund a coin. Marks the
+ * respective coin as refunded.
+ *
+ * @param cls closure
+ * @param instance_id instance to refund payment for
+ * @param h_contract_terms hash of the contract to refund coin for
+ * @param refund_timestamp timestamp of the refund
+ * @param coin_pub public key of the coin to refund (fully)
+ * @param reason text justifying the refund
+ * @return transaction status
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a coin_pub is unknown to us;
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid,
+ * regardless of whether it actually increased the refund
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_refund_coin (void *cls,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp refund_timestamp,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const char *reason);
+
+#endif
diff --git a/src/backenddb/pg_select_account.c b/src/backenddb/pg_select_account.c
new file mode 100644
index 00000000..29fda632
--- /dev/null
+++ b/src/backenddb/pg_select_account.c
@@ -0,0 +1,79 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_account.c
+ * @brief Implementation of the select_account function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_accounts.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_account (void *cls,
+ const char *id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ struct TALER_MERCHANTDB_AccountDetails *ad)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (id),
+ GNUNET_PQ_query_param_auto_from_type (h_wire),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("salt",
+ &ad->salt),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &ad->payto_uri),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("credit_facade_url",
+ &ad->credit_facade_url),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_json ("credit_facade_credentials",
+ &ad->credit_facade_credentials),
+ NULL),
+ GNUNET_PQ_result_spec_bool ("active",
+ &ad->active),
+ GNUNET_PQ_result_spec_end
+ };
+
+ ad->h_wire = *h_wire;
+ check_connection (pg);
+ PREPARE (pg,
+ "select_account",
+ "SELECT"
+ " salt"
+ ",payto_uri"
+ ",credit_facade_url"
+ ",credit_facade_credentials"
+ ",active"
+ " FROM merchant_accounts"
+ " WHERE merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1) AND "
+ " h_wire=$2;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_account",
+ params,
+ rs);
+}
diff --git a/src/backenddb/pg_select_account.h b/src/backenddb/pg_select_account.h
new file mode 100644
index 00000000..d74eaf1e
--- /dev/null
+++ b/src/backenddb/pg_select_account.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_account.h
+ * @brief implementation of the select_account function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_ACCOUNT_H
+#define PG_SELECT_ACCOUNT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Obtain information about an instance's accounts.
+ *
+ * @param cls closure
+ * @param id identifier of the instance
+ * @param h_wire wire hash of the account
+ * @param[out] ad account details returned
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_account (void *cls,
+ const char *id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ struct TALER_MERCHANTDB_AccountDetails *ad);
+
+#endif
diff --git a/src/backenddb/pg_select_account_by_uri.c b/src/backenddb/pg_select_account_by_uri.c
new file mode 100644
index 00000000..fd05793f
--- /dev/null
+++ b/src/backenddb/pg_select_account_by_uri.c
@@ -0,0 +1,82 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_account_by_uri.c
+ * @brief Implementation of the select_account_by_uri function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_account_by_uri.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_account_by_uri (void *cls,
+ const char *id,
+ const char *payto_uri,
+ struct TALER_MERCHANTDB_AccountDetails *ad)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (id),
+ GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("salt",
+ &ad->salt),
+ GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+ &ad->h_wire),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("credit_facade_url",
+ &ad->credit_facade_url),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_json ("credit_facade_credentials",
+ &ad->credit_facade_credentials),
+ NULL),
+ GNUNET_PQ_result_spec_bool ("active",
+ &ad->active),
+ GNUNET_PQ_result_spec_end
+ };
+
+ ad->credit_facade_url = NULL;
+ ad->credit_facade_credentials = NULL;
+ ad->payto_uri = GNUNET_strdup (payto_uri);
+ check_connection (pg);
+ PREPARE (pg,
+ "select_account_by_uri",
+ "SELECT"
+ " salt"
+ ",h_wire"
+ ",credit_facade_url"
+ ",credit_facade_credentials"
+ ",active"
+ " FROM merchant_accounts"
+ " WHERE merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND REGEXP_REPLACE(payto_uri,'\\?.*','')"
+ " =REGEXP_REPLACE($2,'\\?.*','')");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_account_by_uri",
+ params,
+ rs);
+}
diff --git a/src/backenddb/pg_select_account_by_uri.h b/src/backenddb/pg_select_account_by_uri.h
new file mode 100644
index 00000000..718209be
--- /dev/null
+++ b/src/backenddb/pg_select_account_by_uri.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_account_by_uri.h
+ * @brief implementation of the select_account_by_uri function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_ACCOUNT_BY_URI_H
+#define PG_SELECT_ACCOUNT_BY_URI_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Obtain information about an instance's accounts.
+ *
+ * @param cls closure
+ * @param id identifier of the instance
+ * @param payto_uri URI of the account
+ * @param[out] ad account details returned
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_account_by_uri (void *cls,
+ const char *id,
+ const char *payto_uri,
+ struct TALER_MERCHANTDB_AccountDetails *ad);
+
+#endif
diff --git a/src/backenddb/pg_select_accounts.c b/src/backenddb/pg_select_accounts.c
new file mode 100644
index 00000000..ffb6c4c9
--- /dev/null
+++ b/src/backenddb/pg_select_accounts.c
@@ -0,0 +1,159 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_accounts.c
+ * @brief Implementation of the select_accounts function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_accounts.h"
+#include "pg_helper.h"
+
+
+/**
+ * Context for select_accounts().
+ */
+struct SelectAccountsContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_AccountCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Database context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to the return value on errors.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about accounts.
+ *
+ * @param cls of type `struct SelectAccountsContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+select_account_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct SelectAccountsContext *lic = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *payto;
+ char *facade_url = NULL;
+ json_t *credential = NULL;
+ struct TALER_MERCHANTDB_AccountDetails acc;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+ &acc.h_wire),
+ GNUNET_PQ_result_spec_auto_from_type ("salt",
+ &acc.salt),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("credit_facade_url",
+ &facade_url),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_json ("credit_facade_credentials",
+ &credential),
+ NULL),
+ GNUNET_PQ_result_spec_bool ("active",
+ &acc.active),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ lic->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ acc.payto_uri = payto;
+ acc.credit_facade_url = facade_url;
+ acc.credit_facade_credentials = credential;
+ lic->cb (lic->cb_cls,
+ &acc);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_accounts (void *cls,
+ const char *id,
+ TALER_MERCHANTDB_AccountCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct SelectAccountsContext lic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (id),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "select_accounts",
+ "SELECT"
+ " h_wire"
+ ",salt"
+ ",payto_uri"
+ ",credit_facade_url"
+ ",credit_facade_credentials"
+ ",active"
+ " FROM merchant_accounts"
+ " WHERE active"
+ " AND merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1);");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "select_accounts",
+ params,
+ &select_account_cb,
+ &lic);
+ if (0 > lic.qs)
+ return lic.qs;
+ return qs;
+}
diff --git a/src/backenddb/pg_select_accounts.h b/src/backenddb/pg_select_accounts.h
new file mode 100644
index 00000000..935fada6
--- /dev/null
+++ b/src/backenddb/pg_select_accounts.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_accounts.h
+ * @brief implementation of the select_accounts function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_ACCOUNTS_H
+#define PG_SELECT_ACCOUNTS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Obtain information about an instance's accounts.
+ *
+ * @param cls closure
+ * @param id identifier of the instance
+ * @param cb function to call on each account
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_accounts (void *cls,
+ const char *id,
+ TALER_MERCHANTDB_AccountCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_select_accounts_by_exchange.c b/src/backenddb/pg_select_accounts_by_exchange.c
new file mode 100644
index 00000000..c7637031
--- /dev/null
+++ b/src/backenddb/pg_select_accounts_by_exchange.c
@@ -0,0 +1,146 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_accounts_by_exchange.c
+ * @brief Implementation of the select_accounts_by_exchange function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_accounts_by_exchange.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #parse_accounts.
+ */
+struct SelectAccountContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_MERCHANTDB_ExchangeAccountCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Set to true on failure.
+ */
+ bool failed;
+};
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about accounts.
+ *
+ * @param cls of type `struct SelectAccountContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+parse_accounts (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct SelectAccountContext *ctx = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *payto_uri;
+ char *conversion_url = NULL;
+ json_t *debit_restrictions;
+ json_t *credit_restrictions;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &master_sig),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("conversion_url",
+ &conversion_url),
+ NULL),
+ TALER_PQ_result_spec_json ("debit_restrictions",
+ &debit_restrictions),
+ TALER_PQ_result_spec_json ("credit_restrictions",
+ &credit_restrictions),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->failed = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ payto_uri,
+ conversion_url,
+ debit_restrictions,
+ credit_restrictions,
+ &master_sig);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_accounts_by_exchange (
+ void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ TALER_MERCHANTDB_ExchangeAccountCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct SelectAccountContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (master_pub),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "select_exchange_accounts",
+ "SELECT"
+ " payto_uri"
+ ",conversion_url"
+ ",debit_restrictions"
+ ",credit_restrictions"
+ ",master_sig"
+ " FROM merchant_exchange_accounts"
+ " WHERE master_pub=$1;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "select_exchange_accounts",
+ params,
+ &parse_accounts,
+ &ctx);
+ if (ctx.failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_select_accounts_by_exchange.h b/src/backenddb/pg_select_accounts_by_exchange.h
new file mode 100644
index 00000000..e22c1601
--- /dev/null
+++ b/src/backenddb/pg_select_accounts_by_exchange.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_accounts_by_exchange.h
+ * @brief implementation of the select_accounts_by_exchange function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_ACCOUNTS_BY_EXCHANGE_H
+#define PG_SELECT_ACCOUNTS_BY_EXCHANGE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Return information about wire accounts of an exchange.
+ *
+ * @param cls closure
+ * @param master_pub public key of the exchange
+ * @param cb function to call on each account
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_accounts_by_exchange (
+ void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ TALER_MERCHANTDB_ExchangeAccountCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_select_exchange_keys.c b/src/backenddb/pg_select_exchange_keys.c
new file mode 100644
index 00000000..b14da501
--- /dev/null
+++ b/src/backenddb/pg_select_exchange_keys.c
@@ -0,0 +1,68 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_exchange_keys.c
+ * @brief Implementation of the select_exchange_keys function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_exchange_keys.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_exchange_keys (void *cls,
+ const char *exchange_url,
+ struct TALER_EXCHANGE_Keys **keys)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (exchange_url),
+ GNUNET_PQ_query_param_end
+ };
+ json_t *jkeys;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_json ("keys_json",
+ &jkeys),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "select_exchange_keys",
+ "SELECT"
+ " keys_json"
+ " FROM merchant_exchange_keys"
+ " WHERE exchange_url=$1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_exchange_keys",
+ params,
+ rs);
+ if (qs <= 0)
+ return qs;
+ *keys = TALER_EXCHANGE_keys_from_json (jkeys);
+ json_decref (jkeys);
+ if (NULL == *keys)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return qs;
+}
diff --git a/src/backenddb/pg_select_exchange_keys.h b/src/backenddb/pg_select_exchange_keys.h
new file mode 100644
index 00000000..e2cd0be7
--- /dev/null
+++ b/src/backenddb/pg_select_exchange_keys.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_exchange_keys.h
+ * @brief implementation of the select_exchange_keys function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_EXCHANGE_KEYS_H
+#define PG_SELECT_EXCHANGE_KEYS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Retrieve exchange's keys from the database.
+ *
+ * @param cls plugin closure
+ * @param exchange_url base URL of the exchange
+ * @param[out] keys set to the keys of the exchange
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_exchange_keys (void *cls,
+ const char *exchange_url,
+ struct TALER_EXCHANGE_Keys **keys);
+
+
+#endif
diff --git a/src/backenddb/pg_select_login_token.c b/src/backenddb/pg_select_login_token.c
new file mode 100644
index 00000000..7b72b373
--- /dev/null
+++ b/src/backenddb/pg_select_login_token.c
@@ -0,0 +1,67 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_login_token.c
+ * @brief Implementation of the select_login_token function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_login_token.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_login_token (
+ void *cls,
+ const char *id,
+ const struct TALER_MERCHANTDB_LoginTokenP *token,
+ struct GNUNET_TIME_Timestamp *expiration_time,
+ uint32_t *validity_scope)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (id),
+ GNUNET_PQ_query_param_auto_from_type (token),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("expiration_time",
+ expiration_time),
+ GNUNET_PQ_result_spec_uint32 ("validity_scope",
+ validity_scope),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "select_login_token",
+ "SELECT"
+ " expiration_time"
+ ",validity_scope"
+ " FROM merchant_login_tokens"
+ " WHERE token=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_login_token",
+ params,
+ rs);
+}
diff --git a/src/backenddb/pg_select_login_token.h b/src/backenddb/pg_select_login_token.h
new file mode 100644
index 00000000..1a91ffb1
--- /dev/null
+++ b/src/backenddb/pg_select_login_token.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_login_token.h
+ * @brief implementation of the select_login_token function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_LOGIN_TOKEN_H
+#define PG_SELECT_LOGIN_TOKEN_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Lookup information about a login token from database.
+ *
+ * @param cls closure
+ * @param id identifier of the instance
+ * @param token value of the token
+ * @param[out] expiration_time set to expiration time
+ * @param[out] validity_scope set to scope of the token
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_login_token (
+ void *cls,
+ const char *id,
+ const struct TALER_MERCHANTDB_LoginTokenP *token,
+ struct GNUNET_TIME_Timestamp *expiration_time,
+ uint32_t *validity_scope);
+
+
+#endif
diff --git a/src/backenddb/pg_select_open_transfers.c b/src/backenddb/pg_select_open_transfers.c
new file mode 100644
index 00000000..40982011
--- /dev/null
+++ b/src/backenddb/pg_select_open_transfers.c
@@ -0,0 +1,167 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_open_transfers.c
+ * @brief Implementation of the select_open_transfers function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_open_transfers.h"
+#include "pg_helper.h"
+
+
+/**
+ * Context used for open_transfers_cb().
+ */
+struct SelectOpenTransfersContext
+{
+ /**
+ * Postgres context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_OpenTransferCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Internal result.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about rewards.
+ *
+ * @param[in,out] cls of type `struct SelectOpenTransfersContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+open_transfers_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct SelectOpenTransfersContext *plc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t rowid;
+ char *instance_id;
+ char *exchange_url;
+ char *payto_uri;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct TALER_Amount total;
+ struct GNUNET_TIME_Absolute next_attempt;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("credit_serial",
+ &rowid),
+ GNUNET_PQ_result_spec_string ("instance_id",
+ &instance_id),
+ GNUNET_PQ_result_spec_string ("exchange_url",
+ &exchange_url),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid",
+ &wtid),
+ TALER_PQ_result_spec_amount_with_currency ("credit_amount",
+ &total),
+ GNUNET_PQ_result_spec_absolute_time ("next_attempt",
+ &next_attempt),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ plc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ plc->cb (plc->cb_cls,
+ rowid,
+ instance_id,
+ exchange_url,
+ payto_uri,
+ &wtid,
+ &total,
+ next_attempt);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_open_transfers (void *cls,
+ uint64_t limit,
+ TALER_MERCHANTDB_OpenTransferCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct SelectOpenTransfersContext plc = {
+ .pg = pg,
+ .cb = cb,
+ .cb_cls = cb_cls
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&limit),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "select_open_transfers",
+ "SELECT"
+ " credit_serial"
+ ",merchant_id AS instance_id"
+ ",exchange_url"
+ ",payto_uri"
+ ",wtid"
+ ",credit_amount"
+ ",ready_time AS next_attempt"
+ " FROM merchant_transfers"
+ " JOIN merchant_accounts"
+ " USING (account_serial)"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE confirmed AND"
+ " NOT (failed OR verified)"
+ " ORDER BY ready_time ASC"
+ " LIMIT $1;");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "select_open_transfers",
+ params,
+ &open_transfers_cb,
+ &plc);
+ if (0 != plc.qs)
+ return plc.qs;
+ return qs;
+}
diff --git a/src/backenddb/pg_select_open_transfers.h b/src/backenddb/pg_select_open_transfers.h
new file mode 100644
index 00000000..5857ed80
--- /dev/null
+++ b/src/backenddb/pg_select_open_transfers.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_open_transfers.h
+ * @brief implementation of the select_open_transfers function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_OPEN_TRANSFERS_H
+#define PG_SELECT_OPEN_TRANSFERS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Retrieve wire transfer details of wire details
+ * that taler-merchant-exchange still needs to
+ * investigate.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param limit maximum number of results to return
+ * @param cb function called with the wire transfer data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_open_transfers (void *cls,
+ uint64_t limit,
+ TALER_MERCHANTDB_OpenTransferCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/backenddb/pg_select_otp.c b/src/backenddb/pg_select_otp.c
new file mode 100644
index 00000000..f105488a
--- /dev/null
+++ b/src/backenddb/pg_select_otp.c
@@ -0,0 +1,91 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_otp.c
+ * @brief Implementation of the select_otp function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_otp.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_otp (void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ struct TALER_MERCHANTDB_OtpDeviceDetails *td)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (otp_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "select_otp",
+ "SELECT"
+ " otp_description"
+ ",otp_ctr"
+ ",otp_key"
+ ",otp_algorithm"
+ " FROM merchant_otp_devices"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"
+ " AND merchant_otp_devices.otp_id=$2");
+ if (NULL == td)
+ {
+ struct GNUNET_PQ_ResultSpec rs_null[] = {
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_otp",
+ params,
+ rs_null);
+ }
+ else
+ {
+ uint32_t pos32;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("otp_description",
+ &td->otp_description),
+ GNUNET_PQ_result_spec_uint64 ("otp_ctr",
+ &td->otp_ctr),
+ GNUNET_PQ_result_spec_string ("otp_key",
+ &td->otp_key),
+ GNUNET_PQ_result_spec_uint32 ("otp_algorithm",
+ &pos32),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_otp",
+ params,
+ rs);
+ td->otp_algorithm = (enum TALER_MerchantConfirmationAlgorithm) pos32;
+ return qs;
+ }
+}
+
diff --git a/src/backenddb/pg_select_otp.h b/src/backenddb/pg_select_otp.h
new file mode 100644
index 00000000..e015cf6b
--- /dev/null
+++ b/src/backenddb/pg_select_otp.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_otp.h
+ * @brief implementation of the select_otp function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_OTP_H
+#define PG_SELECT_OTP_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Lookup details about an OTP device.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup template for
+ * @param otp_id OTP device to lookup
+ * @param[out] td set to the OTP device details on success, can be NULL
+ * (in that case we only want to check if the template exists)
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_otp (void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ struct TALER_MERCHANTDB_OtpDeviceDetails *td);
+
+#endif
diff --git a/src/backenddb/pg_select_otp_serial.c b/src/backenddb/pg_select_otp_serial.c
new file mode 100644
index 00000000..c2011aad
--- /dev/null
+++ b/src/backenddb/pg_select_otp_serial.c
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_otp_serial.c
+ * @brief Implementation of the select_otp_serial function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_otp_serial.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_otp_serial (void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ uint64_t *serial)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (otp_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("otp_serial",
+ serial),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "select_otp_serial",
+ "SELECT"
+ " otp_serial"
+ " FROM merchant_otp_devices"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"
+ " AND merchant_otp_devices.otp_id=$2");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_otp_serial",
+ params,
+ rs);
+}
diff --git a/src/backenddb/pg_select_otp_serial.h b/src/backenddb/pg_select_otp_serial.h
new file mode 100644
index 00000000..46d128ae
--- /dev/null
+++ b/src/backenddb/pg_select_otp_serial.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_otp_serial.h
+ * @brief implementation of the select_otp_serial function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_OTP_SERIAL_H
+#define PG_SELECT_OTP_SERIAL_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Lookup serial number of an OTP device.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup template for
+ * @param otp_id OTP device to lookup
+ * @param[out] serial set to the OTP device serial number * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_otp_serial (void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ uint64_t *serial);
+
+#endif
diff --git a/src/backenddb/pg_select_wirewatch_accounts.c b/src/backenddb/pg_select_wirewatch_accounts.c
new file mode 100644
index 00000000..050caefb
--- /dev/null
+++ b/src/backenddb/pg_select_wirewatch_accounts.c
@@ -0,0 +1,147 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_wirewatch_accounts.c
+ * @brief Implementation of the select_wirewatch_accounts function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_wirewatch_accounts.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #handle_results().
+ */
+struct Context
+{
+ /**
+ * Function to call with results.
+ */
+ TALER_MERCHANTDB_WirewatchWorkCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Set to true if the parsing failed.
+ */
+ bool failure;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about accounts.
+ *
+ * @param cls of type `struct Context *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+handle_results (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct Context *ctx = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *instance;
+ char *payto_uri;
+ char *facade_url;
+ json_t *credential;
+ uint64_t last_serial;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("merchant_id",
+ &instance),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_PQ_result_spec_string ("credit_facade_url",
+ &facade_url),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_json ("credit_facade_credentials",
+ &credential),
+ NULL),
+ GNUNET_PQ_result_spec_uint64 ("last_bank_serial",
+ &last_serial),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->failure = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ instance,
+ payto_uri,
+ facade_url,
+ credential,
+ last_serial);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_wirewatch_accounts (
+ void *cls,
+ TALER_MERCHANTDB_WirewatchWorkCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct Context ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "select_wirewatch_progress",
+ "SELECT"
+ " last_bank_serial"
+ ",merchant_id"
+ ",payto_uri"
+ ",credit_facade_url"
+ ",credit_facade_credentials"
+ " FROM merchant_accounts"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE active"
+ " AND credit_facade_url IS NOT NULL");
+ check_connection (pg);
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "select_wirewatch_progress",
+ params,
+ &handle_results,
+ &ctx);
+ if (ctx.failure)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_select_wirewatch_accounts.h b/src/backenddb/pg_select_wirewatch_accounts.h
new file mode 100644
index 00000000..cff263d3
--- /dev/null
+++ b/src/backenddb/pg_select_wirewatch_accounts.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_wirewatch_accounts.h
+ * @brief implementation of the select_wirewatch_accounts function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_WIREWATCH_ACCOUNTS_H
+#define PG_SELECT_WIREWATCH_ACCOUNTS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Select information about progress made by taler-merchant-wirewatch.
+ *
+ * @param cls closure
+ * @param cb function to call with results
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_wirewatch_accounts (
+ void *cls,
+ TALER_MERCHANTDB_WirewatchWorkCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/backenddb/pg_set_transfer_status_to_confirmed.c b/src/backenddb/pg_set_transfer_status_to_confirmed.c
new file mode 100644
index 00000000..0579162a
--- /dev/null
+++ b/src/backenddb/pg_set_transfer_status_to_confirmed.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_set_transfer_status_to_confirmed.c
+ * @brief Implementation of the set_transfer_status_to_confirmed function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_set_transfer_status_to_confirmed.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_set_transfer_status_to_confirmed (
+ void *cls,
+ const char *instance_id,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *amount)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_string (exchange_url),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ amount),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "set_transfer_status_to_confirmed",
+ "UPDATE merchant_transfers SET"
+ " confirmed=TRUE"
+ " WHERE wtid=$2"
+ " AND credit_amount=cast($4 AS taler_amount_currency)"
+ " AND exchange_url=$3"
+ " AND account_serial IN"
+ " (SELECT account_serial"
+ " FROM merchant_accounts"
+ " WHERE merchant_serial ="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1));");
+ return GNUNET_PQ_eval_prepared_non_select (
+ pg->conn,
+ "set_transfer_status_to_confirmed",
+ params);
+}
diff --git a/src/backenddb/pg_set_transfer_status_to_confirmed.h b/src/backenddb/pg_set_transfer_status_to_confirmed.h
new file mode 100644
index 00000000..859ebb26
--- /dev/null
+++ b/src/backenddb/pg_set_transfer_status_to_confirmed.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_set_transfer_status_to_confirmed.h
+ * @brief implementation of the set_transfer_status_to_confirmed function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SET_TRANSFER_STATUS_TO_CONFIRMED_H
+#define PG_SET_TRANSFER_STATUS_TO_CONFIRMED_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Set transfer status to confirmed.
+ *
+ * @param cls closure
+ * @param instance_id merchant instance with the update
+ * @param exchange_url the exchange that made the transfer
+ * @param wtid wire transfer subject
+ * @param amount confirmed amount of the wire transfer
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_set_transfer_status_to_confirmed (
+ void *cls,
+ const char *instance_id,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *amount);
+
+
+#endif
diff --git a/src/backenddb/pg_store_wire_fee_by_exchange.c b/src/backenddb/pg_store_wire_fee_by_exchange.c
new file mode 100644
index 00000000..5c002589
--- /dev/null
+++ b/src/backenddb/pg_store_wire_fee_by_exchange.c
@@ -0,0 +1,76 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_store_wire_fee_by_exchange.c
+ * @brief Implementation of the store_wire_fee_by_exchange function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_store_wire_fee_by_exchange.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_store_wire_fee_by_exchange (
+ void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct GNUNET_HashCode *h_wire_method,
+ const struct TALER_WireFeeSet *fees,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (master_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_wire_method),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ &fees->wire),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ &fees->closing),
+ GNUNET_PQ_query_param_timestamp (&start_date),
+ GNUNET_PQ_query_param_timestamp (&end_date),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ /* no preflight check here, run in its own transaction by the caller */
+ PREPARE (pg,
+ "insert_wire_fee",
+ "INSERT INTO merchant_exchange_wire_fees"
+ "(master_pub"
+ ",h_wire_method"
+ ",wire_fee"
+ ",closing_fee"
+ ",start_date"
+ ",end_date"
+ ",master_sig)"
+ " VALUES "
+ "($1, $2, $3, $4, $5, $6, $7)"
+ " ON CONFLICT DO NOTHING");
+ check_connection (pg);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Storing wire fee for %s starting at %s of %s\n",
+ TALER_B2S (master_pub),
+ GNUNET_TIME_timestamp2s (start_date),
+ TALER_amount2s (&fees->wire));
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_wire_fee",
+ params);
+}
diff --git a/src/backenddb/pg_store_wire_fee_by_exchange.h b/src/backenddb/pg_store_wire_fee_by_exchange.h
new file mode 100644
index 00000000..5c7f408a
--- /dev/null
+++ b/src/backenddb/pg_store_wire_fee_by_exchange.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_store_wire_fee_by_exchange.h
+ * @brief implementation of the postgres_store_wire_fee_by_exchange function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_STORE_WIRE_FEE_BY_EXCHANGE_H
+#define PG_STORE_WIRE_FEE_BY_EXCHANGE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Store information about wire fees charged by an exchange,
+ * including signature (so we have proof).
+ *
+ * @param cls closure
+ * @param master_pub public key of the exchange
+ * @param h_wire_method hash of wire method
+ * @param fees the fee charged
+ * @param start_date start of fee being used
+ * @param end_date end of fee being used
+ * @param master_sig signature of exchange over fee structure
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_store_wire_fee_by_exchange (
+ void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct GNUNET_HashCode *h_wire_method,
+ const struct TALER_WireFeeSet *fees,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/backenddb/pg_template.c b/src/backenddb/pg_template.c
new file mode 100644
index 00000000..6f9ed277
--- /dev/null
+++ b/src/backenddb/pg_template.c
@@ -0,0 +1,26 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_template.c
+ * @brief Implementation of the template function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_template.h"
+#include "pg_helper.h"
diff --git a/src/backenddb/pg_template.h b/src/backenddb/pg_template.h
new file mode 100644
index 00000000..75e10d6e
--- /dev/null
+++ b/src/backenddb/pg_template.h
@@ -0,0 +1,29 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_template.h
+ * @brief implementation of the template function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_TEMPLATE_H
+#define PG_TEMPLATE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+#endif
diff --git a/src/backenddb/pg_template.sh b/src/backenddb/pg_template.sh
new file mode 100755
index 00000000..087d6ab8
--- /dev/null
+++ b/src/backenddb/pg_template.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# This file is in the public domain.
+#
+# Instantiates pg_template for a particular function.
+
+for n in $*
+do
+ NCAPS=`echo $n | tr a-z A-Z`
+ if test ! -e pg_$n.c
+ then
+ cat pg_template.c | sed -e s/template/$n/g -e s/TEMPLATE/$NCAPS/g > pg_$n.c
+ cat pg_template.h | sed -e s/template/$n/g -e s/TEMPLATE/$NCAPS/g > pg_$n.h
+ echo " plugin->$n\n = &TMH_PG_$n;" >> tmpl.c
+ echo "#include \"pg_$n.h\"" >> tmpl.inc
+ echo " pg_$n.h pg_$n.c \\" >> tmpl.am
+ fi
+done
+
+echo "Add lines from tmpl.am to Makefile.am"
+echo "Add lines from tmpl.inc to plugin_merchantdb_postgres.c at the beginning"
+echo "Add lines from tmpl.c to plugin_merchantdb_postgres.c at the end"
diff --git a/src/backenddb/pg_unlock_inventory.c b/src/backenddb/pg_unlock_inventory.c
new file mode 100644
index 00000000..5f4d8a7b
--- /dev/null
+++ b/src/backenddb/pg_unlock_inventory.c
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_unlock_inventory.c
+ * @brief Implementation of the unlock_inventory function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_unlock_inventory.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_unlock_inventory (void *cls,
+ const struct GNUNET_Uuid *uuid)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (uuid),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "unlock_inventory",
+ "DELETE"
+ " FROM merchant_inventory_locks"
+ " WHERE lock_uuid=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "unlock_inventory",
+ params);
+}
diff --git a/src/backenddb/pg_unlock_inventory.h b/src/backenddb/pg_unlock_inventory.h
new file mode 100644
index 00000000..cec734e8
--- /dev/null
+++ b/src/backenddb/pg_unlock_inventory.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_unlock_inventory.h
+ * @brief implementation of the unlock_inventory function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_UNLOCK_INVENTORY_H
+#define PG_UNLOCK_INVENTORY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Release an inventory lock by UUID. Releases ALL stocks locked under
+ * the given UUID.
+ *
+ * @param cls closure
+ * @param uuid the UUID to release locks for
+ * @return transaction status,
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are no locks under @a uuid
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_unlock_inventory (void *cls,
+ const struct GNUNET_Uuid *uuid);
+
+#endif
diff --git a/src/backenddb/pg_update_account.c b/src/backenddb/pg_update_account.c
new file mode 100644
index 00000000..7458c095
--- /dev/null
+++ b/src/backenddb/pg_update_account.c
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_account.c
+ * @brief Implementation of the update_account function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_update_account.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_account (
+ void *cls,
+ const char *id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *credit_facade_url,
+ const json_t *credit_facade_credentials)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (id),
+ GNUNET_PQ_query_param_auto_from_type (h_wire),
+ NULL == credit_facade_url
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (credit_facade_url),
+ NULL == credit_facade_credentials
+ ? GNUNET_PQ_query_param_null ()
+ : TALER_PQ_query_param_json (credit_facade_credentials),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "update_account",
+ "UPDATE merchant_accounts SET"
+ " credit_facade_url=$3"
+ ",credit_facade_credentials=COALESCE($4,credit_facade_credentials)"
+ " WHERE h_wire=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_account",
+ params);
+}
diff --git a/src/backenddb/pg_update_account.h b/src/backenddb/pg_update_account.h
new file mode 100644
index 00000000..794b99d8
--- /dev/null
+++ b/src/backenddb/pg_update_account.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_account.h
+ * @brief implementation of the update_account function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_ACCOUNT_H
+#define PG_UPDATE_ACCOUNT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Update information about an instance's account in our database.
+ *
+ * @param cls closure
+ * @param id identifier of the instance
+ * @param h_wire which account to update
+ * @param credit_facade_url new facade URL, can be NULL
+ * @param credit_facade_credentials new credentials, can be NULL to keep previous credentials
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_account (
+ void *cls,
+ const char *id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *credit_facade_url,
+ const json_t *credit_facade_credentials);
+
+
+#endif
diff --git a/src/backenddb/pg_update_contract_terms.c b/src/backenddb/pg_update_contract_terms.c
new file mode 100644
index 00000000..51e449a0
--- /dev/null
+++ b/src/backenddb/pg_update_contract_terms.c
@@ -0,0 +1,103 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_contract_terms.c
+ * @brief Implementation of the update_contract_terms function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_update_contract_terms.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_contract_terms (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ json_t *contract_terms)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp pay_deadline;
+ struct GNUNET_TIME_Timestamp refund_deadline;
+ const char *fulfillment_url = NULL;
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ if (GNUNET_OK !=
+ TALER_JSON_contract_hash (contract_terms,
+ &h_contract_terms))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("pay_deadline",
+ &pay_deadline),
+ GNUNET_JSON_spec_timestamp ("refund_deadline",
+ &refund_deadline),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("fulfillment_url",
+ &fulfillment_url),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (NULL,
+ contract_terms,
+ spec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+
+ check_connection (pg);
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (order_id),
+ TALER_PQ_query_param_json (contract_terms),
+ GNUNET_PQ_query_param_auto_from_type (&h_contract_terms),
+ GNUNET_PQ_query_param_timestamp (&pay_deadline),
+ GNUNET_PQ_query_param_timestamp (&refund_deadline),
+ (NULL == fulfillment_url)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (fulfillment_url),
+ GNUNET_PQ_query_param_end
+ };
+ PREPARE (pg,
+ "update_contract_terms",
+ "UPDATE merchant_contract_terms SET"
+ " contract_terms=$3"
+ ",h_contract_terms=$4"
+ ",pay_deadline=$5"
+ ",refund_deadline=$6"
+ ",fulfillment_url=$7"
+ " WHERE order_id=$2"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_contract_terms",
+ params);
+ }
+}
diff --git a/src/backenddb/pg_update_contract_terms.h b/src/backenddb/pg_update_contract_terms.h
new file mode 100644
index 00000000..fe428dc0
--- /dev/null
+++ b/src/backenddb/pg_update_contract_terms.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_contract_terms.h
+ * @brief implementation of the update_contract_terms function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_UPDATE_CONTRACT_TERMS_H
+#define PG_UPDATE_CONTRACT_TERMS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Update the contract terms stored for @a order_id. Note that some attributes are
+ * expected to be calculated inside of the function, like the hash of the
+ * contract terms (to be hashed), the creation_time and pay_deadline (to be
+ * obtained from the merchant_orders table). The "session_id" should be
+ * initially set to the empty string. The "fulfillment_url" and "refund_deadline"
+ * must be extracted from @a contract_terms.
+ *
+ * @param cls closure
+ * @param instance_id instance's identifier
+ * @param order_id order_id used to store
+ * @param contract_terms contract to store
+ * @return transaction status, #GNUNET_DB_STATUS_HARD_ERROR if @a contract_terms
+ * is malformed
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_contract_terms (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ json_t *contract_terms);
+
+#endif
diff --git a/src/backenddb/pg_update_deposit_confirmation_status.c b/src/backenddb/pg_update_deposit_confirmation_status.c
new file mode 100644
index 00000000..d83dc9b0
--- /dev/null
+++ b/src/backenddb/pg_update_deposit_confirmation_status.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_deposit_confirmation_status.c
+ * @brief Implementation of the update_deposit_confirmation_status function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_update_deposit_confirmation_status.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_deposit_confirmation_status (
+ void *cls,
+ uint64_t deposit_serial,
+ bool wire_pending,
+ struct GNUNET_TIME_Timestamp future_retry,
+ struct GNUNET_TIME_Relative retry_backoff,
+ const char *emsg)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&deposit_serial),
+ GNUNET_PQ_query_param_timestamp (&future_retry),
+ NULL == emsg
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (emsg),
+ GNUNET_PQ_query_param_relative_time (&retry_backoff),
+ GNUNET_PQ_query_param_bool (wire_pending),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "update_deposit_confirmation_status",
+ "UPDATE merchant_deposit_confirmations SET"
+ " wire_transfer_deadline=$2"
+ ",exchange_failure=$3"
+ ",retry_backoff=$4"
+ ",wire_pending=$5"
+ " WHERE deposit_confirmation_serial="
+ " (SELECT deposit_confirmation_serial"
+ " FROM merchant_deposits"
+ " WHERE deposit_serial=$1);");
+ return GNUNET_PQ_eval_prepared_non_select (
+ pg->conn,
+ "update_deposit_confirmation_status",
+ params);
+}
diff --git a/src/backenddb/pg_update_deposit_confirmation_status.h b/src/backenddb/pg_update_deposit_confirmation_status.h
new file mode 100644
index 00000000..ae995fec
--- /dev/null
+++ b/src/backenddb/pg_update_deposit_confirmation_status.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_deposit_confirmation_status.h
+ * @brief implementation of the update_deposit_confirmation_status function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_DEPOSIT_CONFIRMATION_STATUS_H
+#define PG_UPDATE_DEPOSIT_CONFIRMATION_STATUS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Update the deposit confirmation status associated with
+ * the given @a deposit_serial.
+ *
+ * @param cls closure
+ * @param deposit_serial deposit to update status for
+ * @param wire_pending did the exchange say that the wire is still pending?
+ * @param future_retry when should we ask the exchange again
+ * @param retry_backoff current value for the retry backoff
+ * @param emsg error message to record
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_deposit_confirmation_status (
+ void *cls,
+ uint64_t deposit_serial,
+ bool wire_pending,
+ struct GNUNET_TIME_Timestamp future_retry,
+ struct GNUNET_TIME_Relative retry_backoff,
+ const char *emsg);
+
+
+#endif
diff --git a/src/backenddb/pg_update_instance.c b/src/backenddb/pg_update_instance.c
new file mode 100644
index 00000000..228e2031
--- /dev/null
+++ b/src/backenddb/pg_update_instance.c
@@ -0,0 +1,77 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_instance.c
+ * @brief Implementation of the update_instance function for Postgres
+ * @author Christian Grothoff
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_update_instance.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_instance (void *cls,
+ const struct TALER_MERCHANTDB_InstanceSettings *is)
+{
+ struct PostgresClosure *pg = cls;
+ uint32_t ut32 = (uint32_t) is->ut;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (is->id),
+ GNUNET_PQ_query_param_string (is->name),
+ TALER_PQ_query_param_json (is->address),
+ TALER_PQ_query_param_json (is->jurisdiction),
+ GNUNET_PQ_query_param_bool (is->use_stefan),
+ GNUNET_PQ_query_param_relative_time (
+ &is->default_wire_transfer_delay),
+ GNUNET_PQ_query_param_relative_time (
+ &is->default_pay_delay),
+ (NULL == is->website)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (is->website),
+ (NULL == is->email)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (is->email),
+ (NULL == is->logo)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (is->logo),
+ GNUNET_PQ_query_param_uint32 (&ut32),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "update_instance",
+ "UPDATE merchant_instances SET"
+ " merchant_name=$2"
+ ",address=$3"
+ ",jurisdiction=$4"
+ ",use_stefan=$5"
+ ",default_wire_transfer_delay=$6"
+ ",default_pay_delay=$7"
+ ",website=$8"
+ ",email=$9"
+ ",logo=$10"
+ ",user_type=$11"
+ " WHERE merchant_id=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_instance",
+ params);
+}
diff --git a/src/backenddb/pg_update_instance.h b/src/backenddb/pg_update_instance.h
new file mode 100644
index 00000000..9c8c1d22
--- /dev/null
+++ b/src/backenddb/pg_update_instance.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_instance.h
+ * @brief implementation of the update_instance function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_UPDATE_INSTANCE_H
+#define PG_UPDATE_INSTANCE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Update information about an instance into our database.
+ *
+ * @param cls closure
+ * @param is details about the instance
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_instance (void *cls,
+ const struct TALER_MERCHANTDB_InstanceSettings *is);
+
+#endif
diff --git a/src/backenddb/pg_update_instance_auth.c b/src/backenddb/pg_update_instance_auth.c
new file mode 100644
index 00000000..d7077761
--- /dev/null
+++ b/src/backenddb/pg_update_instance_auth.c
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_instance_auth.c
+ * @brief Implementation of the update_instance_auth function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_update_instance_auth.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_instance_auth (
+ void *cls,
+ const char *merchant_id,
+ const struct TALER_MERCHANTDB_InstanceAuthSettings *is)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (merchant_id),
+ GNUNET_PQ_query_param_auto_from_type (&is->auth_hash),
+ GNUNET_PQ_query_param_auto_from_type (&is->auth_salt),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "update_instance_auth",
+ "UPDATE merchant_instances SET"
+ " auth_hash=$2"
+ ",auth_salt=$3"
+ " WHERE merchant_id=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_instance_auth",
+ params);
+}
diff --git a/src/backenddb/pg_update_instance_auth.h b/src/backenddb/pg_update_instance_auth.h
new file mode 100644
index 00000000..9b637b7c
--- /dev/null
+++ b/src/backenddb/pg_update_instance_auth.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_instance_auth.h
+ * @brief implementation of the update_instance_auth function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_UPDATE_INSTANCE_AUTH_H
+#define PG_UPDATE_INSTANCE_AUTH_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Update information about an instance's authentication settings
+ * into our database.
+ *
+ * @param cls closure
+ * @param merchant_id identity of the instance
+ * @param is authentication details about the instance
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_instance_auth (void *cls,
+ const char *merchant_id,
+ const struct
+ TALER_MERCHANTDB_InstanceAuthSettings *is);
+
+#endif
diff --git a/src/backenddb/pg_update_otp.c b/src/backenddb/pg_update_otp.c
new file mode 100644
index 00000000..bdcb9624
--- /dev/null
+++ b/src/backenddb/pg_update_otp.c
@@ -0,0 +1,78 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_otp.c
+ * @brief Implementation of the update_otp function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_update_otp.h"
+#include "pg_helper.h"
+
+
+/**
+ * Update details about a particular OTP device.
+ *
+ * @param cls closure
+ * @param instance_id instance to update OTP device for
+ * @param otp_id OTP device to update
+ * @param td update to the OTP device details on success, can be NULL
+ * (in that case we only want to check if the template exists)
+ * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the template
+ * does not yet exist.
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_otp (void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ const struct TALER_MERCHANTDB_OtpDeviceDetails *td)
+{
+ struct PostgresClosure *pg = cls;
+ uint32_t pos32 = (uint32_t) td->otp_algorithm;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (otp_id),
+ GNUNET_PQ_query_param_string (td->otp_description),
+ GNUNET_PQ_query_param_uint32 (&pos32),
+ GNUNET_PQ_query_param_uint64 (&td->otp_ctr),
+ (NULL == td->otp_key)
+ ? GNUNET_PQ_query_param_null()
+ : GNUNET_PQ_query_param_string (td->otp_key),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "update_otp",
+ "UPDATE merchant_otp_devices SET"
+ " otp_description=$3"
+ ",otp_algorithm=$4"
+ ",otp_ctr=$5"
+ ",otp_key=COALESCE($6,otp_key)"
+ " WHERE merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND otp_id=$2");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_otp",
+ params);
+}
+
+
diff --git a/src/backenddb/pg_update_otp.h b/src/backenddb/pg_update_otp.h
new file mode 100644
index 00000000..7568608b
--- /dev/null
+++ b/src/backenddb/pg_update_otp.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_otp.h
+ * @brief implementation of the update_otp function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_OTP_H
+#define PG_UPDATE_OTP_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Update details about a particular OTP device.
+ *
+ * @param cls closure
+ * @param instance_id instance to update OTP device for
+ * @param otp_id OTP device to update
+ * @param td update to the OTP device details on success, can be NULL
+ * (in that case we only want to check if the template exists)
+ * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the template
+ * does not yet exist.
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_otp (void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ const struct TALER_MERCHANTDB_OtpDeviceDetails *td);
+
+
+#endif
diff --git a/src/backenddb/pg_update_pending_webhook.c b/src/backenddb/pg_update_pending_webhook.c
new file mode 100644
index 00000000..23ef5f04
--- /dev/null
+++ b/src/backenddb/pg_update_pending_webhook.c
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_pending_webhook.c
+ * @brief Implementation of the update_pending_webhook function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_update_pending_webhook.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_pending_webhook (void *cls,
+ uint64_t webhook_pending_serial,
+ struct GNUNET_TIME_Absolute next_attempt)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&webhook_pending_serial),
+ GNUNET_PQ_query_param_absolute_time (&next_attempt),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "update_pending_webhook",
+ "UPDATE merchant_pending_webhooks SET"
+ " retries=retries+1"
+ ",next_attempt=$2"
+ " WHERE webhook_pending_serial=$1");
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_pending_webhook",
+ params);
+}
diff --git a/src/backenddb/pg_update_pending_webhook.h b/src/backenddb/pg_update_pending_webhook.h
new file mode 100644
index 00000000..2ccf519e
--- /dev/null
+++ b/src/backenddb/pg_update_pending_webhook.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_pending_webhook.h
+ * @brief implementation of the update_pending_webhook function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_UPDATE_PENDING_WEBHOOK_H
+#define PG_UPDATE_PENDING_WEBHOOK_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Update the pending webhook. It is use if the webhook can't be send.
+ *
+ * @param cls closure
+ * @param webhook_pending_serial pending_webhook that need to be update
+ * @param next_attempt when to try the webhook next
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_pending_webhook (void *cls,
+ uint64_t webhook_pending_serial,
+ struct GNUNET_TIME_Absolute next_attempt);
+
+#endif
diff --git a/src/backenddb/pg_update_product.c b/src/backenddb/pg_update_product.c
new file mode 100644
index 00000000..cd7c1857
--- /dev/null
+++ b/src/backenddb/pg_update_product.c
@@ -0,0 +1,86 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_product.c
+ * @brief Implementation of the update_product function for Postgres
+ * @author Christian Grothoff
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_update_product.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_product (void *cls,
+ const char *instance_id,
+ const char *product_id,
+ const struct TALER_MERCHANTDB_ProductDetails *pd)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id), /* $1 */
+ GNUNET_PQ_query_param_string (product_id),
+ GNUNET_PQ_query_param_string (pd->description),
+ TALER_PQ_query_param_json (pd->description_i18n),
+ GNUNET_PQ_query_param_string (pd->unit),
+ GNUNET_PQ_query_param_string (pd->image), /* $6 */
+ TALER_PQ_query_param_json (pd->taxes),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ &pd->price), /* $8 */
+ GNUNET_PQ_query_param_uint64 (&pd->total_stock), /* $9 */
+ GNUNET_PQ_query_param_uint64 (&pd->total_lost),
+ TALER_PQ_query_param_json (pd->address),
+ GNUNET_PQ_query_param_timestamp (&pd->next_restock),
+ GNUNET_PQ_query_param_uint32 (&pd->minimum_age),
+ GNUNET_PQ_query_param_end
+ };
+
+ if ( (pd->total_stock < pd->total_lost + pd->total_sold) ||
+ (pd->total_lost < pd->total_lost
+ + pd->total_sold) /* integer overflow */)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ check_connection (pg);
+ PREPARE (pg,
+ "update_product",
+ "UPDATE merchant_inventory SET"
+ " description=$3"
+ ",description_i18n=$4"
+ ",unit=$5"
+ ",image=$6"
+ ",taxes=$7"
+ ",price=$8"
+ ",total_stock=$9"
+ ",total_lost=$10"
+ ",address=$11"
+ ",next_restock=$12"
+ ",minimum_age=$13"
+ " WHERE merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND product_id=$2"
+ " AND total_stock <= $9"
+ " AND total_lost <= $10");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_product",
+ params);
+}
diff --git a/src/backenddb/pg_update_product.h b/src/backenddb/pg_update_product.h
new file mode 100644
index 00000000..3ad280ef
--- /dev/null
+++ b/src/backenddb/pg_update_product.h
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_product.h
+ * @brief implementation of the update_product function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_PRODUCT_H
+#define PG_UPDATE_PRODUCT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Update details about a particular product. Note that the
+ * transaction must enforce that the sold/stocked/lost counters
+ * are not reduced (i.e. by expanding the WHERE clause on the existing
+ * values).
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup products for
+ * @param product_id product to lookup
+ * @param[out] pd set to the product details on success, can be NULL
+ * (in that case we only want to check if the product exists)
+ * total_sold in @a pd is ignored, total_lost must not
+ * exceed total_stock minus the existing total_sold;
+ * total_sold and total_stock must be larger or equal to
+ * the existing value;
+ * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the
+ * non-decreasing constraints are not met *or* if the product
+ * does not yet exist.
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_product (void *cls,
+ const char *instance_id,
+ const char *product_id,
+ const struct TALER_MERCHANTDB_ProductDetails *pd);
+
+#endif
diff --git a/src/backenddb/pg_update_template.c b/src/backenddb/pg_update_template.c
new file mode 100644
index 00000000..c0c35df3
--- /dev/null
+++ b/src/backenddb/pg_update_template.c
@@ -0,0 +1,80 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_template.c
+ * @brief Implementation of the update_template function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_update_template.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_template (void *cls,
+ const char *instance_id,
+ const char *template_id,
+ const struct TALER_MERCHANTDB_TemplateDetails *td)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (template_id),
+ GNUNET_PQ_query_param_string (td->template_description),
+ (NULL == td->otp_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (td->otp_id),
+ TALER_PQ_query_param_json (td->template_contract),
+ (NULL == td->editable_defaults)
+ ? GNUNET_PQ_query_param_null ()
+ : TALER_PQ_query_param_json (td->editable_defaults),
+ (NULL == td->required_currency)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (td->required_currency),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "update_template",
+ "WITH mid AS ("
+ " SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ ",otp AS ("
+ " SELECT otp_serial"
+ " FROM merchant_otp_devices"
+ " JOIN mid USING (merchant_serial)"
+ " WHERE otp_id=$4)"
+ "UPDATE merchant_template SET"
+ " template_description=$3"
+ ",otp_device_id="
+ " COALESCE((SELECT otp_serial"
+ " FROM otp), NULL)"
+ ",template_contract=$5"
+ ",editable_defaults=$6"
+ ",required_currency=$7"
+ " WHERE merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM mid)"
+ " AND template_id=$2");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_template",
+ params);
+}
diff --git a/src/backenddb/pg_update_template.h b/src/backenddb/pg_update_template.h
new file mode 100644
index 00000000..26b932a2
--- /dev/null
+++ b/src/backenddb/pg_update_template.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_template.h
+ * @brief implementation of the update_template function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_TEMPLATE_H
+#define PG_UPDATE_TEMPLATE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Update details about a particular template.
+ *
+ * @param cls closure
+ * @param instance_id instance to update template for
+ * @param template_id template to update
+ * @param td update to the template details on success, can be NULL
+ * (in that case we only want to check if the template exists)
+ * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the template
+ * does not yet exist.
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_template (void *cls,
+ const char *instance_id,
+ const char *template_id,
+ const struct TALER_MERCHANTDB_TemplateDetails *td);
+
+#endif
diff --git a/src/backenddb/pg_update_token_family.c b/src/backenddb/pg_update_token_family.c
new file mode 100644
index 00000000..7864c60b
--- /dev/null
+++ b/src/backenddb/pg_update_token_family.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_token_family.c
+ * @brief Implementation of the update_token_family function for Postgres
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_update_token_family.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_token_family (void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ const struct TALER_MERCHANTDB_TokenFamilyDetails *details)
+{
+ struct PostgresClosure *pg = cls;
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (token_family_slug),
+ GNUNET_PQ_query_param_string (details->name),
+ GNUNET_PQ_query_param_string (details->description),
+ TALER_PQ_query_param_json (details->description_i18n),
+ GNUNET_PQ_query_param_timestamp (&details->valid_after),
+ GNUNET_PQ_query_param_timestamp (&details->valid_before),
+ GNUNET_PQ_query_param_relative_time (&details->duration),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "update_token_family",
+ "UPDATE merchant_token_families SET"
+ " name=$3"
+ ",description=$4"
+ ",description_i18n=$5"
+ ",valid_after=$6"
+ ",valid_before=$7"
+ ",duration=$8"
+ " WHERE merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND slug=$2");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_token_family",
+ params);
+}
diff --git a/src/backenddb/pg_update_token_family.h b/src/backenddb/pg_update_token_family.h
new file mode 100644
index 00000000..84ce65ec
--- /dev/null
+++ b/src/backenddb/pg_update_token_family.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_token_family.h
+ * @brief implementation of the update_token_family function for Postgres
+ * @author Christian Blättler
+ */
+#ifndef PG_UPDATE_TOKEN_FAMILY_H
+#define PG_UPDATE_TOKEN_FAMILY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Update details about a particular token family.
+ *
+ * @param cls closure
+ * @param instance_id instance to update token family for
+ * @param token_family_slug slug of token family to update
+ * @param details set to the updated token family on success, can be NULL
+ * (in that case we only want to check if the token family exists)
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_token_family (void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ const struct TALER_MERCHANTDB_TokenFamilyDetails *details);
+
+#endif
diff --git a/src/backenddb/pg_update_transfer_status.c b/src/backenddb/pg_update_transfer_status.c
new file mode 100644
index 00000000..9898984d
--- /dev/null
+++ b/src/backenddb/pg_update_transfer_status.c
@@ -0,0 +1,65 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_transfer_status.c
+ * @brief Implementation of the update_transfer_status function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_update_transfer_status.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_transfer_status (
+ void *cls,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct GNUNET_TIME_Absolute next_attempt,
+ enum TALER_ErrorCode ec,
+ bool failed,
+ bool verified)
+{
+ struct PostgresClosure *pg = cls;
+ uint32_t ec32 = (uint32_t) ec;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_string (exchange_url),
+ GNUNET_PQ_query_param_uint32 (&ec32),
+ GNUNET_PQ_query_param_bool (failed),
+ GNUNET_PQ_query_param_bool (verified),
+ GNUNET_PQ_query_param_absolute_time (&next_attempt),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "update_transfer_status",
+ "UPDATE merchant_transfers SET"
+ " validation_status=$3"
+ ",failed=$4"
+ ",verified=$5"
+ ",ready_time=$6"
+ " WHERE wtid=$1"
+ " AND exchange_url=$2");
+ return GNUNET_PQ_eval_prepared_non_select (
+ pg->conn,
+ "update_transfer_status",
+ params);
+}
diff --git a/src/backenddb/pg_update_transfer_status.h b/src/backenddb/pg_update_transfer_status.h
new file mode 100644
index 00000000..2828b25e
--- /dev/null
+++ b/src/backenddb/pg_update_transfer_status.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_transfer_status.h
+ * @brief implementation of the update_transfer_status function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_TRANSFER_STATUS_H
+#define PG_UPDATE_TRANSFER_STATUS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Update transfer status.
+ *
+ * @param cls closure
+ * @param exchange_url the exchange that made the transfer
+ * @param wtid wire transfer subject
+ * @param next_attempt when should we try again (if ever)
+ * @param ec current error state of checking the transfer
+ * @param failed true if validation has failed for good
+ * @param verified true if validation has succeeded for good
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_transfer_status (
+ void *cls,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct GNUNET_TIME_Absolute next_attempt,
+ enum TALER_ErrorCode ec,
+ bool failed,
+ bool verified);
+
+#endif
diff --git a/src/backenddb/pg_update_webhook.c b/src/backenddb/pg_update_webhook.c
new file mode 100644
index 00000000..087feb2a
--- /dev/null
+++ b/src/backenddb/pg_update_webhook.c
@@ -0,0 +1,68 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_webhook.c
+ * @brief Implementation of the update_webhook function for Postgres
+ * @author Iván Ãvalos
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_update_webhook.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_webhook (void *cls,
+ const char *instance_id,
+ const char *webhook_id,
+ const struct TALER_MERCHANTDB_WebhookDetails *wb)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (webhook_id),
+ GNUNET_PQ_query_param_string (wb->event_type),
+ GNUNET_PQ_query_param_string (wb->url),
+ GNUNET_PQ_query_param_string (wb->http_method),
+ (NULL == wb->header_template)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (wb->header_template),
+ (NULL == wb->body_template)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (wb->body_template),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "update_webhook",
+ "UPDATE merchant_webhook SET"
+ " event_type=$3"
+ ",url=$4"
+ ",http_method=$5"
+ ",header_template=$6"
+ ",body_template=$7"
+ " WHERE merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND webhook_id=$2");
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_webhook",
+ params);
+}
diff --git a/src/backenddb/pg_update_webhook.h b/src/backenddb/pg_update_webhook.h
new file mode 100644
index 00000000..a34eb598
--- /dev/null
+++ b/src/backenddb/pg_update_webhook.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_webhook.h
+ * @brief implementation of the update_webhook function for Postgres
+ * @author Iván Ãvalos
+ */
+#ifndef PG_UPDATE_WEBHOOK_H
+#define PG_UPDATE_WEBHOOK_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Update details about a particular webhook.
+ *
+ * @param cls closure
+ * @param instance_id instance to update template for
+ * @param webhook_id webhook to update
+ * @param wb update to the webhook details on success, can be NULL
+ * (in that case we only want to check if the webhook exists)
+ * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the webhook
+ * does not yet exist.
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_webhook (void *cls,
+ const char *instance_id,
+ const char *webhook_id,
+ const struct TALER_MERCHANTDB_WebhookDetails *wb);
+
+#endif
diff --git a/src/backenddb/pg_update_wirewatch_progress.c b/src/backenddb/pg_update_wirewatch_progress.c
new file mode 100644
index 00000000..629439e3
--- /dev/null
+++ b/src/backenddb/pg_update_wirewatch_progress.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_wirewatch_progress.c
+ * @brief Implementation of the update_wirewatch_progress function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_update_wirewatch_progress.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_wirewatch_progress (
+ void *cls,
+ const char *instance,
+ const char *payto_uri,
+ uint64_t last_serial)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance),
+ GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_uint64 (&last_serial),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "update_wirewatch_progress",
+ "UPDATE merchant_accounts"
+ " SET last_bank_serial=$3"
+ " WHERE REGEXP_REPLACE(payto_uri,'\\?.*','')"
+ " =REGEXP_REPLACE(CAST ($2 AS TEXT),'\\?.*','')"
+ " AND merchant_serial ="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)");
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_wirewatch_progress",
+ params);
+}
diff --git a/src/backenddb/pg_update_wirewatch_progress.h b/src/backenddb/pg_update_wirewatch_progress.h
new file mode 100644
index 00000000..0e762adc
--- /dev/null
+++ b/src/backenddb/pg_update_wirewatch_progress.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_update_wirewatch_progress.h
+ * @brief implementation of the update_wirewatch_progress function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_WIREWATCH_PROGRESS_H
+#define PG_UPDATE_WIREWATCH_PROGRESS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Update information about progress made by taler-merchant-wirewatch.
+ *
+ * @param cls closure
+ * @param instance name of the instance to record progress for
+ * @param payto_uri bank account URI to record progress for
+ * @param last_serial latest serial of a transaction that was processed
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_wirewatch_progress (
+ void *cls,
+ const char *instance,
+ const char *payto_uri,
+ uint64_t last_serial);
+
+
+#endif
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c
index 823ad819..e09b4827 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014--2023 Taler Systems SA
+ (C) 2014--2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -20,6 +20,7 @@
* @author Christian Grothoff
* @author Marcello Stanisci
* @author Priscilla Huang
+ * @author Iván Ãvalos
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
@@ -29,6 +30,115 @@
#include <taler/taler_json_lib.h>
#include <taler/taler_mhd_lib.h>
#include "taler_merchantdb_plugin.h"
+#include "pg_helper.h"
+#include "pg_insert_otp.h"
+#include "pg_delete_otp.h"
+#include "pg_update_otp.h"
+#include "pg_select_otp.h"
+#include "pg_select_otp_serial.h"
+#include "pg_insert_login_token.h"
+#include "pg_delete_login_token.h"
+#include "pg_select_login_token.h"
+#include "pg_insert_account.h"
+#include "pg_update_account.h"
+#include "pg_lookup_instances.h"
+#include "pg_lookup_transfers.h"
+#include "pg_lookup_pending_deposits.h"
+#include "pg_update_wirewatch_progress.h"
+#include "pg_select_wirewatch_accounts.h"
+#include "pg_select_open_transfers.h"
+#include "pg_delete_exchange_accounts.h"
+#include "pg_select_accounts_by_exchange.h"
+#include "pg_insert_exchange_account.h"
+#include "pg_lookup_instance_auth.h"
+#include "pg_lookup_otp_devices.h"
+#include "pg_update_transfer_status.h"
+#include "pg_insert_instance.h"
+#include "pg_account_kyc_set_status.h"
+#include "pg_account_kyc_get_status.h"
+#include "pg_delete_instance_private_key.h"
+#include "pg_purge_instance.h"
+#include "pg_update_instance.h"
+#include "pg_update_instance_auth.h"
+#include "pg_inactivate_account.h"
+#include "pg_activate_account.h"
+#include "pg_lookup_products.h"
+#include "pg_lookup_product.h"
+#include "pg_delete_product.h"
+#include "pg_insert_product.h"
+#include "pg_update_product.h"
+#include "pg_lock_product.h"
+#include "pg_expire_locks.h"
+#include "pg_delete_order.h"
+#include "pg_lookup_order.h"
+#include "pg_lookup_order_summary.h"
+#include "pg_lookup_orders.h"
+#include "pg_insert_order.h"
+#include "pg_unlock_inventory.h"
+#include "pg_insert_order_lock.h"
+#include "pg_lookup_contract_terms3.h"
+#include "pg_lookup_contract_terms2.h"
+#include "pg_lookup_contract_terms.h"
+#include "pg_insert_contract_terms.h"
+#include "pg_update_contract_terms.h"
+#include "pg_delete_contract_terms.h"
+#include "pg_delete_template.h"
+#include "pg_insert_template.h"
+#include "pg_update_template.h"
+#include "pg_lookup_templates.h"
+#include "pg_lookup_template.h"
+#include "pg_lookup_deposits.h"
+#include "pg_insert_exchange_signkey.h"
+#include "pg_insert_deposit.h"
+#include "pg_insert_deposit_confirmation.h"
+#include "pg_lookup_refunds.h"
+#include "pg_mark_contract_paid.h"
+#include "pg_select_account_by_uri.h"
+#include "pg_refund_coin.h"
+#include "pg_lookup_order_status.h"
+#include "pg_lookup_order_status_by_serial.h"
+#include "pg_lookup_deposits_by_order.h"
+#include "pg_lookup_transfer_details_by_order.h"
+#include "pg_mark_order_wired.h"
+#include "pg_lookup_refunds_detailed.h"
+#include "pg_insert_refund_proof.h"
+#include "pg_lookup_refund_proof.h"
+#include "pg_lookup_order_by_fulfillment.h"
+#include "pg_delete_transfer.h"
+#include "pg_check_transfer_exists.h"
+#include "pg_lookup_account.h"
+#include "pg_lookup_wire_fee.h"
+#include "pg_lookup_deposits_by_contract_and_coin.h"
+#include "pg_lookup_transfer.h"
+#include "pg_lookup_transfer_summary.h"
+#include "pg_lookup_transfer_details.h"
+#include "pg_lookup_webhooks.h"
+#include "pg_lookup_webhook.h"
+#include "pg_delete_webhook.h"
+#include "pg_insert_webhook.h"
+#include "pg_update_webhook.h"
+#include "pg_lookup_webhook_by_event.h"
+#include "pg_delete_pending_webhook.h"
+#include "pg_insert_pending_webhook.h"
+#include "pg_update_pending_webhook.h"
+#include "pg_lookup_pending_webhooks.h"
+#include "pg_update_deposit_confirmation_status.h"
+#include "pg_set_transfer_status_to_confirmed.h"
+#include "pg_insert_exchange_keys.h"
+#include "pg_select_exchange_keys.h"
+#include "pg_insert_deposit_to_transfer.h"
+#include "pg_increase_refund.h"
+#include "pg_select_account.h"
+#include "pg_select_accounts.h"
+#include "pg_insert_transfer.h"
+#include "pg_insert_transfer_details.h"
+#include "pg_store_wire_fee_by_exchange.h"
+#include "pg_insert_token_family.h"
+#include "pg_lookup_token_family.h"
+#include "pg_lookup_token_families.h"
+#include "pg_delete_token_family.h"
+#include "pg_update_token_family.h"
+
/**
* How often do we re-try if we run into a DB serialization error?
@@ -37,76 +147,6 @@
/**
- * Wrapper macro to add the currency from the plugin's state
- * when fetching amounts from the database.
- *
- * @param field name of the database field to fetch amount from
- * @param[out] amountp pointer to amount to set
- */
-#define TALER_PQ_RESULT_SPEC_AMOUNT(field,amountp) \
- TALER_PQ_result_spec_amount ( \
- field,pg->currency,amountp)
-
-/**
- * Wrapper macro to add the currency from the plugin's state
- * when fetching amounts from the database. NBO variant.
- *
- * @param field name of the database field to fetch amount from
- * @param[out] amountp pointer to amount to set
- */
-#define TALER_PQ_RESULT_SPEC_AMOUNT_NBO(field, amountp) \
- TALER_PQ_result_spec_amount_nbo ( \
- field,pg->currency,amountp)
-
-
-/**
- * Wrapper macro to add the currency from the plugin's state
- * when fetching amounts from the database.
- *
- * @param field name of the database field to fetch amount from
- * @param[out] amountp pointer to amount to set
- */
-#define TALER_PQ_RESULT_SPEC_AMOUNT(field,amountp) \
- TALER_PQ_result_spec_amount ( \
- field,pg->currency,amountp)
-
-
-/**
- * Type of the "cls" argument given to each of the functions in
- * our API.
- */
-struct PostgresClosure
-{
-
- /**
- * Postgres connection handle.
- */
- struct GNUNET_PQ_Context *conn;
-
- /**
- * Which currency do we deal in?
- */
- char *currency;
-
- /**
- * Directory with SQL statements to run to create tables.
- */
- char *sql_dir;
-
- /**
- * Underlying configuration.
- */
- const struct GNUNET_CONFIGURATION_Handle *cfg;
-
- /**
- * Name of the currently active transaction, NULL if none is active.
- */
- const char *transaction_name;
-
-};
-
-
-/**
* Drop all Taler tables. This should only be used by testcases.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
@@ -148,6 +188,7 @@ postgres_create_tables (void *cls)
GNUNET_PQ_make_try_execute ("SET search_path TO merchant;"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
+ enum GNUNET_GenericReturnValue ret;
conn = GNUNET_PQ_connect_with_cfg (pc->cfg,
"merchantdb-postgres",
@@ -156,8 +197,10 @@ postgres_create_tables (void *cls)
NULL);
if (NULL == conn)
return GNUNET_SYSERR;
+ ret = GNUNET_PQ_exec_sql (conn,
+ "procedures");
GNUNET_PQ_disconnect (conn);
- return GNUNET_OK;
+ return ret;
}
@@ -224,14 +267,7 @@ postgres_event_notify (void *cls,
}
-/**
- * Do a pre-flight check that we are not in an uncommitted transaction.
- * If we are, die.
- * Does not return anything, as we will continue regardless of the outcome.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- */
-static void
+void
postgres_preflight (void *cls)
{
struct PostgresClosure *pg = cls;
@@ -245,14 +281,7 @@ postgres_preflight (void *cls)
}
-/**
- * Check that the database connection is still up
- * and automatically reconnects unless we are
- * already inside of a transaction.
- *
- * @param pg connection to check
- */
-static void
+void
check_connection (struct PostgresClosure *pg)
{
if (NULL != pg->transaction_name)
@@ -262,10250 +291,15 @@ check_connection (struct PostgresClosure *pg)
/**
- * Start a transaction.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param name unique name identifying the transaction (for debugging),
- * must point to a constant
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-postgres_start (void *cls,
- const char *name)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- check_connection (pg);
- postgres_preflight (pg);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Starting merchant DB transaction `%s'\n",
- name);
- if (GNUNET_OK !=
- GNUNET_PQ_exec_statements (pg->conn,
- es))
- {
- TALER_LOG_ERROR ("Failed to start transaction\n");
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- pg->transaction_name = name;
- return GNUNET_OK;
-}
-
-
-/**
- * Start a transaction in 'read committed' mode.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param name unique name identifying the transaction (for debugging),
- * must point to a constant
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-postgres_start_read_committed (void *cls,
- const char *name)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL READ COMMITTED"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- check_connection (pg);
- postgres_preflight (pg);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Starting merchant DB transaction %s (READ COMMITTED)\n",
- name);
- if (GNUNET_OK !=
- GNUNET_PQ_exec_statements (pg->conn,
- es))
- {
- TALER_LOG_ERROR ("Failed to start transaction\n");
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- pg->transaction_name = name;
- return GNUNET_OK;
-}
-
-
-/**
- * Roll back the current transaction of a database connection.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- */
-static void
-postgres_rollback (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_execute ("ROLLBACK"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Rolling back merchant DB transaction `%s'\n",
- pg->transaction_name);
- GNUNET_break (GNUNET_OK ==
- GNUNET_PQ_exec_statements (pg->conn,
- es));
- pg->transaction_name = NULL;
-}
-
-
-/**
- * Commit the current transaction of a database connection.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_commit (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Committing merchant DB transaction %s\n",
- pg->transaction_name);
- pg->transaction_name = NULL;
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "end_transaction",
- params);
-}
-
-
-/**
- * Context for lookup_instances().
- */
-struct LookupInstancesContext
-{
- /**
- * Function to call with the results.
- */
- TALER_MERCHANTDB_InstanceCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Database context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Instance settings, valid only during find_instances_cb().
- */
- struct TALER_MERCHANTDB_InstanceSettings is;
-
- /**
- * Instance authentication settings, valid only during find_instances_cb().
- */
- struct TALER_MERCHANTDB_InstanceAuthSettings ias;
-
- /**
- * Instance serial number, valid only during find_instances_cb().
- */
- uint64_t instance_serial;
-
- /**
- * Public key of the current instance, valid only during find_instances_cb().
- */
- struct TALER_MerchantPublicKeyP merchant_pub;
-
- /**
- * Set to the return value on errors.
- */
- enum GNUNET_DB_QueryStatus qs;
-
- /**
- * true if we only are interested in instances for which we have the private key.
- */
- bool active_only;
-};
-
-
-/**
- * We are processing an instances lookup and have the @a accounts.
- * Find the private key if possible, and invoke the callback.
- *
- * @param lic context we are handling
- * @param num_accounts length of @a accounts array
- * @param accounts information about accounts of the instance in @a lic
- */
-static void
-call_with_accounts (struct LookupInstancesContext *lic,
- unsigned int num_accounts,
- const struct TALER_MERCHANTDB_AccountDetails accounts[])
-{
- struct PostgresClosure *pg = lic->pg;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&lic->instance_serial),
- GNUNET_PQ_query_param_end
- };
- struct TALER_MerchantPrivateKeyP merchant_priv;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("merchant_priv",
- &merchant_priv),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_instance_private_key",
- params,
- rs);
- if (qs < 0)
- {
- GNUNET_break (0);
- lic->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- if ( (0 == qs) &&
- (lic->active_only) )
- return; /* skip, not interesting */
- lic->cb (lic->cb_cls,
- &lic->merchant_pub,
- (0 == qs) ? NULL : &merchant_priv,
- &lic->is,
- &lic->ias,
- num_accounts,
- accounts);
-}
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about accounts.
- *
- * @param cls of type `struct FindInstancesContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_accounts_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupInstancesContext *lic = cls;
- char *paytos[num_results];
- struct TALER_MERCHANTDB_AccountDetails accounts[num_results];
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- uint8_t active;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("h_wire",
- &accounts[i].h_wire),
- GNUNET_PQ_result_spec_auto_from_type ("salt",
- &accounts[i].salt),
- GNUNET_PQ_result_spec_string ("payto_uri",
- &paytos[i]),
- GNUNET_PQ_result_spec_auto_from_type ("active",
- &active),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- lic->qs = GNUNET_DB_STATUS_HARD_ERROR;
- for (unsigned int j = 0; j < i; j++)
- GNUNET_free (paytos[j]);
- return;
- }
- accounts[i].active = (0 != active);
- accounts[i].payto_uri = paytos[i];
- }
- call_with_accounts (lic,
- num_results,
- accounts);
- for (unsigned int i = 0; i < num_results; i++)
- GNUNET_free (paytos[i]);
-}
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about instances.
- *
- * @param cls of type `struct FindInstancesContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_instances_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupInstancesContext *lic = cls;
- struct PostgresClosure *pg = lic->pg;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- bool no_auth;
- bool no_salt;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("merchant_serial",
- &lic->instance_serial),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
- &lic->merchant_pub),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_auto_from_type ("auth_hash",
- &lic->ias.auth_hash),
- &no_auth),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_auto_from_type ("auth_salt",
- &lic->ias.auth_salt),
- &no_salt),
- GNUNET_PQ_result_spec_string ("merchant_id",
- &lic->is.id),
- GNUNET_PQ_result_spec_string ("merchant_name",
- &lic->is.name),
- TALER_PQ_result_spec_json ("address",
- &lic->is.address),
- TALER_PQ_result_spec_json ("jurisdiction",
- &lic->is.jurisdiction),
- TALER_PQ_RESULT_SPEC_AMOUNT ("default_max_deposit_fee",
- &lic->is.default_max_deposit_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("default_max_wire_fee",
- &lic->is.default_max_wire_fee),
- GNUNET_PQ_result_spec_uint32 ("default_wire_fee_amortization",
- &lic->is.default_wire_fee_amortization),
- GNUNET_PQ_result_spec_relative_time ("default_wire_transfer_delay",
- &lic->is.default_wire_transfer_delay),
- GNUNET_PQ_result_spec_relative_time ("default_pay_delay",
- &lic->is.default_pay_delay),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_string ("website",
- &lic->is.website),
- NULL),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_string ("email",
- &lic->is.email),
- NULL),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_string ("logo",
- &lic->is.logo),
- NULL),
- GNUNET_PQ_result_spec_end
- };
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&lic->instance_serial),
- GNUNET_PQ_query_param_end
- };
-
- memset (&lic->ias.auth_salt,
- 0,
- sizeof (lic->ias.auth_salt));
- memset (&lic->ias.auth_hash,
- 0,
- sizeof (lic->ias.auth_hash));
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- lic->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- lic->qs = GNUNET_PQ_eval_prepared_multi_select (lic->pg->conn,
- "lookup_accounts",
- params,
- &lookup_accounts_cb,
- lic);
- if (0 > lic->qs)
- {
- /* lookup_accounts_cb() did not run, still notify about the
- account-less instance! */
- call_with_accounts (lic,
- 0,
- NULL);
- }
- GNUNET_PQ_cleanup_result (rs);
- if (0 > lic->qs)
- break;
- }
-}
-
-
-/**
- * Lookup all of the instances this backend has configured.
- *
- * @param cls closure
- * @param active_only only find 'active' instances
- * @param cb function to call on all instances found
- * @param cb_cls closure for @a cb
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_instances (void *cls,
- bool active_only,
- TALER_MERCHANTDB_InstanceCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupInstancesContext lic = {
- .cb = cb,
- .cb_cls = cb_cls,
- .active_only = active_only,
- .pg = pg
- };
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_instances",
- params,
- &lookup_instances_cb,
- &lic);
- if (0 > lic.qs)
- return lic.qs;
- return qs;
-}
-
-
-/**
- * Lookup all one of the instances this backend has configured.
- *
- * @param cls closure
- * @param id instance ID to resolve
- * @param active_only only find 'active' instances
- * @param cb function to call on all instances found
- * @param cb_cls closure for @a cb
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_instance (void *cls,
- const char *id,
- bool active_only,
- TALER_MERCHANTDB_InstanceCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupInstancesContext lic = {
- .cb = cb,
- .cb_cls = cb_cls,
- .active_only = active_only,
- .pg = pg
- };
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (id),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_instance",
- params,
- &lookup_instances_cb,
- &lic);
- if (0 > lic.qs)
- return lic.qs;
- return qs;
-}
-
-
-/**
- * Lookup authentication data of an instance.
- *
- * @param cls closure
- * @param instance_id instance to query
- * @param[out] ias where to store the auth data
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_instance_auth (
- void *cls,
- const char *instance_id,
- struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("auth_hash",
- &ias->auth_hash),
- GNUNET_PQ_result_spec_auto_from_type ("auth_salt",
- &ias->auth_salt),
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_instance_auth",
- params,
- rs);
-}
-
-
-/**
- * Insert information about an instance into our database.
- *
- * @param cls closure
- * @param merchant_pub public key of the instance
- * @param merchant_priv private key of the instance
- * @param is details about the instance
- * @param ias authentication settings for the instance
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_instance (
- void *cls,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_MerchantPrivateKeyP *merchant_priv,
- const struct TALER_MERCHANTDB_InstanceSettings *is,
- const struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (merchant_pub),
- GNUNET_PQ_query_param_auto_from_type (&ias->auth_hash),
- GNUNET_PQ_query_param_auto_from_type (&ias->auth_salt),
- GNUNET_PQ_query_param_string (is->id),
- GNUNET_PQ_query_param_string (is->name),
- TALER_PQ_query_param_json (is->address),
- TALER_PQ_query_param_json (is->jurisdiction),
- TALER_PQ_query_param_amount (&is->default_max_deposit_fee),
- TALER_PQ_query_param_amount (&is->default_max_wire_fee),
- GNUNET_PQ_query_param_uint32 (&is->default_wire_fee_amortization),
- GNUNET_PQ_query_param_relative_time (
- &is->default_wire_transfer_delay),
- GNUNET_PQ_query_param_relative_time (&is->default_pay_delay),
- (NULL == is->website)
- ? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_string (is->website),
- (NULL == is->email)
- ? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_string (is->email),
- (NULL == is->logo)
- ? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_string (is->logo),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_QueryParam params_priv[] = {
- GNUNET_PQ_query_param_auto_from_type (merchant_priv),
- GNUNET_PQ_query_param_string (is->id),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_instance",
- params);
- if (qs <= 0)
- return qs;
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_keys",
- params_priv);
-}
-
-
-/**
- * Insert information about an instance's account into our database.
- *
- * @param cls closure
- * @param id identifier of the instance
- * @param account_details details about the account
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_account (
- void *cls,
- const char *id,
- const struct TALER_MERCHANTDB_AccountDetails *account_details)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (id),
- GNUNET_PQ_query_param_auto_from_type (&account_details->h_wire),
- GNUNET_PQ_query_param_auto_from_type (&account_details->salt),
- GNUNET_PQ_query_param_string (account_details->payto_uri),
- GNUNET_PQ_query_param_bool (account_details->active),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_account",
- params);
-}
-
-
-/**
- * Closure for kyc_status_cb().
- */
-struct KycStatusContext
-{
- /**
- * Function to call with results.
- */
- TALER_MERCHANTDB_KycCallback kyc_cb;
-
- /**
- * Closure for @e kyc_cb.
- */
- void *kyc_cb_cls;
-
- /**
- * Filter, NULL to not filter.
- */
- const struct TALER_MerchantWireHashP *h_wire;
-
- /**
- * Filter, NULL to not filter.
- */
- const char *exchange_url;
-
- /**
- * Number of results found.
- */
- unsigned int count;
-
- /**
- * Set to true on failure(s).
- */
- bool failure;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about accounts.
- *
- * @param[in,out] cls of type `struct KycStatusContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-kyc_status_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct KycStatusContext *ksc = cls;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct TALER_MerchantWireHashP h_wire;
- uint64_t kyc_serial;
- char *exchange_url;
- char *payto_uri;
- struct GNUNET_TIME_Timestamp last_check;
- uint8_t kyc_ok;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("h_wire",
- &h_wire),
- GNUNET_PQ_result_spec_uint64 ("exchange_kyc_serial",
- &kyc_serial),
- GNUNET_PQ_result_spec_string ("payto_uri",
- &payto_uri),
- GNUNET_PQ_result_spec_string ("exchange_url",
- &exchange_url),
- GNUNET_PQ_result_spec_timestamp ("kyc_timestamp",
- &last_check),
- GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
- &kyc_ok),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ksc->failure = true;
- return;
- }
- if ( (NULL != ksc->exchange_url) &&
- (0 != strcmp (ksc->exchange_url,
- exchange_url)) )
- {
- GNUNET_PQ_cleanup_result (rs);
- continue;
- }
- if ( (NULL != ksc->h_wire) &&
- (0 != GNUNET_memcmp (ksc->h_wire,
- &h_wire)) )
- {
- GNUNET_PQ_cleanup_result (rs);
- continue;
- }
- ksc->count++;
- ksc->kyc_cb (ksc->kyc_cb_cls,
- &h_wire,
- kyc_serial,
- payto_uri,
- exchange_url,
- last_check,
- 0 != kyc_ok);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Check an instance's account's KYC status.
- *
- * @param cls closure
- * @param merchant_id merchant backend instance ID
- * @param h_wire hash of the wire account to check,
- * NULL to check all accounts of the merchant
- * @param exchange_url base URL of the exchange to check,
- * NULL to check all exchanges
- * @param kyc_cb KYC status callback to invoke
- * @param kyc_cb_cls closure for @a kyc_cb
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_account_kyc_get_status (void *cls,
- const char *merchant_id,
- const struct TALER_MerchantWireHashP *h_wire,
- const char *exchange_url,
- TALER_MERCHANTDB_KycCallback kyc_cb,
- void *kyc_cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct KycStatusContext ksc = {
- .kyc_cb = kyc_cb,
- .kyc_cb_cls = kyc_cb_cls,
- .exchange_url = exchange_url,
- .h_wire = h_wire
- };
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (merchant_id),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_kyc_status",
- params,
- &kyc_status_cb,
- &ksc);
- if (ksc.failure)
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (0 > qs)
- return qs;
- return ksc.count;
-}
-
-
-/**
- * Update an instance's account's KYC status.
- *
- * @param cls closure
- * @param merchant_id merchant backend instance ID
- * @param h_wire hash of the wire account to check
- * @param exchange_url base URL of the exchange to check
- * @param exchange_kyc_serial serial number for our account at the exchange (0 if unknown)
- * @param exchange_sig signature of the exchange, or NULL for none
- * @param exchange_pub public key of the exchange, or NULL for none
- * @param timestamp timestamp to store
- * @param kyc_ok current KYC status (true for satisfied)
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_account_kyc_set_status (
- void *cls,
- const char *merchant_id,
- const struct TALER_MerchantWireHashP *h_wire,
- const char *exchange_url,
- uint64_t exchange_kyc_serial,
- const struct TALER_ExchangeSignatureP *exchange_sig,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- struct GNUNET_TIME_Timestamp timestamp,
- bool kyc_ok)
-{
- struct PostgresClosure *pg = cls;
- uint8_t ok = kyc_ok;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (merchant_id),
- GNUNET_PQ_query_param_auto_from_type (h_wire),
- GNUNET_PQ_query_param_string (exchange_url),
- GNUNET_PQ_query_param_uint64 (&exchange_kyc_serial),
- GNUNET_PQ_query_param_timestamp (&timestamp),
- GNUNET_PQ_query_param_auto_from_type (&ok),
- exchange_pub
- ? GNUNET_PQ_query_param_auto_from_type (exchange_pub)
- : GNUNET_PQ_query_param_null (),
- exchange_sig
- ? GNUNET_PQ_query_param_auto_from_type (exchange_sig)
- : GNUNET_PQ_query_param_null (),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "upsert_account_kyc",
- params);
-}
-
-
-/**
- * Delete private key of an instance from our database.
- *
- * @param cls closure
- * @param merchant_id identifier of the instance
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_delete_instance_private_key (
- void *cls,
- const char *merchant_id)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (merchant_id),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_key",
- params);
-}
-
-
-/**
- * Purge an instance and all associated information from our database.
- * Highly likely to cause undesired data loss. Use with caution.
- *
- * @param cls closure
- * @param merchant_id identifier of the instance
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_purge_instance (void *cls,
- const char *merchant_id)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (merchant_id),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "purge_instance",
- params);
-}
-
-
-/**
- * Update information about an instance into our database.
- *
- * @param cls closure
- * @param is details about the instance
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_instance (void *cls,
- const struct TALER_MERCHANTDB_InstanceSettings *is)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (is->id),
- GNUNET_PQ_query_param_string (is->name),
- TALER_PQ_query_param_json (is->address),
- TALER_PQ_query_param_json (is->jurisdiction),
- TALER_PQ_query_param_amount (&is->default_max_deposit_fee),
- TALER_PQ_query_param_amount (&is->default_max_wire_fee),
- GNUNET_PQ_query_param_uint32 (&is->default_wire_fee_amortization),
- GNUNET_PQ_query_param_relative_time (
- &is->default_wire_transfer_delay),
- GNUNET_PQ_query_param_relative_time (&is->default_pay_delay),
- (NULL == is->website)
- ? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_string (is->website),
- (NULL == is->email)
- ? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_string (is->email),
- (NULL == is->logo)
- ? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_string (is->logo),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_instance",
- params);
-}
-
-
-/**
- * Update information about an instance's authentication settings
- * into our database.
- *
- * @param cls closure
- * @param merchant_id identity of the instance
- * @param is authentication details about the instance
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_instance_auth (
- void *cls,
- const char *merchant_id,
- const struct TALER_MERCHANTDB_InstanceAuthSettings *is)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (merchant_id),
- GNUNET_PQ_query_param_auto_from_type (&is->auth_hash),
- GNUNET_PQ_query_param_auto_from_type (&is->auth_salt),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_instance_auth",
- params);
-}
-
-
-/**
- * Set an instance's account in our database to "inactive".
- *
- * @param cls closure
- * @param merchant_id merchant backend instance ID
- * @param h_wire hash of the wire account to set to inactive
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_inactivate_account (void *cls,
- const char *merchant_id,
- const struct TALER_MerchantWireHashP *h_wire)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (merchant_id),
- GNUNET_PQ_query_param_auto_from_type (h_wire),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "inactivate_account",
- params);
-}
-
-
-/**
- * Set an instance's account in our database to "active".
- *
- * @param cls closure
- * @param merchant_id merchant backend instance ID
- * @param h_wire hash of the wire account to set to active
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_activate_account (void *cls,
- const char *merchant_id,
- const struct TALER_MerchantWireHashP *h_wire)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (merchant_id),
- GNUNET_PQ_query_param_auto_from_type (h_wire),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "activate_account",
- params);
-}
-
-
-/**
- * Context used for postgres_lookup_products().
- */
-struct LookupProductsContext
-{
- /**
- * Function to call with the results.
- */
- TALER_MERCHANTDB_ProductsCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Did database result extraction fail?
- */
- bool extract_failed;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about products.
- *
- * @param[in,out] cls of type `struct LookupProductsContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_products_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupProductsContext *plc = cls;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- char *product_id;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("product_id",
- &product_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- plc->extract_failed = true;
- return;
- }
- plc->cb (plc->cb_cls,
- product_id);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Lookup all of the products the given instance has configured.
- *
- * @param cls closure
- * @param instance_id instance to lookup products for
- * @param cb function to call on all products found
- * @param cb_cls closure for @a cb
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_products (void *cls,
- const char *instance_id,
- TALER_MERCHANTDB_ProductsCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupProductsContext plc = {
- .cb = cb,
- .cb_cls = cb_cls,
- /* Can be overwritten by the lookup_products_cb */
- .extract_failed = false,
- };
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_products",
- params,
- &lookup_products_cb,
- &plc);
- /* If there was an error inside lookup_products_cb, return a hard error. */
- if (plc.extract_failed)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Lookup details about a particular product.
- *
- * @param cls closure
- * @param instance_id instance to lookup products for
- * @param product_id product to lookup
- * @param[out] pd set to the product details on success, can be NULL
- * (in that case we only want to check if the product exists)
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_product (void *cls,
- const char *instance_id,
- const char *product_id,
- struct TALER_MERCHANTDB_ProductDetails *pd)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (product_id),
- GNUNET_PQ_query_param_end
- };
-
- if (NULL == pd)
- {
- struct GNUNET_PQ_ResultSpec rs_null[] = {
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_product",
- params,
- rs_null);
- }
- else
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("description",
- &pd->description),
- TALER_PQ_result_spec_json ("description_i18n",
- &pd->description_i18n),
- GNUNET_PQ_result_spec_string ("unit",
- &pd->unit),
- TALER_PQ_RESULT_SPEC_AMOUNT ("price",
- &pd->price),
- TALER_PQ_result_spec_json ("taxes",
- &pd->taxes),
- GNUNET_PQ_result_spec_uint64 ("total_stock",
- &pd->total_stock),
- GNUNET_PQ_result_spec_uint64 ("total_sold",
- &pd->total_sold),
- GNUNET_PQ_result_spec_uint64 ("total_lost",
- &pd->total_lost),
- GNUNET_PQ_result_spec_string ("image",
- &pd->image),
- TALER_PQ_result_spec_json ("address",
- &pd->address),
- GNUNET_PQ_result_spec_timestamp ("next_restock",
- &pd->next_restock),
- GNUNET_PQ_result_spec_uint32 ("minimum_age",
- &pd->minimum_age),
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_product",
- params,
- rs);
- }
-}
-
-
-/**
- * Delete information about a product. Note that the transaction must
- * enforce that no stocks are currently locked.
- *
- * @param cls closure
- * @param instance_id instance to delete product of
- * @param product_id product to delete
- * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
- * if locks prevent deletion OR product unknown
- */
-static enum GNUNET_DB_QueryStatus
-postgres_delete_product (void *cls,
- const char *instance_id,
- const char *product_id)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (product_id),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_product",
- params);
-}
-
-
-/**
- * Insert details about a particular product.
- *
- * @param cls closure
- * @param instance_id instance to insert product for
- * @param product_id product identifier of product to insert
- * @param pd the product details to insert
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_product (void *cls,
- const char *instance_id,
- const char *product_id,
- const struct TALER_MERCHANTDB_ProductDetails *pd)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (product_id),
- GNUNET_PQ_query_param_string (pd->description),
- TALER_PQ_query_param_json (pd->description_i18n),
- GNUNET_PQ_query_param_string (pd->unit),
- GNUNET_PQ_query_param_string (pd->image),
- TALER_PQ_query_param_json (pd->taxes),
- TALER_PQ_query_param_amount (&pd->price),
- GNUNET_PQ_query_param_uint64 (&pd->total_stock),
- TALER_PQ_query_param_json (pd->address),
- GNUNET_PQ_query_param_timestamp (&pd->next_restock),
- GNUNET_PQ_query_param_uint32 (&pd->minimum_age),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_product",
- params);
-}
-
-
-/**
- * Update details about a particular product. Note that the
- * transaction must enforce that the sold/stocked/lost counters
- * are not reduced (i.e. by expanding the WHERE clause on the existing
- * values).
- *
- * @param cls closure
- * @param instance_id instance to lookup products for
- * @param product_id product to lookup
- * @param[out] pd set to the product details on success, can be NULL
- * (in that case we only want to check if the product exists)
- * total_sold in @a pd is ignored, total_lost must not
- * exceed total_stock minus the existing total_sold;
- * total_sold and total_stock must be larger or equal to
- * the existing value;
- * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the
- * non-decreasing constraints are not met *or* if the product
- * does not yet exist.
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_product (void *cls,
- const char *instance_id,
- const char *product_id,
- const struct TALER_MERCHANTDB_ProductDetails *pd)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id), /* $1 */
- GNUNET_PQ_query_param_string (product_id),
- GNUNET_PQ_query_param_string (pd->description),
- TALER_PQ_query_param_json (pd->description_i18n),
- GNUNET_PQ_query_param_string (pd->unit),
- GNUNET_PQ_query_param_string (pd->image), /* $6 */
- TALER_PQ_query_param_json (pd->taxes),
- TALER_PQ_query_param_amount (&pd->price), /* $8+$9 */
- GNUNET_PQ_query_param_uint64 (&pd->total_stock), /* $10 */
- GNUNET_PQ_query_param_uint64 (&pd->total_lost),
- TALER_PQ_query_param_json (pd->address),
- GNUNET_PQ_query_param_timestamp (&pd->next_restock),
- GNUNET_PQ_query_param_uint32 (&pd->minimum_age),
- GNUNET_PQ_query_param_end
- };
-
- if ( (pd->total_stock < pd->total_lost + pd->total_sold) ||
- (pd->total_lost < pd->total_lost
- + pd->total_sold) /* integer overflow */)
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_product",
- params);
-}
-
-
-/**
- * Lock stocks of a particular product. Note that the transaction must
- * enforce that the "stocked-sold-lost >= locked" constraint holds.
- *
- * @param cls closure
- * @param instance_id instance to lookup products for
- * @param product_id product to lookup
- * @param uuid the UUID that holds the lock
- * @param quantity how many units should be locked
- * @param expiration_time when should the lock expire
- * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the
- * product is unknown OR if there insufficient stocks remaining
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lock_product (void *cls,
- const char *instance_id,
- const char *product_id,
- const struct GNUNET_Uuid *uuid,
- uint64_t quantity,
- struct GNUNET_TIME_Timestamp expiration_time)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (product_id),
- GNUNET_PQ_query_param_auto_from_type (uuid),
- GNUNET_PQ_query_param_uint64 (&quantity),
- GNUNET_PQ_query_param_timestamp (&expiration_time),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "lock_product",
- params);
-}
-
-
-/**
- * Release all expired product locks, including
- * those from expired offers -- across all
- * instances.
- *
- * @param cls closure
- */
-static void
-postgres_expire_locks (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs1;
- enum GNUNET_DB_QueryStatus qs2;
- enum GNUNET_DB_QueryStatus qs3;
-
- check_connection (pg);
- qs1 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "unlock_products",
- params);
- if (qs1 < 0)
- {
- GNUNET_break (0);
- return;
- }
- qs2 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "unlock_orders",
- params);
- if (qs2 < 0)
- {
- GNUNET_break (0);
- return;
- }
- qs3 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "unlock_contracts",
- params);
- if (qs3 < 0)
- {
- GNUNET_break (0);
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Released %d+%d+%d locks\n",
- qs1,
- qs2,
- qs3);
-}
-
-
-/**
- * Delete information about an order. Note that the transaction must
- * enforce that the order is not awaiting payment anymore.
- *
- * @param cls closure
- * @param instance_id instance to delete order of
- * @param order_id order to delete
- * @param force delete claimed but unpaid orders as well
- * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
- * if pending payment prevents deletion OR order unknown
- */
-static enum GNUNET_DB_QueryStatus
-postgres_delete_order (void *cls,
- const char *instance_id,
- const char *order_id,
- bool force)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (order_id),
- GNUNET_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_bool (force),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_QueryParam params2[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (order_id),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_order",
- params);
- if ( (qs <= 0) || (! force))
- return qs;
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_contract",
- params2);
-}
-
-
-/**
- * Retrieve order given its @a order_id and the @a instance_id.
- *
- * @param cls closure
- * @param instance_id instance to obtain order of
- * @param order_id order id used to perform the lookup
- * @param[out] claim_token the claim token generated for the order,
- * NULL to only test if the order exists
- * @param[out] h_post_data set to the hash of the POST data that created the order
- * @param[out] contract_terms where to store the retrieved contract terms,
- * NULL to only test if the order exists
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_order (void *cls,
- const char *instance_id,
- const char *order_id,
- struct TALER_ClaimTokenP *claim_token,
- struct TALER_MerchantPostDataHashP *h_post_data,
- json_t **contract_terms)
-{
- struct PostgresClosure *pg = cls;
- json_t *j;
- struct TALER_ClaimTokenP ct;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (order_id),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_result_spec_json ("contract_terms",
- &j),
- GNUNET_PQ_result_spec_auto_from_type ("claim_token",
- &ct),
- GNUNET_PQ_result_spec_auto_from_type ("h_post_data",
- h_post_data),
- GNUNET_PQ_result_spec_end
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Finding contract term, order_id: '%s', instance_id: '%s'.\n",
- order_id,
- instance_id);
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_order",
- params,
- rs);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- if (NULL != contract_terms)
- *contract_terms = j;
- else
- json_decref (j);
- if (NULL != claim_token)
- *claim_token = ct;
- }
- else
- {
- /* just to be safe: NULL it */
- if (NULL != contract_terms)
- *contract_terms = NULL;
- if (NULL != claim_token)
- *claim_token = (struct TALER_ClaimTokenP) { 0 }
- ;
- }
- return qs;
-}
-
-
-/**
- * Retrieve order summary given its @a order_id and the @a instance_id.
- *
- * @param cls closure
- * @param instance_id instance to obtain order of
- * @param order_id order id used to perform the lookup
- * @param[out] timestamp when was the order created
- * @param[out] order_serial under which serial do we keep this order
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_order_summary (void *cls,
- const char *instance_id,
- const char *order_id,
- struct GNUNET_TIME_Timestamp *timestamp,
- uint64_t *order_serial)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (order_id),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("order_serial",
- order_serial),
- GNUNET_PQ_result_spec_timestamp ("creation_time",
- timestamp),
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_order_summary",
- params,
- rs);
-}
-
-
-/**
- * Context used for postgres_lookup_orders().
- */
-struct LookupOrdersContext
-{
- /**
- * Function to call with the results.
- */
- TALER_MERCHANTDB_OrdersCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Did database result extraction fail?
- */
- bool extract_failed;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about orders.
- *
- * @param[in,out] cls of type `struct LookupOrdersContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_orders_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupOrdersContext *plc = cls;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- char *order_id;
- uint64_t order_serial;
- struct GNUNET_TIME_Timestamp ts;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("order_id",
- &order_id),
- GNUNET_PQ_result_spec_uint64 ("order_serial",
- &order_serial),
- GNUNET_PQ_result_spec_timestamp ("creation_time",
- &ts),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- plc->extract_failed = true;
- return;
- }
- plc->cb (plc->cb_cls,
- order_id,
- order_serial,
- ts);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Retrieve orders given the @a instance_id.
- *
- * @param cls closure
- * @param instance_id instance to obtain order of
- * @param of filter to apply when looking up orders
- * @param cb callback to pass all the orders that are found
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_orders (void *cls,
- const char *instance_id,
- const struct TALER_MERCHANTDB_OrderFilter *of,
- TALER_MERCHANTDB_OrdersCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupOrdersContext plc = {
- .cb = cb,
- .cb_cls = cb_cls
- };
- uint64_t limit = (of->delta > 0) ? of->delta : -of->delta;
- uint8_t paid;
- uint8_t refunded;
- uint8_t wired;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_uint64 (&limit),
- GNUNET_PQ_query_param_uint64 (&of->start_row),
- GNUNET_PQ_query_param_timestamp (&of->date),
- GNUNET_PQ_query_param_auto_from_type (&paid),
- GNUNET_PQ_query_param_auto_from_type (&refunded),
- GNUNET_PQ_query_param_auto_from_type (&wired),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
- char stmt[128];
-
- paid = (TALER_EXCHANGE_YNA_YES == of->paid);
- refunded = (TALER_EXCHANGE_YNA_YES == of->refunded);
- wired = (TALER_EXCHANGE_YNA_YES == of->wired);
- /* painfully many cases..., note that "_xxx" being present in 'stmt' merely
- means that we filter by that variable, the value we filter for is
- computed above */
- GNUNET_snprintf (stmt,
- sizeof (stmt),
- "lookup_orders_%s%s%s%s",
- (of->delta > 0) ? "inc" : "dec",
- (TALER_EXCHANGE_YNA_ALL == of->paid) ? "" : "_paid",
- (TALER_EXCHANGE_YNA_ALL == of->refunded) ? "" :
- "_refunded",
- (TALER_EXCHANGE_YNA_ALL == of->wired) ? "" : "_wired");
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- stmt,
- params,
- &lookup_orders_cb,
- &plc);
- if (plc.extract_failed)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Insert order into the DB.
- *
- * @param cls closure
- * @param instance_id identifies the instance responsible for the order
- * @param order_id alphanumeric string that uniquely identifies the proposal
- * @param h_post_data hash of the POST data for idempotency checks
- * @param pay_deadline how long does the customer have to pay for the order
- * @param claim_token token to use for access control
- * @param contract_terms proposal data to store
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_order (void *cls,
- const char *instance_id,
- const char *order_id,
- const struct TALER_MerchantPostDataHashP *h_post_data,
- struct GNUNET_TIME_Timestamp pay_deadline,
- const struct TALER_ClaimTokenP *claim_token,
- const json_t *contract_terms)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_TIME_Timestamp now;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (order_id),
- GNUNET_PQ_query_param_timestamp (&pay_deadline),
- GNUNET_PQ_query_param_auto_from_type (claim_token),
- GNUNET_PQ_query_param_auto_from_type (h_post_data),
- GNUNET_PQ_query_param_timestamp (&now),
- TALER_PQ_query_param_json (contract_terms),
- GNUNET_PQ_query_param_end
- };
-
- now = GNUNET_TIME_timestamp_get ();
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "inserting order: order_id: %s, instance_id: %s.\n",
- order_id,
- instance_id);
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_order",
- params);
-}
-
-
-/**
- * Release an inventory lock by UUID. Releases ALL stocks locked under
- * the given UUID.
- *
- * @param cls closure
- * @param uuid the UUID to release locks for
- * @return transaction status,
- * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are no locks under @a uuid
- * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
- */
-static enum GNUNET_DB_QueryStatus
-postgres_unlock_inventory (void *cls,
- const struct GNUNET_Uuid *uuid)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (uuid),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "unlock_inventory",
- params);
-}
-
-
-/**
- * Lock inventory stock to a particular order.
- *
- * @param cls closure
- * @param instance_id identifies the instance responsible for the order
- * @param order_id alphanumeric string that uniquely identifies the order
- * @param product_id uniquely identifies the product to be locked
- * @param quantity how many units should be locked to the @a order_id
- * @return transaction status,
- * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are insufficient stocks
- * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_order_lock (void *cls,
- const char *instance_id,
- const char *order_id,
- const char *product_id,
- uint64_t quantity)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (order_id),
- GNUNET_PQ_query_param_string (product_id),
- GNUNET_PQ_query_param_uint64 (&quantity),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_order_lock",
- params);
-}
-
-
-/**
- * Retrieve contract terms given its @a order_id
- *
- * @param cls closure
- * @param instance_id instance's identifier
- * @param order_id order_id used to lookup.
- * @param[out] contract_terms where to store the result, NULL to only check for existence
- * @param[out] order_serial set to the order's serial number
- * @param[out] paid set to true if the order is fully paid
- * @param[out] claim_token set to the claim token, NULL to only check for existence
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_contract_terms (void *cls,
- const char *instance_id,
- const char *order_id,
- json_t **contract_terms,
- uint64_t *order_serial,
- bool *paid,
- struct TALER_ClaimTokenP *claim_token)
-{
- struct PostgresClosure *pg = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_ClaimTokenP ct;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (order_id),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- /* contract_terms must be first! */
- TALER_PQ_result_spec_json ("contract_terms",
- contract_terms),
- GNUNET_PQ_result_spec_uint64 ("order_serial",
- order_serial),
- GNUNET_PQ_result_spec_bool ("paid",
- paid),
- GNUNET_PQ_result_spec_auto_from_type ("claim_token",
- &ct),
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_contract_terms",
- params,
- (NULL != contract_terms)
- ? rs
- : &rs[1]);
- if (NULL != claim_token)
- *claim_token = ct;
- return qs;
-}
-
-
-/**
- * Store contract terms given its @a order_id. Note that some attributes are
- * expected to be calculated inside of the function, like the hash of the
- * contract terms (to be hashed), the creation_time and pay_deadline (to be
- * obtained from the merchant_orders table). The "session_id" should be
- * initially set to the empty string. The "fulfillment_url" and "refund_deadline"
- * must be extracted from @a contract_terms.
- *
- * @param cls closure
- * @param instance_id instance's identifier
- * @param order_id order_id used to store
- * @param contract_terms contract terms to store
- * @param[out] order_serial set to the serial of the order
- * @return transaction status, #GNUNET_DB_STATUS_HARD_ERROR if @a contract_terms
- * is malformed
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_contract_terms (void *cls,
- const char *instance_id,
- const char *order_id,
- json_t *contract_terms,
- uint64_t *order_serial)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_TIME_Timestamp pay_deadline;
- struct GNUNET_TIME_Timestamp refund_deadline;
- const char *fulfillment_url;
- struct TALER_PrivateContractHashP h_contract_terms;
-
- if (GNUNET_OK !=
- TALER_JSON_contract_hash (contract_terms,
- &h_contract_terms))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_timestamp ("pay_deadline",
- &pay_deadline),
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &refund_deadline),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (NULL,
- contract_terms,
- spec);
- if (GNUNET_OK != res)
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
-
- fulfillment_url =
- json_string_value (json_object_get (contract_terms,
- "fulfillment_url"));
- check_connection (pg);
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (order_id),
- TALER_PQ_query_param_json (contract_terms),
- GNUNET_PQ_query_param_auto_from_type (&h_contract_terms),
- GNUNET_PQ_query_param_timestamp (&pay_deadline),
- GNUNET_PQ_query_param_timestamp (&refund_deadline),
- (NULL == fulfillment_url)
- ? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_string (fulfillment_url),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("order_serial",
- order_serial),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "insert_contract_terms",
- params,
- rs);
- }
-}
-
-
-/**
- * Update the contract terms stored for @a order_id. Note that some attributes are
- * expected to be calculated inside of the function, like the hash of the
- * contract terms (to be hashed), the creation_time and pay_deadline (to be
- * obtained from the merchant_orders table). The "session_id" should be
- * initially set to the empty string. The "fulfillment_url" and "refund_deadline"
- * must be extracted from @a contract_terms.
- *
- * @param cls closure
- * @param instance_id instance's identifier
- * @param order_id order_id used to store
- * @param contract_terms contract to store
- * @return transaction status, #GNUNET_DB_STATUS_HARD_ERROR if @a contract_terms
- * is malformed
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_contract_terms (void *cls,
- const char *instance_id,
- const char *order_id,
- json_t *contract_terms)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_TIME_Timestamp pay_deadline;
- struct GNUNET_TIME_Timestamp refund_deadline;
- const char *fulfillment_url = NULL;
- struct TALER_PrivateContractHashP h_contract_terms;
-
- if (GNUNET_OK !=
- TALER_JSON_contract_hash (contract_terms,
- &h_contract_terms))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_timestamp ("pay_deadline",
- &pay_deadline),
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &refund_deadline),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("fulfillment_url",
- &fulfillment_url),
- NULL),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (NULL,
- contract_terms,
- spec);
- if (GNUNET_OK != res)
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
-
- check_connection (pg);
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (order_id),
- TALER_PQ_query_param_json (contract_terms),
- GNUNET_PQ_query_param_auto_from_type (&h_contract_terms),
- GNUNET_PQ_query_param_timestamp (&pay_deadline),
- GNUNET_PQ_query_param_timestamp (&refund_deadline),
- (NULL == fulfillment_url)
- ? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_string (fulfillment_url),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_contract_terms",
- params);
- }
-}
-
-
-/**
- * Delete information about a contract. Note that the transaction must
- * enforce that the contract is not awaiting payment anymore AND was not
- * paid, or is past the legal expiration.
- *
- * @param cls closure
- * @param instance_id instance to delete order of
- * @param order_id order to delete
- * @param legal_expiration how long do we need to keep (paid) contracts on
- * file for legal reasons (i.e. taxation)
- * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
- * if locks prevent deletion OR order unknown
- */
-static enum GNUNET_DB_QueryStatus
-postgres_delete_contract_terms (void *cls,
- const char *instance_id,
- const char *order_id,
- struct GNUNET_TIME_Relative legal_expiration)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (order_id),
- GNUNET_PQ_query_param_relative_time (&legal_expiration),
- GNUNET_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_contract_terms",
- params);
-}
-
-
-/**
- * Closure for #lookup_deposits_cb().
- */
-struct LookupDepositsContext
-{
- /**
- * Function to call with results.
- */
- TALER_MERCHANTDB_DepositsCallback cb;
-
- /**
- * Closure for @e cls.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Transaction status (set).
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param[in,out] cls of type `struct LookupDepositsContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_deposits_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupDepositsContext *ldc = cls;
- struct PostgresClosure *pg = ldc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_Amount amount_with_fee;
- struct TALER_Amount deposit_fee;
- struct TALER_Amount refund_fee;
- struct TALER_Amount wire_fee;
- char *exchange_url;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("exchange_url",
- &exchange_url),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &coin_pub),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee",
- &deposit_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee",
- &refund_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
- &wire_fee),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ldc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- ldc->cb (ldc->cb_cls,
- exchange_url,
- &coin_pub,
- &amount_with_fee,
- &deposit_fee,
- &refund_fee,
- &wire_fee);
- GNUNET_PQ_cleanup_result (rs);
- }
- ldc->qs = num_results;
-}
-
-
-/**
- * Lookup information about coins that were successfully deposited for a
- * given contract.
- *
- * @param cls closure
- * @param instance_id instance to lookup deposits for
- * @param h_contract_terms proposal data's hashcode
- * @param cb function to call with payment data
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_deposits (
- void *cls,
- const char *instance_id,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- TALER_MERCHANTDB_DepositsCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
- GNUNET_PQ_query_param_end
- };
- struct LookupDepositsContext ldc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg
- };
- enum GNUNET_DB_QueryStatus qs;
-
- /* no preflight check here, run in its own transaction by the caller! */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Finding deposits for h_contract_terms '%s'\n",
- GNUNET_h2s (&h_contract_terms->hash));
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_deposits",
- params,
- &lookup_deposits_cb,
- &ldc);
- if (qs <= 0)
- return qs;
- return ldc.qs;
-}
-
-
-/**
- * Insert an exchange signing key into our database.
- *
- * @param cls closure
- * @param master_pub exchange master public key used for @a master_sig
- * @param exchange_pub exchange signing key to insert
- * @param start_date when does the signing key become valid
- * @param expire_date when does the signing key stop being used
- * @param end_date when does the signing key become void as proof
- * @param master_sig signature of @a master_pub over the @a exchange_pub and the dates
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_exchange_signkey (
- void *cls,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- struct GNUNET_TIME_Timestamp start_date,
- struct GNUNET_TIME_Timestamp expire_date,
- struct GNUNET_TIME_Timestamp end_date,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_auto_from_type (exchange_pub),
- GNUNET_PQ_query_param_timestamp (&start_date),
- GNUNET_PQ_query_param_timestamp (&expire_date),
- GNUNET_PQ_query_param_timestamp (&end_date),
- GNUNET_PQ_query_param_auto_from_type (master_sig),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- postgres_preflight (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_exchange_signkey",
- params);
-
-}
-
-
-/**
- * Insert payment confirmation from the exchange into the database.
- *
- * @param cls closure
- * @param instance_id instance to lookup deposits for
- * @param deposit_timestamp time when the exchange generated the deposit confirmation
- * @param h_contract_terms proposal data's hashcode
- * @param coin_pub public key of the coin
- * @param exchange_url URL of the exchange that issued @a coin_pub
- * @param amount_with_fee amount the exchange will deposit for this coin
- * @param deposit_fee fee the exchange will charge for this coin
- * @param wire_fee wire fee the exchange charges
- * @param refund_fee fee the exchange charges to refund this coin
- * @param h_wire hash of the wire details of the target account of the merchant
- * @param exchange_sig signature from exchange that coin was accepted
- * @param exchange_pub signgin key that was used for @a exchange_sig
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_deposit (
- void *cls,
- const char *instance_id,
- struct GNUNET_TIME_Timestamp deposit_timestamp,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const char *exchange_url,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct TALER_Amount *refund_fee,
- const struct TALER_Amount *wire_fee,
- const struct TALER_MerchantWireHashP *h_wire,
- const struct TALER_ExchangeSignatureP *exchange_sig,
- const struct TALER_ExchangePublicKeyP *exchange_pub)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
- GNUNET_PQ_query_param_timestamp (&deposit_timestamp), /* $3 */
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_string (exchange_url),
- TALER_PQ_query_param_amount (amount_with_fee), /* $6/$7 */
- TALER_PQ_query_param_amount (deposit_fee), /* $8, $9 */
- TALER_PQ_query_param_amount (refund_fee), /* $10, $11 */
- TALER_PQ_query_param_amount (wire_fee), /* $12, $13 */
- GNUNET_PQ_query_param_auto_from_type (h_wire), /* $14 */
- GNUNET_PQ_query_param_auto_from_type (exchange_sig), /* $15 */
- GNUNET_PQ_query_param_auto_from_type (exchange_pub), /* $16 */
- GNUNET_PQ_query_param_end
- };
-
- /* no preflight check here, run in transaction by caller! */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Storing deposit for instance `%s' h_contract_terms `%s', coin_pub: `%s', amount_with_fee: %s\n",
- instance_id,
- GNUNET_h2s (&h_contract_terms->hash),
- TALER_B2S (coin_pub),
- TALER_amount2s (amount_with_fee));
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_deposit",
- params);
-
-}
-
-
-/**
- * Closure for #lookup_refunds_cb().
- */
-struct LookupRefundsContext
-{
- /**
- * Function to call for each refund.
- */
- TALER_MERCHANTDB_RefundCallback rc;
-
- /**
- * Closure for @e rc.
- */
- void *rc_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Transaction result.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls of type `struct LookupRefundsContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_refunds_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRefundsContext *lrc = cls;
- struct PostgresClosure *pg = lrc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_Amount refund_amount;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &coin_pub),
- TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount",
- &refund_amount),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- lrc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- lrc->rc (lrc->rc_cls,
- &coin_pub,
- &refund_amount);
- GNUNET_PQ_cleanup_result (rs); /* technically useless here */
- }
- lrc->qs = num_results;
-}
-
-
-/**
- * Obtain refunds associated with a contract.
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id instance to lookup refunds for
- * @param h_contract_terms hash code of the contract
- * @param rc function to call for each coin on which there is a refund
- * @param rc_cls closure for @a rc
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_refunds (
- void *cls,
- const char *instance_id,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- TALER_MERCHANTDB_RefundCallback rc,
- void *rc_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
- GNUNET_PQ_query_param_end
- };
- struct LookupRefundsContext lrc = {
- .rc = rc,
- .rc_cls = rc_cls,
- .pg = pg
- };
- enum GNUNET_DB_QueryStatus qs;
-
- /* no preflight check here, run in transaction by caller! */
- TALER_LOG_DEBUG ("Looking for refund of h_contract_terms %s at `%s'\n",
- GNUNET_h2s (&h_contract_terms->hash),
- instance_id);
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_refunds",
- params,
- &lookup_refunds_cb,
- &lrc);
- if (0 >= qs)
- return qs;
- return lrc.qs;
-}
-
-
-/**
- * Mark contract as paid and store the current @a session_id
- * for which the contract was paid. Deletes the underlying order
- * and marks the locked stocks of the order as sold.
- *
- * @param cls closure
- * @param instance_id instance to mark contract as paid for
- * @param h_contract_terms hash of the contract that is now paid
- * @param session_id the session that paid the contract
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_mark_contract_paid (
- void *cls,
- const char *instance_id,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- const char *session_id)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
- GNUNET_PQ_query_param_string (session_id),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_QueryParam uparams[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- /* Session ID must always be given by the caller. */
- GNUNET_assert (NULL != session_id);
-
- /* no preflight check here, run in transaction by caller! */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Marking h_contract_terms '%s' of %s as paid for session `%s'\n",
- GNUNET_h2s (&h_contract_terms->hash),
- instance_id,
- session_id);
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "mark_contract_paid",
- params);
- if (qs <= 0)
- return qs;
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "mark_inventory_sold",
- uparams);
- if (qs < 0)
- return qs; /* 0: no inventory management, that's OK! */
- /* ON DELETE CASCADE deletes from merchant_order_locks */
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_completed_order",
- uparams);
-}
-
-
-/**
- * Function called during aborts to refund a coin. Marks the
- * respective coin as refunded.
- *
- * @param cls closure
- * @param instance_id instance to refund payment for
- * @param h_contract_terms hash of the contract to refund coin for
- * @param refund_timestamp timestamp of the refund
- * @param coin_pub public key of the coin to refund (fully)
- * @param reason text justifying the refund
- * @return transaction status
- * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a coin_pub is unknown to us;
- * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid,
- * regardless of whether it actually increased the refund
- */
-static enum GNUNET_DB_QueryStatus
-postgres_refund_coin (void *cls,
- const char *instance_id,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- struct GNUNET_TIME_Timestamp refund_timestamp,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const char *reason)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
- GNUNET_PQ_query_param_timestamp (&refund_timestamp),
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_string (reason),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "refund_coin",
- params);
-}
-
-
-/**
- * Retrieve contract terms given its @a order_id
- *
- * @param cls closure
- * @param instance_id instance's identifier
- * @param order_id order to lookup contract for
- * @param[out] h_contract_terms set to the hash of the contract.
- * @param[out] paid set to the payment status of the contract
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_order_status (void *cls,
- const char *instance_id,
- const char *order_id,
- struct TALER_PrivateContractHashP *
- h_contract_terms,
- bool *paid)
-{
- struct PostgresClosure *pg = cls;
- uint8_t paid8;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (order_id),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- h_contract_terms),
- GNUNET_PQ_result_spec_auto_from_type ("paid",
- &paid8),
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_order_status",
- params,
- rs);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- *paid = (0 != paid8);
- else
- *paid = false; /* just to be safe(r) */
- return qs;
-}
-
-
-/**
- * Retrieve contract terms given its @a order_serial
- *
- * @param cls closure
- * @param instance_id instance's identifier
- * @param order_serial serial ID of the order to look up
- * @param[out] order_id set to ID of the order
- * @param[out] h_contract_terms set to the hash of the contract.
- * @param[out] paid set to the payment status of the contract
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_order_status_by_serial (void *cls,
- const char *instance_id,
- uint64_t order_serial,
- char **order_id,
- struct TALER_PrivateContractHashP *
- h_contract_terms,
- bool *paid)
-{
- struct PostgresClosure *pg = cls;
- uint8_t paid8;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_uint64 (&order_serial),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- h_contract_terms),
- GNUNET_PQ_result_spec_auto_from_type ("paid",
- &paid8),
- GNUNET_PQ_result_spec_string ("order_id",
- order_id),
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_order_status_by_serial",
- params,
- rs);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- *paid = (0 != paid8);
- else
- *paid = false; /* just to be safe(r) */
- return qs;
-}
-
-
-/**
- * Retrieve payment and wire status for a given @a order_serial and session ID.
- *
- * @param cls closure
- * @param order_serial identifies the order
- * @param session_id session for which to check the payment status, NULL for any
- * @param[out] paid set to the payment status of the contract
- * @param[out] wired set to the wire transfer status of the exchange payment
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_payment_status (void *cls,
- uint64_t order_serial,
- const char *session_id,
- bool *paid,
- bool *wired)
-{
- struct PostgresClosure *pg = cls;
- uint8_t paid8;
- uint8_t wired8;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("paid",
- &paid8),
- GNUNET_PQ_result_spec_auto_from_type ("wired",
- &wired8),
- GNUNET_PQ_result_spec_end
- };
- check_connection (pg);
- if (NULL == session_id)
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&order_serial),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_payment_status",
- params,
- rs);
- }
- else
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&order_serial),
- GNUNET_PQ_query_param_string (session_id),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_payment_status_session_id",
- params,
- rs);
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- *paid = (0 != paid8);
- *wired = (0 != wired8);
- }
- else
- {
- *paid = false; /* just to be safe(r) */
- *wired = false; /* just to be safe(r) */
- }
- return qs;
-}
-
-
-/**
- * Closure for lookup_deposits_by_order_cb().
- */
-struct LookupDepositsByOrderContext
-{
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Function to call with all results.
- */
- TALER_MERCHANTDB_DepositedCoinsCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Set to the query result.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls of type `struct LookupDepositsByOrderContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_deposits_by_order_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupDepositsByOrderContext *ldoc = cls;
- struct PostgresClosure *pg = ldoc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint64_t deposit_serial;
- char *exchange_url;
- struct TALER_MerchantWireHashP h_wire;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_Amount amount_with_fee;
- struct TALER_Amount deposit_fee;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("deposit_serial",
- &deposit_serial),
- GNUNET_PQ_result_spec_string ("exchange_url",
- &exchange_url),
- GNUNET_PQ_result_spec_auto_from_type ("h_wire",
- &h_wire),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee",
- &deposit_fee),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &coin_pub),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ldoc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- ldoc->cb (ldoc->cb_cls,
- deposit_serial,
- exchange_url,
- &h_wire,
- &amount_with_fee,
- &deposit_fee,
- &coin_pub);
- GNUNET_PQ_cleanup_result (rs); /* technically useless here */
- }
- ldoc->qs = num_results;
-}
-
-
-/**
- * Retrieve details about coins that were deposited for an order.
- *
- * @param cls closure
- * @param order_serial identifies the order
- * @param cb function to call for each deposited coin
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_deposits_by_order (void *cls,
- uint64_t order_serial,
- TALER_MERCHANTDB_DepositedCoinsCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupDepositsByOrderContext ldoc = {
- .pg = pg,
- .cb = cb,
- .cb_cls = cb_cls
- };
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&order_serial),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_deposits_by_order",
- params,
- &lookup_deposits_by_order_cb,
- &ldoc);
- if (qs < 0)
- return qs;
- return ldoc.qs;
-}
-
-
-/**
- * Closure for lookup_deposits_by_order_cb().
- */
-struct LookupTransferDetailsByOrderContext
-{
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Function to call with all results.
- */
- TALER_MERCHANTDB_OrderTransferDetailsCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Set to the query result.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls of type `struct LookupTransferDetailsByOrderContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_transfer_details_by_order_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupTransferDetailsByOrderContext *ltdo = cls;
- struct PostgresClosure *pg = ltdo->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_WireTransferIdentifierRawP wtid;
- char *exchange_url;
- uint64_t deposit_serial;
- struct GNUNET_TIME_Timestamp execution_time;
- struct TALER_Amount deposit_value;
- struct TALER_Amount deposit_fee;
- uint8_t transfer_confirmed;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("deposit_serial",
- &deposit_serial),
- GNUNET_PQ_result_spec_timestamp ("deposit_timestamp",
- &execution_time),
- GNUNET_PQ_result_spec_string ("exchange_url",
- &exchange_url),
- GNUNET_PQ_result_spec_auto_from_type ("wtid",
- &wtid),
- TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_value",
- &deposit_value),
- TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_fee",
- &deposit_fee),
- GNUNET_PQ_result_spec_auto_from_type ("transfer_confirmed",
- &transfer_confirmed),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ltdo->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- ltdo->cb (ltdo->cb_cls,
- &wtid,
- exchange_url,
- execution_time,
- &deposit_value,
- &deposit_fee,
- (0 != transfer_confirmed));
- GNUNET_PQ_cleanup_result (rs); /* technically useless here */
- }
- ltdo->qs = num_results;
-}
-
-
-/**
- * Retrieve wire transfer details for all deposits associated with
- * a given @a order_serial.
- *
- * @param cls closure
- * @param order_serial identifies the order
- * @param cb function called with the wire transfer details
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_transfer_details_by_order (
- void *cls,
- uint64_t order_serial,
- TALER_MERCHANTDB_OrderTransferDetailsCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupTransferDetailsByOrderContext ltdo = {
- .pg = pg,
- .cb = cb,
- .cb_cls = cb_cls
- };
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&order_serial),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (
- pg->conn,
- "lookup_transfer_details_by_order",
- params,
- &lookup_transfer_details_by_order_cb,
- &ltdo);
- if (qs < 0)
- return qs;
- return ltdo.qs;
-}
-
-
-/**
- * Insert wire transfer details for a deposit.
- *
- * @param cls closure
- * @param deposit_serial serial number of the deposit
- * @param dd deposit transfer data from the exchange to store
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_deposit_to_transfer (
- void *cls,
- uint64_t deposit_serial,
- const struct TALER_EXCHANGE_DepositData *dd)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&deposit_serial),
- TALER_PQ_query_param_amount (&dd->coin_contribution),
- GNUNET_PQ_query_param_timestamp (&dd->execution_time),
- GNUNET_PQ_query_param_auto_from_type (&dd->exchange_sig),
- GNUNET_PQ_query_param_auto_from_type (&dd->exchange_pub),
- GNUNET_PQ_query_param_auto_from_type (&dd->wtid),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_deposit_to_transfer",
- params);
-}
-
-
-/**
- * Set 'wired' status for an order to 'true'.
- *
- * @param cls closure
- * @param order_serial serial number of the order
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_mark_order_wired (void *cls,
- uint64_t order_serial)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&order_serial),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "mark_order_wired",
- params);
-}
-
-
-/**
- * Closure for #process_refund_cb().
- */
-struct FindRefundContext
-{
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Updated to reflect total amount refunded so far.
- */
- struct TALER_Amount refunded_amount;
-
- /**
- * Set to the largest refund transaction ID encountered.
- */
- uint64_t max_rtransaction_id;
-
- /**
- * Set to true on hard errors.
- */
- bool err;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure, our `struct FindRefundContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-process_refund_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct FindRefundContext *ictx = cls;
- struct PostgresClosure *pg = ictx->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- /* Sum up existing refunds */
- struct TALER_Amount acc;
- uint64_t rtransaction_id;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount",
- &acc),
- GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
- &rtransaction_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ictx->err = true;
- return;
- }
- if (0 >
- TALER_amount_add (&ictx->refunded_amount,
- &ictx->refunded_amount,
- &acc))
- {
- GNUNET_break (0);
- ictx->err = true;
- return;
- }
- ictx->max_rtransaction_id = GNUNET_MAX (ictx->max_rtransaction_id,
- rtransaction_id);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Found refund of %s\n",
- TALER_amount2s (&acc));
- }
-}
-
-
-/**
- * Closure for #process_deposits_for_refund_cb().
- */
-struct InsertRefundContext
-{
- /**
- * Used to provide a connection to the db
- */
- struct PostgresClosure *pg;
-
- /**
- * Amount to which increase the refund for this contract
- */
- const struct TALER_Amount *refund;
-
- /**
- * Human-readable reason behind this refund
- */
- const char *reason;
-
- /**
- * Transaction status code.
- */
- enum TALER_MERCHANTDB_RefundStatus rs;
-};
-
-
-/**
- * Data extracted per coin.
- */
-struct RefundCoinData
-{
-
- /**
- * Public key of a coin.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Amount deposited for this coin.
- */
- struct TALER_Amount deposited_with_fee;
-
- /**
- * Amount refunded already for this coin.
- */
- struct TALER_Amount refund_amount;
-
- /**
- * Order serial (actually not really per-coin).
- */
- uint64_t order_serial;
-
- /**
- * Maximum rtransaction_id for this coin so far.
- */
- uint64_t max_rtransaction_id;
-
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure, our `struct InsertRefundContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-process_deposits_for_refund_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct InsertRefundContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
- struct TALER_Amount current_refund;
- struct RefundCoinData rcd[GNUNET_NZL (num_results)];
- struct GNUNET_TIME_Timestamp now;
-
- now = GNUNET_TIME_timestamp_get ();
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (ctx->refund->currency,
- &current_refund));
- memset (rcd, 0, sizeof (rcd));
- /* Pass 1: Collect amount of existing refunds into current_refund.
- * Also store existing refunded amount for each deposit in deposit_refund. */
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &rcd[i].coin_pub),
- GNUNET_PQ_result_spec_uint64 ("order_serial",
- &rcd[i].order_serial),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &rcd[i].deposited_with_fee),
- GNUNET_PQ_result_spec_end
- };
- struct FindRefundContext ictx = {
- .pg = pg
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
- return;
- }
-
- {
- enum GNUNET_DB_QueryStatus ires;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&rcd[i].coin_pub),
- GNUNET_PQ_query_param_uint64 (&rcd[i].order_serial),
- GNUNET_PQ_query_param_end
- };
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (ctx->refund->currency,
- &ictx.refunded_amount));
- ires = GNUNET_PQ_eval_prepared_multi_select (ctx->pg->conn,
- "find_refunds_by_coin",
- params,
- &process_refund_cb,
- &ictx);
- if ( (ictx.err) ||
- (GNUNET_DB_STATUS_HARD_ERROR == ires) )
- {
- GNUNET_break (0);
- ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
- return;
- }
- if (GNUNET_DB_STATUS_SOFT_ERROR == ires)
- {
- ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
- return;
- }
- }
- if (0 >
- TALER_amount_add (&current_refund,
- &current_refund,
- &ictx.refunded_amount))
- {
- GNUNET_break (0);
- ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
- return;
- }
- rcd[i].refund_amount = ictx.refunded_amount;
- rcd[i].max_rtransaction_id = ictx.max_rtransaction_id;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Existing refund for coin %s is %s\n",
- TALER_B2S (&rcd[i].coin_pub),
- TALER_amount2s (&ictx.refunded_amount));
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Total existing refund is %s\n",
- TALER_amount2s (&current_refund));
-
- /* stop immediately if we are 'done' === amount already
- * refunded. */
- if (0 >= TALER_amount_cmp (ctx->refund,
- &current_refund))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Existing refund of %s at or above requested refund. Finished early.\n",
- TALER_amount2s (&current_refund));
- ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
- return;
- }
-
- /* Phase 2: Try to increase current refund until it matches desired refund */
- for (unsigned int i = 0; i<num_results; i++)
- {
- const struct TALER_Amount *increment;
- struct TALER_Amount left;
- struct TALER_Amount remaining_refund;
-
- /* How much of the coin is left after the existing refunds? */
- if (0 >
- TALER_amount_subtract (&left,
- &rcd[i].deposited_with_fee,
- &rcd[i].refund_amount))
- {
- GNUNET_break (0);
- ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
- return;
- }
-
- if ( (0 == left.value) &&
- (0 == left.fraction) )
- {
- /* coin was fully refunded, move to next coin */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Coin %s fully refunded, moving to next coin\n",
- TALER_B2S (&rcd[i].coin_pub));
- continue;
- }
-
- rcd[i].max_rtransaction_id++;
- /* How much of the refund is still to be paid back? */
- if (0 >
- TALER_amount_subtract (&remaining_refund,
- ctx->refund,
- &current_refund))
- {
- GNUNET_break (0);
- ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
- return;
- }
-
- /* By how much will we increase the refund for this coin? */
- if (0 >= TALER_amount_cmp (&remaining_refund,
- &left))
- {
- /* remaining_refund <= left */
- increment = &remaining_refund;
- }
- else
- {
- increment = &left;
- }
-
- if (0 >
- TALER_amount_add (&current_refund,
- &current_refund,
- increment))
- {
- GNUNET_break (0);
- ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
- return;
- }
-
- /* actually run the refund */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Coin %s deposit amount is %s\n",
- TALER_B2S (&rcd[i].coin_pub),
- TALER_amount2s (&rcd[i].deposited_with_fee));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Coin %s refund will be incremented by %s\n",
- TALER_B2S (&rcd[i].coin_pub),
- TALER_amount2s (increment));
- {
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&rcd[i].order_serial),
- GNUNET_PQ_query_param_uint64 (&rcd[i].max_rtransaction_id), /* already inc'ed */
- GNUNET_PQ_query_param_timestamp (&now),
- GNUNET_PQ_query_param_auto_from_type (&rcd[i].coin_pub),
- GNUNET_PQ_query_param_string (ctx->reason),
- TALER_PQ_query_param_amount (increment),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_refund",
- params);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
- return;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
- return;
- default:
- ctx->rs = (enum TALER_MERCHANTDB_RefundStatus) qs;
- break;
- }
- }
-
- /* stop immediately if we are done */
- if (0 == TALER_amount_cmp (ctx->refund,
- &current_refund))
- {
- ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
- return;
- }
- }
-
- /**
- * We end up here if not all of the refund has been covered.
- * Although this should be checked as the business should never
- * issue a refund bigger than the contract's actual price, we cannot
- * rely upon the frontend being correct.
- *///
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "The refund of %s is bigger than the order's value\n",
- TALER_amount2s (ctx->refund));
- ctx->rs = TALER_MERCHANTDB_RS_TOO_HIGH;
-}
-
-
-/**
- * Function called when some backoffice staff decides to award or
- * increase the refund on an existing contract. This function
- * MUST be called from within a transaction scope setup by the
- * caller as it executes multiple SQL statements.
- *
- * @param cls closure
- * @param instance_id instance identifier
- * @param order_id the order to increase the refund for
- * @param refund maximum refund to return to the customer for this contract
- * @param reason 0-terminated UTF-8 string giving the reason why the customer
- * got a refund (free form, business-specific)
- * @return transaction status
- * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a refund is ABOVE the amount we
- * were originally paid and thus the transaction failed;
- * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid,
- * regardless of whether it actually increased the refund beyond
- * what was already refunded (idempotency!)
- */
-static enum TALER_MERCHANTDB_RefundStatus
-postgres_increase_refund (void *cls,
- const char *instance_id,
- const char *order_id,
- const struct TALER_Amount *refund,
- const char *reason)
-{
- struct PostgresClosure *pg = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (order_id),
- GNUNET_PQ_query_param_end
- };
- struct InsertRefundContext ctx = {
- .pg = pg,
- .refund = refund,
- .reason = reason
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Asked to refund %s on order %s\n",
- TALER_amount2s (refund),
- order_id);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "find_deposits_for_refund",
- params,
- &process_deposits_for_refund_cb,
- &ctx);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* never paid, means we clearly cannot refund anything */
- return TALER_MERCHANTDB_RS_NO_SUCH_ORDER;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- return TALER_MERCHANTDB_RS_SOFT_ERROR;
- case GNUNET_DB_STATUS_HARD_ERROR:
- return TALER_MERCHANTDB_RS_HARD_ERROR;
- default:
- /* Got one or more deposits */
- return ctx.rs;
- }
-}
-
-
-/**
- * Closure for #lookup_refunds_detailed_cb().
- */
-struct LookupRefundsDetailedContext
-{
- /**
- * Function to call for each refund.
- */
- TALER_MERCHANTDB_RefundDetailCallback rc;
-
- /**
- * Closure for @e rc.
- */
- void *rc_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Transaction result.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls of type `struct GetRefundsContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_refunds_detailed_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRefundsDetailedContext *lrdc = cls;
- struct PostgresClosure *pg = lrdc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint64_t refund_serial;
- struct GNUNET_TIME_Timestamp timestamp;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- uint64_t rtransaction_id;
- struct TALER_Amount refund_amount;
- char *reason;
- char *exchange_url;
- uint8_t pending8;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("refund_serial",
- &refund_serial),
- GNUNET_PQ_result_spec_timestamp ("refund_timestamp",
- &timestamp),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &coin_pub),
- GNUNET_PQ_result_spec_string ("exchange_url",
- &exchange_url),
- GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
- &rtransaction_id),
- GNUNET_PQ_result_spec_string ("reason",
- &reason),
- TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount",
- &refund_amount),
- GNUNET_PQ_result_spec_auto_from_type ("pending",
- &pending8),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- lrdc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- lrdc->rc (lrdc->rc_cls,
- refund_serial,
- timestamp,
- &coin_pub,
- exchange_url,
- rtransaction_id,
- reason,
- &refund_amount,
- 0 != pending8);
- GNUNET_PQ_cleanup_result (rs);
- }
- lrdc->qs = num_results;
-}
-
-
-/**
- * Obtain detailed refund data associated with a contract.
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id instance to lookup refunds for
- * @param h_contract_terms hash code of the contract
- * @param rc function to call for each coin on which there is a refund
- * @param rc_cls closure for @a rc
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_refunds_detailed (
- void *cls,
- const char *instance_id,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- TALER_MERCHANTDB_RefundDetailCallback rc,
- void *rc_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
- GNUNET_PQ_query_param_end
- };
- struct LookupRefundsDetailedContext lrdc = {
- .rc = rc,
- .rc_cls = rc_cls,
- .pg = pg
- };
- enum GNUNET_DB_QueryStatus qs;
-
- /* no preflight check here, run in transaction by caller! */
- TALER_LOG_DEBUG ("Looking for refund %s + %s\n",
- GNUNET_h2s (&h_contract_terms->hash),
- instance_id);
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_refunds_detailed",
- params,
- &lookup_refunds_detailed_cb,
- &lrdc);
- if (0 >= qs)
- return qs;
- return lrdc.qs;
-}
-
-
-/**
- * Insert refund proof data from the exchange into the database.
- *
- * @param cls closure
- * @param refund_serial serial number of the refund
- * @param exchange_sig signature from exchange that coin was refunded
- * @param exchange_pub signing key that was used for @a exchange_sig
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_refund_proof (
- void *cls,
- uint64_t refund_serial,
- const struct TALER_ExchangeSignatureP *exchange_sig,
- const struct TALER_ExchangePublicKeyP *exchange_pub)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&refund_serial),
- GNUNET_PQ_query_param_auto_from_type (exchange_sig),
- GNUNET_PQ_query_param_auto_from_type (exchange_pub),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_refund_proof",
- params);
-}
-
-
-/**
- * Lookup refund proof data.
- *
- * @param cls closure
- * @param refund_serial serial number of the refund
- * @param[out] exchange_sig set to signature from exchange
- * @param[out] exchange_pub signing key that was used for @a exchange_sig
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_refund_proof (void *cls,
- uint64_t refund_serial,
- struct TALER_ExchangeSignatureP *exchange_sig,
- struct TALER_ExchangePublicKeyP *exchange_pub)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&refund_serial),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("exchange_sig",
- exchange_sig),
- GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
- exchange_pub),
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_refund_proof",
- params,
- rs);
-}
-
-
-/**
- * Retrieve the order ID that was used to pay for a resource within a session.
- *
- * @param cls closure
- * @param instance_id identifying the instance
- * @param fulfillment_url URL that canonically identifies the resource
- * being paid for
- * @param session_id session id
- * @param[out] order_id where to store the order ID that was used when
- * paying for the resource URL
- * @return transaction status
- */
-enum GNUNET_DB_QueryStatus
-postgres_lookup_order_by_fulfillment (void *cls,
- const char *instance_id,
- const char *fulfillment_url,
- const char *session_id,
- char **order_id)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (fulfillment_url),
- GNUNET_PQ_query_param_string (session_id),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("order_id",
- order_id),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_order_by_fulfillment",
- params,
- rs);
-}
-
-
-/**
- * Insert information about a wire transfer the merchant has received.
- *
- * @param cls closure
- * @param instance_id the instance that received the transfer
- * @param exchange_url which exchange made the transfer
- * @param wtid identifier of the wire transfer
- * @param credit_amount how much did we receive
- * @param payto_uri what is the merchant's bank account that received the transfer
- * @param confirmed whether the transfer was confirmed by the merchant or
- * was merely claimed by the exchange at this point
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_transfer (
- void *cls,
- const char *instance_id,
- const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_Amount *credit_amount,
- const char *payto_uri,
- bool confirmed)
-{
- struct PostgresClosure *pg = cls;
- uint8_t confirmed8 = confirmed;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (exchange_url),
- GNUNET_PQ_query_param_auto_from_type (wtid),
- TALER_PQ_query_param_amount (credit_amount),
- GNUNET_PQ_query_param_string (payto_uri),
- GNUNET_PQ_query_param_auto_from_type (&confirmed8),
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_transfer",
- params);
-}
-
-
-/**
- * Delete information about a transfer. Note that transfers
- * confirmed by the exchange cannot be deleted anymore.
- *
- * @param cls closure
- * @param instance_id instance to delete transfer of
- * @param transfer_serial_id transfer to delete
- * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
- * if deletion is prohibited OR transfer is unknown
- */
-static enum GNUNET_DB_QueryStatus
-postgres_delete_transfer (void *cls,
- const char *instance_id,
- uint64_t transfer_serial_id)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_uint64 (&transfer_serial_id),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_transfer",
- params);
-}
-
-
-/**
- * Check if information about a transfer exists with the
- * backend. Returns no data, only the query status.
- *
- * @param cls closure
- * @param instance_id instance to delete transfer of
- * @param transfer_serial_id transfer to delete
- * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
- * if the transfer record exists
- */
-static enum GNUNET_DB_QueryStatus
-postgres_check_transfer_exists (void *cls,
- const char *instance_id,
- uint64_t transfer_serial_id)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_uint64 (&transfer_serial_id),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "check_transfer_exists",
- params,
- rs);
-}
-
-
-/**
- * Lookup account serial by payto URI.
- *
- * @param cls closure
- * @param instance_id instance to lookup the account from
- * @param payto_uri what is the merchant's bank account to lookup
- * @param[out] account_serial serial number of the account
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_account (void *cls,
- const char *instance_id,
- const char *payto_uri,
- uint64_t *account_serial)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (payto_uri),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("account_serial",
- account_serial),
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_account",
- params,
- rs);
-}
-
-
-/**
- * Insert information about a wire transfer the merchant has received.
- *
- * @param cls closure
- * @param instance_id instance to provide transfer details for
- * @param exchange_url which exchange made the transfer
- * @param payto_uri what is the merchant's bank account that received the transfer
- * @param wtid identifier of the wire transfer
- * @param td transfer details to store
- * @return transaction status,
- * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the @a wtid and @a exchange_uri are not known for this @a instance_id
- * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT on success
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_transfer_details (
- void *cls,
- const char *instance_id,
- const char *exchange_url,
- const char *payto_uri,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_EXCHANGE_TransferData *td)
-{
- struct PostgresClosure *pg = cls;
- enum GNUNET_DB_QueryStatus qs;
- uint64_t credit_serial;
- unsigned int retries;
-
- retries = 0;
- check_connection (pg);
-RETRY:
- if (MAX_RETRIES < ++retries)
- return GNUNET_DB_STATUS_SOFT_ERROR;
- if (GNUNET_OK !=
- postgres_start_read_committed (pg,
- "insert transfer details"))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- /* lookup credit serial */
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (exchange_url),
- GNUNET_PQ_query_param_string (payto_uri),
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (wtid),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("credit_serial",
- &credit_serial),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_credit_serial",
- params,
- rs);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- postgres_rollback (pg);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- goto RETRY;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "'lookup_credit_serial' for account %s and amount %s failed with status %d\n",
- payto_uri,
- TALER_amount2s (&td->total_amount),
- qs);
- return qs;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- postgres_rollback (pg);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "'lookup_credit_serial' for account %s failed with transfer unknown\n",
- payto_uri);
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- }
- }
-
- /* update merchant_transfer_signatures table */
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&credit_serial),
- TALER_PQ_query_param_amount (&td->total_amount),
- TALER_PQ_query_param_amount (&td->wire_fee),
- GNUNET_PQ_query_param_timestamp (&td->execution_time),
- GNUNET_PQ_query_param_auto_from_type (&td->exchange_sig),
- GNUNET_PQ_query_param_auto_from_type (&td->exchange_pub),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_transfer_signature",
- params);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- postgres_rollback (pg);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- goto RETRY;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "'insert_transfer_signature' failed with status %d\n",
- qs);
- return qs;
- }
- if (0 == qs)
- {
- postgres_rollback (pg);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "'insert_transfer_signature' failed with status %d\n",
- qs);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
-
- /* Update transfer-coin association table */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Updating transfer-coin association table\n");
- for (unsigned int i = 0; i<td->details_length; i++)
- {
- const struct TALER_TrackTransferDetails *d = &td->details[i];
- uint64_t i64 = (uint64_t) i;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&credit_serial),
- GNUNET_PQ_query_param_uint64 (&i64),
- TALER_PQ_query_param_amount (&d->coin_value),
- TALER_PQ_query_param_amount (&d->coin_fee), /* deposit fee */
- GNUNET_PQ_query_param_auto_from_type (&d->coin_pub),
- GNUNET_PQ_query_param_auto_from_type (&d->h_contract_terms),
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_transfer_to_coin_mapping",
- params);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- postgres_rollback (pg);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- goto RETRY;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "'insert_transfer_to_coin_mapping' failed with status %d\n",
- qs);
- return qs;
- }
- if (0 == qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "'insert_transfer_to_coin_mapping' failed at %u: deposit unknown\n",
- i);
- }
- }
- /* Update merchant_contract_terms 'wired' status: for all coins
- that were wired, set the respective order's "wired" status to
- true, *if* all other deposited coins associated with that order
- have also been wired (this time or earlier) */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Updating contract terms 'wired' status\n");
- for (unsigned int i = 0; i<td->details_length; i++)
- {
- const struct TALER_TrackTransferDetails *d = &td->details[i];
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&d->coin_pub),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_wired_by_coin_pub",
- params);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- postgres_rollback (pg);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- goto RETRY;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "'update_wired_by_coin_pub' failed with status %d\n",
- qs);
- return qs;
- }
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Committing transaction...\n");
- qs = postgres_commit (pg);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- goto RETRY;
- return qs;
-}
-
-
-/**
- * Obtain information about wire fees charged by an exchange,
- * including signature (so we have proof).
- *
- * @param cls closure
- * @param master_pub public key of the exchange
- * @param wire_method the wire method
- * @param contract_date date of the contract to use for the lookup
- * @param[out] fees wire fees charged
- * @param[out] start_date start of fee being used
- * @param[out] end_date end of fee being used
- * @param[out] master_sig signature of exchange over fee structure
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_wire_fee (void *cls,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *wire_method,
- struct GNUNET_TIME_Timestamp contract_date,
- struct TALER_WireFeeSet *fees,
- struct GNUNET_TIME_Timestamp *start_date,
- struct GNUNET_TIME_Timestamp *end_date,
- struct TALER_MasterSignatureP *master_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_HashCode h_wire_method;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_auto_from_type (&h_wire_method),
- GNUNET_PQ_query_param_timestamp (&contract_date),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
- &fees->wire),
- TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
- &fees->closing),
- GNUNET_PQ_result_spec_timestamp ("start_date",
- start_date),
- GNUNET_PQ_result_spec_timestamp ("end_date",
- end_date),
- GNUNET_PQ_result_spec_auto_from_type ("master_sig",
- master_sig),
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- GNUNET_CRYPTO_hash (wire_method,
- strlen (wire_method) + 1,
- &h_wire_method);
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_wire_fee",
- params,
- rs);
-}
-
-
-/**
- * Closure for #lookup_deposits_by_contract_and_coin_cb().
- */
-struct LookupDepositsByCnCContext
-{
- /**
- * Function to call for each deposit.
- */
- TALER_MERCHANTDB_CoinDepositCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Transaction result.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls of type `struct LookupDepositsByCnCContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_deposits_by_contract_and_coin_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupDepositsByCnCContext *ldcc = cls;
- struct PostgresClosure *pg = ldcc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- char *exchange_url;
- struct TALER_Amount amount_with_fee;
- struct TALER_Amount deposit_fee;
- struct TALER_Amount refund_fee;
- struct TALER_Amount wire_fee;
- struct TALER_MerchantWireHashP h_wire;
- struct GNUNET_TIME_Timestamp deposit_timestamp;
- struct GNUNET_TIME_Timestamp refund_deadline;
- struct TALER_ExchangeSignatureP exchange_sig;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("exchange_url",
- &exchange_url),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee",
- &deposit_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee",
- &refund_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
- &wire_fee),
- GNUNET_PQ_result_spec_auto_from_type ("h_wire",
- &h_wire),
- GNUNET_PQ_result_spec_timestamp ("deposit_timestamp",
- &deposit_timestamp),
- GNUNET_PQ_result_spec_timestamp ("refund_deadline",
- &refund_deadline),
- GNUNET_PQ_result_spec_auto_from_type ("exchange_sig",
- &exchange_sig),
- GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
- &exchange_pub),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- ldcc->cb (ldcc->cb_cls,
- exchange_url,
- &amount_with_fee,
- &deposit_fee,
- &refund_fee,
- &wire_fee,
- &h_wire,
- deposit_timestamp,
- refund_deadline,
- &exchange_sig,
- &exchange_pub);
- GNUNET_PQ_cleanup_result (rs);
- }
- ldcc->qs = num_results;
-}
-
-
-/**
- * Lookup information about coin payments by @a h_contract_terms and
- * @a coin_pub.
- *
- * @param cls closure
- * @param instance_id instance to lookup payments for
- * @param h_contract_terms proposal data's hashcode
- * @param coin_pub public key to use for the search
- * @param cb function to call with payment data
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_deposits_by_contract_and_coin (
- void *cls,
- const char *instance_id,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- TALER_MERCHANTDB_CoinDepositCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_end
- };
- struct LookupDepositsByCnCContext ldcc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (
- pg->conn,
- "lookup_deposits_by_contract_and_coin",
- params,
- &lookup_deposits_by_contract_and_coin_cb,
- &ldcc);
- if (0 >= qs)
- return qs;
- return ldcc.qs;
-}
-
-
-/**
- * Lookup transfer status.
- *
- * @param cls closure
- * @param instance_id at which instance should we resolve the transfer
- * @param exchange_url the exchange that made the transfer
- * @param wtid wire transfer subject
- * @param[out] total_amount amount that was debited from our
- * aggregate balance at the exchange (in total, sum of
- * the wire transfer amount and the @a wire_fee)
- * @param[out] wire_fee the wire fee the exchange charged (only set if @a have_exchange_sig is true)
- * @param[out] exchange_amount the amount the exchange claims was transferred (only set if @a have_exchange_sig is true)
- * @param[out] execution_time when the transfer was executed by the exchange (only set if @a have_exchange_sig is true)
- * @param[out] have_exchange_sig do we have a response from the exchange about this transfer
- * @param[out] verified did we confirm the transfer was OK
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_transfer (
- void *cls,
- const char *instance_id,
- const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- struct TALER_Amount *total_amount,
- struct TALER_Amount *wire_fee,
- struct TALER_Amount *exchange_amount,
- struct GNUNET_TIME_Timestamp *execution_time,
- bool *have_exchange_sig,
- bool *verified)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (exchange_url),
- GNUNET_PQ_query_param_auto_from_type (wtid),
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_end
- };
- uint8_t verified8;
- /** Amount we got actually credited, _excludes_ the wire fee */
- bool no_sig;
- struct TALER_Amount credit_amount;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("credit_amount",
- &credit_amount),
- GNUNET_PQ_result_spec_allow_null (
- TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
- wire_fee),
- &no_sig),
- GNUNET_PQ_result_spec_allow_null (
- TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_amount",
- exchange_amount),
- NULL),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_timestamp ("execution_time",
- execution_time),
- NULL),
- GNUNET_PQ_result_spec_auto_from_type ("verified",
- &verified8),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- *execution_time = GNUNET_TIME_UNIT_ZERO_TS;
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_transfer",
- params,
- rs);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Lookup transfer returned %d\n",
- qs);
- if (qs > 0)
- {
- *have_exchange_sig = ! no_sig;
- *verified = (0 != verified8);
- if ( (! no_sig) &&
- (0 >
- TALER_amount_add (total_amount,
- &credit_amount,
- wire_fee)) )
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- else
- {
- *verified = false;
- *have_exchange_sig = false;
- }
- return qs;
-}
-
-
-/**
- * Set transfer status to verified.
- *
- * @param cls closure
- * @param exchange_url the exchange that made the transfer
- * @param wtid wire transfer subject
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_set_transfer_status_to_verified (
- void *cls,
- const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (wtid),
- GNUNET_PQ_query_param_string (exchange_url),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (
- pg->conn,
- "set_transfer_status_to_verified",
- params);
-}
-
-
-/**
- * Closure for #lookup_transfer_summary_cb().
- */
-struct LookupTransferSummaryContext
-{
- /**
- * Function to call for each order that was aggregated.
- */
- TALER_MERCHANTDB_TransferSummaryCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Transaction result.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls of type `struct LookupTransferSummaryContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_transfer_summary_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupTransferSummaryContext *ltdc = cls;
- struct PostgresClosure *pg = ltdc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- char *order_id;
- struct TALER_Amount deposit_value;
- struct TALER_Amount deposit_fee;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("order_id",
- &order_id),
- TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_value",
- &deposit_value),
- TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_fee",
- &deposit_fee),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ltdc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- ltdc->cb (ltdc->cb_cls,
- order_id,
- &deposit_value,
- &deposit_fee);
- GNUNET_PQ_cleanup_result (rs);
- }
- ltdc->qs = num_results;
-}
-
-
-/**
- * Lookup transfer summary.
- *
- * @param cls closure
- * @param exchange_url the exchange that made the transfer
- * @param wtid wire transfer subject
- * @param cb function to call with detailed transfer data
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_transfer_summary (
- void *cls,
- const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- TALER_MERCHANTDB_TransferSummaryCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (exchange_url),
- GNUNET_PQ_query_param_auto_from_type (wtid),
- GNUNET_PQ_query_param_end
- };
- struct LookupTransferSummaryContext ltdc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (
- pg->conn,
- "lookup_transfer_summary",
- params,
- &lookup_transfer_summary_cb,
- &ltdc);
- if (0 >= qs)
- return qs;
- return ltdc.qs;
-}
-
-
-/**
- * Closure for #lookup_transfer_details_cb().
- */
-struct LookupTransferDetailsContext
-{
- /**
- * Function to call for each order that was aggregated.
- */
- TALER_MERCHANTDB_TransferDetailsCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Transaction result.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls of type `struct LookupTransferDetailsContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_transfer_details_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupTransferDetailsContext *ltdc = cls;
- struct PostgresClosure *pg = ltdc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint64_t current_offset;
- struct TALER_TrackTransferDetails ttd;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("offset_in_exchange_list",
- &current_offset),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &ttd.h_contract_terms),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &ttd.coin_pub),
- TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_value",
- &ttd.coin_value),
- TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_fee",
- &ttd.coin_fee),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ltdc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- ltdc->cb (ltdc->cb_cls,
- (unsigned int) current_offset,
- &ttd);
- GNUNET_PQ_cleanup_result (rs);
- }
- ltdc->qs = num_results;
-}
-
-
-/**
- * Lookup transfer details.
- *
- * @param cls closure
- * @param exchange_url the exchange that made the transfer
- * @param wtid wire transfer subject
- * @param cb function to call with detailed transfer data
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_transfer_details (
- void *cls,
- const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- TALER_MERCHANTDB_TransferDetailsCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (exchange_url),
- GNUNET_PQ_query_param_auto_from_type (wtid),
- GNUNET_PQ_query_param_end
- };
- struct LookupTransferDetailsContext ltdc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (
- pg->conn,
- "lookup_transfer_details",
- params,
- &lookup_transfer_details_cb,
- &ltdc);
- if (0 >= qs)
- return qs;
- return ltdc.qs;
-}
-
-
-/**
- * Closure for #lookup_transfers_cb().
- */
-struct LookupTransfersContext
-{
- /**
- * Function to call on results.
- */
- TALER_MERCHANTDB_TransferCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Postgres context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Transaction status (set).
- */
- enum GNUNET_DB_QueryStatus qs;
-
- /**
- * Filter to apply by verification status.
- */
- enum TALER_EXCHANGE_YesNoAll verified;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls of type `struct LookupTransfersContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_transfers_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupTransfersContext *ltc = cls;
- struct PostgresClosure *pg = ltc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_Amount credit_amount;
- struct TALER_WireTransferIdentifierRawP wtid;
- char *payto_uri;
- char *exchange_url;
- uint64_t transfer_serial_id;
- struct GNUNET_TIME_Timestamp execution_time;
- enum TALER_EXCHANGE_YesNoAll verified;
- uint8_t verified8;
- uint8_t confirmed8;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("credit_amount",
- &credit_amount),
- GNUNET_PQ_result_spec_auto_from_type ("wtid",
- &wtid),
- GNUNET_PQ_result_spec_string ("payto_uri",
- &payto_uri),
- GNUNET_PQ_result_spec_string ("exchange_url",
- &exchange_url),
- GNUNET_PQ_result_spec_uint64 ("credit_serial",
- &transfer_serial_id),
- GNUNET_PQ_result_spec_timestamp ("execution_time",
- &execution_time),
- GNUNET_PQ_result_spec_auto_from_type ("verified",
- &verified8),
- GNUNET_PQ_result_spec_auto_from_type ("confirmed",
- &confirmed8),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ltc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- if (0 == verified8)
- verified = TALER_EXCHANGE_YNA_NO;
- else
- verified = TALER_EXCHANGE_YNA_YES;
- if ( (ltc->verified == TALER_EXCHANGE_YNA_ALL) ||
- (ltc->verified == verified) )
- {
- ltc->cb (ltc->cb_cls,
- &credit_amount,
- &wtid,
- payto_uri,
- exchange_url,
- transfer_serial_id,
- execution_time,
- TALER_EXCHANGE_YNA_YES == verified,
- 0 != confirmed8);
- }
- GNUNET_PQ_cleanup_result (rs);
- }
- ltc->qs = num_results;
-}
-
-
-/**
- * Lookup transfers. Note that filtering by @a verified status is done
- * outside of SQL, as we already have 8 prepared statements and adding
- * a filter on verified would further double the number of statements for
- * a likely rather ineffective filter. So we apply that filter in
- * #lookup_transfers_cb().
- *
- * @param cls closure
- * @param instance_id instance to lookup payments for
- * @param payto_uri account that we are interested in transfers to
- * @param before timestamp for the earliest transfer we care about
- * @param after timestamp for the last transfer we care about
- * @param limit number of entries to return, negative for descending in execution time,
- * positive for ascending in execution time
- * @param offset transfer_serial number of the transfer we want to offset from
- * @param verified filter transfers by verification status
- * @param cb function to call with detailed transfer data
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_transfers (void *cls,
- const char *instance_id,
- const char *payto_uri,
- struct GNUNET_TIME_Timestamp before,
- struct GNUNET_TIME_Timestamp after,
- int64_t limit,
- uint64_t offset,
- enum TALER_EXCHANGE_YesNoAll verified,
- TALER_MERCHANTDB_TransferCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
- struct LookupTransfersContext ltc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .verified = verified
- };
- enum GNUNET_DB_QueryStatus qs;
- bool by_time;
-
- by_time = ( (! GNUNET_TIME_absolute_is_never (before.abs_time)) ||
- (! GNUNET_TIME_absolute_is_zero (after.abs_time)) );
- check_connection (pg);
- if (by_time)
- {
- if (NULL != payto_uri)
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_timestamp (&before),
- GNUNET_PQ_query_param_timestamp (&after),
- GNUNET_PQ_query_param_uint64 (&offset),
- GNUNET_PQ_query_param_uint64 (&plimit),
- GNUNET_PQ_query_param_string (payto_uri),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_multi_select (
- pg->conn,
- (limit > 0)
- ? "lookup_transfers_time_payto_asc"
- : "lookup_transfers_time_payto_desc",
- params,
- &lookup_transfers_cb,
- &ltc);
- }
- else
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_timestamp (&before),
- GNUNET_PQ_query_param_timestamp (&after),
- GNUNET_PQ_query_param_uint64 (&offset),
- GNUNET_PQ_query_param_uint64 (&plimit),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_multi_select (
- pg->conn,
- (limit > 0)
- ? "lookup_transfers_time_asc"
- : "lookup_transfers_time_desc",
- params,
- &lookup_transfers_cb,
- &ltc);
- }
- }
- else
- {
- if (NULL != payto_uri)
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_uint64 (&offset),
- GNUNET_PQ_query_param_uint64 (&plimit),
- GNUNET_PQ_query_param_string (payto_uri),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_multi_select (
- pg->conn,
- (limit > 0)
- ? "lookup_transfers_payto_asc"
- : "lookup_transfers_payto_desc",
- params,
- &lookup_transfers_cb,
- &ltc);
- }
- else
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_uint64 (&offset),
- GNUNET_PQ_query_param_uint64 (&plimit),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_multi_select (
- pg->conn,
- (limit > 0)
- ? "lookup_transfers_asc"
- : "lookup_transfers_desc",
- params,
- &lookup_transfers_cb,
- &ltc);
- }
- }
- if (0 >= qs)
- return qs;
- return ltc.qs;
-}
-
-
-/**
- * Store information about wire fees charged by an exchange,
- * including signature (so we have proof).
- *
- * @param cls closure
- * @param master_pub public key of the exchange
- * @param h_wire_method hash of wire method
- * @param fees the fee charged
- * @param start_date start of fee being used
- * @param end_date end of fee being used
- * @param master_sig signature of exchange over fee structure
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_store_wire_fee_by_exchange (
- void *cls,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct GNUNET_HashCode *h_wire_method,
- const struct TALER_WireFeeSet *fees,
- struct GNUNET_TIME_Timestamp start_date,
- struct GNUNET_TIME_Timestamp end_date,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_auto_from_type (h_wire_method),
- TALER_PQ_query_param_amount (&fees->wire),
- TALER_PQ_query_param_amount (&fees->closing),
- GNUNET_PQ_query_param_timestamp (&start_date),
- GNUNET_PQ_query_param_timestamp (&end_date),
- GNUNET_PQ_query_param_auto_from_type (master_sig),
- GNUNET_PQ_query_param_end
- };
-
- /* no preflight check here, run in its own transaction by the caller */
- check_connection (pg);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Storing wire fee for %s starting at %s of %s\n",
- TALER_B2S (master_pub),
- GNUNET_TIME_timestamp2s (start_date),
- TALER_amount2s (&fees->wire));
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_wire_fee",
- params);
-}
-
-
-/**
- * Add @a credit to a reserve to be used for tipping. Note that
- * this function does not actually perform any wire transfers to
- * credit the reserve, it merely tells the merchant backend that
- * a reserve now exists. This has to happen before tips can be
- * authorized.
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance is the reserve tied to
- * @param reserve_priv which reserve is topped up or created
- * @param reserve_pub which reserve is topped up or created
- * @param exchange_url what URL is the exchange reachable at where the reserve is located
- * @param payto_uri URI to use to fund the reserve
- * @param initial_balance how much money will be added to the reserve
- * @param expiration when does the reserve expire?
- * @return transaction status, usually
- * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
- */
-static enum TALER_ErrorCode
-postgres_insert_reserve (void *cls,
- const char *instance_id,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const char *exchange_url,
- const char *payto_uri,
- const struct TALER_Amount *initial_balance,
- struct GNUNET_TIME_Timestamp expiration)
-{
- struct PostgresClosure *pg = cls;
- unsigned int retries;
- enum GNUNET_DB_QueryStatus qs;
-
- retries = 0;
- check_connection (pg);
-RETRY:
- if (MAX_RETRIES < ++retries)
- return TALER_EC_GENERIC_DB_SOFT_FAILURE;
- if (GNUNET_OK !=
- postgres_start (pg,
- "insert reserve"))
- {
- GNUNET_break (0);
- return TALER_EC_GENERIC_DB_START_FAILED;
- }
-
- /* Setup reserve */
- {
- struct GNUNET_TIME_Timestamp now;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- GNUNET_PQ_query_param_timestamp (&now),
- GNUNET_PQ_query_param_timestamp (&expiration),
- TALER_PQ_query_param_amount (initial_balance),
- GNUNET_PQ_query_param_end
- };
-
- now = GNUNET_TIME_timestamp_get ();
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_reserve",
- params);
- if (0 > qs)
- {
- postgres_rollback (pg);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- goto RETRY;
- return qs;
- }
- }
- /* Store private key */
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- GNUNET_PQ_query_param_auto_from_type (reserve_priv),
- GNUNET_PQ_query_param_string (exchange_url),
- GNUNET_PQ_query_param_string (payto_uri),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_reserve_key",
- params);
- if (0 > qs)
- {
- postgres_rollback (pg);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- goto RETRY;
- return qs;
- }
- }
- qs = postgres_commit (pg);
- if (0 <= qs)
- return TALER_EC_NONE; /* success */
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- goto RETRY;
- return qs;
-}
-
-
-/**
- * Confirms @a credit as the amount the exchange claims to have received and
- * thus really 'activates' the reserve. This has to happen before tips can
- * be authorized.
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance is the reserve tied to
- * @param reserve_pub which reserve is topped up or created
- * @param initial_exchange_balance how much money was be added to the reserve
- * according to the exchange
- * @return transaction status, usually
- * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
- */
-static enum GNUNET_DB_QueryStatus
-postgres_activate_reserve (void *cls,
- const char *instance_id,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_Amount *initial_exchange_balance)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- TALER_PQ_query_param_amount (initial_exchange_balance),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "activate_reserve",
- params);
-}
-
-
-/**
- * Closure for #lookup_reserves_cb.
- */
-struct LookupReservesContext
-{
- /**
- * Postgres context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Function to call with the results
- */
- TALER_MERCHANTDB_ReservesCallback cb;
-
- /**
- * Closure for @e cb
- */
- void *cb_cls;
-
- /**
- * Filter by active reserves.
- */
- enum TALER_EXCHANGE_YesNoAll active;
-
- /**
- * Filter by failures (mismatch in exchange claimed and
- * merchant claimed initial amounts).
- */
- enum TALER_EXCHANGE_YesNoAll failures;
-
- /**
- * Set in case of errors.
- */
- enum GNUNET_DB_QueryStatus qs;
-
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about accounts.
- *
- * @param[in,out] cls of type `struct LookupReservesContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_reserves_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupReservesContext *lrc = cls;
- struct PostgresClosure *pg = lrc->pg;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct TALER_ReservePublicKeyP reserve_pub;
- struct GNUNET_TIME_Timestamp creation_time;
- struct GNUNET_TIME_Timestamp expiration_time;
- struct TALER_Amount merchant_initial_balance;
- struct TALER_Amount exchange_initial_balance;
- struct TALER_Amount pickup_amount;
- struct TALER_Amount committed_amount;
- uint8_t active;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &reserve_pub),
- GNUNET_PQ_result_spec_timestamp ("creation_time",
- &creation_time),
- GNUNET_PQ_result_spec_timestamp ("expiration",
- &expiration_time),
- TALER_PQ_RESULT_SPEC_AMOUNT ("merchant_initial_balance",
- &merchant_initial_balance),
- TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance",
- &exchange_initial_balance),
- TALER_PQ_RESULT_SPEC_AMOUNT ("tips_committed",
- &committed_amount),
- TALER_PQ_RESULT_SPEC_AMOUNT ("tips_picked_up",
- &pickup_amount),
- GNUNET_PQ_result_spec_auto_from_type ("active",
- &active),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- lrc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- switch (lrc->active)
- {
- case TALER_EXCHANGE_YNA_YES:
- if (0 == active)
- continue;
- break;
- case TALER_EXCHANGE_YNA_NO:
- if (0 != active)
- continue;
- break;
- case TALER_EXCHANGE_YNA_ALL:
- break;
- }
- switch (lrc->failures)
- {
- case TALER_EXCHANGE_YNA_YES:
- if (0 ==
- TALER_amount_cmp (&merchant_initial_balance,
- &exchange_initial_balance))
- continue;
- break;
- case TALER_EXCHANGE_YNA_NO:
- if (0 !=
- TALER_amount_cmp (&merchant_initial_balance,
- &exchange_initial_balance))
- continue;
- break;
- case TALER_EXCHANGE_YNA_ALL:
- break;
- }
- lrc->cb (lrc->cb_cls,
- &reserve_pub,
- creation_time,
- expiration_time,
- &merchant_initial_balance,
- &exchange_initial_balance,
- &pickup_amount,
- &committed_amount,
- (0 != active));
- }
-}
-
-
-/**
- * Lookup reserves.
- *
- * @param cls closure
- * @param instance_id instance to lookup payments for
- * @param created_after filter by reserves created after this date
- * @param active filter by active reserves
- * @param failures filter by reserves with a disagreement on the initial balance
- * @param cb function to call with reserve summary data
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_reserves (void *cls,
- const char *instance_id,
- struct GNUNET_TIME_Timestamp created_after,
- enum TALER_EXCHANGE_YesNoAll active,
- enum TALER_EXCHANGE_YesNoAll failures,
- TALER_MERCHANTDB_ReservesCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupReservesContext lrc = {
- .pg = pg,
- .active = active,
- .failures = failures,
- .cb = cb,
- .cb_cls = cb_cls
- };
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_timestamp (&created_after),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_reserves",
- params,
- &lookup_reserves_cb,
- &lrc);
- if (lrc.qs < 0)
- return lrc.qs;
- return qs;
-}
-
-
-/**
- * Closure for #lookup_pending_reserves_cb.
- */
-struct LookupPendingReservesContext
-{
- /**
- * Postgres context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Function to call with the results
- */
- TALER_MERCHANTDB_PendingReservesCallback cb;
-
- /**
- * Closure for @e cb
- */
- void *cb_cls;
-
- /**
- * Set in case of errors.
- */
- enum GNUNET_DB_QueryStatus qs;
-
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about accounts.
- *
- * @param[in,out] cls of type `struct LookupReservesContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_pending_reserves_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupPendingReservesContext *lrc = cls;
- struct PostgresClosure *pg = lrc->pg;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct TALER_ReservePublicKeyP reserve_pub;
- struct TALER_Amount merchant_initial_balance;
- char *exchange_url;
- char *instance_id;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &reserve_pub),
- GNUNET_PQ_result_spec_string ("merchant_id",
- &instance_id),
- GNUNET_PQ_result_spec_string ("exchange_url",
- &exchange_url),
- TALER_PQ_RESULT_SPEC_AMOUNT ("merchant_initial_balance",
- &merchant_initial_balance),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- lrc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- lrc->cb (lrc->cb_cls,
- instance_id,
- exchange_url,
- &reserve_pub,
- &merchant_initial_balance);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Lookup reserves pending activation across all instances.
- *
- * @param cls closure
- * @param cb function to call with reserve summary data
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_pending_reserves (void *cls,
- TALER_MERCHANTDB_PendingReservesCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupPendingReservesContext lrc = {
- .pg = pg,
- .cb = cb,
- .cb_cls = cb_cls
- };
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_pending_reserves",
- params,
- &lookup_pending_reserves_cb,
- &lrc);
- if (lrc.qs < 0)
- return lrc.qs;
- return qs;
-}
-
-
-/**
- * Closure for #lookup_reserve_tips_cb().
- */
-struct LookupTipsContext
-{
- /**
- * Postgres context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Array with information about tips generated from this reserve.
- */
- struct TALER_MERCHANTDB_TipDetails *tips;
-
- /**
- * Length of the @e tips array.
- */
- unsigned int tips_length;
-
- /**
- * Set in case of errors.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about accounts.
- *
- * @param[in,out] cls of type `struct LookupTipsContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_reserve_tips_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupTipsContext *ltc = cls;
- struct PostgresClosure *pg = ltc->pg;
-
- GNUNET_array_grow (ltc->tips,
- ltc->tips_length,
- num_results);
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct TALER_MERCHANTDB_TipDetails *td = &ltc->tips[i];
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("justification",
- &td->reason),
- GNUNET_PQ_result_spec_auto_from_type ("tip_id",
- &td->tip_id),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &td->total_amount),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ltc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- }
-}
-
-
-/**
- * Lookup reserve details.
- *
- * @param cls closure
- * @param instance_id instance to lookup payments for
- * @param reserve_pub public key of the reserve to inspect
- * @param fetch_tips if true, also return information about tips
- * @param cb function to call with reserve summary data
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_reserve (void *cls,
- const char *instance_id,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- bool fetch_tips,
- TALER_MERCHANTDB_ReserveDetailsCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupTipsContext ltc = {
- .pg = pg,
- .qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
- };
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_TIME_Timestamp creation_time;
- struct GNUNET_TIME_Timestamp expiration_time;
- struct TALER_Amount merchant_initial_balance;
- struct TALER_Amount exchange_initial_balance;
- struct TALER_Amount pickup_amount;
- struct TALER_Amount committed_amount;
- uint8_t active;
- char *exchange_url = NULL;
- char *payto_uri = NULL;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_timestamp ("creation_time",
- &creation_time),
- GNUNET_PQ_result_spec_timestamp ("expiration",
- &expiration_time),
- TALER_PQ_RESULT_SPEC_AMOUNT ("merchant_initial_balance",
- &merchant_initial_balance),
- TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance",
- &exchange_initial_balance),
- TALER_PQ_RESULT_SPEC_AMOUNT ("tips_picked_up",
- &pickup_amount),
- TALER_PQ_RESULT_SPEC_AMOUNT ("tips_committed",
- &committed_amount),
- GNUNET_PQ_result_spec_auto_from_type ("active",
- &active),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_string ("exchange_url",
- &exchange_url),
- NULL),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_string ("payto_uri",
- &payto_uri),
- NULL),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_reserve",
- params,
- rs);
- if (qs < 0)
- return qs;
- if (! fetch_tips)
- {
- cb (cb_cls,
- creation_time,
- expiration_time,
- &merchant_initial_balance,
- &exchange_initial_balance,
- &pickup_amount,
- &committed_amount,
- (0 != active),
- exchange_url,
- payto_uri,
- 0,
- NULL);
- GNUNET_PQ_cleanup_result (rs);
- return qs;
- }
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_reserve_tips",
- params,
- &lookup_reserve_tips_cb,
- &ltc);
- if (qs < 0)
- return qs;
- if (ltc.qs >= 0)
- {
- cb (cb_cls,
- creation_time,
- expiration_time,
- &merchant_initial_balance,
- &exchange_initial_balance,
- &pickup_amount,
- &committed_amount,
- 0 != active,
- exchange_url,
- payto_uri,
- ltc.tips_length,
- ltc.tips);
- }
- for (unsigned int i = 0; i<ltc.tips_length; i++)
- GNUNET_free (ltc.tips[i].reason);
- GNUNET_array_grow (ltc.tips,
- ltc.tips_length,
- 0);
- GNUNET_PQ_cleanup_result (rs);
- return ltc.qs;
-}
-
-
-/**
- * Delete a reserve's private key.
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance is the reserve tied to
- * @param reserve_pub which reserve is to be deleted
- * @return transaction status, usually
- * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
- */
-static enum GNUNET_DB_QueryStatus
-postgres_delete_reserve (void *cls,
- const char *instance_id,
- const struct TALER_ReservePublicKeyP *reserve_pub)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_reserve",
- params);
-}
-
-
-/**
- * Purge all of the information about a reserve, including tips.
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance is the reserve tied to
- * @param reserve_pub which reserve is to be purged
- * @return transaction status, usually
- * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
- */
-static enum GNUNET_DB_QueryStatus
-postgres_purge_reserve (void *cls,
- const char *instance_id,
- const struct TALER_ReservePublicKeyP *reserve_pub)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "purge_reserve",
- params);
-}
-
-
-/**
- * Closure for #lookup_reserve_for_tip_cb().
- */
-struct LookupReserveForTipContext
-{
- /**
- * Postgres context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Public key of the reserve we found.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * How much money must be left in the reserve.
- */
- struct TALER_Amount required_amount;
-
- /**
- * Set to the expiration time of the reserve we found.
- * #GNUNET_TIME_UNIT_FOREVER_ABS if we found none.
- */
- struct GNUNET_TIME_Timestamp expiration;
-
- /**
- * Error status.
- */
- enum TALER_ErrorCode ec;
-
- /**
- * Did we find a good reserve?
- */
- bool ok;
-};
-
-
-/**
- * How long must a reserve be at least still valid before we use
- * it for a tip?
- */
-#define MIN_EXPIRATION GNUNET_TIME_UNIT_HOURS
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about accounts.
- *
- * @param[in,out] cls of type `struct LookupReserveForTipContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_reserve_for_tip_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupReserveForTipContext *lac = cls;
- struct PostgresClosure *pg = lac->pg;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct TALER_ReservePublicKeyP reserve_pub;
- struct TALER_Amount committed_amount;
- struct TALER_Amount remaining;
- struct TALER_Amount initial_balance;
- struct GNUNET_TIME_Timestamp expiration;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &reserve_pub),
- TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance",
- &initial_balance),
- TALER_PQ_RESULT_SPEC_AMOUNT ("tips_committed",
- &committed_amount),
- GNUNET_PQ_result_spec_timestamp ("expiration",
- &expiration),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- lac->ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
- return;
- }
- if (0 >
- TALER_amount_subtract (&remaining,
- &initial_balance,
- &committed_amount))
- {
- GNUNET_break (0);
- continue;
- }
- if (0 >
- TALER_amount_cmp (&remaining,
- &lac->required_amount))
- {
- /* insufficient balance */
- if (lac->ok)
- continue; /* got another reserve */
- lac->ec = TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_INSUFFICIENT_FUNDS;
- continue;
- }
- if ( (! GNUNET_TIME_absolute_is_never (lac->expiration.abs_time)) &&
- GNUNET_TIME_timestamp_cmp (expiration, >, lac->expiration) &&
- GNUNET_TIME_relative_cmp (
- GNUNET_TIME_absolute_get_remaining (lac->expiration.abs_time),
- >,
- MIN_EXPIRATION) )
- {
- /* reserve expired */
- if (lac->ok)
- continue; /* got another reserve */
- lac->ec = TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_EXPIRED;
- continue;
- }
- lac->ok = true;
- lac->ec = TALER_EC_NONE;
- lac->expiration = expiration;
- lac->reserve_pub = reserve_pub;
- }
-}
-
-
-/**
- * Authorize a tip over @a amount from reserve @a reserve_pub. Remember
- * the authorization under @a tip_id for later, together with the
- * @a justification.
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance should generate the tip
- * @param reserve_pub which reserve is debited, NULL to pick one in the DB
- * @param amount how high is the tip (with fees)
- * @param justification why was the tip approved
- * @param next_url where to send the URL post tip pickup
- * @param[out] tip_id set to the unique ID for the tip
- * @param[out] expiration set to when the tip expires
- * @return transaction status,
- * #TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but has expired
- * #TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_NOT_FOUND if the reserve is not known
- * #TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has insufficient funds left
- * #TALER_EC_GENERIC_DB_START_FAILED on hard DB errors
- * #TALER_EC_GENERIC_DB_FETCH_FAILED on hard DB errors
- * #TALER_EC_GENERIC_DB_STORE_FAILED on hard DB errors
- * #TALER_EC_GENERIC_DB_INVARIANT_FAILURE on hard DB errors
- * #TALER_EC_GENERIC_DB_SOFT_FAILURE soft DB errors (client should retry)
- * #TALER_EC_NONE upon success
- */
-static enum TALER_ErrorCode
-postgres_authorize_tip (void *cls,
- const char *instance_id,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_Amount *amount,
- const char *justification,
- const char *next_url,
- struct TALER_TipIdentifierP *tip_id,
- struct GNUNET_TIME_Timestamp *expiration)
-{
- struct PostgresClosure *pg = cls;
- unsigned int retries = 0;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_Amount tips_committed;
- struct TALER_Amount exchange_initial_balance;
- const struct TALER_ReservePublicKeyP *reserve_pubp;
- struct LookupReserveForTipContext lac = {
- .pg = pg,
- .required_amount = *amount,
- .expiration = GNUNET_TIME_UNIT_FOREVER_TS
- };
-
- check_connection (pg);
-RETRY:
- reserve_pubp = reserve_pub;
- if (MAX_RETRIES < ++retries)
- {
- GNUNET_break (0);
- return
- TALER_EC_GENERIC_DB_SOFT_FAILURE;
- }
- if (GNUNET_OK !=
- postgres_start (pg,
- "authorize tip"))
- {
- GNUNET_break (0);
- return TALER_EC_GENERIC_DB_START_FAILED;
- }
- if (NULL == reserve_pubp)
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_reserve_for_tip",
- params,
- &lookup_reserve_for_tip_cb,
- &lac);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_rollback (pg);
- goto RETRY;
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return TALER_EC_GENERIC_DB_FETCH_FAILED;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- postgres_rollback (pg);
- return TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_NOT_FOUND;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- default:
- break;
- }
- if (TALER_EC_NONE != lac.ec)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Enabling tip reserved failed with status %d\n",
- lac.ec);
- postgres_rollback (pg);
- return lac.ec;
- }
- GNUNET_assert (lac.ok);
- reserve_pubp = &lac.reserve_pub;
- }
-
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (reserve_pubp),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_timestamp ("expiration",
- expiration),
- TALER_PQ_RESULT_SPEC_AMOUNT ("tips_committed",
- &tips_committed),
- TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance",
- &exchange_initial_balance),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_reserve_status",
- params,
- rs);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- postgres_rollback (pg);
- goto RETRY;
- }
- if (qs < 0)
- {
- GNUNET_break (0);
- postgres_rollback (pg);
- return TALER_EC_GENERIC_DB_FETCH_FAILED;
- }
- if (0 == qs)
- {
- postgres_rollback (pg);
- return TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_NOT_FOUND;
- }
- }
- {
- struct TALER_Amount remaining;
-
- if (0 >
- TALER_amount_subtract (&remaining,
- &exchange_initial_balance,
- &tips_committed))
- {
- GNUNET_break (0);
- postgres_rollback (pg);
- return TALER_EC_GENERIC_DB_INVARIANT_FAILURE;
- }
- if (0 >
- TALER_amount_cmp (&remaining,
- amount))
- {
- postgres_rollback (pg);
- return TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_INSUFFICIENT_FUNDS;
- }
- }
- GNUNET_assert (0 <=
- TALER_amount_add (&tips_committed,
- &tips_committed,
- amount));
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (reserve_pubp),
- TALER_PQ_query_param_amount (&tips_committed),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_reserve_tips_committed",
- params);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- postgres_rollback (pg);
- goto RETRY;
- }
- if (qs < 0)
- {
- GNUNET_break (0);
- postgres_rollback (pg);
- return TALER_EC_GENERIC_DB_STORE_FAILED;
- }
- }
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
- tip_id,
- sizeof (*tip_id));
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (reserve_pubp),
- GNUNET_PQ_query_param_auto_from_type (tip_id),
- GNUNET_PQ_query_param_string (justification),
- GNUNET_PQ_query_param_string (next_url),
- GNUNET_PQ_query_param_timestamp (expiration),
- TALER_PQ_query_param_amount (amount),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_tip",
- params);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- postgres_rollback (pg);
- goto RETRY;
- }
- if (qs < 0)
- {
- GNUNET_break (0);
- postgres_rollback (pg);
- return TALER_EC_GENERIC_DB_STORE_FAILED;
- }
- }
- qs = postgres_commit (pg);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- goto RETRY;
- if (qs < 0)
- {
- GNUNET_break (0);
- postgres_rollback (pg);
- return TALER_EC_GENERIC_DB_COMMIT_FAILED;
- }
- return TALER_EC_NONE;
-}
-
-
-/**
- * Closure for #lookup_signatures_cb().
- */
-struct LookupSignaturesContext
-{
- /**
- * Length of the @e sigs array
- */
- unsigned int sigs_length;
-
- /**
- * Where to store the signatures.
- */
- struct TALER_BlindedDenominationSignature *sigs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about accounts.
- *
- * @param[in,out] cls of type `struct LookupSignaturesContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_signatures_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupSignaturesContext *lsc = cls;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- uint32_t offset;
- struct TALER_BlindedDenominationSignature bsig;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint32 ("coin_offset",
- &offset),
- TALER_PQ_result_spec_blinded_denom_sig ("blind_sig",
- &bsig),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- return;
- }
- if (offset >= lsc->sigs_length)
- {
- GNUNET_break_op (0);
- GNUNET_PQ_cleanup_result (rs);
- continue;
- }
- /* Must be NULL due to UNIQUE constraint on offset and
- requirement that client launched us with 'sigs'
- pre-initialized to NULL. */
- lsc->sigs[offset] = bsig;
- }
-}
-
-
-/**
- * Lookup pickup details for pickup @a pickup_id.
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance should we lookup tip details for
- * @param tip_id which tip should we lookup details on
- * @param pickup_id which pickup should we lookup details on
- * @param[out] exchange_url which exchange is the tip withdrawn from
- * @param[out] reserve_priv private key the tip is withdrawn from (set if still available!)
- * @param sigs_length length of the @a sigs array
- * @param[out] sigs set to the (blind) signatures we have for this @a pickup_id,
- * those that are unavailable are left at NULL
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_pickup (void *cls,
- const char *instance_id,
- const struct TALER_TipIdentifierP *tip_id,
- const struct TALER_PickupIdentifierP *pickup_id,
- char **exchange_url,
- struct TALER_ReservePrivateKeyP *reserve_priv,
- unsigned int sigs_length,
- struct TALER_BlindedDenominationSignature sigs[])
-{
- struct PostgresClosure *pg = cls;
- uint64_t pickup_serial;
-
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (tip_id),
- GNUNET_PQ_query_param_auto_from_type (pickup_id),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("exchange_url",
- exchange_url),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_priv",
- reserve_priv),
- GNUNET_PQ_result_spec_uint64 ("pickup_serial",
- &pickup_serial),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_pickup",
- params,
- rs);
- if (qs <= 0)
- return qs;
- }
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&pickup_serial),
- GNUNET_PQ_query_param_end
- };
- struct LookupSignaturesContext lsc = {
- .sigs_length = sigs_length,
- .sigs = sigs
- };
-
- return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_pickup_signatures",
- params,
- &lookup_signatures_cb,
- &lsc);
- }
-}
-
-
-/**
- * Lookup tip details for tip @a tip_id.
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance should we lookup tip details for
- * @param tip_id which tip should we lookup details on
- * @param[out] total_authorized amount how high is the tip (with fees)
- * @param[out] total_picked_up how much of the tip was so far picked up (with fees)
- * @param[out] expiration set to when the tip expires
- * @param[out] exchange_url set to the exchange URL where the reserve is
- * @param[out] reserve_priv set to private key of reserve to be debited
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_tip (void *cls,
- const char *instance_id,
- const struct TALER_TipIdentifierP *tip_id,
- struct TALER_Amount *total_authorized,
- struct TALER_Amount *total_picked_up,
- struct GNUNET_TIME_Timestamp *expiration,
- char **exchange_url,
- struct TALER_ReservePrivateKeyP *reserve_priv)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (tip_id),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- total_authorized),
- TALER_PQ_RESULT_SPEC_AMOUNT ("picked_up",
- total_picked_up),
- GNUNET_PQ_result_spec_timestamp ("expiration",
- expiration),
- GNUNET_PQ_result_spec_string ("exchange_url",
- exchange_url),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_priv",
- reserve_priv),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_tip",
- params,
- rs);
-}
-
-
-/**
- * Context used for postgres_lookup_tips().
- */
-struct LookupMerchantTipsContext
-{
- /**
- * Postgres context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Function to call with the results.
- */
- TALER_MERCHANTDB_TipsCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Internal result.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about tips.
- *
- * @param[in,out] cls of type `struct LookupTipsContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_tips_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupMerchantTipsContext *plc = cls;
- struct PostgresClosure *pg = plc->pg;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- uint64_t row_id;
- struct TALER_TipIdentifierP tip_id;
- struct TALER_Amount tip_amount;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("tip_serial",
- &row_id),
- GNUNET_PQ_result_spec_auto_from_type ("tip_id",
- &tip_id),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &tip_amount),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- plc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- plc->cb (plc->cb_cls,
- row_id,
- tip_id,
- tip_amount);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Lookup tips
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance should we lookup tips for
- * @param expired should we include expired tips?
- * @param limit maximum number of results to return, positive for
- * ascending row id, negative for descending
- * @param offset row id to start returning results from
- * @param cb function to call with tip data
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_tips (void *cls,
- const char *instance_id,
- enum TALER_EXCHANGE_YesNoAll expired,
- int64_t limit,
- uint64_t offset,
- TALER_MERCHANTDB_TipsCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupMerchantTipsContext plc = {
- .pg = pg,
- .cb = cb,
- .cb_cls = cb_cls
- };
- uint64_t ulimit = (limit > 0) ? limit : -limit;
- uint8_t bexpired;
- struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_uint64 (&ulimit),
- GNUNET_PQ_query_param_uint64 (&offset),
- GNUNET_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_auto_from_type (&bexpired),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
- char stmt[128];
-
- bexpired = (TALER_EXCHANGE_YNA_YES == expired);
- GNUNET_snprintf (stmt,
- sizeof (stmt),
- "lookup_tips_%s%s",
- (limit > 0) ? "inc" : "dec",
- (TALER_EXCHANGE_YNA_ALL == expired) ? "" : "_expired");
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- stmt,
- params,
- &lookup_tips_cb,
- &plc);
- if (0 != plc.qs)
- return plc.qs;
- return qs;
-}
-
-
-/**
- * Closure for #lookup_pickup_details_cb().
- */
-struct LookupTipDetailsContext
-{
- /**
- * Length of the @e sigs array
- */
- unsigned int *pickups_length;
-
- /**
- * Where to store the signatures.
- */
- struct TALER_MERCHANTDB_PickupDetails **pickups;
-
- /**
- * Database handle.
- */
- struct PostgresClosure *pg;
-
- /**
- * Transaction status.
- */
- enum GNUNET_DB_QueryStatus qs;
-
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about pickups.
- *
- * @param[in,out] cls of type `struct LookupTipDetailsContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_pickup_details_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupTipDetailsContext *ltdc = cls;
- struct PostgresClosure *pg = ltdc->pg;
-
- *ltdc->pickups_length = num_results;
- *ltdc->pickups = GNUNET_new_array (num_results,
- struct TALER_MERCHANTDB_PickupDetails);
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct TALER_MERCHANTDB_PickupDetails *pd = &((*ltdc->pickups)[i]);
- uint64_t num_planchets = 0;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("pickup_id",
- &pd->pickup_id),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &pd->requested_amount),
- GNUNET_PQ_result_spec_uint64 ("num_planchets",
- &num_planchets),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ltdc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- GNUNET_array_grow (*ltdc->pickups,
- *ltdc->pickups_length,
- 0);
- return;
- }
-
- pd->num_planchets = num_planchets;
- }
-}
-
-
-/**
- * Lookup tip details for tip @a tip_id.
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance should we lookup tip details for
- * @param tip_id which tip should we lookup details on
- * @param fpu should we fetch details about individual pickups
- * @param[out] total_authorized amount how high is the tip (with fees)
- * @param[out] total_picked_up how much of the tip was so far picked up (with fees)
- * @param[out] justification why was the tip approved
- * @param[out] expiration set to when the tip expires
- * @param[out] reserve_pub set to which reserve is debited
- * @param[out] pickups_length set to the length of @e pickups
- * @param[out] pickups if @a fpu is true, set to details about the pickup operations
- * @return transaction status,
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_tip_details (void *cls,
- const char *instance_id,
- const struct TALER_TipIdentifierP *tip_id,
- bool fpu,
- struct TALER_Amount *total_authorized,
- struct TALER_Amount *total_picked_up,
- char **justification,
- struct GNUNET_TIME_Timestamp *expiration,
- struct TALER_ReservePublicKeyP *reserve_pub,
- unsigned int *pickups_length,
- struct TALER_MERCHANTDB_PickupDetails **pickups)
-{
- struct PostgresClosure *pg = cls;
- uint64_t tip_serial;
- enum GNUNET_DB_QueryStatus qs;
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (tip_id),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("tip_serial",
- &tip_serial),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- total_authorized),
- TALER_PQ_RESULT_SPEC_AMOUNT ("picked_up",
- total_picked_up),
- GNUNET_PQ_result_spec_string ("justification",
- justification),
- GNUNET_PQ_result_spec_timestamp ("expiration",
- expiration),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- reserve_pub),
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_tip_details",
- params,
- rs);
- if (qs <= 0)
- return qs;
- if (! fpu)
- {
- *pickups_length = 0;
- *pickups = NULL;
- return qs;
- }
- }
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&tip_serial),
- GNUNET_PQ_query_param_end
- };
-
- struct LookupTipDetailsContext ltdc = {
- .pickups_length = pickups_length,
- .pickups = pickups,
- .pg = pg,
- .qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
- };
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_pickup_details",
- params,
- &lookup_pickup_details_cb,
- &ltdc);
- if (qs < 0)
- return qs;
- return ltdc.qs;
- }
-}
-
-
-/**
- * Insert details about a tip pickup operation. The @a total_picked_up
- * UPDATES the total amount under the @a tip_id, while the @a
- * total_requested is the amount to be associated with this @a pickup_id.
- * While there is usually only one pickup event that picks up the entire
- * amount, our schema allows for wallets to pick up the amount incrementally
- * over multiple pick up operations.
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance gave the tip
- * @param tip_id the unique ID for the tip
- * @param total_picked_up how much was picked up overall at this
- * point (includes @a total_requested)
- * @param pickup_id unique ID for the operation
- * @param total_requested how much is being picked up in this operation
- * @return transaction status, usually
- * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
- * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a credit_uuid already known
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_pickup (void *cls,
- const char *instance_id,
- const struct TALER_TipIdentifierP *tip_id,
- const struct TALER_Amount *total_picked_up,
- const struct TALER_PickupIdentifierP *pickup_id,
- const struct TALER_Amount *total_requested)
-{
- struct PostgresClosure *pg = cls;
- enum GNUNET_DB_QueryStatus qs;
-
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (tip_id),
- GNUNET_PQ_query_param_auto_from_type (pickup_id),
- TALER_PQ_query_param_amount (total_requested),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_pickup",
- params);
- if (0 > qs)
- return qs;
- }
-
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (tip_id),
- TALER_PQ_query_param_amount (total_picked_up),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_picked_up_tip",
- params);
- if (0 > qs)
- return qs;
- }
- {
- uint64_t reserve_serial;
- struct TALER_Amount reserve_picked_up;
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (tip_id),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("reserve_serial",
- &reserve_serial),
- TALER_PQ_RESULT_SPEC_AMOUNT ("tips_picked_up",
- &reserve_picked_up),
- GNUNET_PQ_result_spec_end
-
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_picked_up_reserve",
- params,
- rs);
- if (0 > qs)
- return qs;
- }
- if (0 >=
- TALER_amount_add (&reserve_picked_up,
- &reserve_picked_up,
- total_requested))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&reserve_serial),
- TALER_PQ_query_param_amount (&reserve_picked_up),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_picked_up_reserve",
- params);
- if (0 > qs)
- return qs;
- }
- }
- return qs;
-}
-
-
-/**
- * Insert blind signature obtained from the exchange during a
- * tip pickup operation.
- *
- * @param cls closure, typically a connection to the db
- * @param pickup_id unique ID for the operation
- * @param offset offset of the blind signature for the pickup
- * @param blind_sig the blind signature
- * @return transaction status, usually
- * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
- * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a credit_uuid already known
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_pickup_blind_signature (
- void *cls,
- const struct TALER_PickupIdentifierP *pickup_id,
- uint32_t offset,
- const struct TALER_BlindedDenominationSignature *blind_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (pickup_id),
- GNUNET_PQ_query_param_uint32 (&offset),
- TALER_PQ_query_param_blinded_denom_sig (blind_sig),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_pickup_blind_signature",
- params);
-}
-
-
-/**
- * Delete information about a template.
- *
- * @param cls closure
- * @param instance_id instance to delete template of
- * @param template_id template to delete
- * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
- * if template unknown.
- */
-static enum GNUNET_DB_QueryStatus
-postgres_delete_template (void *cls,
- const char *instance_id,
- const char *template_id)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (template_id),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_template",
- params);
-}
-
-
-/**
- * Insert details about a particular template.
- *
- * @param cls closure
- * @param instance_id instance to insert template for
- * @param template_id template identifier of template to insert
- * @param td the template details to insert
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_template (void *cls,
- const char *instance_id,
- const char *template_id,
- const struct TALER_MERCHANTDB_TemplateDetails *td)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (template_id),
- GNUNET_PQ_query_param_string (td->template_description),
- (NULL == td->image)
- ? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_string (td->image),
- TALER_PQ_query_param_json (td->template_contract),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_template",
- params);
-}
-
-
-/**
- * Update details about a particular template.
- *
- * @param cls closure
- * @param instance_id instance to update template for
- * @param template_id template to update
- * @param td update to the template details on success, can be NULL
- * (in that case we only want to check if the template exists)
- * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the template
- * does not yet exist.
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_template (void *cls,
- const char *instance_id,
- const char *template_id,
- const struct TALER_MERCHANTDB_TemplateDetails *td)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (template_id),
- GNUNET_PQ_query_param_string (td->template_description),
- (NULL == td->image)
- ? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_string (td->image),
- TALER_PQ_query_param_json (td->template_contract),
- GNUNET_PQ_query_param_end
- };
-
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_template",
- params);
-}
-
-
-/**
- * Context used for postgres_lookup_template().
- */
-struct LookupTemplateContext
-{
- /**
- * Function to call with the results.
- */
- TALER_MERCHANTDB_TemplatesCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Did database result extraction fail?
- */
- bool extract_failed;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about template.
- *
- * @param[in,out] cls of type `struct LookupTemplateContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_templates_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupTemplateContext *tlc = cls;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- char *template_id;
- char *template_description;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("template_id",
- &template_id),
- GNUNET_PQ_result_spec_string ("template_description",
- &template_description),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- tlc->extract_failed = true;
- return;
- }
- tlc->cb (tlc->cb_cls,
- template_id,
- template_description);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Lookup all of the templates the given instance has configured.
- *
- * @param cls closure
- * @param instance_id instance to lookup template for
- * @param cb function to call on all template found
- * @param cb_cls closure for @a cb
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_templates (void *cls,
- const char *instance_id,
- TALER_MERCHANTDB_TemplatesCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupTemplateContext tlc = {
- .cb = cb,
- .cb_cls = cb_cls,
- /* Can be overwritten by the lookup_template_cb */
- .extract_failed = false,
- };
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_templates",
- params,
- &lookup_templates_cb,
- &tlc);
- /* If there was an error inside lookup_template_cb, return a hard error. */
- if (tlc.extract_failed)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Lookup details about a particular template.
- *
- * @param cls closure
- * @param instance_id instance to lookup template for
- * @param template_id template to lookup
- * @param[out] td set to the template details on success, can be NULL
- * (in that case we only want to check if the template exists)
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_template (void *cls,
- const char *instance_id,
- const char *template_id,
- struct TALER_MERCHANTDB_TemplateDetails *td)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (template_id),
- GNUNET_PQ_query_param_end
- };
-
- if (NULL == td)
- {
- struct GNUNET_PQ_ResultSpec rs_null[] = {
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_template",
- params,
- rs_null);
- }
- else
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("template_description",
- &td->template_description),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_string ("image",
- &td->image),
- NULL),
- TALER_PQ_result_spec_json ("template_contract",
- &td->template_contract),
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- td->image = NULL;
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_template",
- params,
- rs);
- }
-}
-
-
-/**
- * Delete information about a webhook.
- *
- * @param cls closure
- * @param instance_id instance to delete webhook of
- * @param webhook_id webhook to delete
- * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
- * if webhook unknown.
- */
-static enum GNUNET_DB_QueryStatus
-postgres_delete_webhook (void *cls,
- const char *instance_id,
- const char *webhook_id)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (webhook_id),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_webhook",
- params);
-}
-
-
-/**
- * Insert details about a particular webhook.
- *
- * @param cls closure
- * @param instance_id instance to insert webhook for
- * @param webhook_id webhook identifier of webhook to insert
- * @param wb the webhook details to insert
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_webhook (void *cls,
- const char *instance_id,
- const char *webhook_id,
- const struct TALER_MERCHANTDB_WebhookDetails *wb)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (webhook_id),
- GNUNET_PQ_query_param_string (wb->event_type),
- GNUNET_PQ_query_param_string (wb->url),
- GNUNET_PQ_query_param_string (wb->http_method),
- GNUNET_PQ_query_param_string (wb->header_template),
- GNUNET_PQ_query_param_string (wb->body_template),
- GNUNET_PQ_query_param_end
-
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_webhook",
- params);
-}
-
-
-/**
- * Update details about a particular webhook.
- *
- * @param cls closure
- * @param instance_id instance to update template for
- * @param webhook_id webhook to update
- * @param wb update to the webhook details on success, can be NULL
- * (in that case we only want to check if the webhook exists)
- * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the webhook
- * does not yet exist.
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_webhook (void *cls,
- const char *instance_id,
- const char *webhook_id,
- const struct TALER_MERCHANTDB_WebhookDetails *wb)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (webhook_id),
- GNUNET_PQ_query_param_string (wb->event_type),
- GNUNET_PQ_query_param_string (wb->url),
- GNUNET_PQ_query_param_string (wb->http_method),
- GNUNET_PQ_query_param_string (wb->header_template),
- GNUNET_PQ_query_param_string (wb->body_template),
- GNUNET_PQ_query_param_end
- };
-
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_webhook",
- params);
-}
-
-
-/**
- * Context used for postgres_lookup_webhook().
- */
-struct LookupWebhookContext
-{
- /**
- * Function to call with the results.
- */
- TALER_MERCHANTDB_WebhooksCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Did database result extraction fail?
- */
- bool extract_failed;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about webhook.
- *
- * @param[in,out] cls of type `struct LookupWebhookContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_webhooks_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupWebhookContext *wlc = cls;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- char *webhook_id;
- char *event_type;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("webhook_id",
- &webhook_id),
- GNUNET_PQ_result_spec_string ("event_type",
- &event_type),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- wlc->extract_failed = true;
- return;
- }
- wlc->cb (wlc->cb_cls,
- webhook_id,
- event_type);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Lookup all of the webhooks the given instance has configured.
- *
- * @param cls closure
- * @param instance_id instance to lookup webhook for
- * @param cb function to call on all webhook found
- * @param cb_cls closure for @a cb
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_webhooks (void *cls,
- const char *instance_id,
- TALER_MERCHANTDB_WebhooksCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupWebhookContext wlc = {
- .cb = cb,
- .cb_cls = cb_cls,
- /* Can be overwritten by the lookup_webhook_cb */
- .extract_failed = false,
- };
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_webhooks",
- params,
- &lookup_webhooks_cb,
- &wlc);
- /* If there was an error inside lookup_webhook_cb, return a hard error. */
- if (wlc.extract_failed)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Lookup details about a particular webhook.
- *
- * @param cls closure
- * @param instance_id instance to lookup webhook for
- * @param webhook_id webhook to lookup
- * @param[out] wb set to the webhook details on success, can be NULL
- * (in that case we only want to check if the webhook exists)
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_webhook (void *cls,
- const char *instance_id,
- const char *webhook_id,
- struct TALER_MERCHANTDB_WebhookDetails *wb)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (webhook_id),
- GNUNET_PQ_query_param_end
- };
-
- if (NULL == wb)
- {
- struct GNUNET_PQ_ResultSpec rs_null[] = {
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_webhook",
- params,
- rs_null);
- }
- else
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("event_type",
- &wb->event_type),
- GNUNET_PQ_result_spec_string ("url",
- &wb->url),
- GNUNET_PQ_result_spec_string ("http_method",
- &wb->http_method),
- GNUNET_PQ_result_spec_string ("header_template",
- &wb->header_template),
- GNUNET_PQ_result_spec_string ("body_template",
- &wb->body_template),
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_webhook",
- params,
- rs);
- }
-}
-
-
-/**
- * Context used for postgres_lookup_webhook().
- */
-struct LookupWebhookDetailContext
-{
- /**
- * Function to call with the results.
- */
- TALER_MERCHANTDB_WebhookDetailCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Did database result extraction fail?
- */
- bool extract_failed;
-};
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about webhook.
- *
- * @param[in,out] cls of type `struct LookupPendingWebhookContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_webhook_by_event_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupWebhookDetailContext *wlc = cls;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- uint64_t webhook_serial;
- char *event_type;
- char *url;
- char *http_method;
- char *header_template;
- char *body_template;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("webhook_serial",
- &webhook_serial),
- GNUNET_PQ_result_spec_string ("event_type",
- &event_type),
- GNUNET_PQ_result_spec_string ("url",
- &url),
- GNUNET_PQ_result_spec_string ("http_method",
- &http_method),
- GNUNET_PQ_result_spec_string ("header_template",
- &header_template),
- GNUNET_PQ_result_spec_string ("body_template",
- &body_template),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- wlc->extract_failed = true;
- return;
- }
- wlc->cb (wlc->cb_cls,
- webhook_serial,
- event_type,
- url,
- http_method,
- header_template,
- body_template);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Lookup webhook by event
- *
- * @param cls closure
- * @param instance_id instance to lookup webhook for
- * @param event_type event that we need to put in the pending webhook
- * @param[out] cb set to the webhook details on success
- * @param cb_cls callback closure
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_webhook_by_event (void *cls,
- const char *instance_id,
- const char *event_type,
- TALER_MERCHANTDB_WebhookDetailCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupWebhookDetailContext wlc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .extract_failed = false,
- };
-
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (event_type),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_webhook_by_event",
- params,
- &lookup_webhook_by_event_cb,
- &wlc);
-
- if (wlc.extract_failed)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Insert webhook in the pending webhook.
- *
- * @param cls closure
- * @param instance_id instance to insert webhook for
- * @param webhook_serial webhook to insert in the pending webhook
- * @param url to make the request to
- * @param http_method for the webhook
- * @param header of the webhook
- * @param body of the webhook
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_pending_webhook (void *cls,
- const char *instance_id,
- uint64_t webhook_serial,
- const char *url,
- const char *http_method,
- const char *header,
- const char *body)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_uint64 (&webhook_serial),
- GNUNET_PQ_query_param_string (url),
- GNUNET_PQ_query_param_string (http_method),
- NULL == header
- ? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_string (header),
- NULL == body
- ? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_string (body),
- GNUNET_PQ_query_param_end
- };
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_pending_webhook",
- params);
-}
-
-
-/**
- * Lookup details about a particular pending webhook.
- *
- * @param cls closure
- * @param instance_id instance to lookup webhook for
- * @param webhook_serial webhook to lookup
- * @param[out] pwb set to the pending webhook details on success, can be NULL
- * (in that case we only want to check if the webhook exists)
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_pending_webhook (void *cls,
- const char *instance_id,
- uint64_t *webhook_serial,
- struct TALER_MERCHANTDB_PendingWebhookDetails *
- pwb)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_uint64 (webhook_serial),
- GNUNET_PQ_query_param_end
- };
-
- if (NULL == pwb)
- {
- struct GNUNET_PQ_ResultSpec rs_null[] = {
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_pending_webhook",
- params,
- rs_null);
- }
- else
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_absolute_time ("next_attempt",
- &pwb->next_attempt),
- GNUNET_PQ_result_spec_uint32 ("retries",
- &pwb->retries),
- GNUNET_PQ_result_spec_string ("url",
- &pwb->url),
- GNUNET_PQ_result_spec_string ("http_method",
- &pwb->http_method),
- GNUNET_PQ_result_spec_string ("header",
- &pwb->header),
- GNUNET_PQ_result_spec_string ("body",
- &pwb->body),
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_pending_webhook",
- params,
- rs);
- }
-}
-
-
-/**
- * Context used for postgres_lookup_future_webhook().
- */
-struct LookupPendingWebhookContext
-{
- /**
- * Function to call with the results.
- */
- TALER_MERCHANTDB_PendingWebhooksCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Did database result extraction fail?
- */
- bool extract_failed;
-};
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about webhook.
- *
- * @param[in,out] cls of type `struct LookupPendingWebhookContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lookup_pending_webhooks_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupPendingWebhookContext *pwlc = cls;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- uint64_t webhook_serial;
- struct GNUNET_TIME_Absolute next_attempt;
- uint32_t retries;
- char *url;
- char *http_method;
- char *header;
- char *body;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("webhook_serial",
- &webhook_serial),
- GNUNET_PQ_result_spec_absolute_time ("next_attempt",
- &next_attempt),
- GNUNET_PQ_result_spec_uint32 ("retries",
- &retries),
- GNUNET_PQ_result_spec_string ("url",
- &url),
- GNUNET_PQ_result_spec_string ("http_method",
- &http_method),
- GNUNET_PQ_result_spec_string ("header",
- &header),
- GNUNET_PQ_result_spec_string ("body",
- &body),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- pwlc->extract_failed = true;
- return;
- }
- pwlc->cb (pwlc->cb_cls,
- webhook_serial,
- next_attempt,
- retries,
- url,
- http_method,
- header,
- body);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Lookup the webhook that need to be send in priority.
- * send.
- *
- * @param cls closure
- * @param cb pending webhook callback
- * @param cb_cls callback closure
- */
-// WHERE next_attempt <= now ORDER BY next_attempt ASC
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_pending_webhooks (void *cls,
- TALER_MERCHANTDB_PendingWebhooksCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupPendingWebhookContext pwlc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .extract_failed = false,
- };
- struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
- struct GNUNET_PQ_QueryParam params_null[] = {
- GNUNET_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_end
- };
-
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_pending_webhooks",
- params_null,
- &lookup_pending_webhooks_cb,
- &pwlc);
-
- if (pwlc.extract_failed)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Lookup future webhook in the pending webhook that need to be send.
- * With that we can know how long the system can 'sleep'.
- *
- * @param cls closure
- * @param cb pending webhook callback
- * @param cb_cls callback closure
- */
-// ORDER BY next_attempt ASC LIMIT 1
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_future_webhook (void *cls,
- TALER_MERCHANTDB_PendingWebhooksCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupPendingWebhookContext pwlc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .extract_failed = false,
- };
- struct GNUNET_PQ_QueryParam params_null[] = {
- GNUNET_PQ_query_param_end
- };
-
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_future_webhook",
- params_null,
- &lookup_pending_webhooks_cb,
- &pwlc);
-
- if (pwlc.extract_failed)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Lookup all the webhooks in the pending webhook.
- * Use by the administrator
- *
- * @param cls closure
- * @param instance_id to lookup webhooks for this instance particularly
- * @param min_row to see the list of the pending webhook that it is started with this minimum row.
- * @param max_results to see the list of the pending webhook that it is end with this max results.
- * @param cb pending webhook callback
- * @param cb_cls callback closure
- */
-// WHERE webhook_pending_serial > min_row ORDER BY webhook_pending_serial ASC LIMIT max_results
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_all_webhooks (void *cls,
- const char *instance_id,
- uint64_t min_row,
- uint32_t max_results,
- TALER_MERCHANTDB_PendingWebhooksCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct LookupPendingWebhookContext pwlc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .extract_failed = false,
- };
- uint64_t max_results64 = max_results;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_uint64 (&min_row),
- GNUNET_PQ_query_param_uint64 (&max_results64),
- GNUNET_PQ_query_param_end
- };
-
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_all_webhooks",
- params,
- &lookup_pending_webhooks_cb,
- &pwlc);
-
- if (pwlc.extract_failed)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Update the pending webhook. It is use if the webhook can't be send.
- *
- * @param cls closure
- * @param webhook_serial webhook that need to be update
- * @param next_attempt when to try the webhook next
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_pending_webhook (void *cls,
- uint64_t webhook_serial,
- struct GNUNET_TIME_Absolute next_attempt)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&webhook_serial),
- GNUNET_PQ_query_param_absolute_time (&next_attempt),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_pending_webhook",
- params);
-}
-
-
-/**
- * Delete a webhook in the pending webhook after the
- * webhook was completed successfully.
- *
- * @param cls closure
- * @param webhook_serial webhook that need to be delete in the pending webhook
- * @return database result code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_delete_pending_webhook (void *cls,
- uint64_t webhook_serial)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&webhook_serial),
- GNUNET_PQ_query_param_end
- };
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_pending_webhook",
- params);
-}
-
-
-/**
* Establish connection to the database.
*
* @param cls plugin context
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
-static int
+static enum GNUNET_GenericReturnValue
postgres_connect (void *cls)
{
struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_PreparedStatement ps[] = {
- GNUNET_PQ_make_prepare ("end_transaction",
- "COMMIT"),
- /* for call_with_accounts(), part of postgres_lookup_instances() */
- GNUNET_PQ_make_prepare ("lookup_instance_private_key",
- "SELECT"
- " merchant_priv"
- " FROM merchant_keys"
- " WHERE merchant_serial=$1"),
- /* for find_instances_cb(), part of postgres_lookup_instances() */
- GNUNET_PQ_make_prepare ("lookup_accounts",
- "SELECT"
- " h_wire"
- ",salt"
- ",payto_uri"
- ",active"
- " FROM merchant_accounts"
- " WHERE merchant_serial=$1"),
- /* for postgres_lookup_instances() */
- GNUNET_PQ_make_prepare ("lookup_instance_auth",
- "SELECT"
- " auth_hash"
- ",auth_salt"
- " FROM merchant_instances"
- " WHERE merchant_id=$1"),
- /* for postgres_lookup_instances() */
- GNUNET_PQ_make_prepare ("lookup_instances",
- "SELECT"
- " merchant_serial"
- ",merchant_pub"
- ",auth_hash"
- ",auth_salt"
- ",merchant_id"
- ",merchant_name"
- ",address"
- ",jurisdiction"
- ",default_max_deposit_fee_val"
- ",default_max_deposit_fee_frac"
- ",default_max_wire_fee_val"
- ",default_max_wire_fee_frac"
- ",default_wire_fee_amortization"
- ",default_wire_transfer_delay"
- ",default_pay_delay"
- ",website"
- ",email"
- ",logo"
- " FROM merchant_instances"),
- /* for postgres_lookup_instance() */
- GNUNET_PQ_make_prepare ("lookup_instance",
- "SELECT"
- " merchant_serial"
- ",merchant_pub"
- ",auth_hash"
- ",auth_salt"
- ",merchant_id"
- ",merchant_name"
- ",address"
- ",jurisdiction"
- ",default_max_deposit_fee_val"
- ",default_max_deposit_fee_frac"
- ",default_max_wire_fee_val"
- ",default_max_wire_fee_frac"
- ",default_wire_fee_amortization"
- ",default_wire_transfer_delay"
- ",default_pay_delay"
- ",website"
- ",email"
- ",logo"
- " FROM merchant_instances"
- " WHERE merchant_id=$1"),
- /* for postgres_insert_instance() */
- GNUNET_PQ_make_prepare ("insert_instance",
- "INSERT INTO merchant_instances"
- "(merchant_pub"
- ",auth_hash"
- ",auth_salt"
- ",merchant_id"
- ",merchant_name"
- ",address"
- ",jurisdiction"
- ",default_max_deposit_fee_val"
- ",default_max_deposit_fee_frac"
- ",default_max_wire_fee_val"
- ",default_max_wire_fee_frac"
- ",default_wire_fee_amortization"
- ",default_wire_transfer_delay"
- ",default_pay_delay"
- ",website"
- ",email"
- ",logo)"
- "VALUES"
- "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)"),
- /* for postgres_insert_instance() */
- GNUNET_PQ_make_prepare ("insert_keys",
- "INSERT INTO merchant_keys"
- "(merchant_priv"
- ",merchant_serial)"
- " SELECT $1, merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$2"),
- /* for postgres_account_kyc_set_status */
- GNUNET_PQ_make_prepare ("upsert_account_kyc",
- "INSERT INTO merchant_kyc"
- "(kyc_timestamp"
- ",kyc_ok"
- ",exchange_kyc_serial"
- ",account_serial"
- ",exchange_url"
- ",exchange_pub"
- ",exchange_sig)"
- " SELECT $5, $6, $4, account_serial, $3, $7, $8"
- " FROM merchant_instances"
- " JOIN merchant_accounts USING (merchant_serial)"
- " WHERE merchant_id=$1"
- " AND h_wire=$2"
- " ON CONFLICT(account_serial,exchange_url) DO "
- "UPDATE"
- " SET exchange_kyc_serial=$4"
- " ,kyc_timestamp=$5"
- " ,kyc_ok=$6"
- " ,exchange_pub=$7"
- " ,exchange_sig=$8"),
- /* for postgres_account_kyc_get_status */
- GNUNET_PQ_make_prepare ("lookup_kyc_status",
- "SELECT"
- " h_wire"
- ",exchange_kyc_serial"
- ",payto_uri"
- ",exchange_url"
- ",kyc_timestamp"
- ",kyc_ok"
- " FROM merchant_instances"
- " JOIN merchant_accounts"
- " USING (merchant_serial)"
- " JOIN merchant_kyc"
- " USING (account_serial)"
- " WHERE merchant_instances.merchant_id=$1"),
- /* for postgres_insert_account() */
- GNUNET_PQ_make_prepare ("insert_account",
- "INSERT INTO merchant_accounts"
- "(merchant_serial"
- ",h_wire"
- ",salt"
- ",payto_uri"
- ",active)"
- " SELECT merchant_serial, $2, $3, $4, $5"
- " FROM merchant_instances"
- " WHERE merchant_id=$1"),
- /* for postgres_delete_instance_private_key() */
- GNUNET_PQ_make_prepare ("delete_key",
- "DELETE FROM merchant_keys"
- " USING merchant_instances"
- " WHERE merchant_keys.merchant_serial"
- " = merchant_instances.merchant_serial"
- " AND merchant_instances.merchant_id = $1"),
- /* for postgres_purge_instance() */
- GNUNET_PQ_make_prepare ("purge_instance",
- "DELETE FROM merchant_instances"
- " WHERE merchant_instances.merchant_id = $1"),
- /* for postgres_update_instance() */
- GNUNET_PQ_make_prepare ("update_instance",
- "UPDATE merchant_instances SET"
- " merchant_name=$2"
- ",address=$3"
- ",jurisdiction=$4"
- ",default_max_deposit_fee_val=$5"
- ",default_max_deposit_fee_frac=$6"
- ",default_max_wire_fee_val=$7"
- ",default_max_wire_fee_frac=$8"
- ",default_wire_fee_amortization=$9"
- ",default_wire_transfer_delay=$10"
- ",default_pay_delay=$11"
- ",website=$12"
- ",email=$13"
- ",logo=$14"
- " WHERE merchant_id = $1"),
- /* for postgres_update_instance_auth() */
- GNUNET_PQ_make_prepare ("update_instance_auth",
- "UPDATE merchant_instances SET"
- " auth_hash=$2"
- ",auth_salt=$3"
- " WHERE merchant_id=$1"),
- /* for postgres_inactivate_account(); the merchant
- instance is implied from the random salt that
- is part of the h_wire calculation */
- GNUNET_PQ_make_prepare ("inactivate_account",
- "UPDATE merchant_accounts SET"
- " active=FALSE"
- " WHERE h_wire=$2 AND"
- " merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* for postgres_activate_account() */
- GNUNET_PQ_make_prepare ("activate_account",
- "UPDATE merchant_accounts SET"
- " active=TRUE"
- " WHERE h_wire=$2 AND"
- " merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* for postgres_lookup_products() */
- GNUNET_PQ_make_prepare ("lookup_products",
- "SELECT"
- " product_id"
- " FROM merchant_inventory"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"),
- /* for postgres_lookup_product() */
- GNUNET_PQ_make_prepare ("lookup_product",
- "SELECT"
- " description"
- ",description_i18n"
- ",unit"
- ",price_val"
- ",price_frac"
- ",taxes"
- ",total_stock"
- ",total_sold"
- ",total_lost"
- ",image"
- ",merchant_inventory.address"
- ",next_restock"
- ",minimum_age"
- " FROM merchant_inventory"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND merchant_inventory.product_id=$2"),
- /* for postgres_delete_product() */
- GNUNET_PQ_make_prepare ("delete_product",
- "DELETE"
- " FROM merchant_inventory"
- " WHERE merchant_inventory.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_inventory.product_id=$2"
- " AND product_serial NOT IN "
- " (SELECT product_serial FROM merchant_order_locks)"
- " AND product_serial NOT IN "
- " (SELECT product_serial FROM merchant_inventory_locks)"),
- /* for postgres_insert_product() */
- GNUNET_PQ_make_prepare ("insert_product",
- "INSERT INTO merchant_inventory"
- "(merchant_serial"
- ",product_id"
- ",description"
- ",description_i18n"
- ",unit"
- ",image"
- ",taxes"
- ",price_val"
- ",price_frac"
- ",total_stock"
- ",address"
- ",next_restock"
- ",minimum_age"
- ")"
- " SELECT merchant_serial,"
- " $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13"
- " FROM merchant_instances"
- " WHERE merchant_id=$1"),
- /* for postgres_update_product() */
- GNUNET_PQ_make_prepare ("update_product",
- "UPDATE merchant_inventory SET"
- " description=$3"
- ",description_i18n=$4"
- ",unit=$5"
- ",image=$6"
- ",taxes=$7"
- ",price_val=$8"
- ",price_frac=$9"
- ",total_stock=$10"
- ",total_lost=$11"
- ",address=$12"
- ",next_restock=$13"
- ",minimum_age=$14"
- " WHERE merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND product_id=$2"
- " AND total_stock <= $10"
- " AND total_lost <= $11"),
-
- /* for postgres_lock_product() */
- GNUNET_PQ_make_prepare ("lock_product",
- "WITH ps AS"
- " (SELECT product_serial"
- " FROM merchant_inventory"
- " WHERE product_id=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))"
- "INSERT INTO merchant_inventory_locks"
- "(product_serial"
- ",lock_uuid"
- ",total_locked"
- ",expiration)"
- " SELECT product_serial, $3, $4, $5"
- " FROM merchant_inventory"
- " JOIN ps USING (product_serial)"
- " WHERE "
- " total_stock - total_sold - total_lost - $4 >= "
- " (SELECT COALESCE(SUM(total_locked), 0)"
- " FROM merchant_inventory_locks"
- " WHERE product_serial=ps.product_serial) + "
- " (SELECT COALESCE(SUM(total_locked), 0)"
- " FROM merchant_order_locks"
- " WHERE product_serial=ps.product_serial)"),
-
- /* for postgres_expire_locks() */
- GNUNET_PQ_make_prepare ("unlock_products",
- "DELETE FROM merchant_inventory_locks"
- " WHERE expiration < $1"),
- /* for postgres_expire_locks() */
- GNUNET_PQ_make_prepare ("unlock_orders",
- "DELETE FROM merchant_orders"
- " WHERE pay_deadline < $1"),
- /* for postgres_expire_locks() */
- GNUNET_PQ_make_prepare ("unlock_contracts",
- "DELETE FROM merchant_contract_terms"
- " WHERE NOT paid"
- " AND pay_deadline < $1"),
-
- /* for postgres_delete_order() */
- GNUNET_PQ_make_prepare ("delete_order",
- "WITH ms AS"
- "(SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- ", mc AS"
- "(SELECT paid"
- " FROM merchant_contract_terms"
- " JOIN ms USING (merchant_serial)"
- " WHERE order_id=$2) "
- "DELETE"
- " FROM merchant_orders mo"
- " WHERE order_id=$2"
- " AND merchant_serial=(SELECT merchant_serial FROM ms)"
- " AND ( (pay_deadline < $3)"
- " OR (NOT EXISTS (SELECT paid FROM mc))"
- " OR ($4 AND (FALSE=(SELECT paid FROM mc))) );"),
- GNUNET_PQ_make_prepare ("delete_contract",
- "DELETE"
- " FROM merchant_contract_terms"
- " WHERE order_id=$2 AND"
- " merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND NOT paid;"),
- /* for postgres_lookup_order() */
- GNUNET_PQ_make_prepare ("lookup_order",
- "SELECT"
- " contract_terms"
- ",claim_token"
- ",h_post_data"
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_orders.order_id=$2"),
- /* for postgres_lookup_order_summary() */
- GNUNET_PQ_make_prepare ("lookup_order_summary",
- "(SELECT"
- " creation_time"
- ",order_serial"
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_contract_terms.order_id=$2)"
- "UNION"
- "(SELECT"
- " creation_time"
- ",order_serial"
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_orders.order_id=$2)"),
- /* for postgres_lookup_orders() */
- GNUNET_PQ_make_prepare ("lookup_orders_inc",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- " ORDER BY order_serial ASC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_orders_inc_paid",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND"
- " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
- " AND"
- " order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms)" /* only select unclaimed orders */
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND"
- " BOOL($5) = paid"
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- " ORDER BY order_serial ASC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_orders_inc_refunded",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND"
- " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
- " AND"
- " order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms)" /* only select unclaimed orders */
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND"
- " CAST($6 as BOOL) = (order_serial IN"
- " (SELECT order_serial "
- " FROM merchant_refunds))"
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- " ORDER BY order_serial ASC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_orders_inc_wired",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND"
- " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
- " AND"
- " order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms)" /* only select unclaimed orders */
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND"
- " BOOL($7) = wired"
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- " ORDER BY order_serial ASC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_orders_inc_paid_refunded",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND"
- " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
- " AND"
- " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
- " AND"
- " order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms)" /* only select unclaimed orders */
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND"
- " BOOL($5) = paid"
- " AND"
- " BOOL($6) = (order_serial IN"
- " (SELECT order_serial "
- " FROM merchant_refunds))"
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- " ORDER BY order_serial ASC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_orders_inc_paid_wired",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND"
- " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
- " AND"
- " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
- " AND"
- " order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms)" /* only select unclaimed orders */
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND"
- " BOOL($5) = paid"
- " AND"
- " BOOL($7) = wired"
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- " ORDER BY order_serial ASC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_orders_inc_refunded_wired",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND"
- " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
- " AND"
- " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
- " AND"
- " order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms)" /* only select unclaimed orders */
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND"
- " BOOL($6) = (order_serial IN"
- " (SELECT order_serial "
- " FROM merchant_refunds))"
- " AND"
- " BOOL($7) = wired"
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- " ORDER BY order_serial ASC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_orders_inc_paid_refunded_wired",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND"
- " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
- " AND"
- " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
- " AND"
- " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
- " AND"
- " order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms)" /* only select unclaimed orders */
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND"
- " BOOL($5) = paid"
- " AND"
- " BOOL($6) = (order_serial IN"
- " (SELECT order_serial "
- " FROM merchant_refunds))"
- " AND"
- " BOOL($7) = wired"
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- " ORDER BY order_serial ASC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_orders_dec",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- " ORDER BY order_serial DESC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_orders_dec_paid",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND"
- " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
- " AND"
- " order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms)" /* only select unclaimed orders */
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND"
- " BOOL($5) = paid"
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- " ORDER BY order_serial DESC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_orders_dec_refunded",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND"
- " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
- " AND"
- " order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms)" /* only select unclaimed orders */
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND"
- " BOOL($6) = (order_serial IN"
- " (SELECT order_serial "
- " FROM merchant_refunds))"
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- " ORDER BY order_serial DESC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_orders_dec_wired",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND"
- " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
- " AND"
- " order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms)" /* only select unclaimed orders */
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND"
- " BOOL($7) = wired"
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- " ORDER BY order_serial DESC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_orders_dec_paid_refunded",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND"
- " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
- " AND"
- " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
- " AND"
- " order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms)" /* only select unclaimed orders */
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND"
- " BOOL($5) = paid"
- " AND"
- " BOOL($6) = (order_serial IN"
- " (SELECT order_serial "
- " FROM merchant_refunds))"
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- " ORDER BY order_serial DESC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_orders_dec_paid_wired",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND"
- " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
- " AND"
- " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
- " AND"
- " order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms)" /* only select unclaimed orders */
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND"
- " BOOL($5) = paid"
- " AND"
- " BOOL($7) = wired"
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- " ORDER BY order_serial DESC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_orders_dec_refunded_wired",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND"
- " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
- " AND"
- " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
- " AND"
- " order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms)" /* only select unclaimed orders */
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND"
- " BOOL($6) = (order_serial IN"
- " (SELECT order_serial "
- " FROM merchant_refunds))"
- " AND"
- " BOOL($7) = wired"
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- " ORDER BY order_serial DESC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_orders_dec_paid_refunded_wired",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND"
- " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
- " AND"
- " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
- " AND"
- " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
- " AND"
- " order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms)" /* only select unclaimed orders */
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND"
- " BOOL($5) = paid"
- " AND"
- " BOOL($6) = (order_serial IN"
- " (SELECT order_serial "
- " FROM merchant_refunds))"
- " AND"
- " BOOL($7) = wired"
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- " ORDER BY order_serial DESC"
- " LIMIT $2"),
- /* for postgres_insert_order() */
- GNUNET_PQ_make_prepare ("insert_order",
- "INSERT INTO merchant_orders"
- "(merchant_serial"
- ",order_id"
- ",pay_deadline"
- ",claim_token"
- ",h_post_data"
- ",creation_time"
- ",contract_terms)"
- " SELECT merchant_serial,"
- " $2, $3, $4, $5, $6, $7"
- " FROM merchant_instances"
- " WHERE merchant_id=$1"),
- /* for postgres_unlock_inventory() */
- GNUNET_PQ_make_prepare ("unlock_inventory",
- "DELETE"
- " FROM merchant_inventory_locks"
- " WHERE lock_uuid=$1"),
- /* for postgres_insert_order_lock() */
- GNUNET_PQ_make_prepare ("insert_order_lock",
- "WITH tmp AS"
- " (SELECT "
- " product_serial"
- " ,merchant_serial"
- " ,total_stock"
- " ,total_sold"
- " ,total_lost"
- " FROM merchant_inventory"
- " WHERE product_id=$3"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))"
- " INSERT INTO merchant_order_locks"
- " (product_serial"
- " ,total_locked"
- " ,order_serial)"
- " SELECT tmp.product_serial, $4, order_serial"
- " FROM merchant_orders"
- " JOIN tmp USING(merchant_serial)"
- " WHERE order_id=$2 AND"
- " tmp.total_stock - tmp.total_sold - tmp.total_lost - $4 >= "
- " (SELECT COALESCE(SUM(total_locked), 0)"
- " FROM merchant_inventory_locks"
- " WHERE product_serial=tmp.product_serial) + "
- " (SELECT COALESCE(SUM(total_locked), 0)"
- " FROM merchant_order_locks"
- " WHERE product_serial=tmp.product_serial)"),
- /* for postgres_lookup_contract_terms() */
- GNUNET_PQ_make_prepare ("lookup_contract_terms",
- "SELECT"
- " contract_terms"
- ",order_serial"
- ",claim_token"
- ",paid"
- " FROM merchant_contract_terms"
- " WHERE order_id=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* for postgres_insert_contract_terms() */
- GNUNET_PQ_make_prepare ("insert_contract_terms",
- "INSERT INTO merchant_contract_terms"
- "(order_serial"
- ",merchant_serial"
- ",order_id"
- ",contract_terms"
- ",h_contract_terms"
- ",creation_time"
- ",pay_deadline"
- ",refund_deadline"
- ",fulfillment_url"
- ",claim_token)"
- "SELECT"
- " mo.order_serial"
- ",mo.merchant_serial"
- ",mo.order_id"
- ",$3" /* contract_terms */
- ",$4" /* h_contract_terms */
- ",mo.creation_time"
- ",$5" /* pay_deadline */
- ",$6" /* refund_deadline */
- ",$7" /* fulfillment_url */
- ",mo.claim_token "
- "FROM merchant_orders mo"
- " WHERE order_id=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " RETURNING order_serial"),
- /* for postgres_update_contract_terms() */
- GNUNET_PQ_make_prepare ("update_contract_terms",
- "UPDATE merchant_contract_terms SET"
- " contract_terms=$3"
- ",h_contract_terms=$4"
- ",pay_deadline=$5"
- ",refund_deadline=$6"
- ",fulfillment_url=$7"
- " WHERE order_id=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* for postgres_delete_contract_terms() */
- GNUNET_PQ_make_prepare ("delete_contract_terms",
- "DELETE FROM merchant_contract_terms"
- " WHERE order_id=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND ( ( (pay_deadline < $4) AND"
- " (NOT paid) ) OR"
- " (creation_time + $3 < $4) )"),
- /* for postgres_lookup_deposits() */
- GNUNET_PQ_make_prepare ("lookup_deposits",
- "SELECT"
- " exchange_url"
- ",coin_pub"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",deposit_fee_val"
- ",deposit_fee_frac"
- ",refund_fee_val"
- ",refund_fee_frac"
- ",wire_fee_val"
- ",wire_fee_frac"
- " FROM merchant_deposits"
- " WHERE order_serial="
- " (SELECT order_serial"
- " FROM merchant_contract_terms"
- " WHERE h_contract_terms=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))"),
- /* for postgres_insert_exchange_signkey() */
- GNUNET_PQ_make_prepare ("insert_exchange_signkey",
- "INSERT INTO merchant_exchange_signing_keys"
- "(master_pub"
- ",exchange_pub"
- ",start_date"
- ",expire_date"
- ",end_date"
- ",master_sig)"
- "VALUES"
- "($1, $2, $3, $4, $5, $6)"),
- /* for postgres_insert_deposit() */
- GNUNET_PQ_make_prepare ("insert_deposit",
- "WITH md AS"
- " (SELECT account_serial, merchant_serial"
- " FROM merchant_accounts"
- " WHERE h_wire=$14"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))"
- ", ed AS"
- " (SELECT signkey_serial"
- " FROM merchant_exchange_signing_keys"
- " WHERE exchange_pub=$16"
- " ORDER BY start_date DESC"
- " LIMIT 1)"
- "INSERT INTO merchant_deposits"
- "(order_serial"
- ",deposit_timestamp"
- ",coin_pub"
- ",exchange_url"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",deposit_fee_val"
- ",deposit_fee_frac"
- ",refund_fee_val"
- ",refund_fee_frac"
- ",wire_fee_val"
- ",wire_fee_frac"
- ",exchange_sig"
- ",signkey_serial"
- ",account_serial)"
- " SELECT "
- " order_serial"
- " ,$3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $15"
- " ,ed.signkey_serial"
- " ,md.account_serial"
- " FROM merchant_contract_terms"
- " JOIN md USING (merchant_serial)"
- " FULL OUTER JOIN ed ON TRUE"
- " WHERE h_contract_terms=$2"),
- /* for postgres_lookup_refunds() */
- GNUNET_PQ_make_prepare ("lookup_refunds",
- "SELECT"
- " coin_pub"
- ",refund_amount_val"
- ",refund_amount_frac"
- " FROM merchant_refunds"
- " WHERE order_serial="
- " (SELECT order_serial"
- " FROM merchant_contract_terms"
- " WHERE h_contract_terms=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))"),
- /* for postgres_mark_contract_paid() */
- GNUNET_PQ_make_prepare ("mark_contract_paid",
- "UPDATE merchant_contract_terms SET"
- " paid=TRUE"
- ",session_id=$3"
- " WHERE h_contract_terms=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* for postgres_mark_contract_paid() */
- GNUNET_PQ_make_prepare ("mark_inventory_sold",
- "UPDATE merchant_inventory SET"
- " total_sold=total_sold + order_locks.total_locked"
- " FROM (SELECT total_locked,product_serial"
- " FROM merchant_order_locks"
- " WHERE order_serial="
- " (SELECT order_serial"
- " FROM merchant_contract_terms"
- " WHERE h_contract_terms=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))"
- " ) AS order_locks"
- " WHERE merchant_inventory.product_serial"
- " =order_locks.product_serial"),
- /* for postgres_mark_contract_paid() */
- GNUNET_PQ_make_prepare ("delete_completed_order",
- "WITH md AS"
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1) "
- "DELETE"
- " FROM merchant_orders"
- " WHERE order_serial="
- " (SELECT order_serial"
- " FROM merchant_contract_terms"
- " JOIN md USING (merchant_serial)"
- " WHERE h_contract_terms=$2)"),
- /* for postgres_refund_coin() */
- GNUNET_PQ_make_prepare ("refund_coin",
- "INSERT INTO merchant_refunds"
- "(order_serial"
- ",rtransaction_id"
- ",refund_timestamp"
- ",coin_pub"
- ",reason"
- ",refund_amount_val"
- ",refund_amount_frac"
- ") "
- "SELECT "
- " order_serial"
- ",0" /* rtransaction_id always 0 for /abort */
- ",$3"
- ",coin_pub"
- ",$5"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- " FROM merchant_deposits"
- " WHERE coin_pub=$4"
- " AND order_serial="
- " (SELECT order_serial"
- " FROM merchant_contract_terms"
- " WHERE h_contract_terms=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))"),
-
- /* for postgres_lookup_order_status() */
- GNUNET_PQ_make_prepare ("lookup_order_status",
- "SELECT"
- " h_contract_terms"
- ",paid"
- " FROM merchant_contract_terms"
- " WHERE merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND order_id=$2"),
-
- /* for postgres_lookup_order_status_by_serial() */
- GNUNET_PQ_make_prepare ("lookup_order_status_by_serial",
- "SELECT"
- " h_contract_terms"
- ",order_id"
- ",paid"
- " FROM merchant_contract_terms"
- " WHERE merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND order_serial=$2"),
-
- /* for postgres_lookup_payment_status() */
- GNUNET_PQ_make_prepare ("lookup_payment_status",
- "SELECT"
- " wired"
- ",paid"
- " FROM merchant_contract_terms"
- " WHERE order_serial=$1"),
- /* for postgres_lookup_payment_status() */
- GNUNET_PQ_make_prepare ("lookup_payment_status_session_id",
- "SELECT"
- " wired"
- ",paid"
- " FROM merchant_contract_terms"
- " WHERE order_serial=$1"
- " AND session_id=$2"),
- /* for postgres_lookup_deposits_by_order() */
- GNUNET_PQ_make_prepare ("lookup_deposits_by_order",
- "SELECT"
- " deposit_serial"
- ",exchange_url"
- ",h_wire"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",deposit_fee_val"
- ",deposit_fee_frac"
- ",coin_pub"
- " FROM merchant_deposits"
- " JOIN merchant_accounts USING (account_serial)"
- " WHERE order_serial=$1"),
- /* for postgres_lookup_transfer_details_by_order() */
- GNUNET_PQ_make_prepare ("lookup_transfer_details_by_order",
- "SELECT"
- " md.deposit_serial"
- ",md.exchange_url"
- ",mt.wtid"
- ",exchange_deposit_value_val"
- ",exchange_deposit_value_frac"
- ",exchange_deposit_fee_val"
- ",exchange_deposit_fee_frac"
- ",deposit_timestamp"
- ",mt.confirmed AS transfer_confirmed"
- " FROM merchant_transfer_to_coin"
- " JOIN merchant_deposits AS md USING (deposit_serial)"
- " JOIN merchant_transfers AS mt USING (credit_serial)"
- " WHERE deposit_serial IN"
- " (SELECT deposit_serial"
- " FROM merchant_deposits"
- " WHERE order_serial=$1)"),
- /* for postgres_insert_deposit_to_transfer() */
- GNUNET_PQ_make_prepare ("insert_deposit_to_transfer",
- "INSERT INTO merchant_deposit_to_transfer"
- "(deposit_serial"
- ",coin_contribution_value_val"
- ",coin_contribution_value_frac"
- ",credit_serial"
- ",execution_time"
- ",signkey_serial"
- ",exchange_sig"
- ") SELECT $1, $2, $3, credit_serial, $4, signkey_serial, $5"
- " FROM merchant_transfers"
- " CROSS JOIN merchant_exchange_signing_keys"
- " WHERE exchange_pub=$6"
- " AND wtid=$7"),
- /* for postgres_mark_order_wired() */
- GNUNET_PQ_make_prepare ("mark_order_wired",
- "UPDATE merchant_contract_terms SET"
- " wired=true"
- " WHERE order_serial=$1"),
- /* for process_refund_cb() used in postgres_increase_refund() */
- GNUNET_PQ_make_prepare ("find_refunds_by_coin",
- "SELECT"
- " refund_amount_val"
- ",refund_amount_frac"
- ",rtransaction_id"
- " FROM merchant_refunds"
- " WHERE coin_pub=$1"
- " AND order_serial=$2"),
- /* for process_deposits_for_refund_cb() used in postgres_increase_refund() */
- GNUNET_PQ_make_prepare ("insert_refund",
- "INSERT INTO merchant_refunds"
- "(order_serial"
- ",rtransaction_id"
- ",refund_timestamp"
- ",coin_pub"
- ",reason"
- ",refund_amount_val"
- ",refund_amount_frac"
- ") VALUES"
- "($1, $2, $3, $4, $5, $6, $7)"),
- /* for postgres_increase_refund() */
- GNUNET_PQ_make_prepare ("find_deposits_for_refund",
- "SELECT"
- " coin_pub"
- ",order_serial"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- " FROM merchant_deposits"
- " WHERE order_serial="
- " (SELECT order_serial"
- " FROM merchant_contract_terms"
- " WHERE order_id=$2"
- " AND paid=TRUE"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))"),
- /* for postgres_lookup_refunds_detailed() */
- GNUNET_PQ_make_prepare ("lookup_refunds_detailed",
- "SELECT"
- " refund_serial"
- ",refund_timestamp"
- ",coin_pub"
- ",merchant_deposits.exchange_url"
- ",rtransaction_id"
- ",reason"
- ",refund_amount_val"
- ",refund_amount_frac"
- ",merchant_refund_proofs.exchange_sig IS NULL AS pending"
- " FROM merchant_refunds"
- " JOIN merchant_deposits USING (order_serial, coin_pub)"
- " LEFT JOIN merchant_refund_proofs USING (refund_serial)"
- " WHERE order_serial="
- " (SELECT order_serial"
- " FROM merchant_contract_terms"
- " WHERE h_contract_terms=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))"),
- /* for postgres_insert_refund_proof() */
- GNUNET_PQ_make_prepare ("insert_refund_proof",
- "INSERT INTO merchant_refund_proofs"
- "(refund_serial"
- ",exchange_sig"
- ",signkey_serial)"
- "SELECT $1, $2, signkey_serial"
- " FROM merchant_exchange_signing_keys"
- " WHERE exchange_pub=$3"
- " ORDER BY start_date DESC"
- " LIMIT 1"),
- /* for postgres_lookup_refund_proof() */
- GNUNET_PQ_make_prepare ("lookup_refund_proof",
- "SELECT"
- " merchant_exchange_signing_keys.exchange_pub"
- ",exchange_sig"
- " FROM merchant_refund_proofs"
- " JOIN merchant_exchange_signing_keys"
- " USING (signkey_serial)"
- " WHERE"
- " refund_serial=$1"),
- /* for postgres_lookup_order_by_fulfillment() */
- GNUNET_PQ_make_prepare ("lookup_order_by_fulfillment",
- "SELECT"
- " order_id"
- " FROM merchant_contract_terms"
- " WHERE fulfillment_url=$2"
- " AND session_id=$3"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* for postgres_insert_transfer() */
- GNUNET_PQ_make_prepare ("insert_transfer",
- "INSERT INTO merchant_transfers"
- "(exchange_url"
- ",wtid"
- ",credit_amount_val"
- ",credit_amount_frac"
- ",account_serial"
- ",confirmed)"
- "SELECT"
- " $1, $2, $3, $4, account_serial, $6"
- " FROM merchant_accounts"
- " WHERE payto_uri=$5"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$7)"),
- /* for postgres_delete_transfer() */
- GNUNET_PQ_make_prepare ("delete_transfer",
- "DELETE FROM merchant_transfers"
- " WHERE"
- " credit_serial=$2"
- " AND account_serial IN "
- " (SELECT account_serial "
- " FROM merchant_accounts"
- " WHERE merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))"),
- /* for postgres_check_transfer_exists() */
- GNUNET_PQ_make_prepare ("check_transfer_exists",
- "SELECT"
- " 1"
- " FROM merchant_transfers"
- " JOIN merchant_accounts"
- " USING (account_serial)"
- " WHERE"
- " credit_serial=$2"
- " AND"
- " merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* for postgres_lookup_account() */
- GNUNET_PQ_make_prepare ("lookup_account",
- "SELECT"
- " account_serial"
- " FROM merchant_accounts"
- " WHERE payto_uri=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* for postgres_insert_transfer_details() */
- GNUNET_PQ_make_prepare ("lookup_credit_serial",
- "SELECT"
- " credit_serial"
- " FROM merchant_transfers"
- " WHERE exchange_url=$1"
- " AND wtid=$4"
- " AND account_serial="
- " (SELECT account_serial"
- " FROM merchant_accounts"
- " WHERE payto_uri=$2"
- " AND exchange_url=$1"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$3))"),
- /* for postgres_insert_transfer_details() */
- GNUNET_PQ_make_prepare ("insert_transfer_signature",
- "INSERT INTO merchant_transfer_signatures"
- "(credit_serial"
- ",signkey_serial"
- ",credit_amount_val"
- ",credit_amount_frac"
- ",wire_fee_val"
- ",wire_fee_frac"
- ",execution_time"
- ",exchange_sig) "
- "SELECT $1, signkey_serial, $2, $3, $4, $5, $6, $7"
- " FROM merchant_exchange_signing_keys"
- " WHERE exchange_pub=$8"
- " ORDER BY start_date DESC"
- " LIMIT 1"),
- /* for postgres_insert_transfer_details() */
- GNUNET_PQ_make_prepare ("insert_transfer_to_coin_mapping",
- "INSERT INTO merchant_transfer_to_coin"
- "(deposit_serial"
- ",credit_serial"
- ",offset_in_exchange_list"
- ",exchange_deposit_value_val"
- ",exchange_deposit_value_frac"
- ",exchange_deposit_fee_val"
- ",exchange_deposit_fee_frac) "
- "SELECT deposit_serial, $1, $2, $3, $4, $5, $6"
- " FROM merchant_deposits"
- " JOIN merchant_contract_terms USING (order_serial)"
- " WHERE coin_pub=$7"
- " AND h_contract_terms=$8"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$9)"),
- /* for postgres_insert_transfer_details() */
- GNUNET_PQ_make_prepare ("update_wired_by_coin_pub",
- "WITH os AS" /* select orders affected by the coin */
- "(SELECT order_serial"
- " FROM merchant_deposits"
- " WHERE coin_pub=$1)"
- "UPDATE merchant_contract_terms "
- " SET wired=TRUE "
- " WHERE order_serial IN "
- " (SELECT order_serial FROM merchant_deposits" /* only orders for which NO un-wired coin exists*/
- " WHERE NOT EXISTS "
- " (SELECT order_serial FROM merchant_deposits" /* orders for which ANY un-wired coin exists */
- " JOIN os USING (order_serial)" /* filter early */
- " WHERE deposit_serial NOT IN"
- " (SELECT deposit_serial " /* all coins associated with order that WERE wired */
- " FROM merchant_deposits "
- " JOIN os USING (order_serial)" /* filter early */
- " JOIN merchant_deposit_to_transfer USING (deposit_serial)"
- " JOIN merchant_transfers USING (credit_serial)"
- " WHERE confirmed=TRUE)))"),
- /* for postgres_lookup_wire_fee() */
- GNUNET_PQ_make_prepare ("lookup_wire_fee",
- "SELECT"
- " wire_fee_val"
- ",wire_fee_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",start_date"
- ",end_date"
- ",master_sig"
- " FROM merchant_exchange_wire_fees"
- " WHERE master_pub=$1"
- " AND h_wire_method=$2"
- " AND start_date <= $3"
- " AND end_date > $3"),
- /* for postgres_lookup_deposits_by_contract_and_coin() */
- GNUNET_PQ_make_prepare ("lookup_deposits_by_contract_and_coin",
- "SELECT"
- " exchange_url"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",deposit_fee_val"
- ",deposit_fee_frac"
- ",refund_fee_val"
- ",refund_fee_frac"
- ",wire_fee_val"
- ",wire_fee_frac"
- ",h_wire"
- ",deposit_timestamp"
- ",refund_deadline"
- ",exchange_sig"
- ",exchange_pub"
- " FROM merchant_contract_terms"
- " JOIN merchant_deposits USING (order_serial)"
- " JOIN merchant_exchange_signing_keys USING (signkey_serial)"
- " JOIN merchant_accounts USING (account_serial)"
- " WHERE h_contract_terms=$2"
- " AND coin_pub=$3"
- " AND merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* for postgres_lookup_transfer() */
- GNUNET_PQ_make_prepare ("lookup_transfer",
- "SELECT"
- " mt.credit_amount_val AS credit_amount_val"
- ",mt.credit_amount_frac AS credit_amount_frac"
- ",mts.credit_amount_val AS exchange_amount_val"
- ",mts.credit_amount_frac AS exchange_amount_frac"
- ",wire_fee_val"
- ",wire_fee_frac"
- ",execution_time"
- ",verified"
- " FROM merchant_transfers mt"
- " JOIN merchant_accounts USING (account_serial)"
- " JOIN merchant_instances USING (merchant_serial)"
- " LEFT JOIN merchant_transfer_signatures mts USING (credit_serial)"
- " WHERE wtid=$2"
- " AND exchange_url=$1"
- " AND merchant_id=$3;"),
- /* for postgres_set_transfer_status_to_verified() */
- GNUNET_PQ_make_prepare ("set_transfer_status_to_verified",
- "UPDATE merchant_transfers SET"
- " verified=TRUE"
- " WHERE wtid=$1"
- " AND exchange_url=$2"),
- /* for postgres_lookup_transfer_summary() */
- GNUNET_PQ_make_prepare ("lookup_transfer_summary",
- "SELECT"
- " order_id"
- ",exchange_deposit_value_val"
- ",exchange_deposit_value_frac"
- ",exchange_deposit_fee_val"
- ",exchange_deposit_fee_frac"
- " FROM merchant_transfers"
- " JOIN merchant_transfer_to_coin USING (credit_serial)"
- " JOIN merchant_deposits USING (deposit_serial)"
- " JOIN merchant_contract_terms USING (order_serial)"
- " WHERE wtid=$2"
- " AND merchant_transfers.exchange_url=$1"),
- /* for postgres_lookup_transfer_details() */
- GNUNET_PQ_make_prepare ("lookup_transfer_details",
- "SELECT"
- " merchant_contract_terms.h_contract_terms"
- ",merchant_transfer_to_coin.offset_in_exchange_list"
- ",merchant_deposits.coin_pub"
- ",exchange_deposit_value_val"
- ",exchange_deposit_value_frac"
- ",exchange_deposit_fee_val"
- ",exchange_deposit_fee_frac"
- " FROM merchant_transfer_to_coin"
- " JOIN merchant_deposits USING (deposit_serial)"
- " JOIN merchant_contract_terms USING (order_serial)"
- " JOIN merchant_transfers USING (credit_serial)"
- " WHERE merchant_transfers.wtid=$2"
- " AND merchant_transfers.exchange_url=$1"),
- /* for postgres_lookup_transfers() */
- GNUNET_PQ_make_prepare ("lookup_transfers_time_payto_asc",
- "SELECT"
- " mt.credit_amount_val"
- ",mt.credit_amount_frac"
- ",wtid"
- ",merchant_accounts.payto_uri"
- ",exchange_url"
- ",credit_serial"
- ",merchant_transfer_signatures.execution_time"
- ",verified"
- ",confirmed"
- " FROM merchant_transfers mt"
- " JOIN merchant_accounts USING (account_serial)"
- " JOIN merchant_transfer_signatures USING (credit_serial)"
- " WHERE execution_time < $2"
- " AND execution_time >= $3"
- " AND credit_serial > $4"
- " AND payto_uri = $6"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " ORDER BY credit_serial ASC"
- " LIMIT $5"),
- /* for postgres_lookup_transfers() */
- GNUNET_PQ_make_prepare ("lookup_transfers_time_asc",
- "SELECT"
- " mt.credit_amount_val"
- ",mt.credit_amount_frac"
- ",wtid"
- ",merchant_accounts.payto_uri"
- ",exchange_url"
- ",credit_serial"
- ",merchant_transfer_signatures.execution_time"
- ",verified"
- ",confirmed"
- " FROM merchant_transfers mt"
- " JOIN merchant_accounts USING (account_serial)"
- " JOIN merchant_transfer_signatures USING (credit_serial)"
- " WHERE execution_time < $2"
- " AND execution_time >= $3"
- " AND credit_serial > $4"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " ORDER BY credit_serial ASC"
- " LIMIT $5"),
- /* for postgres_lookup_transfers() */
- GNUNET_PQ_make_prepare ("lookup_transfers_payto_asc",
- "SELECT"
- " mt.credit_amount_val"
- ",mt.credit_amount_frac"
- ",wtid"
- ",merchant_accounts.payto_uri"
- ",exchange_url"
- ",credit_serial"
- ",CASE WHEN (merchant_transfer_signatures.execution_time) IS NULL"
- " THEN 9223372036854775807" /* largest BIGINT possible */
- " ELSE merchant_transfer_signatures.execution_time"
- " END AS execution_time"
- ",verified"
- ",confirmed"
- " FROM merchant_transfers mt"
- " JOIN merchant_accounts USING (account_serial)"
- " LEFT JOIN merchant_transfer_signatures USING (credit_serial)"
- " WHERE credit_serial > $2"
- " AND payto_uri = $4"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " ORDER BY credit_serial ASC"
- " LIMIT $3"),
- /* for postgres_lookup_transfers() */
- GNUNET_PQ_make_prepare ("lookup_transfers_asc",
- "SELECT"
- " mt.credit_amount_val"
- ",mt.credit_amount_frac"
- ",wtid"
- ",merchant_accounts.payto_uri"
- ",exchange_url"
- ",credit_serial"
- ",CASE WHEN (merchant_transfer_signatures.execution_time) IS NULL"
- " THEN 9223372036854775807" /* largest BIGINT possible */
- " ELSE merchant_transfer_signatures.execution_time"
- " END AS execution_time"
- ",verified"
- ",confirmed"
- " FROM merchant_transfers mt"
- " JOIN merchant_accounts USING (account_serial)"
- " LEFT JOIN merchant_transfer_signatures USING (credit_serial)"
- " WHERE credit_serial > $2"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " ORDER BY credit_serial ASC"
- " LIMIT $3"),
- /* for postgres_lookup_transfers() */
- GNUNET_PQ_make_prepare ("lookup_transfers_time_payto_desc",
- "SELECT"
- " mt.credit_amount_val"
- ",mt.credit_amount_frac"
- ",wtid"
- ",merchant_accounts.payto_uri"
- ",exchange_url"
- ",credit_serial"
- ",merchant_transfer_signatures.execution_time"
- ",verified"
- ",confirmed"
- " FROM merchant_transfers mt"
- " JOIN merchant_accounts USING (account_serial)"
- " JOIN merchant_transfer_signatures USING (credit_serial)"
- " WHERE execution_time < $2"
- " AND execution_time >= $3"
- " AND credit_serial < $4"
- " AND payto_uri = $6"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " ORDER BY credit_serial DESC"
- " LIMIT $5"),
- /* for postgres_lookup_transfers() */
- GNUNET_PQ_make_prepare ("lookup_transfers_time_desc",
- "SELECT"
- " mt.credit_amount_val"
- ",mt.credit_amount_frac"
- ",wtid"
- ",merchant_accounts.payto_uri"
- ",exchange_url"
- ",credit_serial"
- ",merchant_transfer_signatures.execution_time"
- ",verified"
- ",confirmed"
- " FROM merchant_transfers mt"
- " JOIN merchant_accounts USING (account_serial)"
- " JOIN merchant_transfer_signatures USING (credit_serial)"
- " WHERE execution_time < $2"
- " AND execution_time >= $3"
- " AND credit_serial < $4"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " ORDER BY credit_serial DESC"
- " LIMIT $5"),
- /* for postgres_lookup_transfers() */
- GNUNET_PQ_make_prepare ("lookup_transfers_payto_desc",
- "SELECT"
- " mt.credit_amount_val"
- ",mt.credit_amount_frac"
- ",wtid"
- ",merchant_accounts.payto_uri"
- ",exchange_url"
- ",credit_serial"
- ",CASE WHEN (merchant_transfer_signatures.execution_time) IS NULL"
- " THEN 9223372036854775807" /* largest BIGINT possible */
- " ELSE merchant_transfer_signatures.execution_time"
- " END AS execution_time"
- ",verified"
- ",confirmed"
- " FROM merchant_transfers mt"
- " JOIN merchant_accounts USING (account_serial)"
- " LEFT JOIN merchant_transfer_signatures USING (credit_serial)"
- " WHERE credit_serial < $2"
- " AND payto_uri = $4"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " ORDER BY credit_serial DESC"
- " LIMIT $3"),
- /* for postgres_lookup_transfers() */
- GNUNET_PQ_make_prepare ("lookup_transfers_desc",
- "SELECT"
- " mt.credit_amount_val"
- ",mt.credit_amount_frac"
- ",wtid"
- ",merchant_accounts.payto_uri"
- ",exchange_url"
- ",credit_serial"
- ",CASE WHEN (merchant_transfer_signatures.execution_time) IS NULL"
- " THEN 9223372036854775807" /* largest BIGINT possible */
- " ELSE merchant_transfer_signatures.execution_time"
- " END AS execution_time"
- ",verified"
- ",confirmed"
- " FROM merchant_transfers mt"
- " JOIN merchant_accounts USING (account_serial)"
- " LEFT JOIN merchant_transfer_signatures USING (credit_serial)"
- " WHERE credit_serial < $2"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " ORDER BY credit_serial DESC"
- " LIMIT $3"),
- /* For postgres_store_wire_fee_by_exchange() */
- GNUNET_PQ_make_prepare ("insert_wire_fee",
- "INSERT INTO merchant_exchange_wire_fees"
- "(master_pub"
- ",h_wire_method"
- ",wire_fee_val"
- ",wire_fee_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",start_date"
- ",end_date"
- ",master_sig)"
- " VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8, $9)"),
- /* For postgres_insert_reserve() */
- GNUNET_PQ_make_prepare ("insert_reserve",
- "INSERT INTO merchant_tip_reserves"
- "(reserve_pub"
- ",merchant_serial"
- ",creation_time"
- ",expiration"
- ",merchant_initial_balance_val"
- ",merchant_initial_balance_frac"
- ")"
- "SELECT $2, merchant_serial, $3, $4, $5, $6"
- " FROM merchant_instances"
- " WHERE merchant_id=$1"),
- /* For postgres_activate_reserve() */
- GNUNET_PQ_make_prepare ("activate_reserve",
- "UPDATE merchant_tip_reserves SET"
- " exchange_initial_balance_val=$3"
- ",exchange_initial_balance_frac=$4"
- " WHERE reserve_pub=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* For postgres_insert_reserve() */
- GNUNET_PQ_make_prepare ("insert_reserve_key",
- "INSERT INTO merchant_tip_reserve_keys"
- "(reserve_serial"
- ",reserve_priv"
- ",exchange_url"
- ",payto_uri"
- ")"
- "SELECT reserve_serial, $3, $4, $5"
- " FROM merchant_tip_reserves"
- " WHERE reserve_pub=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* For postgres_lookup_reserves() */
- GNUNET_PQ_make_prepare ("lookup_reserves",
- "SELECT"
- " reserve_pub"
- ",creation_time"
- ",expiration"
- ",merchant_initial_balance_val"
- ",merchant_initial_balance_frac"
- ",exchange_initial_balance_val"
- ",exchange_initial_balance_frac"
- ",tips_committed_val"
- ",tips_committed_frac"
- ",tips_picked_up_val"
- ",tips_picked_up_frac"
- ",reserve_priv IS NOT NULL AS active"
- " FROM merchant_tip_reserves"
- " FULL OUTER JOIN merchant_tip_reserve_keys USING (reserve_serial)"
- " WHERE creation_time > $2"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* For postgres_lookup_pending_reserves() */
- GNUNET_PQ_make_prepare ("lookup_pending_reserves",
- "SELECT"
- " reserve_pub"
- ",merchant_id"
- ",exchange_url"
- ",merchant_initial_balance_val"
- ",merchant_initial_balance_frac"
- " FROM merchant_tip_reserves"
- " JOIN merchant_instances USING (merchant_serial)"
- " JOIN merchant_tip_reserve_keys USING (reserve_serial)"
- " WHERE exchange_initial_balance_val=0"
- " AND exchange_initial_balance_frac=0"),
- /* For postgres_lookup_reserve() */
- GNUNET_PQ_make_prepare ("lookup_reserve",
- "SELECT"
- " creation_time"
- ",expiration"
- ",merchant_initial_balance_val"
- ",merchant_initial_balance_frac"
- ",exchange_initial_balance_val"
- ",exchange_initial_balance_frac"
- ",tips_committed_val"
- ",tips_committed_frac"
- ",tips_picked_up_val"
- ",tips_picked_up_frac"
- ",reserve_priv IS NOT NULL AS active"
- ",exchange_url"
- ",payto_uri"
- " FROM merchant_tip_reserves"
- " FULL OUTER JOIN merchant_tip_reserve_keys USING (reserve_serial)"
- " WHERE reserve_pub = $2"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* For postgres_lookup_reserve() */
- GNUNET_PQ_make_prepare ("lookup_reserve_tips",
- "SELECT"
- " justification"
- ",tip_id"
- ",amount_val"
- ",amount_frac"
- " FROM merchant_tips"
- " WHERE reserve_serial ="
- " (SELECT reserve_serial"
- " FROM merchant_tip_reserves"
- " WHERE reserve_pub=$2"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))"),
- /* for postgres_delete_reserve() */
- GNUNET_PQ_make_prepare ("delete_reserve",
- "DELETE"
- " FROM merchant_tip_reserve_keys"
- " WHERE reserve_serial="
- " (SELECT reserve_serial"
- " FROM merchant_tip_reserves"
- " WHERE reserve_pub=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))"),
- /* for postgres_purge_reserve() */
- GNUNET_PQ_make_prepare ("purge_reserve",
- "DELETE"
- " FROM merchant_tip_reserves"
- " WHERE reserve_pub=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* For postgres_authorize_tip() */
- GNUNET_PQ_make_prepare ("lookup_reserve_for_tip",
- "SELECT"
- " reserve_pub"
- ",expiration"
- ",exchange_initial_balance_val"
- ",exchange_initial_balance_frac"
- ",tips_committed_val"
- ",tips_committed_frac"
- " FROM merchant_tip_reserves"
- " WHERE"
- " merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
-
- /* For postgres_authorize_tip() */
- GNUNET_PQ_make_prepare ("lookup_reserve_status",
- "SELECT"
- " expiration"
- ",exchange_initial_balance_val"
- ",exchange_initial_balance_frac"
- ",tips_committed_val"
- ",tips_committed_frac"
- " FROM merchant_tip_reserves"
- " WHERE reserve_pub = $2"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* For postgres_authorize_tip() */
- GNUNET_PQ_make_prepare ("update_reserve_tips_committed",
- "UPDATE merchant_tip_reserves SET"
- " tips_committed_val=$3"
- ",tips_committed_frac=$4"
- " WHERE reserve_pub = $2"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* For postgres_authorize_tip() */
- GNUNET_PQ_make_prepare ("insert_tip",
- "INSERT INTO merchant_tips"
- "(reserve_serial"
- ",tip_id"
- ",justification"
- ",next_url"
- ",expiration"
- ",amount_val"
- ",amount_frac"
- ") "
- "SELECT"
- " reserve_serial, $3, $4, $5, $6, $7, $8"
- " FROM merchant_tip_reserves"
- " WHERE reserve_pub=$2"
- " AND merchant_serial = "
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* For postgres_lookup_pickup() */
- GNUNET_PQ_make_prepare ("lookup_pickup",
- "SELECT"
- " exchange_url"
- ",reserve_priv"
- ",pickup_serial"
- " FROM merchant_tip_pickups"
- " JOIN merchant_tips USING (tip_serial)"
- " JOIN merchant_tip_reserves USING (reserve_serial)"
- " JOIN merchant_tip_reserve_keys USING (reserve_serial)"
- " WHERE pickup_id = $3"
- " AND tip_id = $2"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* For postgres_lookup_pickup() */
- GNUNET_PQ_make_prepare ("lookup_pickup_signatures",
- "SELECT"
- " coin_offset"
- ",blind_sig"
- " FROM merchant_tip_pickup_signatures"
- " WHERE pickup_serial = $1"),
-
- /* For postgres_lookup_tip() */
- GNUNET_PQ_make_prepare ("lookup_tip",
- "SELECT"
- " amount_val"
- ",amount_frac"
- ",picked_up_val"
- ",picked_up_frac"
- ",merchant_tips.expiration"
- ",exchange_url"
- ",reserve_priv"
- " FROM merchant_tips"
- " JOIN merchant_tip_reserves USING (reserve_serial)"
- " JOIN merchant_tip_reserve_keys USING (reserve_serial)"
- " WHERE tip_id = $2"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* For postgres_lookup_tip() */
- GNUNET_PQ_make_prepare ("lookup_tips_inc",
- "SELECT"
- " tip_serial"
- ",tip_id"
- ",amount_val"
- ",amount_frac"
- ",CAST($4 as BIGINT)" /* otherwise $4 is unused and Postgres unhappy */
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- " FROM merchant_tips"
- " JOIN merchant_tip_reserves USING (reserve_serial)"
- " WHERE merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " tip_serial > $3"
- " ORDER BY tip_serial ASC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_tips_dec",
- "SELECT"
- " tip_serial"
- ",tip_id"
- ",amount_val"
- ",amount_frac"
- ",CAST($4 as BIGINT)" /* otherwise $4 is unused and Postgres unhappy */
- ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
- " FROM merchant_tips"
- " JOIN merchant_tip_reserves USING (reserve_serial)"
- " WHERE merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " tip_serial < $3"
- " ORDER BY tip_serial DESC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_tips_inc_expired",
- "SELECT"
- " tip_serial"
- ",tip_id"
- ",amount_val"
- ",amount_frac"
- " FROM merchant_tips"
- " JOIN merchant_tip_reserves USING (reserve_serial)"
- " WHERE merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " tip_serial > $3"
- " AND"
- " CAST($5 as BOOL) = (merchant_tips.expiration < $4)"
- " ORDER BY tip_serial ASC"
- " LIMIT $2"),
- GNUNET_PQ_make_prepare ("lookup_tips_dec_expired",
- "SELECT"
- " tip_serial"
- ",tip_id"
- ",amount_val"
- ",amount_frac"
- " FROM merchant_tips"
- " JOIN merchant_tip_reserves USING (reserve_serial)"
- " WHERE merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " tip_serial < $3"
- " AND"
- " CAST($5 as BOOL) = (merchant_tips.expiration < $4)"
- " ORDER BY tip_serial DESC"
- " LIMIT $2"),
- /* for postgres_lookup_tip_details() */
- GNUNET_PQ_make_prepare ("lookup_tip_details",
- "SELECT"
- " tip_serial"
- ",amount_val"
- ",amount_frac"
- ",picked_up_val"
- ",picked_up_frac"
- ",justification"
- ",merchant_tips.expiration"
- ",reserve_pub"
- " FROM merchant_tips"
- " JOIN merchant_tip_reserves USING (reserve_serial)"
- " WHERE tip_id = $2"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* for postgres_lookup_tip_details() */
- GNUNET_PQ_make_prepare ("lookup_pickup_details",
- "SELECT"
- " pickup_id"
- ",amount_val"
- ",amount_frac"
- ",COUNT(blind_sig) AS num_planchets"
- " FROM merchant_tip_pickups"
- " JOIN merchant_tip_pickup_signatures USING (pickup_serial)"
- " WHERE tip_serial = $1"
- " GROUP BY pickup_serial"),
- /* for postgres_insert_pickup() */
- GNUNET_PQ_make_prepare ("insert_pickup",
- "INSERT INTO merchant_tip_pickups"
- "(tip_serial"
- ",pickup_id"
- ",amount_val"
- ",amount_frac"
- ") "
- "SELECT"
- " tip_serial, $3, $4, $5"
- " FROM merchant_tips"
- " JOIN merchant_tip_reserves USING (reserve_serial)"
- " WHERE tip_id=$2"
- " AND merchant_serial = "
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* for postgres_insert_pickup() */
- GNUNET_PQ_make_prepare ("update_picked_up_tip",
- "UPDATE merchant_tips SET"
- " picked_up_val=$2"
- ",picked_up_frac=$3"
- ",was_picked_up = ($2 = amount_val AND $3 = amount_frac)"
- " WHERE tip_id = $1"),
- /* for postgres_insert_pickup() */
- GNUNET_PQ_make_prepare ("lookup_picked_up_reserve",
- "SELECT"
- " reserve_serial"
- ",tips_picked_up_val"
- ",tips_picked_up_frac"
- " FROM merchant_tip_reserves"
- " JOIN merchant_tips USING (reserve_serial)"
- " WHERE tip_id=$2"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"),
- /* for postgres_insert_pickup() */
- GNUNET_PQ_make_prepare ("update_picked_up_reserve",
- "UPDATE merchant_tip_reserves SET"
- " tips_picked_up_val=$2"
- ",tips_picked_up_frac=$3"
- " WHERE reserve_serial = $1"),
- /* for postgres_insert_pickup_blind_signature() */
- GNUNET_PQ_make_prepare ("insert_pickup_blind_signature",
- "INSERT INTO merchant_tip_pickup_signatures"
- "(pickup_serial"
- ",coin_offset"
- ",blind_sig"
- ") "
- "SELECT"
- " pickup_serial, $2, $3"
- " FROM merchant_tip_pickups"
- " WHERE pickup_id=$1"),
- /* for postgres_lookup_templates() */
- GNUNET_PQ_make_prepare ("lookup_templates",
- "SELECT"
- " template_id"
- ",template_description"
- " FROM merchant_template"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"),
- /* for postgres_lookup_template() */
- GNUNET_PQ_make_prepare ("lookup_template",
- "SELECT"
- " template_description"
- ",image"
- ",template_contract"
- " FROM merchant_template"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND merchant_template.template_id=$2"),
- /* for postgres_delete_template() */
- GNUNET_PQ_make_prepare ("delete_template",
- "DELETE"
- " FROM merchant_template"
- " WHERE merchant_template.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_template.template_id=$2"),
- /* for postgres_insert_template() */
- GNUNET_PQ_make_prepare ("insert_template",
- "INSERT INTO merchant_template"
- "(merchant_serial"
- ",template_id"
- ",template_description"
- ",image"
- ",template_contract"
- ")"
- " SELECT merchant_serial,"
- " $2, $3, $4, $5"
- " FROM merchant_instances"
- " WHERE merchant_id=$1"),
- /* for postgres_update_template() */
- GNUNET_PQ_make_prepare ("update_template",
- "UPDATE merchant_template SET"
- " template_description=$3"
- ",image=$4"
- ",template_contract=$5"
- " WHERE merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND template_id=$2"),
- /* for postgres_lookup_webhooks() */
- GNUNET_PQ_make_prepare ("lookup_webhooks",
- "SELECT"
- " webhook_id"
- ",event_type"
- " FROM merchant_webhook"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"),
- /* for postgres_lookup_webhook() */
- GNUNET_PQ_make_prepare ("lookup_webhook",
- "SELECT"
- " event_type"
- ",url"
- ",http_method"
- ",header_template"
- ",body_template"
- " FROM merchant_webhook"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND merchant_webhook.webhook_id=$2"),
- /* for postgres_delete_webhook() */
- GNUNET_PQ_make_prepare ("delete_webhook",
- "DELETE"
- " FROM merchant_webhook"
- " WHERE merchant_webhook.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_webhook.webhook_id=$2"),
- /* for postgres_insert_webhook() */
- GNUNET_PQ_make_prepare ("insert_webhook",
- "INSERT INTO merchant_webhook"
- "(merchant_serial"
- ",webhook_id"
- ",event_type"
- ",url"
- ",http_method"
- ",header_template"
- ",body_template"
- ")"
- " SELECT merchant_serial,"
- " $2, $3, $4, $5, $6, $7"
- " FROM merchant_instances"
- " WHERE merchant_id=$1"),
- /* for postgres_update_webhook() */
- GNUNET_PQ_make_prepare ("update_webhook",
- "UPDATE merchant_webhook SET"
- " event_type=$3"
- ",url=$4"
- ",http_method=$5"
- ",header_template=$6"
- ",body_template=$7"
- " WHERE merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND webhook_id=$2"),
- /* for postgres_lookup_webhook_by_event() */
- GNUNET_PQ_make_prepare ("lookup_webhook_by_event",
- "SELECT"
- " webhook_serial"
- ",event_type"
- ",url"
- ",http_method"
- ",header_template"
- ",body_template"
- " FROM merchant_webhook"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND event_type=$2"),
- /* for postgres_delete_pending_webhook() */
- GNUNET_PQ_make_prepare ("delete_pending_webhook",
- "DELETE"
- " FROM merchant_pending_webhooks"
- " WHERE merchant_pending_webhooks.webhook_serial="
- " (SELECT webhook_serial "
- " FROM merchant_webhook"
- " WHERE webhook_serial=$1)"),
- /* for postgres_insert_pending_webhook() */
- GNUNET_PQ_make_prepare ("insert_pending_webhook",
- "INSERT INTO merchant_pending_webhooks"
- "(merchant_serial"
- ",webhook_serial"
- ",url"
- ",http_method"
- ",header"
- ",body"
- ")"
- " SELECT mi.merchant_serial,"
- " $2, $3, $4, $5, $6"
- " FROM merchant_instances mi"
- " WHERE mi.merchant_id=$1"),
- /* for postgres_update_pending_webhook() */
- GNUNET_PQ_make_prepare ("update_pending_webhook",
- "UPDATE merchant_pending_webhooks SET"
- " retries=retries+1"
- ",next_attempt=$2"
- " WHERE webhook_serial="
- " (SELECT webhook_serial"
- " FROM merchant_webhook"
- " WHERE webhook_serial=$1)"),
- /* for postgres_lookup_pending_webhook() */
- GNUNET_PQ_make_prepare ("lookup_pending_webhook",
- "SELECT"
- " next_attempt"
- ",retries"
- ",url"
- ",http_method"
- ",header"
- ",body"
- " FROM merchant_pending_webhooks"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND merchant_pending_webhooks.webhook_serial=$2"),
- /* for postgres_lookup_pending_webhooks() */
- GNUNET_PQ_make_prepare ("lookup_pending_webhooks",
- "SELECT"
- " webhook_serial"
- ",next_attempt"
- ",retries"
- ",url"
- ",http_method"
- ",header"
- ",body"
- " FROM merchant_pending_webhooks"
- " WHERE next_attempt <= $1"
- " ORDER BY next_attempt ASC"
- ),
- /* for postgres_lookup_future_webhook() */
- GNUNET_PQ_make_prepare ("lookup_future_webhook",
- "SELECT"
- " webhook_serial"
- ",next_attempt"
- ",retries"
- ",url"
- ",http_method"
- ",header"
- ",body"
- " FROM merchant_pending_webhooks"
- " ORDER BY next_attempt ASC LIMIT 1"
- ),
- /* for postgres_lookup_all_webhooks() */
- GNUNET_PQ_make_prepare ("lookup_all_webhooks",
- " SELECT"
- " webhook_serial"
- ",next_attempt"
- ",retries"
- ",url"
- ",http_method"
- ",header"
- ",body"
- " FROM merchant_pending_webhooks"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND webhook_serial > $2"
- " ORDER BY webhook_serial"
- " ASC LIMIT $3"),
- GNUNET_PQ_PREPARED_STATEMENT_END
- };
struct GNUNET_PQ_ExecuteStatement es[] = {
GNUNET_PQ_make_try_execute ("SET search_path TO merchant;"),
GNUNET_PQ_EXECUTE_STATEMENT_END
@@ -10515,7 +309,8 @@ postgres_connect (void *cls)
"merchantdb-postgres",
NULL,
es,
- ps);
+ NULL);
+ pg->prep_gen++;
if (NULL == pg->conn)
return GNUNET_SYSERR;
return GNUNET_OK;
@@ -10549,14 +344,6 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
GNUNET_free (pg);
return NULL;
}
- if (GNUNET_OK !=
- TALER_config_get_currency (cfg,
- &pg->currency))
- {
- GNUNET_free (pg->sql_dir);
- GNUNET_free (pg);
- return NULL;
- }
plugin = GNUNET_new (struct TALER_MERCHANTDB_Plugin);
plugin->cls = pg;
plugin->connect = &postgres_connect;
@@ -10566,111 +353,231 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
plugin->event_listen_cancel = &postgres_event_listen_cancel;
plugin->event_notify = &postgres_event_notify;
plugin->preflight = &postgres_preflight;
- plugin->start = &postgres_start;
- plugin->start_read_committed = &postgres_start_read_committed;
- plugin->rollback = &postgres_rollback;
- plugin->commit = &postgres_commit;
- plugin->lookup_instances = &postgres_lookup_instances;
- plugin->lookup_instance = &postgres_lookup_instance;
- plugin->lookup_instance_auth = &postgres_lookup_instance_auth;
- plugin->insert_instance = &postgres_insert_instance;
- plugin->insert_account = &postgres_insert_account;
+ plugin->start = &TMH_PG_start;
+ plugin->start_read_committed = &TMH_PG_start_read_committed;
+ plugin->rollback = &TMH_PG_rollback;
+ plugin->commit = &TMH_PG_commit;
+ plugin->insert_login_token
+ = &TMH_PG_insert_login_token;
+ plugin->delete_login_token
+ = &TMH_PG_delete_login_token;
+ plugin->select_login_token
+ = &TMH_PG_select_login_token;
+ plugin->select_account_by_uri
+ = &TMH_PG_select_account_by_uri;
+ plugin->lookup_instance_auth
+ = &TMH_PG_lookup_instance_auth;
+ plugin->insert_instance
+ = &TMH_PG_insert_instance;
+ plugin->insert_account
+ = &TMH_PG_insert_account;
+ plugin->lookup_otp_devices
+ = &TMH_PG_lookup_otp_devices;
+ plugin->delete_template
+ = &TMH_PG_delete_template;
+ plugin->insert_template
+ = &TMH_PG_insert_template;
+ plugin->update_template
+ = &TMH_PG_update_template;
+ plugin->lookup_templates
+ = &TMH_PG_lookup_templates;
+ plugin->lookup_template
+ = &TMH_PG_lookup_template;
+ plugin->update_account
+ = &TMH_PG_update_account;
plugin->account_kyc_set_status
- = &postgres_account_kyc_set_status;
+ = &TMH_PG_account_kyc_set_status;
plugin->account_kyc_get_status
- = &postgres_account_kyc_get_status;
- plugin->delete_instance_private_key = &postgres_delete_instance_private_key;
- plugin->purge_instance = &postgres_purge_instance;
- plugin->update_instance = &postgres_update_instance;
- plugin->update_instance_auth = &postgres_update_instance_auth;
- plugin->activate_account = &postgres_activate_account;
- plugin->inactivate_account = &postgres_inactivate_account;
- plugin->lookup_products = &postgres_lookup_products;
- plugin->lookup_product = &postgres_lookup_product;
- plugin->delete_product = &postgres_delete_product;
- plugin->insert_product = &postgres_insert_product;
- plugin->update_product = &postgres_update_product;
- plugin->lock_product = &postgres_lock_product;
- plugin->expire_locks = &postgres_expire_locks;
- plugin->delete_order = &postgres_delete_order;
- plugin->lookup_order = &postgres_lookup_order;
- plugin->lookup_order_summary = &postgres_lookup_order_summary;
- plugin->lookup_orders = &postgres_lookup_orders;
- plugin->insert_order = &postgres_insert_order;
- plugin->unlock_inventory = &postgres_unlock_inventory;
- plugin->insert_order_lock = &postgres_insert_order_lock;
- plugin->lookup_contract_terms = &postgres_lookup_contract_terms;
- plugin->insert_contract_terms = &postgres_insert_contract_terms;
- plugin->update_contract_terms = &postgres_update_contract_terms;
- plugin->delete_contract_terms = &postgres_delete_contract_terms;
- plugin->lookup_deposits = &postgres_lookup_deposits;
- plugin->insert_exchange_signkey = &postgres_insert_exchange_signkey;
- plugin->insert_deposit = &postgres_insert_deposit;
- plugin->lookup_refunds = &postgres_lookup_refunds;
- plugin->mark_contract_paid = &postgres_mark_contract_paid;
- plugin->refund_coin = &postgres_refund_coin;
- plugin->lookup_order_status = &postgres_lookup_order_status;
- plugin->lookup_order_status_by_serial =
- &postgres_lookup_order_status_by_serial;
- plugin->lookup_payment_status = &postgres_lookup_payment_status;
- plugin->lookup_deposits_by_order = &postgres_lookup_deposits_by_order;
- plugin->lookup_transfer_details_by_order =
- &postgres_lookup_transfer_details_by_order;
- plugin->insert_deposit_to_transfer = &postgres_insert_deposit_to_transfer;
- plugin->mark_order_wired = &postgres_mark_order_wired;
- plugin->increase_refund = &postgres_increase_refund;
- plugin->lookup_refunds_detailed = &postgres_lookup_refunds_detailed;
- plugin->insert_refund_proof = &postgres_insert_refund_proof;
- plugin->lookup_refund_proof = &postgres_lookup_refund_proof;
- plugin->lookup_order_by_fulfillment = &postgres_lookup_order_by_fulfillment;
- plugin->insert_transfer = &postgres_insert_transfer;
- plugin->delete_transfer = &postgres_delete_transfer;
- plugin->check_transfer_exists = &postgres_check_transfer_exists;
- plugin->lookup_account = &postgres_lookup_account;
- plugin->insert_transfer_details = &postgres_insert_transfer_details;
- plugin->lookup_wire_fee = &postgres_lookup_wire_fee;
- plugin->lookup_deposits_by_contract_and_coin =
- &postgres_lookup_deposits_by_contract_and_coin;
- plugin->lookup_transfer = &postgres_lookup_transfer;
- plugin->set_transfer_status_to_verified =
- &postgres_set_transfer_status_to_verified;
- plugin->lookup_transfer_summary = &postgres_lookup_transfer_summary;
- plugin->lookup_transfer_details = &postgres_lookup_transfer_details;
- plugin->lookup_transfers = &postgres_lookup_transfers;
- plugin->store_wire_fee_by_exchange = &postgres_store_wire_fee_by_exchange;
- plugin->insert_reserve = &postgres_insert_reserve;
- plugin->activate_reserve = &postgres_activate_reserve;
- plugin->lookup_reserves = &postgres_lookup_reserves;
- plugin->lookup_pending_reserves = &postgres_lookup_pending_reserves;
- plugin->lookup_reserve = &postgres_lookup_reserve;
- plugin->delete_reserve = &postgres_delete_reserve;
- plugin->purge_reserve = &postgres_purge_reserve;
- plugin->authorize_tip = &postgres_authorize_tip;
- plugin->lookup_pickup = &postgres_lookup_pickup;
- plugin->lookup_tip = &postgres_lookup_tip;
- plugin->lookup_tips = &postgres_lookup_tips;
- plugin->lookup_tip_details = &postgres_lookup_tip_details;
- plugin->insert_pickup = &postgres_insert_pickup;
- plugin->insert_pickup_blind_signature =
- &postgres_insert_pickup_blind_signature;
- plugin->lookup_templates = &postgres_lookup_templates;
- plugin->lookup_template = &postgres_lookup_template;
- plugin->delete_template = &postgres_delete_template;
- plugin->insert_template = &postgres_insert_template;
- plugin->update_template = &postgres_update_template;
- plugin->lookup_webhooks = &postgres_lookup_webhooks;
- plugin->lookup_webhook = &postgres_lookup_webhook;
- plugin->delete_webhook = &postgres_delete_webhook;
- plugin->insert_webhook = &postgres_insert_webhook;
- plugin->update_webhook = &postgres_update_webhook;
- plugin->lookup_pending_webhook = &postgres_lookup_pending_webhook;
- plugin->lookup_webhook_by_event = &postgres_lookup_webhook_by_event;
- plugin->lookup_all_webhooks = &postgres_lookup_all_webhooks;
- plugin->lookup_future_webhook = &postgres_lookup_future_webhook;
- plugin->lookup_pending_webhooks = &postgres_lookup_pending_webhooks;
- plugin->delete_pending_webhook = &postgres_delete_pending_webhook;
- plugin->insert_pending_webhook = &postgres_insert_pending_webhook;
- plugin->update_pending_webhook = &postgres_update_pending_webhook;
+ = &TMH_PG_account_kyc_get_status;
+ plugin->delete_instance_private_key
+ = &TMH_PG_delete_instance_private_key;
+ plugin->purge_instance
+ = &TMH_PG_purge_instance;
+ plugin->update_instance
+ = &TMH_PG_update_instance;
+ plugin->update_instance_auth
+ = &TMH_PG_update_instance_auth;
+ plugin->activate_account
+ = &TMH_PG_activate_account;
+ plugin->inactivate_account
+ = &TMH_PG_inactivate_account;
+ plugin->update_transfer_status
+ = &TMH_PG_update_transfer_status;
+ plugin->lookup_products
+ = &TMH_PG_lookup_products;
+ plugin->lookup_product
+ = &TMH_PG_lookup_product;
+ plugin->delete_product
+ = &TMH_PG_delete_product;
+ plugin->insert_product
+ = &TMH_PG_insert_product;
+ plugin->update_product
+ = &TMH_PG_update_product;
+ plugin->insert_otp
+ = &TMH_PG_insert_otp;
+ plugin->delete_otp
+ = &TMH_PG_delete_otp;
+ plugin->update_otp
+ = &TMH_PG_update_otp;
+ plugin->select_otp
+ = &TMH_PG_select_otp;
+ plugin->select_otp_serial
+ = &TMH_PG_select_otp_serial;
+ plugin->lock_product
+ = &TMH_PG_lock_product;
+ plugin->expire_locks
+ = &TMH_PG_expire_locks;
+ plugin->delete_order
+ = &TMH_PG_delete_order;
+ plugin->lookup_order
+ = &TMH_PG_lookup_order;
+ plugin->lookup_order_summary
+ = &TMH_PG_lookup_order_summary;
+ plugin->lookup_orders
+ = &TMH_PG_lookup_orders;
+ plugin->insert_order
+ = &TMH_PG_insert_order;
+ plugin->unlock_inventory
+ = &TMH_PG_unlock_inventory;
+ plugin->insert_order_lock
+ = &TMH_PG_insert_order_lock;
+ plugin->lookup_contract_terms
+ = &TMH_PG_lookup_contract_terms;
+ plugin->lookup_contract_terms2
+ = &TMH_PG_lookup_contract_terms2;
+ plugin->lookup_contract_terms3
+ = &TMH_PG_lookup_contract_terms3;
+ plugin->insert_contract_terms
+ = &TMH_PG_insert_contract_terms;
+ plugin->update_contract_terms
+ = &TMH_PG_update_contract_terms;
+ plugin->delete_contract_terms
+ = &TMH_PG_delete_contract_terms;
+ plugin->lookup_deposits
+ = &TMH_PG_lookup_deposits;
+ plugin->insert_exchange_signkey
+ = &TMH_PG_insert_exchange_signkey;
+ plugin->insert_deposit_confirmation
+ = &TMH_PG_insert_deposit_confirmation;
+ plugin->insert_deposit
+ = &TMH_PG_insert_deposit;
+ plugin->lookup_refunds
+ = &TMH_PG_lookup_refunds;
+ plugin->mark_contract_paid
+ = &TMH_PG_mark_contract_paid;
+ plugin->refund_coin
+ = &TMH_PG_refund_coin;
+ plugin->lookup_order_status
+ = &TMH_PG_lookup_order_status;
+ plugin->lookup_order_status_by_serial
+ = &TMH_PG_lookup_order_status_by_serial;
+ plugin->lookup_deposits_by_order
+ = &TMH_PG_lookup_deposits_by_order;
+ plugin->lookup_transfer_details_by_order
+ = &TMH_PG_lookup_transfer_details_by_order;
+ plugin->mark_order_wired
+ = &TMH_PG_mark_order_wired;
+ plugin->increase_refund
+ = &TMH_PG_increase_refund;
+ plugin->lookup_refunds_detailed
+ = &TMH_PG_lookup_refunds_detailed;
+ plugin->insert_refund_proof
+ = &TMH_PG_insert_refund_proof;
+ plugin->lookup_refund_proof
+ = &TMH_PG_lookup_refund_proof;
+ plugin->lookup_order_by_fulfillment
+ = &TMH_PG_lookup_order_by_fulfillment;
+ plugin->delete_transfer
+ = &TMH_PG_delete_transfer;
+ plugin->check_transfer_exists
+ = &TMH_PG_check_transfer_exists;
+ plugin->lookup_account
+ = &TMH_PG_lookup_account;
+ plugin->lookup_wire_fee
+ = &TMH_PG_lookup_wire_fee;
+ plugin->lookup_deposits_by_contract_and_coin
+ = &TMH_PG_lookup_deposits_by_contract_and_coin;
+ plugin->lookup_transfer
+ = &TMH_PG_lookup_transfer;
+ plugin->set_transfer_status_to_confirmed
+ = &TMH_PG_set_transfer_status_to_confirmed;
+ plugin->lookup_transfer_summary
+ = &TMH_PG_lookup_transfer_summary;
+ plugin->lookup_transfer_details
+ = &TMH_PG_lookup_transfer_details;
+ plugin->lookup_instances
+ = &TMH_PG_lookup_instances;
+ plugin->lookup_instance
+ = &TMH_PG_lookup_instance;
+ plugin->lookup_transfers
+ = &TMH_PG_lookup_transfers;
+ plugin->update_wirewatch_progress
+ = &TMH_PG_update_wirewatch_progress;
+ plugin->select_wirewatch_accounts
+ = &TMH_PG_select_wirewatch_accounts;
+ plugin->select_account
+ = &TMH_PG_select_account;
+ plugin->select_accounts
+ = &TMH_PG_select_accounts;
+ plugin->select_open_transfers
+ = &TMH_PG_select_open_transfers;
+ plugin->insert_exchange_keys
+ = &TMH_PG_insert_exchange_keys;
+ plugin->select_exchange_keys
+ = &TMH_PG_select_exchange_keys;
+ plugin->insert_deposit_to_transfer
+ = &TMH_PG_insert_deposit_to_transfer;
+ plugin->insert_transfer
+ = &TMH_PG_insert_transfer;
+ plugin->insert_transfer_details
+ = &TMH_PG_insert_transfer_details;
+ plugin->store_wire_fee_by_exchange
+ = &TMH_PG_store_wire_fee_by_exchange;
+ plugin->lookup_webhooks
+ = &TMH_PG_lookup_webhooks;
+ plugin->lookup_webhook
+ = &TMH_PG_lookup_webhook;
+ plugin->delete_webhook
+ = &TMH_PG_delete_webhook;
+ plugin->insert_webhook
+ = &TMH_PG_insert_webhook;
+ plugin->update_webhook
+ = &TMH_PG_update_webhook;
+ plugin->lookup_pending_deposits
+ = &TMH_PG_lookup_pending_deposits;
+ plugin->lookup_webhook_by_event
+ = &TMH_PG_lookup_webhook_by_event;
+ plugin->lookup_all_webhooks
+ = &TMH_PG_lookup_all_webhooks;
+ plugin->lookup_future_webhook
+ = &TMH_PG_lookup_future_webhook;
+ plugin->lookup_pending_webhooks
+ = &TMH_PG_lookup_pending_webhooks;
+ plugin->delete_pending_webhook
+ = &TMH_PG_delete_pending_webhook;
+ plugin->insert_pending_webhook
+ = &TMH_PG_insert_pending_webhook;
+ plugin->update_pending_webhook
+ = &TMH_PG_update_pending_webhook;
+ plugin->delete_exchange_accounts
+ = &TMH_PG_delete_exchange_accounts;
+ plugin->select_accounts_by_exchange
+ = &TMH_PG_select_accounts_by_exchange;
+ plugin->insert_exchange_account
+ = &TMH_PG_insert_exchange_account;
+ plugin->insert_token_family
+ = &TMH_PG_insert_token_family;
+ plugin->lookup_token_family
+ = &TMH_PG_lookup_token_family;
+ plugin->lookup_token_families
+ = &TMH_PG_lookup_token_families;
+ plugin->delete_token_family
+ = &TMH_PG_delete_token_family;
+ plugin->update_token_family
+ = &TMH_PG_update_token_family;
+ plugin->update_deposit_confirmation_status
+ = &TMH_PG_update_deposit_confirmation_status;
+
return plugin;
}
@@ -10693,7 +600,6 @@ libtaler_plugin_merchantdb_postgres_done (void *cls)
pg->conn = NULL;
}
GNUNET_free (pg->sql_dir);
- GNUNET_free (pg->currency);
GNUNET_free (pg);
GNUNET_free (plugin);
return NULL;
diff --git a/src/backenddb/procedures.sql.in b/src/backenddb/procedures.sql.in
new file mode 100644
index 00000000..3ebf8b8b
--- /dev/null
+++ b/src/backenddb/procedures.sql.in
@@ -0,0 +1,24 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+BEGIN;
+
+SET search_path TO merchant;
+
+#include "pg_insert_deposit_to_transfer.sql"
+#include "pg_insert_transfer_details.sql"
+
+COMMIT;
diff --git a/src/backenddb/test-merchantdb-postgres.conf b/src/backenddb/test-merchantdb-postgres.conf
index 03bbc35a..5ddb86d8 100644
--- a/src/backenddb/test-merchantdb-postgres.conf
+++ b/src/backenddb/test-merchantdb-postgres.conf
@@ -6,7 +6,7 @@ CONFIG = postgres:///talercheck
# Where are the SQL files to setup our tables?
# Important: this MUST end with a "/"!
-SQL_DIR = $DATADIR/sql/merchant/
+SQL_DIR = ${DATADIR}sql/merchant/
[taler]
CURRENCY = "EUR" \ No newline at end of file
diff --git a/src/backenddb/test.conf b/src/backenddb/test.conf
new file mode 100644
index 00000000..b3955d6f
--- /dev/null
+++ b/src/backenddb/test.conf
@@ -0,0 +1,172 @@
+# This file is in the public domain.
+#
+[PATHS]
+# Persistent data storage for the testcase
+TALER_TEST_HOME = test_merchant_api_home/
+TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
+
+# Persistent data storage
+TALER_DATA_HOME = $TALER_HOME/.local/share/taler/
+
+# Configuration files
+TALER_CONFIG_HOME = $TALER_HOME/.config/taler/
+
+# Cached data, no big deal if lost
+TALER_CACHE_HOME = $TALER_HOME/.cache/taler/
+
+[taler]
+# What currency do we use?
+CURRENCY = EUR
+CURRENCY_ROUND_UNIT = EUR:0.01
+
+[taler-helper-crypto-rsa]
+# Reduce from 1 year to speed up test
+LOOKAHEAD_SIGN = 24 days
+
+[taler-helper-crypto-eddsa]
+# Reduce from 1 year to speed up test
+LOOKAHEAD_SIGN = 24 days
+# Reduce from 12 weeks to ensure we have mulrewardle
+DURATION = 14 days
+
+[bank]
+HTTP_PORT = 8082
+
+##########################################
+# Configuration for the merchant backend #
+##########################################
+
+[merchant]
+
+# Which port do we run the backend on? (HTTP server)
+PORT = 8080
+
+# Which plugin (backend) do we use for the DB.
+DB = postgres
+
+# This specifies which database the postgres backend uses.
+[merchantdb-postgres]
+CONFIG = postgres:///talercheck
+
+# Sections starting with "merchant-exchange-" specify trusted exchanges
+# (by the merchant)
+[merchant-exchange-test]
+MASTER_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
+EXCHANGE_BASE_URL = http://localhost:8081/
+CURRENCY = EUR
+
+
+#######################################################
+# Configuration for the auditor for the testcase
+#######################################################
+[auditor]
+BASE_URL = http://the.auditor/
+
+
+#######################################################
+# Configuration for ??? Is this used?
+#######################################################
+
+# Auditors must be in sections "auditor-", the rest of the section
+# name could be anything.
+[auditor-ezb]
+# Informal name of the auditor. Just for the user.
+NAME = European Central Bank
+
+# URL of the auditor (especially for in the future, when the
+# auditor offers an automated issue reporting system).
+# Not really used today.
+URL = http://taler.ezb.eu/
+
+# This is the important bit: the signing key of the auditor.
+PUBLIC_KEY = 9QXF7XY7E9VPV47B5Z806NDFSX2VJ79SVHHD29QEQ3BG31ANHZ60
+
+# Which currency is this auditor trusted for?
+CURRENCY = EUR
+
+
+###################################################
+# Configuration for the exchange for the testcase #
+###################################################
+
+[exchange]
+# How to access our database
+DB = postgres
+
+# HTTP port the exchange listens to
+PORT = 8081
+
+# Our public key
+MASTER_PUBLIC_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
+
+# Base URL of the exchange.
+BASE_URL = "http://localhost:8081/"
+
+
+[exchangedb-postgres]
+CONFIG = "postgres:///talercheck"
+
+
+[auditordb-postgres]
+CONFIG = postgres:///talercheck
+
+
+# Account of the EXCHANGE
+[exchange-account-exchange]
+# What is the exchange's bank account (with the "Taler Bank" demo system)?
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-exchange]
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+WIRE_GATEWAY_AUTH_METHOD = NONE
+
+
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+CIPHER = CS
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+CIPHER = CS
+
+[coin_eur_1]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+CIPHER = CS
+
+[coin_eur_5]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+CIPHER = CS
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
index 69a376aa..1a4c15db 100644
--- a/src/backenddb/test_merchantdb.c
+++ b/src/backenddb/test_merchantdb.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2021 Taler Systems SA
+ (C) 2014-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -117,15 +117,7 @@ make_instance (char *instance_id,
GNUNET_assert (NULL != instance->instance.jurisdiction);
GNUNET_assert (0 == json_array_append_new (instance->instance.jurisdiction,
json_string ("Ohio")));
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:1200.40",
- &instance->instance.
- default_max_deposit_fee));
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:1200.40",
- &instance->instance.
- default_max_wire_fee));
- instance->instance.default_wire_fee_amortization = 1;
+ instance->instance.use_stefan = true;
instance->instance.default_wire_transfer_delay =
GNUNET_TIME_relative_get_minute_ ();
instance->instance.default_pay_delay = GNUNET_TIME_relative_get_second_ ();
@@ -153,6 +145,9 @@ free_instance_data (struct InstanceData *instance)
static void
make_account (struct TALER_MERCHANTDB_AccountDetails *account)
{
+ memset (account,
+ 0,
+ sizeof (*account));
GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG,
&account->h_wire.hash);
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
@@ -232,15 +227,7 @@ check_instances_equal (const struct TALER_MERCHANTDB_InstanceSettings *a,
b->address)) ||
(1 != json_equal (a->jurisdiction,
b->jurisdiction)) ||
- (GNUNET_OK != TALER_amount_cmp_currency (&a->default_max_deposit_fee,
- &b->default_max_deposit_fee)) ||
- (0 != TALER_amount_cmp (&a->default_max_deposit_fee,
- &b->default_max_deposit_fee)) ||
- (GNUNET_OK != TALER_amount_cmp_currency (&a->default_max_wire_fee,
- &b->default_max_wire_fee)) ||
- (0 != TALER_amount_cmp (&a->default_max_wire_fee,
- &b->default_max_wire_fee)) ||
- (a->default_wire_fee_amortization != b->default_wire_fee_amortization) ||
+ (a->use_stefan != b->use_stefan) ||
(a->default_wire_transfer_delay.rel_value_us !=
b->default_wire_transfer_delay.rel_value_us) ||
(a->default_pay_delay.rel_value_us != b->default_pay_delay.rel_value_us))
@@ -272,6 +259,19 @@ check_accounts_equal (const struct TALER_MERCHANTDB_AccountDetails *a,
}
+// FIXME: use this!
+void
+lookup_accounts_cb (void *cls,
+ const struct TALER_MERCHANTDB_AccountDetails *account)
+{
+ const struct TALER_MERCHANTDB_AccountDetails *want = cls;
+
+ GNUNET_assert (0 ==
+ check_accounts_equal (want,
+ account));
+}
+
+
/**
* Called after testing 'lookup_instances'.
*
@@ -280,50 +280,26 @@ check_accounts_equal (const struct TALER_MERCHANTDB_AccountDetails *a,
* @param merchant_priv private key of the instance, NULL if not available
* @param is general instance settings
* @param ias instance authentication settings
- * @param accounts_length length of the @a accounts array
- * @param accounts list of accounts of the merchant
*/
static void
lookup_instances_cb (void *cls,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_MerchantPrivateKeyP *merchant_priv,
const struct TALER_MERCHANTDB_InstanceSettings *is,
- const struct TALER_MERCHANTDB_InstanceAuthSettings *ias,
- unsigned int accounts_length,
- const struct TALER_MERCHANTDB_AccountDetails accounts[])
+ const struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
{
struct TestLookupInstances_Closure *cmp = cls;
+
if (NULL == cmp)
return;
cmp->results_length += 1;
/* Look through the closure and test each instance for equality */
for (unsigned int i = 0; cmp->instances_to_cmp_length > i; ++i)
{
- int accounts_matching[accounts_length];
- bool accounts_match = true;
if (0 != check_instances_equal (cmp->instances_to_cmp[i].instance,
is))
continue;
- if (accounts_length != cmp->instances_to_cmp[i].accounts_length)
- continue;
- /* Count matches between the accounts found and accounts in cls */
- for (unsigned int j = 0; accounts_length > j; ++j)
- accounts_matching[j] = 0;
- for (unsigned int j = 0; accounts_length > j; ++j)
- {
- for (unsigned int k = 0; accounts_length > k; ++k)
- {
- if (0 == check_accounts_equal (&cmp->instances_to_cmp[i].accounts[j],
- &accounts[k]))
- accounts_matching[j] += 1;
- }
- }
- /* Each account from the lookup should match with one and only one from cls */
- for (unsigned int j = 0; accounts_length > j; ++j)
- if (1 != accounts_matching[j])
- accounts_match = false;
- if (true == accounts_match)
- cmp->results_matching[i] += 1;
+ cmp->results_matching[i] += 1;
}
}
@@ -385,7 +361,7 @@ test_lookup_instances (bool active_only,
unsigned int instances_length,
struct InstanceWithAccounts instances[])
{
- unsigned int results_matching[instances_length];
+ unsigned int results_matching[GNUNET_NZL (instances_length)];
struct TestLookupInstances_Closure cmp = {
.instances_to_cmp_length = instances_length,
.instances_to_cmp = instances,
@@ -433,9 +409,9 @@ test_delete_instance_private_key (const struct InstanceData *instance,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
- plugin->delete_instance_private_key (plugin->cls,
- instance->instance
- .id),
+ plugin->delete_instance_private_key (
+ plugin->cls,
+ instance->instance.id),
"Delete instance private key failed\n");
return 0;
}
@@ -596,15 +572,7 @@ run_test_instances (struct TestInstances_Closure *cls)
json_array_append_new (cls->instances[0].instance.jurisdiction,
json_pack ("{s:s}",
"vegetables", "bad"));
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:0.04",
- &cls->instances[0].instance.
- default_max_deposit_fee));
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:1.23",
- &cls->instances[0].instance.
- default_max_wire_fee));
- cls->instances[0].instance.default_wire_fee_amortization = 2;
+ cls->instances[0].instance.use_stefan = false;
cls->instances[0].instance.default_wire_transfer_delay =
GNUNET_TIME_UNIT_HOURS;
cls->instances[0].instance.default_pay_delay = GNUNET_TIME_UNIT_MINUTES;
@@ -819,7 +787,7 @@ check_products_equal (const struct TALER_MERCHANTDB_ProductDetails *a,
!=,
b->next_restock)))
- return 1;
+ return 1;
return 0;
}
@@ -938,13 +906,17 @@ struct TestLookupProducts_Closure
* Function called after calling @e test_lookup_products
*
* @param cls a pointer to the lookup closure.
+ * @param product_serial DB row ID
* @param product_id the identifier of the product found.
*/
static void
lookup_products_cb (void *cls,
+ uint64_t product_serial,
const char *product_id)
{
struct TestLookupProducts_Closure *cmp = cls;
+
+ GNUNET_assert (product_serial > 0);
if (NULL == cmp)
return;
cmp->results_length += 1;
@@ -970,7 +942,7 @@ test_lookup_products (const struct InstanceData *instance,
unsigned int products_length,
const struct ProductData *products)
{
- unsigned int results_matching[products_length];
+ unsigned int results_matching[GNUNET_NZL (products_length)];
struct TestLookupProducts_Closure cls = {
.products_to_cmp_length = products_length,
.products_to_cmp = products,
@@ -980,6 +952,8 @@ test_lookup_products (const struct InstanceData *instance,
memset (results_matching, 0, sizeof (unsigned int) * products_length);
if (0 > plugin->lookup_products (plugin->cls,
instance->instance.id,
+ 0,
+ 20,
&lookup_products_cb,
&cls))
{
@@ -1360,10 +1334,13 @@ test_insert_order (const struct InstanceData *instance,
plugin->insert_order (plugin->cls,
instance->instance.id,
order->id,
+ NULL, /* session_id */
&h_post,
order->pay_deadline,
&order->claim_token,
- order->contract),
+ order->contract,
+ NULL,
+ 0),
"Insert order failed\n");
return 0;
}
@@ -1493,7 +1470,7 @@ test_lookup_orders (const struct InstanceData *instance,
unsigned int orders_length,
const struct OrderData *orders)
{
- bool results_match[orders_length];
+ bool results_match[GNUNET_NZL (orders_length)];
struct TestLookupOrders_Closure cls = {
.orders_to_cmp_length = orders_length,
.orders_to_cmp = orders,
@@ -1694,7 +1671,6 @@ test_lookup_contract_terms (const struct InstanceData *instance,
{
json_t *contract = NULL;
uint64_t order_serial;
- bool paid;
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->lookup_contract_terms (plugin->cls,
@@ -1702,7 +1678,6 @@ test_lookup_contract_terms (const struct InstanceData *instance,
order->id,
&contract,
&order_serial,
- &paid,
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -1842,6 +1817,7 @@ test_lookup_order_by_fulfillment (const struct InstanceData *instance,
instance->instance.id,
fulfillment_url,
session_id,
+ false,
&order_id))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -1872,25 +1848,44 @@ test_lookup_order_by_fulfillment (const struct InstanceData *instance,
* @return 0 on success, 1 otherwise.
*/
static int
-test_lookup_payment_status (uint64_t order_id,
+test_lookup_payment_status (const char *instance_id,
+ const char *order_id,
const char *session_id,
bool expected_paid,
bool expected_wired)
{
bool paid;
bool wired;
+ bool matches;
+ uint64_t os;
+
TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
- plugin->lookup_payment_status (plugin->cls,
- order_id,
- session_id,
- &paid,
- &wired),
+ plugin->lookup_contract_terms3 (plugin->cls,
+ instance_id,
+ order_id,
+ session_id,
+ NULL,
+ &os,
+ &paid,
+ &wired,
+ &matches,
+ NULL),
"Lookup payment status failed\n");
- if ((expected_paid != paid) ||
- (expected_wired != wired))
+ if ( (NULL != session_id) && (! matches) )
+ {
+ paid = false;
+ wired = false;
+ }
+ if (expected_wired != wired)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup payment status failed: wired status is wrong\n");
+ return 1;
+ }
+ if (expected_paid != paid)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup payment status failed\n");
+ "Lookup payment status failed: paid status is wrong\n");
return 1;
}
return 0;
@@ -2080,7 +2075,6 @@ run_test_orders (struct TestOrders_Closure *cls)
{
json_t *lookup_contract = NULL;
uint64_t lookup_order_serial;
- bool paid;
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->lookup_contract_terms (plugin->cls,
@@ -2088,7 +2082,6 @@ run_test_orders (struct TestOrders_Closure *cls)
cls->orders[1].id,
&lookup_contract,
&lookup_order_serial,
- &paid,
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -2127,21 +2120,11 @@ run_test_orders (struct TestOrders_Closure *cls)
}
}
/* Test lookup payment status */
- TEST_RET_ON_FAIL (test_lookup_payment_status (serial,
+ TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
+ cls->orders[0].id,
NULL,
false,
false));
- {
- bool paid;
- bool wired;
- TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
- plugin->lookup_payment_status (plugin->cls,
- 256,
- NULL,
- &paid,
- &wired),
- "Lookup payment status failed\n");
- }
/* Test lookup order status fails for nonexistent order */
{
struct TALER_PrivateContractHashP h_contract_terms;
@@ -2163,25 +2146,21 @@ run_test_orders (struct TestOrders_Closure *cls)
TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
&cls->orders[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
- TEST_RET_ON_FAIL (test_lookup_payment_status (serial,
+ TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
+ cls->orders[0].id,
NULL,
true,
false));
- TEST_RET_ON_FAIL (test_lookup_payment_status (serial,
+ TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
+ cls->orders[0].id,
"test_orders_session",
true,
false));
- {
- bool paid;
- bool wired;
- TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
- plugin->lookup_payment_status (plugin->cls,
- serial,
- "bad_session",
- &paid,
- &wired),
- "Lookup payment status failed\n");
- }
+ TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
+ cls->orders[0].id,
+ "bad_session",
+ false,
+ false));
/* Test lookup order by fulfillment */
TEST_RET_ON_FAIL (test_lookup_order_by_fulfillment (&cls->instance,
&cls->orders[0],
@@ -2193,6 +2172,7 @@ run_test_orders (struct TestOrders_Closure *cls)
cls->instance.instance.id,
"fulfillment_url",
"test_orders_session",
+ false,
&order_id))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -2216,7 +2196,8 @@ run_test_orders (struct TestOrders_Closure *cls)
/* Test marking orders as wired */
TEST_RET_ON_FAIL (test_mark_order_wired (serial,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
- TEST_RET_ON_FAIL (test_lookup_payment_status (serial,
+ TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
+ cls->orders[0].id,
NULL,
true,
true));
@@ -2386,6 +2367,11 @@ struct DepositData
struct TALER_CoinSpendPublicKeyP coin_pub;
/**
+ * Signature of the coin that has been deposited.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
* URL of the exchange.
*/
const char *exchange_url;
@@ -2424,39 +2410,6 @@ struct DepositData
/**
- * Private key for my_sign_cb().
- */
-static const struct TALER_ExchangePrivateKeyP *msc_exchange_priv;
-
-/**
- * Function that signs the message in @a purpose with the
- * exchange's signing key from #msc_exchange_priv.
- *
- * The @a purpose data is the beginning of the data of which the signature is
- * to be created. The `size` field in @a purpose must correctly indicate the
- * number of bytes of the data structure, including its header. *
- * @param purpose the message to sign
- * @param[out] pub set to the current public signing key of the exchange
- * @param[out] sig signature over purpose using current signing key
- * @return #TALER_EC_NONE on success
- */
-static enum TALER_ErrorCode
-my_sign_cb (
- const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
- struct TALER_ExchangePublicKeyP *pub,
- struct TALER_ExchangeSignatureP *sig)
-{
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign_ (&msc_exchange_priv->eddsa_priv,
- purpose,
- &sig->eddsa_signature));
- GNUNET_CRYPTO_eddsa_key_get_public (&msc_exchange_priv->eddsa_priv,
- &pub->eddsa_pub);
- return TALER_EC_NONE;
-}
-
-
-/**
* Generates deposit data for an order.
*
* @param instance the instance to make the deposit to.
@@ -2475,7 +2428,6 @@ make_deposit (const struct InstanceData *instance,
struct TALER_CoinSpendPrivateKeyP coin_priv;
struct GNUNET_TIME_Timestamp now;
struct TALER_Amount amount_without_fee;
- struct TALER_ExchangePublicKeyP exchange_pub;
now = GNUNET_TIME_timestamp_get ();
deposit->timestamp = now;
@@ -2503,22 +2455,12 @@ make_deposit (const struct InstanceData *instance,
&deposit->amount_with_fee,
&deposit->deposit_fee));
deposit->h_wire = account->h_wire;
- msc_exchange_priv = &signkey->exchange_priv;
- GNUNET_assert (TALER_EC_NONE ==
- TALER_exchange_online_deposit_confirmation_sign (
- &my_sign_cb,
- &deposit->h_contract_terms,
- &deposit->h_wire,
- NULL,
- now,
- now,
- now,
- &amount_without_fee,
- &deposit->coin_pub,
- &instance->merchant_pub,
- &exchange_pub,
- &deposit->exchange_sig));
- msc_exchange_priv = NULL;
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &deposit->exchange_sig,
+ sizeof (deposit->exchange_sig));
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &deposit->coin_sig,
+ sizeof (deposit->coin_sig));
}
@@ -2561,21 +2503,39 @@ test_insert_deposit (const struct InstanceData *instance,
const struct DepositData *deposit,
enum GNUNET_DB_QueryStatus expected_result)
{
- TEST_COND_RET_ON_FAIL (expected_result ==
- plugin->insert_deposit (plugin->cls,
- instance->instance.id,
- deposit->timestamp,
- &deposit->h_contract_terms,
- &deposit->coin_pub,
- deposit->exchange_url,
- &deposit->amount_with_fee,
- &deposit->deposit_fee,
- &deposit->refund_fee,
- &deposit->wire_fee,
- &deposit->h_wire,
- &deposit->exchange_sig,
- &signkey->exchange_pub),
- "Insert deposit failed\n");
+ uint64_t row;
+ struct TALER_Amount awf;
+
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&awf,
+ &deposit->amount_with_fee,
+ &deposit->deposit_fee));
+ TEST_COND_RET_ON_FAIL (
+ expected_result ==
+ plugin->insert_deposit_confirmation (plugin->cls,
+ instance->instance.id,
+ deposit->timestamp,
+ &deposit->h_contract_terms,
+ deposit->exchange_url,
+ deposit->timestamp,
+ &awf,
+ &deposit->wire_fee,
+ &deposit->h_wire,
+ &deposit->exchange_sig,
+ &signkey->exchange_pub,
+ &row),
+ "Insert deposit confirmation failed\n");
+ TEST_COND_RET_ON_FAIL (
+ expected_result ==
+ plugin->insert_deposit (plugin->cls,
+ 0,
+ row,
+ &deposit->coin_pub,
+ &deposit->coin_sig,
+ &deposit->amount_with_fee,
+ &deposit->deposit_fee,
+ &deposit->refund_fee),
+ "Insert deposit failed\n");
return 0;
}
@@ -2615,7 +2575,6 @@ struct TestLookupDeposits_Closure
* @param amount_with_fee amount of the deposit with fees.
* @param deposit_fee fee charged for the deposit.
* @param refund_fee fee charged in case of a refund.
- * @param wire_fee fee charged when the money is wired.
*/
static void
lookup_deposits_cb (void *cls,
@@ -2623,8 +2582,7 @@ lookup_deposits_cb (void *cls,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
- const struct TALER_Amount *refund_fee,
- const struct TALER_Amount *wire_fee)
+ const struct TALER_Amount *refund_fee)
{
struct TestLookupDeposits_Closure *cmp = cls;
if (NULL == cmp)
@@ -2652,14 +2610,7 @@ lookup_deposits_cb (void *cls,
refund_fee)) &&
(0 ==
TALER_amount_cmp (&cmp->deposits_to_cmp[i].refund_fee,
- refund_fee)) &&
- (GNUNET_OK ==
- TALER_amount_cmp_currency (
- &cmp->deposits_to_cmp[i].wire_fee,
- wire_fee)) &&
- (0 ==
- TALER_amount_cmp (&cmp->deposits_to_cmp[i].wire_fee,
- wire_fee)))
+ refund_fee)))
{
cmp->results_matching[i] += 1;
}
@@ -2683,7 +2634,7 @@ test_lookup_deposits (const struct InstanceData *instance,
unsigned int deposits_length,
const struct DepositData *deposits)
{
- unsigned int results_matching[deposits_length];
+ unsigned int results_matching[GNUNET_NZL (deposits_length)];
struct TestLookupDeposits_Closure cmp = {
.deposits_to_cmp_length = deposits_length,
.deposits_to_cmp = deposits,
@@ -3364,9 +3315,11 @@ struct TransferData
static void
make_transfer (const struct ExchangeSignkeyData *signkey,
unsigned int deposits_length,
- const struct DepositData deposits[],
+ const struct DepositData deposits[static deposits_length],
struct TransferData *transfer)
{
+ struct TALER_TrackTransferDetails *details = NULL;
+
GNUNET_CRYPTO_seed_weak_random (585);
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
&transfer->wtid,
@@ -3374,9 +3327,6 @@ make_transfer (const struct ExchangeSignkeyData *signkey,
transfer->exchange_url = deposits[0].exchange_url;
transfer->verified = false;
transfer->confirmed = false;
-
- struct TALER_TrackTransferDetails *details = NULL;
-
transfer->data.details_length = 0;
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (deposits[0].amount_with_fee.currency,
@@ -3804,11 +3754,12 @@ test_lookup_transfer_details_by_order (
memset (results_matching,
0,
sizeof (unsigned int) * transfers_length);
- if (transfers_length != plugin->lookup_transfer_details_by_order (plugin->cls,
- order_serial,
- &
- lookup_transfer_details_order_cb,
- &cmp))
+ if (transfers_length !=
+ plugin->lookup_transfer_details_by_order (
+ plugin->cls,
+ order_serial,
+ &lookup_transfer_details_order_cb,
+ &cmp))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup transfer details by order failed\n");
@@ -4246,6 +4197,9 @@ pre_test_transfers (struct TestTransfers_Closure *cls)
static void
post_test_transfers (struct TestTransfers_Closure *cls)
{
+ GNUNET_array_grow (cls->transfers->data.details,
+ cls->transfers->data.details_length,
+ 0);
free_instance_data (&cls->instance);
free_order_data (&cls->order);
}
@@ -4341,21 +4295,23 @@ run_test_transfers (struct TestTransfers_Closure *cls)
&cls->account,
&cls->transfers[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
- TEST_RET_ON_FAIL (test_lookup_payment_status (order_serial,
+ TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
+ cls->order.id,
NULL,
false,
true));
TEST_RET_ON_FAIL (test_lookup_transfer (&cls->instance,
&cls->transfers[0]));
- TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
- plugin->set_transfer_status_to_verified (plugin->cls,
- cls->deposit.
- exchange_url,
- &cls->
- transfers[0].
- wtid),
- "Set transfer status to verified failed\n");
- cls->transfers[0].verified = true;
+ TEST_COND_RET_ON_FAIL (
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ plugin->set_transfer_status_to_confirmed (
+ plugin->cls,
+ cls->instance.instance.id,
+ cls->deposit.exchange_url,
+ &cls->transfers[0].wtid,
+ &cls->deposit.amount_with_fee),
+ "Set transfer status to confirmed failed\n");
+ cls->transfers[0].confirmed = true;
TEST_RET_ON_FAIL (test_lookup_transfer (&cls->instance,
&cls->transfers[0]));
TEST_RET_ON_FAIL (test_lookup_transfer_summary (cls->deposit.exchange_url,
@@ -4401,1285 +4357,6 @@ test_transfers (void)
}
-/* Reserves and tips */
-
-
-struct ReserveData
-{
- /**
- * The reserve public key
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * The reserve private key
- */
- struct TALER_ReservePrivateKeyP reserve_priv;
-
- /**
- * The reserve initial amount
- */
- struct TALER_Amount initial_amount;
-
- /**
- * The exchange url
- */
- const char *exchange_url;
-
- /**
- * The payto URI
- */
- const char *payto_uri;
-
- /**
- * The expiration date
- */
- struct GNUNET_TIME_Timestamp expiration;
-};
-
-
-/**
- * Tests inserting a reserve into the database.
- * @paper instance the instance the reserve is for.
- * @param reserve the reserve to insert.
- * @param expected_result the result we expect to receive from the db.
- *
- * @return 0 on success, 1 otherwise.
- */
-static int
-test_insert_reserve (const struct InstanceData *instance,
- const struct ReserveData *reserve,
- enum TALER_ErrorCode expected_result)
-{
- TEST_COND_RET_ON_FAIL (expected_result ==
- plugin->insert_reserve (plugin->cls,
- instance->instance.id,
- &reserve->reserve_priv,
- &reserve->reserve_pub,
- reserve->exchange_url,
- reserve->payto_uri,
- &reserve->initial_amount,
- reserve->expiration),
- "Insert reserve failed\n");
- return 0;
-}
-
-
-/**
- * Container for looking up reserves.
- */
-struct TestLookupReserve_Closure
-{
- /**
- * The reserve we expect to find.
- */
- const struct ReserveData *reserve_to_cmp;
-
- /**
- * The length of @e tips.
- */
- unsigned int tips_length;
-
- /**
- * The tips that have been authorized from the reserve.
- */
- const struct TALER_MERCHANTDB_TipDetails *tips;
-
- /**
- * 1 if the result matches, 0 otherwise.
- */
- int result_matches;
-};
-
-
-/**
- * Called after test_lookup_reserve.
- * @param cls a pointer to TestLookupReserve_Closure.
- * @param creation_time time when the reserve was setup
- * @param expiration_time time when the reserve will be closed by the exchange
- * @param merchant_initial_amount initial amount that the merchant claims to have filled the
- * reserve with
- * @param exchange_initial_amount initial amount that the exchange claims to have received
- * @param picked_up_amount total of tips that were picked up from this reserve
- * @param committed_amount total of tips that the merchant committed to, but that were not
- * picked up yet
- * @param active true if the reserve is still active (we have the private key)
- * @param exchange_url base URL of the exchange hosting the reserve, NULL if not @a active
- * @param payto_uri URI to use to fund the reserve, NULL if not @a active
- * @param tips_length length of the @a tips array
- * @param tips information about the tips created by this reserve
- *
- * @return 0 on success, 1 otherwise.
- */
-static void
-lookup_reserve_cb (void *cls,
- struct GNUNET_TIME_Timestamp creation_time,
- struct GNUNET_TIME_Timestamp expiration_time,
- const struct TALER_Amount *merchant_initial_amount,
- const struct TALER_Amount *exchange_initial_amount,
- const struct TALER_Amount *picked_up_amount,
- const struct TALER_Amount *committed_amount,
- bool active,
- const char *exchange_url,
- const char *payto_uri,
- unsigned int tips_length,
- const struct TALER_MERCHANTDB_TipDetails *tips)
-{
- struct TestLookupReserve_Closure *cmp = cls;
- unsigned int tip_cmp_results[tips_length];
-
- if (NULL == cmp)
- return;
- if (GNUNET_TIME_timestamp_cmp (cmp->reserve_to_cmp->expiration,
- !=,
- expiration_time) ||
- (GNUNET_OK !=
- TALER_amount_cmp_currency (
- &cmp->reserve_to_cmp->initial_amount,
- merchant_initial_amount)) ||
- (0 != TALER_amount_cmp (&cmp->reserve_to_cmp->initial_amount,
- merchant_initial_amount)) ||
- (cmp->tips_length != tips_length))
- {
- cmp->result_matches = 1;
- return;
- }
- if (0 != strcmp (exchange_url,
- "https://exch-url/"))
- {
- GNUNET_break (0);
- cmp->result_matches = 0;
- return;
- }
- if (0 != strcmp (payto_uri,
- "payto://other-uri"))
- {
- GNUNET_break (0);
- cmp->result_matches = 0;
- return;
- }
- memset (tip_cmp_results,
- 0,
- sizeof (tip_cmp_results));
- for (unsigned int i = 0; tips_length > i; ++i)
- {
- for (unsigned int j = 0; tips_length > j; ++j)
- {
- if ((GNUNET_OK == TALER_amount_cmp_currency (&cmp->tips[i].total_amount,
- &tips[j].total_amount)) &&
- (0 == TALER_amount_cmp (&cmp->tips[i].total_amount,
- &tips[j].total_amount)) &&
- (0 == strcmp (cmp->tips[i].reason,
- tips[j].reason)))
- {
- tip_cmp_results[i] += 1;
- }
- }
- }
- for (unsigned int i = 0; tips_length > i; ++i)
- {
- if (1 != tip_cmp_results[i])
- {
- cmp->result_matches = 1;
- return;
- }
- }
- cmp->result_matches = 0;
-}
-
-
-/**
- * Tests looking up details of a reserve from the database.
- * @param instance the instance to lookup the reserve from.
- * @param reserve_pub the public key of the reserve we are looking for.
- * @param reserve the data we expect to find.
- *
- * @return 0 on success, 1 otherwise.
- */
-static int
-test_lookup_reserve (const struct InstanceData *instance,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct ReserveData *reserve)
-{
- struct TestLookupReserve_Closure cmp = {
- .reserve_to_cmp = reserve,
- .tips_length = 0,
- .tips = NULL,
- .result_matches = 0
- };
- if (1 != plugin->lookup_reserve (plugin->cls,
- instance->instance.id,
- reserve_pub,
- false,
- &lookup_reserve_cb,
- &cmp))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup reserve failed\n");
- return 1;
- }
- if (0 != cmp.result_matches)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup reserve failed: result does not match\n");
- return 1;
- }
- return 0;
-}
-
-
-/**
- * Container for looking up multiple reserves.
- */
-struct TestLookupReserves_Closure
-{
- /**
- * The length of @e reserves_to_cmp.
- */
- unsigned int reserves_to_cmp_length;
-
- /**
- * The reserves we expect to find from the lookup.
- */
- const struct ReserveData *reserves_to_cmp;
-
- /**
- * The number of results matching each reserve we were looking for.
- */
- unsigned int *results_matching;
-
- /**
- * The total number of results found from the lookup.
- */
- unsigned int results_length;
-};
-
-
-/**
- * Called after test_lookup_reserves.
- * @param cls pointer to a TestLookupReserves_Closure.
- * @param reserve_pub public key of the reserve
- * @param creation_time time when the reserve was setup
- * @param expiration_time time when the reserve will be closed by the exchange
- * @param merchant_initial_amount initial amount that the merchant claims to have filled the
- * reserve with
- * @param exchange_initial_amount initial amount that the exchange claims to have received
- * @param pickup_amount total of tips that were picked up from this reserve
- * @param committed_amount total of tips that the merchant committed to, but that were not
- * picked up yet
- * @param active true if the reserve is still active (we have the private key)
- */
-static void
-lookup_reserves_cb (void *cls,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- struct GNUNET_TIME_Timestamp creation_time,
- struct GNUNET_TIME_Timestamp expiration_time,
- const struct TALER_Amount *merchant_initial_amount,
- const struct TALER_Amount *exchange_initial_amount,
- const struct TALER_Amount *pickup_amount,
- const struct TALER_Amount *committed_amount,
- bool active)
-{
- struct TestLookupReserves_Closure *cmp = cls;
- if (NULL == cmp)
- return;
- for (unsigned int i = 0; cmp->reserves_to_cmp_length > i; ++i)
- {
- if ((0 ==
- GNUNET_memcmp (&cmp->reserves_to_cmp[i].reserve_pub,
- reserve_pub)) &&
- (GNUNET_TIME_timestamp_cmp (cmp->reserves_to_cmp[i].expiration,
- ==,
- expiration_time)) &&
- (GNUNET_OK ==
- TALER_amount_cmp_currency (
- &cmp->reserves_to_cmp[i].initial_amount,
- merchant_initial_amount)) &&
- (0 == TALER_amount_cmp (&cmp->reserves_to_cmp[i].initial_amount,
- merchant_initial_amount)))
- {
- cmp->results_matching[i] += 1;
- }
- }
- cmp->results_length += 1;
-}
-
-
-/**
- * Test looking up reserves for an instance.
- * @param instance the instance to get the reserves from.
- *
- * @return 0 on success, 1 otherwise.
- */
-static int
-test_lookup_reserves (const struct InstanceData *instance,
- unsigned int reserves_length,
- const struct ReserveData *reserves)
-{
- unsigned int results_matching[reserves_length];
- struct TestLookupReserves_Closure cmp = {
- .reserves_to_cmp_length = reserves_length,
- .reserves_to_cmp = reserves,
- .results_matching = results_matching,
- .results_length = 0
- };
- memset (results_matching, 0, sizeof (unsigned int) * reserves_length);
- if (1 != plugin->lookup_reserves (plugin->cls,
- instance->instance.id,
- GNUNET_TIME_UNIT_ZERO_TS,
- TALER_EXCHANGE_YNA_ALL,
- TALER_EXCHANGE_YNA_ALL,
- &lookup_reserves_cb,
- &cmp))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup reserves failed\n");
- return 1;
- }
- if (reserves_length != cmp.results_length)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup reserves failed: incorrect number of results (%d)\n",
- cmp.results_length);
- return 1;
- }
- for (unsigned int i = 0; reserves_length > i; ++i)
- {
- if (1 != cmp.results_matching[i])
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup reserves failed: mismatched data\n");
- return 1;
- }
- }
- return 0;
-}
-
-
-/**
- * Called after test_lookup_pending_reserves.
- * @param cls pointer to a TestLookupReserves_Closure.
- * @param instance_id the id of the instance the reserve belongs to.
- * @param exchange_url url of the exchange for this reserve.
- * @param reserve_pub public key of this reserve.
- * @param expected_amount what the amount in the reserve is, according to the db.
- */
-static void
-lookup_pending_reserves_cb (void *cls,
- const char *instance_id,
- const char *exchange_url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_Amount *expected_amount)
-{
- struct TestLookupReserves_Closure *cmp = cls;
- if (NULL == cmp)
- return;
- for (unsigned int i = 0; cmp->reserves_to_cmp_length > i; ++i)
- {
- if ((0 == GNUNET_memcmp (&cmp->reserves_to_cmp[i].reserve_pub,
- reserve_pub)) &&
- (0 == strcmp (cmp->reserves_to_cmp[i].exchange_url,
- exchange_url)) &&
- (GNUNET_OK == TALER_amount_cmp_currency (
- &cmp->reserves_to_cmp[i].initial_amount,
- expected_amount)) &&
- (0 == TALER_amount_cmp (&cmp->reserves_to_cmp[i].initial_amount,
- expected_amount)))
- {
- cmp->results_matching[i] += 1;
- }
- }
- cmp->results_length += 1;
-}
-
-
-/**
- * Tests looking up reserves that are not activated from the database.
- * @param reserves_length length of @e reserves.
- * @param reserves the reserves that the db is expected to return.
- *
- * @return 0 on success, 1 otherwise.
- */
-static int
-test_lookup_pending_reserves (unsigned int reserves_length,
- const struct ReserveData *reserves)
-{
- unsigned int results_matching[reserves_length];
- struct TestLookupReserves_Closure cmp = {
- .reserves_to_cmp_length = reserves_length,
- .reserves_to_cmp = reserves,
- .results_matching = results_matching,
- .results_length = 0
- };
- memset (results_matching, 0, sizeof (unsigned int) * reserves_length);
- if (0 > plugin->lookup_pending_reserves (plugin->cls,
- &lookup_pending_reserves_cb,
- &cmp))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup pending reserves failed\n");
- return 1;
- }
- if (reserves_length != cmp.results_length)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup pending reserves failed: incorrect number of results (%d)\n",
- cmp.results_length);
- return 1;
- }
- for (unsigned int i = 0; reserves_length > i; ++i)
- {
- if (1 != cmp.results_matching[i])
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup pending reserves failed: mismatched data\n");
- return 1;
- }
- }
- return 0;
-}
-
-
-/**
- * Container for all tip data relevant to the database.
- */
-struct TipData
-{
- /**
- * The details of the tip.
- */
- struct TALER_MERCHANTDB_TipDetails details;
-
- /**
- * Where the user should be redirected.
- */
- const char *next_url;
-
- /**
- * When the tip expires.
- */
- struct GNUNET_TIME_Timestamp expiration;
-};
-
-
-/**
- * Creates a tip for testing.
- * @param tip the tip to fill with data.
- */
-static void
-make_tip (struct TipData *tip)
-{
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:0.99",
- &tip->details.total_amount));
- tip->details.reason = "because";
- tip->next_url = "https://taler.net";
-}
-
-
-/**
- * Tests authorizing a tip.
- * @param instance the instance authorizing the tip.
- * @param reserve where the tip is coming from.
- * @param tip the tip to authorize.
- *
- * @return 0 on success, 1 otherwise.
- */
-static int
-test_authorize_tip (const struct InstanceData *instance,
- const struct ReserveData *reserve,
- struct TipData *tip)
-{
- TEST_COND_RET_ON_FAIL (TALER_EC_NONE ==
- plugin->authorize_tip (plugin->cls,
- instance->instance.id,
- &reserve->reserve_pub,
- &tip->details.total_amount,
- tip->details.reason,
- tip->next_url,
- &tip->details.tip_id,
- &tip->expiration),
- "Authorize tip failed\n");
- return 0;
-}
-
-
-/**
- * Tests looking up a tip from the database.
- * @param instance the instance to look up tips from.
- * @param reserve the reserve to look up tips from.
- * @param tip the tip we expect to find (uses @e tip_id to perform lookup).
- * @param expected_total_picked_up how much of the tip should have been
- * picked up.
- *
- * @return 0 on success, 1 otherwise.
- */
-static int
-test_lookup_tip (const struct InstanceData *instance,
- const struct ReserveData *reserve,
- const struct TipData *tip,
- const struct TALER_Amount *expected_total_picked_up)
-{
- struct TALER_Amount total_authorized;
- struct TALER_Amount total_picked_up;
- struct GNUNET_TIME_Timestamp expiration;
- char *exchange_url = NULL;
- struct TALER_ReservePrivateKeyP reserve_priv;
-
- if (1 != plugin->lookup_tip (plugin->cls,
- instance->instance.id,
- &tip->details.tip_id,
- &total_authorized,
- &total_picked_up,
- &expiration,
- &exchange_url,
- &reserve_priv))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup tip failed\n");
- GNUNET_free (exchange_url);
- return 1;
- }
- if ((GNUNET_OK !=
- TALER_amount_cmp_currency (&tip->details.total_amount,
- &total_authorized)) ||
- (0 != TALER_amount_cmp (&tip->details.total_amount,
- &total_authorized)) ||
- (GNUNET_OK !=
- TALER_amount_cmp_currency (expected_total_picked_up,
- &total_picked_up)) ||
- (0 != TALER_amount_cmp (expected_total_picked_up,
- &total_picked_up)) ||
- (GNUNET_TIME_timestamp_cmp (tip->expiration,
- !=,
- expiration)) ||
- (0 != strcmp (reserve->exchange_url,
- exchange_url)) ||
- (0 != GNUNET_memcmp (&reserve->reserve_priv,
- &reserve_priv)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup tip failed: mismatched data\n");
- GNUNET_free (exchange_url);
- return 1;
- }
- GNUNET_free (exchange_url);
- return 0;
-}
-
-
-/**
- * Tests looking up the details of a tip from the database.
- *
- * @param instance the instance the tip is in.
- * @param reserve the reserve the tip was authorized from.
- * @param tip the tip we expect to find (uses @e tip_id to perform lookup).
- * @param expected_total_picked_up how much of the tip should have been
- * picked up.
- * @param expected_pickups_length the length of @e expected_pickups.
- * @param expected_pickups the pickups that we expect to be associated with
- * the tip.
- * @return 0 on success, 1 otherwise.
- */
-static int
-test_lookup_tip_details (
- const struct InstanceData *instance,
- const struct ReserveData *reserve,
- const struct TipData *tip,
- const struct TALER_Amount *expected_total_picked_up,
- unsigned int expected_pickups_length,
- const struct TALER_MERCHANTDB_PickupDetails *expected_pickups)
-{
- struct TALER_Amount total_authorized;
- struct TALER_Amount total_picked_up;
- char *justification = NULL;
- struct GNUNET_TIME_Timestamp expiration;
- struct TALER_ReservePublicKeyP reserve_pub;
- unsigned int pickups_length;
- struct TALER_MERCHANTDB_PickupDetails *pickups = NULL;
- unsigned int results_matching[expected_pickups_length];
-
- if (0 >
- plugin->lookup_tip_details (plugin->cls,
- instance->instance.id,
- &tip->details.tip_id,
- true,
- &total_authorized,
- &total_picked_up,
- &justification,
- &expiration,
- &reserve_pub,
- &pickups_length,
- &pickups))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup tip details failed\n");
- GNUNET_free (justification);
- GNUNET_free (pickups);
- return 1;
- }
- if ( (GNUNET_OK !=
- TALER_amount_cmp_currency (&tip->details.total_amount,
- &total_authorized)) ||
- (0 != TALER_amount_cmp (&tip->details.total_amount,
- &total_authorized)) ||
- (GNUNET_OK !=
- TALER_amount_cmp_currency (expected_total_picked_up,
- &total_picked_up)) ||
- (0 != TALER_amount_cmp (expected_total_picked_up,
- &total_picked_up)) ||
- (0 != strcmp (tip->details.reason,
- justification)) ||
- (GNUNET_TIME_timestamp_cmp (tip->expiration,
- !=,
- expiration)) ||
- (0 != GNUNET_memcmp (&reserve->reserve_pub,
- &reserve_pub)) ||
- (expected_pickups_length != pickups_length) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup tip details failed: mismatched data\n");
- GNUNET_free (justification);
- GNUNET_free (pickups);
- return 1;
- }
- memset (results_matching,
- 0,
- sizeof (unsigned int) * expected_pickups_length);
- for (unsigned int i = 0; expected_pickups_length > i; ++i)
- {
- for (unsigned int j = 0; pickups_length > j; ++j)
- {
- /* Compare expected_pickups[i] with pickups[j] */
- if ((0 == GNUNET_memcmp (&expected_pickups[i].pickup_id,
- &pickups[j].pickup_id)) &&
- (GNUNET_OK == TALER_amount_cmp_currency (
- &expected_pickups[i].requested_amount,
- &pickups[j].requested_amount)) &&
- (0 == TALER_amount_cmp (&expected_pickups[i].requested_amount,
- &pickups[j].requested_amount)) &&
- (expected_pickups[i].num_planchets == pickups[j].num_planchets))
- {
- results_matching[i] += 1;
- }
- }
- }
- for (unsigned int i = 0; expected_pickups_length > i; ++i)
- {
- if (1 != results_matching[i])
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup tip details failed: mismatched data\n");
- GNUNET_free (justification);
- GNUNET_free (pickups);
- return 1;
- }
- }
- return 0;
-}
-
-
-/**
- * Utility function for freeing an array of RSA signatures.
- * @param sigs_length length of @e sigs.
- * @param sigs the signatures to free.
- */
-static void
-free_signature_array (unsigned int sigs_length,
- struct TALER_BlindedDenominationSignature *sigs)
-{
- for (unsigned int i = 0; sigs_length > i; ++i)
- TALER_blinded_denom_sig_free (&sigs[i]);
-}
-
-
-/**
- * Tests looking up a tip pickup.
- * @param instance the instance to look up from.
- * @param tip the tip the pickup was made for.
- * @param pickup_id id of the pickup to look up.
- * @param expected_exchange_url exchange url for the pickup.
- * @param expected_reserve_priv reserve private key for the pickup.
- * @param expected_sigs_length length of @e expected_sigs.
- * @param expected_sigs the signatures we expect to be made for the pickup.
- *
- * @return 0 on success, 1 otherwise.
- */
-static int
-test_lookup_pickup (
- const struct InstanceData *instance,
- const struct TipData *tip,
- const struct TALER_PickupIdentifierP *pickup_id,
- const char *expected_exchange_url,
- const struct TALER_ReservePrivateKeyP *expected_reserve_priv,
- unsigned int expected_sigs_length,
- const struct TALER_BlindedDenominationSignature *expected_sigs)
-{
- char *exchange_url = NULL;
- struct TALER_ReservePrivateKeyP reserve_priv;
- struct TALER_BlindedDenominationSignature sigs[expected_sigs_length];
- unsigned int results_matching[expected_sigs_length];
-
- memset (sigs,
- 0,
- sizeof (sigs));
- if (0 > plugin->lookup_pickup (plugin->cls,
- instance->instance.id,
- &tip->details.tip_id,
- pickup_id,
- &exchange_url,
- &reserve_priv,
- expected_sigs_length,
- sigs))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup pickup failed\n");
- GNUNET_free (exchange_url);
- free_signature_array (expected_sigs_length,
- sigs);
- return 1;
- }
- if ((0 != strcmp (expected_exchange_url,
- exchange_url)) ||
- (0 != GNUNET_memcmp (expected_reserve_priv,
- &reserve_priv)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup pickup failed: mismatched data\n");
- GNUNET_free (exchange_url);
- free_signature_array (expected_sigs_length,
- sigs);
- return 1;
- }
- memset (results_matching,
- 0,
- sizeof (unsigned int) * expected_sigs_length);
- for (unsigned int i = 0; expected_sigs_length > i; ++i)
- {
- for (unsigned int j = 0; expected_sigs_length > j; ++j)
- {
- /* compare expected_sigs[i] to sigs[j] */
- if (0 ==
- TALER_blinded_denom_sig_cmp (&expected_sigs[i],
- &sigs[j]))
- {
- results_matching[i] += 1;
- }
- }
- }
- for (unsigned int i = 0; expected_sigs_length > i; ++i)
- {
- if (1 != results_matching[i])
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup pickup failed: mismatched data\n");
- GNUNET_free (exchange_url);
- free_signature_array (expected_sigs_length,
- sigs);
- return 1;
- }
- }
- return 0;
-}
-
-
-/**
- * Closure for testing lookup_tips.
- */
-struct TestLookupTips_Closure
-{
- /**
- * The length of @e tips_to_cmp.
- */
- unsigned int tips_to_cmp_length;
-
- /**
- * The tips that we are expecting to find.
- */
- const struct TipData *tips_to_cmp;
-
- /**
- * The number of results found from the lookup.
- */
- unsigned int results_length;
-
- /**
- * Whether each result matches with the corresponding tip in @tips_to_cmp.
- */
- bool *results_match;
-};
-
-
-/**
- * Called after test_lookup_tips.
- * @param cls pointer to a TestLookupTips_Closure.
- * @param row_id the row id of the tip.
- * @param tip_id the id of the tip.
- * @param amount the amount of the tip.
- */
-static void
-lookup_tips_cb (void *cls,
- uint64_t row_id,
- struct TALER_TipIdentifierP tip_id,
- struct TALER_Amount amount)
-{
- struct TestLookupTips_Closure *cmp = cls;
- if (NULL == cmp)
- return;
- unsigned int i = cmp->results_length;
- cmp->results_length += 1;
- if (cmp->tips_to_cmp_length > i)
- {
- if ((0 == GNUNET_memcmp (&cmp->tips_to_cmp[i].details.tip_id,
- &tip_id)) &&
- (GNUNET_OK == TALER_amount_cmp_currency (
- &cmp->tips_to_cmp[i].details.total_amount,
- &amount)) &&
- (0 == TALER_amount_cmp (&cmp->tips_to_cmp[i].details.total_amount,
- &amount)))
- cmp->results_match[i] = true;
- else
- cmp->results_match[i] = false;
- }
-}
-
-
-/**
- * Tests looking up the tips from the database.
- * @param instance the instance to look up tips from.
- * @param expired how to filter expired tips.
- * @param offset where to start retrieving tips.
- * @param tips_length length of @e tips.
- * @param tips the tips that we expect to find.
- *
- * @return 0 on success, 1 otherwise.
- */
-static int
-test_lookup_tips (const struct InstanceData *instance,
- enum TALER_EXCHANGE_YesNoAll expired,
- int64_t limit,
- uint64_t offset,
- unsigned int tips_length,
- const struct TipData *tips)
-{
- bool results_match[tips_length];
- struct TestLookupTips_Closure cmp = {
- .tips_to_cmp_length = tips_length,
- .tips_to_cmp = tips,
- .results_length = 0,
- .results_match = results_match
- };
-
- memset (results_match,
- 0,
- sizeof (bool) * tips_length);
- if (0 > plugin->lookup_tips (plugin->cls,
- instance->instance.id,
- expired,
- limit,
- offset,
- &lookup_tips_cb,
- &cmp))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup tips failed\n");
- return 1;
- }
- if (tips_length != cmp.results_length)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup tips failed: incorrect number of results (%d)\n",
- cmp.results_length);
- return 1;
- }
- for (unsigned int i = 0; i < tips_length; ++i)
- {
- if (true != cmp.results_match[i])
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup tips failed: mismatched data\n");
- return 1;
- }
- }
- return 0;
-}
-
-
-/**
- * Convenience function for testing lookup tips with filters
- * @param tips_length length of @e tips.
- * @param tips the array of tips to reverse.
- */
-static void
-reverse_tip_data_array (unsigned int tips_length,
- struct TipData *tips)
-{
- struct TipData tmp[tips_length];
- for (unsigned int i = 0; i < tips_length; ++i)
- tmp[i] = tips[tips_length - 1 - i];
- for (unsigned int i = 0; i < tips_length; ++i)
- tips[i] = tmp[i];
-}
-
-
-/**
- * Container for data for testing tips.
- */
-struct TestTips_Closure
-{
- /**
- * The instance.
- */
- struct InstanceData instance;
-
- /**
- * The tip reserve data.
- */
- struct ReserveData reserve;
-
- /**
- * Reserve data that is expired.
- */
- struct ReserveData expired_reserve;
-
- /**
- * A normal tip.
- */
- struct TipData tip;
-
- /**
- * A tip that is too large to authorize.
- */
- struct TipData bigtip;
-
- /**
- * Array of tips for testing lookups.
- */
- struct TipData tips[5];
-
- /**
- * Id of a pickup.
- */
- struct TALER_PickupIdentifierP pickup_id;
-
- /**
- * Private key of the pickup.
- */
- struct GNUNET_CRYPTO_RsaPrivateKey *pickup_priv;
-
- /**
- * Signature for the pickup.
- */
- struct TALER_BlindedDenominationSignature pickup_sig;
-};
-
-
-/**
- * Prepares for testing tips functionality.
- * @param cls the data to prepare.
- */
-static void
-pre_test_tips (struct TestTips_Closure *cls)
-{
- /* Instance */
- make_instance ("test_inst_tips",
- &cls->instance);
-
- /* Reserve */
- GNUNET_CRYPTO_eddsa_key_create (&cls->reserve.reserve_priv.eddsa_priv);
- GNUNET_CRYPTO_eddsa_key_get_public (&cls->reserve.reserve_priv.eddsa_priv,
- &cls->reserve.reserve_pub.eddsa_pub);
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:99.99",
- &cls->reserve.initial_amount));
- cls->reserve.exchange_url = "https://exch-url/";
- cls->reserve.payto_uri = "payto://other-uri";
- cls->reserve.expiration = GNUNET_TIME_relative_to_timestamp (
- GNUNET_TIME_UNIT_WEEKS);
-
- GNUNET_CRYPTO_eddsa_key_create (
- &cls->expired_reserve.reserve_priv.eddsa_priv);
- GNUNET_CRYPTO_eddsa_key_get_public (
- &cls->expired_reserve.reserve_priv.eddsa_priv,
- &cls->expired_reserve.reserve_pub.
- eddsa_pub);
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:99.99",
- &cls->expired_reserve.initial_amount));
- cls->expired_reserve.exchange_url = "exch-url";
- cls->expired_reserve.payto_uri = "payto://some-uri";
- cls->expired_reserve.expiration = GNUNET_TIME_UNIT_ZERO_TS;
-
- /* Tip/pickup */
- make_tip (&cls->tip);
- make_tip (&cls->bigtip);
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:99.90",
- &cls->bigtip.details.total_amount));
- for (unsigned int i = 0; i < 5; ++i)
- {
- char amount[16];
- make_tip (&cls->tips[i]);
- GNUNET_snprintf (amount,
- 16,
- "EUR:0.%u0",
- i + 1);
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (amount,
- &cls->tips[i].details.total_amount));
- }
-
- cls->pickup_priv = GNUNET_CRYPTO_rsa_private_key_create (2048);
- cls->pickup_sig.cipher = TALER_DENOMINATION_RSA;
- cls->pickup_sig.details.blinded_rsa_signature
- = GNUNET_CRYPTO_rsa_sign_fdh (cls->pickup_priv,
- &cls->pickup_id.hash);
-}
-
-
-/**
- * Cleans up after testing tips.
- * @param cls the data to clean up.
- */
-static void
-post_test_tips (struct TestTips_Closure *cls)
-{
- free_instance_data (&cls->instance);
- GNUNET_CRYPTO_rsa_private_key_free (cls->pickup_priv);
- TALER_blinded_denom_sig_free (&cls->pickup_sig);
-}
-
-
-/**
- * Runs tests for tips.
- * @param cls container of test data.
- *
- * @return 0 on success, 1 on failure.
- */
-static int
-run_test_tips (struct TestTips_Closure *cls)
-{
- struct TALER_Amount zero;
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero ("EUR",
- &zero));
- TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
- GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
- /* Test insert reserve */
- TEST_RET_ON_FAIL (test_insert_reserve (&cls->instance,
- &cls->reserve,
- TALER_EC_NONE));
- /* Test lookup reserve */
- TEST_RET_ON_FAIL (test_lookup_reserve (&cls->instance,
- &cls->reserve.reserve_pub,
- &cls->reserve));
- /* Test lookup pending reserves */
- TEST_RET_ON_FAIL (test_lookup_pending_reserves (1,
- &cls->reserve));
- /* Test reserve activation */
- TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
- plugin->activate_reserve (plugin->cls,
- cls->instance.instance.id,
- &cls->reserve.reserve_pub,
- &cls->reserve.initial_amount),
- "Activate reserve failed\n");
- TEST_RET_ON_FAIL (test_lookup_pending_reserves (0,
- NULL));
- /* Test inserting a tip */
- TEST_RET_ON_FAIL (test_authorize_tip (&cls->instance,
- &cls->reserve,
- &cls->tip));
- /* Test lookup tip */
- TEST_RET_ON_FAIL (test_lookup_tip (&cls->instance,
- &cls->reserve,
- &cls->tip,
- &zero));
- /* Test lookup tip details */
- TEST_RET_ON_FAIL (test_lookup_tip_details (&cls->instance,
- &cls->reserve,
- &cls->tip,
- &zero,
- 0,
- NULL));
- /* Test insert pickup */
- TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
- plugin->insert_pickup (plugin->cls,
- cls->instance.instance.id,
- &cls->tip.details.tip_id,
- &cls->tip.details.total_amount,
- &cls->pickup_id,
- &cls->tip.details.total_amount),
- "Insert pickup failed\n");
- /* Test lookup pickup */
- TEST_RET_ON_FAIL (test_lookup_pickup (&cls->instance,
- &cls->tip,
- &cls->pickup_id,
- cls->reserve.exchange_url,
- &cls->reserve.reserve_priv,
- 0,
- NULL));
- /* Test insert pickup blind signature */
- TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
- plugin->insert_pickup_blind_signature (plugin->cls,
- &cls->pickup_id,
- 0,
- &cls->pickup_sig),
- "Insert pickup blind signature failed\n");
- /* Test that overdrawing the reserve fails */
- TEST_COND_RET_ON_FAIL (TALER_EC_NONE !=
- plugin->authorize_tip (plugin->cls,
- cls->instance.instance.id,
- &cls->reserve.reserve_pub,
- &cls->bigtip.details.
- total_amount,
- cls->bigtip.details.reason,
- cls->bigtip.next_url,
- &cls->bigtip.details.tip_id,
- &cls->reserve.expiration),
- "Authorize tip failed\n");
- /* Test lookup tips */
- TEST_RET_ON_FAIL (test_lookup_tips (&cls->instance,
- TALER_EXCHANGE_YNA_ALL,
- 1,
- 0,
- 1,
- &cls->tip));
- /* Test lookup reserves */
- TEST_RET_ON_FAIL (test_lookup_reserves (&cls->instance,
- 1,
- &cls->reserve));
- {
- /* Test lookup tips & friends */
- struct TipData expected_tips[6];
- expected_tips[0] = cls->tip;
- TEST_RET_ON_FAIL (test_insert_reserve (&cls->instance,
- &cls->expired_reserve,
- TALER_EC_NONE));
- TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
- plugin->activate_reserve (plugin->cls,
- cls->instance.instance.id,
- &cls->expired_reserve.
- reserve_pub,
- &cls->expired_reserve.
- initial_amount),
- "Activate reserve failed\n");
- for (unsigned int i = 0; i < 5; ++i)
- {
- if (i % 2 == 0)
- {
- TEST_RET_ON_FAIL (test_authorize_tip (&cls->instance,
- &cls->expired_reserve,
- &cls->tips[i]));
- }
- else
- {
- TEST_RET_ON_FAIL (test_authorize_tip (&cls->instance,
- &cls->reserve,
- &cls->tips[i]));
- }
- }
- GNUNET_memcpy (&expected_tips[1],
- cls->tips,
- sizeof (struct TipData) * 5);
- /* Test lookup tips inc */
- TEST_RET_ON_FAIL (test_lookup_tips (&cls->instance,
- TALER_EXCHANGE_YNA_ALL,
- 6,
- 0,
- 6,
- expected_tips));
- reverse_tip_data_array (6,
- expected_tips);
- /* Test lookup tips dec */
- TEST_RET_ON_FAIL (test_lookup_tips (&cls->instance,
- TALER_EXCHANGE_YNA_ALL,
- -6,
- 10,
- 6,
- expected_tips));
- /* Test lookup tips expired inc */
- expected_tips[0] = cls->tips[0];
- expected_tips[1] = cls->tips[2];
- expected_tips[2] = cls->tips[4];
- TEST_RET_ON_FAIL (test_lookup_tips (&cls->instance,
- TALER_EXCHANGE_YNA_YES,
- 6,
- 0,
- 3,
- expected_tips));
- /* Test lookup tips expired dec */
- reverse_tip_data_array (3,
- expected_tips);
- TEST_RET_ON_FAIL (test_lookup_tips (&cls->instance,
- TALER_EXCHANGE_YNA_YES,
- -6,
- 10,
- 3,
- expected_tips));
- /* Test lookup tips unexpired inc */
- expected_tips[0] = cls->tip;
- expected_tips[1] = cls->tips[1];
- expected_tips[2] = cls->tips[3];
- TEST_RET_ON_FAIL (test_lookup_tips (&cls->instance,
- TALER_EXCHANGE_YNA_NO,
- 6,
- 0,
- 3,
- expected_tips));
- /* Test lookup tips unexpired dec */
- reverse_tip_data_array (3,
- expected_tips);
- TEST_RET_ON_FAIL (test_lookup_tips (&cls->instance,
- TALER_EXCHANGE_YNA_NO,
- -6,
- 10,
- 3,
- expected_tips));
- }
- /* Test delete reserve private key */
- TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
- plugin->delete_reserve (plugin->cls,
- cls->instance.instance.id,
- &cls->reserve.reserve_pub),
- "Delete reserve private key failed\n");
- TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
- plugin->delete_reserve (plugin->cls,
- cls->instance.instance.id,
- &cls->reserve.reserve_pub),
- "Delete reserve private key failed\n");
- /* Test purging a reserve */
- TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
- plugin->purge_reserve (plugin->cls,
- cls->instance.instance.id,
- &cls->reserve.reserve_pub),
- "Purge reserve failed\n");
- TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
- plugin->purge_reserve (plugin->cls,
- cls->instance.instance.id,
- &cls->reserve.reserve_pub),
- "Purge reserve failed\n");
-
- return 0;
-}
-
-
-/**
- * Handles all logic for testing tips in the database.
- *
- * @return 0 on success, 1 on failure.
- */
-static int
-test_tips (void)
-{
- struct TestTips_Closure test_cls;
- pre_test_tips (&test_cls);
- int test_result = run_test_tips (&test_cls);
- post_test_tips (&test_cls);
- return test_result;
-}
-
-
/**
* Closure for testing lookup_refunds.
*/
@@ -6512,9 +5189,10 @@ pre_test_lookup_orders_all_filters (
i);
make_order (cls->order_ids[i],
&cls->orders[i]);
- GNUNET_assert (0 == json_object_set (cls->orders[i].contract,
- "order_id",
- json_string (cls->order_ids[i])));
+ GNUNET_assert (0 ==
+ json_object_set_new (cls->orders[i].contract,
+ "order_id",
+ json_string (cls->order_ids[i])));
make_deposit (&cls->instance,
&cls->account,
&cls->orders[i],
@@ -6718,7 +5396,8 @@ kyc_status_ok (void *cls,
const char *payto_uri,
const char *exchange_url,
struct GNUNET_TIME_Timestamp last_check,
- bool kyc_ok)
+ bool kyc_ok,
+ enum TALER_AmlDecisionState ades)
{
bool *fail = cls;
@@ -6734,7 +5413,8 @@ kyc_status_fail (void *cls,
const char *payto_uri,
const char *exchange_url,
struct GNUNET_TIME_Timestamp last_check,
- bool kyc_ok)
+ bool kyc_ok,
+ enum TALER_AmlDecisionState ades)
{
bool *fail = cls;
@@ -6774,7 +5454,8 @@ test_kyc (void)
NULL,
NULL,
now,
- false));
+ false,
+ TALER_AML_NORMAL));
TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->account_kyc_set_status (plugin->cls,
instance.instance.id,
@@ -6784,7 +5465,8 @@ test_kyc (void)
NULL,
NULL,
now,
- false));
+ false,
+ TALER_AML_NORMAL));
TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->account_kyc_set_status (plugin->cls,
instance.instance.id,
@@ -6794,7 +5476,8 @@ test_kyc (void)
NULL,
NULL,
now,
- true));
+ true,
+ TALER_AML_NORMAL));
fail = true;
TEST_RET_ON_FAIL (1 !=
plugin->account_kyc_get_status (plugin->cls,
@@ -6831,6 +5514,8 @@ test_kyc (void)
&kyc_status_ok,
&fail));
TEST_RET_ON_FAIL (fail);
+ json_decref (instance.instance.address);
+ json_decref (instance.instance.jurisdiction);
return 0;
}
@@ -6866,7 +5551,7 @@ make_template (const char *id,
{
template->id = id;
template->template.template_description = "This is a test template";
- template->template.image = NULL;
+ template->template.otp_id = NULL;
template->template.template_contract = json_array ();
GNUNET_assert (NULL != template->template.template_contract);
}
@@ -6880,7 +5565,7 @@ make_template (const char *id,
static void
free_template_data (struct TemplateData *template)
{
- GNUNET_free (template->template.image);
+ GNUNET_free (template->template.otp_id);
json_decref (template->template.template_contract);
}
@@ -6898,9 +5583,10 @@ check_templates_equal (const struct TALER_MERCHANTDB_TemplateDetails *a,
{
if ((0 != strcmp (a->template_description,
b->template_description)) ||
- ( (NULL == a->image) && (NULL != b->image)) ||
- ( (NULL != a->image) && (NULL == b->image)) ||
- ( (NULL != a->image) && (0 != strcmp (a->image, b->image))) ||
+ ( (NULL == a->otp_id) && (NULL != b->otp_id)) ||
+ ( (NULL != a->otp_id) && (NULL == b->otp_id)) ||
+ ( (NULL != a->otp_id) && (0 != strcmp (a->otp_id,
+ b->otp_id))) ||
(1 != json_equal (a->template_contract,
b->template_contract)))
return 1;
@@ -6925,6 +5611,7 @@ test_insert_template (const struct InstanceData *instance,
plugin->insert_template (plugin->cls,
instance->instance.id,
template->id,
+ 0,
&template->template),
"Insert template failed\n");
return 0;
@@ -6966,11 +5653,12 @@ test_lookup_template (const struct InstanceData *instance,
const struct TemplateData *template)
{
struct TALER_MERCHANTDB_TemplateDetails lookup_result;
+
if (0 > plugin->lookup_template (plugin->cls,
instance->instance.id,
template->id,
&lookup_result))
- {
+ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup template failed\n");
TALER_MERCHANTDB_template_details_free (&lookup_result);
@@ -7030,15 +5718,16 @@ lookup_templates_cb (void *cls,
const char *template_description)
{
struct TestLookupTemplates_Closure *cmp = cls;
+
if (NULL == cmp)
return;
cmp->results_length += 1;
for (unsigned int i = 0; cmp->templates_to_cmp_length > i; ++i)
{
- if ((0 == strcmp (cmp->templates_to_cmp[i].id,
- template_id)) &&
- (0 == strcmp (cmp->templates_to_cmp[i].template.template_description,
- template_description)) )
+ if ( (0 == strcmp (cmp->templates_to_cmp[i].id,
+ template_id)) &&
+ (0 == strcmp (cmp->templates_to_cmp[i].template.template_description,
+ template_description)) )
cmp->results_matching[i] += 1;
}
}
@@ -7064,7 +5753,10 @@ test_lookup_templates (const struct InstanceData *instance,
.results_matching = results_matching,
.results_length = 0
};
- memset (results_matching, 0, sizeof (unsigned int) * templates_length);
+
+ memset (results_matching,
+ 0,
+ sizeof (unsigned int) * templates_length);
if (0 > plugin->lookup_templates (plugin->cls,
instance->instance.id,
&lookup_templates_cb,
@@ -7211,8 +5903,22 @@ run_test_templates (struct TestTemplates_Closure *cls)
/* Test template update */
cls->templates[0].template.template_description =
"This is a test template that has been updated!";
- GNUNET_free (cls->templates[0].template.image);
- cls->templates[0].template.image = GNUNET_strdup ("image");
+ GNUNET_free (cls->templates[0].template.otp_id);
+ cls->templates[0].template.otp_id = GNUNET_strdup ("otp_id");
+ {
+ /* ensure OTP device exists */
+ struct TALER_MERCHANTDB_OtpDeviceDetails td = {
+ .otp_description = "my otp",
+ .otp_key = "my key",
+ .otp_algorithm = 1,
+ .otp_ctr = 42
+ };
+ GNUNET_assert (0 <=
+ plugin->insert_otp (plugin->cls,
+ cls->instance.instance.id,
+ "otp_id",
+ &td));
+ }
GNUNET_assert (0 ==
json_array_append_new (
cls->templates[0].template.template_contract,
@@ -7220,7 +5926,6 @@ run_test_templates (struct TestTemplates_Closure *cls)
TEST_RET_ON_FAIL (test_update_template (&cls->instance,
&cls->templates[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
-
TEST_RET_ON_FAIL (test_lookup_template (&cls->instance,
&cls->templates[0]));
TEST_RET_ON_FAIL (test_update_template (&cls->instance,
@@ -7258,8 +5963,13 @@ static int
test_templates (void)
{
struct TestTemplates_Closure test_cls;
+ int test_result;
+
+ memset (&test_cls,
+ 0,
+ sizeof (test_cls));
pre_test_templates (&test_cls);
- int test_result = run_test_templates (&test_cls);
+ test_result = run_test_templates (&test_cls);
post_test_templates (&test_cls);
return test_result;
}
@@ -7514,6 +6224,7 @@ test_lookup_webhooks (const struct InstanceData *instance,
return 0;
}
+
/**
* Function called after calling @e test_lookup_webhooks
*
@@ -7538,16 +6249,17 @@ lookup_webhook_by_event_cb (void *cls,
if ((0 == strcmp (cmp->webhooks_to_cmp[i].webhook.event_type,
event_type)) &&
(0 == strcmp (cmp->webhooks_to_cmp[i].webhook.url,
- url)) &&
+ url)) &&
(0 == strcmp (cmp->webhooks_to_cmp[i].webhook.http_method,
- http_method)) &&
+ http_method)) &&
(0 == strcmp (cmp->webhooks_to_cmp[i].webhook.header_template,
- header_template)) &&
+ header_template)) &&
(0 == strcmp (cmp->webhooks_to_cmp[i].webhook.body_template,
body_template)) )
cmp->results_matching[i] += 1;
}
- }
+}
+
/**
* Tests looking up webhooks by event for an instance.
@@ -7600,7 +6312,6 @@ test_lookup_webhook_by_event (const struct InstanceData *instance,
}
-
/**
* Tests deleting a webhook.
*
@@ -7794,7 +6505,6 @@ test_webhooks (void)
}
-
/* *********** Pending Webhooks ********** */
/**
@@ -7805,7 +6515,7 @@ struct PendingWebhookData
/**
* Reference to the configured webhook template.
*/
- uint64_t webhook_serial;
+ uint64_t webhook_serial;
/**
* The details of the pending webhook.
@@ -7814,8 +6524,6 @@ struct PendingWebhookData
};
-
-
/**
* Creates a pending webhook for testing with.
*
@@ -7828,43 +6536,13 @@ make_pending_webhook (uint64_t webhook_serial,
{
pwebhook->webhook_serial = webhook_serial;
pwebhook->pwebhook.next_attempt = GNUNET_TIME_UNIT_ZERO_ABS;
- pwebhook->pwebhook.retries= 0;
+ pwebhook->pwebhook.retries = 0;
pwebhook->pwebhook.url = "https://exampletest.com";
pwebhook->pwebhook.http_method = "POST";
pwebhook->pwebhook.header = "Authorization:XYJAORKJEO";
pwebhook->pwebhook.body = "$Amount";
}
-/**
- * Compare two pending webhooks for equality.
- *
- * @param a the first pending webhook.
- * @param b the second pending webhook.
- * @return 0 on equality, 1 otherwise.
- */
-static int
-check_pending_webhooks_equal (const struct TALER_MERCHANTDB_PendingWebhookDetails *a,
- const struct TALER_MERCHANTDB_PendingWebhookDetails *b)
-{
- if (GNUNET_TIME_absolute_cmp (a->next_attempt,
- !=,
- b->next_attempt) ||
- (a->retries != b->retries) ||
- (0 != strcmp (a->url,
- b->url)) ||
- (0 != strcmp (a->http_method,
- b->http_method)) ||
- (0 != strcmp (a->header,
- b->header)) ||
- (0 != strcmp (a->body,
- b->body)))
- {
- fprintf(stdout, " retries %d vs %d", a->retries, b->retries);
- return 1;
- }
- return 0;
-}
-
/**
* Tests inserting pending webhook data into the database.
@@ -7883,10 +6561,13 @@ test_insert_pending_webhook (const struct InstanceData *instance,
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_pending_webhook (plugin->cls,
instance->instance.id,
- pwebhook->webhook_serial,
+ pwebhook->
+ webhook_serial,
pwebhook->pwebhook.url,
- pwebhook->pwebhook.http_method,
- pwebhook->pwebhook.header,
+ pwebhook->pwebhook.
+ http_method,
+ pwebhook->pwebhook.
+ header,
pwebhook->pwebhook.body),
"Insert pending webhook failed\n");
return 0;
@@ -7906,51 +6587,108 @@ test_update_pending_webhook (const struct InstanceData *instance,
struct PendingWebhookData *pwebhook,
enum GNUNET_DB_QueryStatus expected_result)
{
+ pwebhook->pwebhook.next_attempt = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_UNIT_HOURS);
+ pwebhook->pwebhook.retries++;
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->update_pending_webhook (plugin->cls,
- pwebhook->webhook_serial,
- pwebhook->pwebhook.next_attempt),
+ pwebhook->
+ webhook_serial,
+ pwebhook->pwebhook.
+ next_attempt),
"Update pending webhook failed\n");
return 0;
}
/**
- * Tests looking up a pending webhook from the db.
+ * Container for information for looking up the row number of a deposit.
+ */
+struct LookupPendingWebhookSerial_Closure
+{
+ /**
+ * The pending webhook we're looking for.
+ */
+ const struct PendingWebhookData *pwebhook;
+
+ /**
+ * The serial found.
+ */
+ uint64_t webhook_pending_serial;
+};
+
+
+/**
+ * Function called after calling @e test_lookup_all_webhook,
+ * test_lookup_future_webhook and test_lookup_pending_webhook
*
- * @param instance the instance to query from.
- * @param pwebhook the pending webhook to query and compare to.
- * @return 0 when successful, 1 otherwise.
+ * @param cls a pointer to the lookup closure.
+ * @param webhook_serial reference to the configured webhook template.
*/
-static int
-test_lookup_pending_webhook (const struct InstanceData *instance,
- struct PendingWebhookData *pwebhook)
-{
- struct TALER_MERCHANTDB_PendingWebhookDetails lookup_result;
- if (0 > plugin->lookup_pending_webhook (plugin->cls,
- instance->instance.id,
- &pwebhook->webhook_serial,
- &lookup_result))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup pending webhook failed\n");
- TALER_MERCHANTDB_pending_webhook_details_free (&lookup_result);
- return 1;
- }
- const struct TALER_MERCHANTDB_PendingWebhookDetails *to_cmp = &pwebhook->pwebhook;
- if (0 != check_pending_webhooks_equal (&lookup_result,
- to_cmp))
+static void
+get_pending_serial_cb (void *cls,
+ uint64_t webhook_pending_serial,
+ struct GNUNET_TIME_Absolute next_attempt,
+ uint32_t retries,
+ const char *url,
+ const char *http_method,
+ const char *header,
+ const char *body)
+{
+ struct LookupPendingWebhookSerial_Closure *lpw = cls;
+
+ if ((0 == strcmp (lpw->pwebhook->pwebhook.url,
+ url)) &&
+ (0 == strcmp (lpw->pwebhook->pwebhook.http_method,
+ http_method)) &&
+ (0 == strcmp (lpw->pwebhook->pwebhook.header,
+ header)) &&
+ (0 == strcmp (lpw->pwebhook->pwebhook.body,
+ body)) )
+ {
+ lpw->webhook_pending_serial = webhook_pending_serial;
+ }
+ /* else
{
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup pending webhook failed: incorrect pending webhook returned\n");
- TALER_MERCHANTDB_pending_webhook_details_free (&lookup_result);
- return 1;
- }
- TALER_MERCHANTDB_pending_webhook_details_free (&lookup_result);
- return 0;
+ fprintf(stdout, "next_attempt: %lu vs %lu\n", lpw->pwebhook->pwebhook.next_attempt.abs_value_us, next_attempt.abs_value_us);
+ fprintf(stdout, "retries: %d vs %d\n", lpw->pwebhook->pwebhook.retries, retries);
+ fprintf(stdout, "url: %s vs %s\n", lpw->pwebhook->pwebhook.url, url);
+ fprintf(stdout, "http_method: %s vs %s\n", lpw->pwebhook->pwebhook.http_method, http_method);
+ fprintf(stdout, "header: %s vs %s\n", lpw->pwebhook->pwebhook.header, header);
+ fprintf(stdout, "body: %s vs %s\n", lpw->pwebhook->pwebhook.body, body);
+ }*/
+}
+
+
+/**
+ * Convenience function to retrieve the row number of a webhook pending in the database.
+ *
+ * @param instance the instance to get webhook pending(wp) from.
+ * @param webhook pending the wp to lookup the serial for.
+ * @return the row number of the deposit.
+ */
+static uint64_t
+get_pending_serial (const struct InstanceData *instance,
+ const struct PendingWebhookData *pwebhook)
+{
+ struct LookupPendingWebhookSerial_Closure lpw = {
+ .pwebhook = pwebhook,
+ .webhook_pending_serial = 0
+ };
+
+ GNUNET_assert (0 <
+ plugin->lookup_all_webhooks (plugin->cls,
+ instance->instance.id,
+ 0,
+ INT_MAX,
+ &get_pending_serial_cb,
+ &lpw));
+ GNUNET_assert (0 != lpw.webhook_pending_serial);
+
+ return lpw.webhook_pending_serial;
}
+
/**
* Closure for testing pending webhook lookup
*/
@@ -7977,6 +6715,7 @@ struct TestLookupPendingWebhooks_Closure
unsigned int results_length;
};
+
/**
* Function called after calling @e test_lookup_all_webhook,
* test_lookup_future_webhook and test_lookup_pending_webhook
@@ -7986,7 +6725,7 @@ struct TestLookupPendingWebhooks_Closure
*/
static void
lookup_pending_webhooks_cb (void *cls,
- uint64_t webhook_serial,
+ uint64_t webhook_pending_serial,
struct GNUNET_TIME_Absolute next_attempt,
uint32_t retries,
const char *url,
@@ -7995,16 +6734,21 @@ lookup_pending_webhooks_cb (void *cls,
const char *body)
{
struct TestLookupPendingWebhooks_Closure *cmp = cls;
- if (NULL == cmp)
- return;
- cmp->results_length += 1;
+
+ cmp->results_length++;
for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
{
- if ((cmp->webhooks_to_cmp[i].webhook_serial != webhook_serial) &&
- (GNUNET_TIME_absolute_cmp (cmp->webhooks_to_cmp[i].pwebhook.next_attempt,
- !=,
- next_attempt)))
- cmp->results_matching[i] += 1;
+ if ((0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.url,
+ url)) &&
+ (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.http_method,
+ http_method)) &&
+ (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.header,
+ header)) &&
+ (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.body,
+ body)) )
+ {
+ cmp->results_matching[i]++;
+ }
}
}
@@ -8019,8 +6763,8 @@ lookup_pending_webhooks_cb (void *cls,
*/
static int
test_lookup_pending_webhooks (const struct InstanceData *instance,
- unsigned int pwebhooks_length,
- const struct PendingWebhookData *pwebhooks)
+ unsigned int pwebhooks_length,
+ const struct PendingWebhookData *pwebhooks)
{
unsigned int results_matching[pwebhooks_length];
struct TestLookupPendingWebhooks_Closure cls = {
@@ -8029,10 +6773,11 @@ test_lookup_pending_webhooks (const struct InstanceData *instance,
.results_matching = results_matching,
.results_length = 0
};
- memset (results_matching, 0, sizeof (unsigned int) * pwebhooks_length);
+
+ memset (results_matching, 0, sizeof (results_matching));
if (0 > plugin->lookup_pending_webhooks (plugin->cls,
- &lookup_pending_webhooks_cb,
- &cls))
+ &lookup_pending_webhooks_cb,
+ &cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup pending webhook failed\n");
@@ -8044,7 +6789,7 @@ test_lookup_pending_webhooks (const struct InstanceData *instance,
"Lookup pending webhook failed: incorrect number of results\n");
return 1;
}
- for (unsigned int i = 0; pwebhooks_length > i; ++i)
+ for (unsigned int i = 0; i < pwebhooks_length; i++)
{
if (1 != cls.results_matching[i])
{
@@ -8054,7 +6799,8 @@ test_lookup_pending_webhooks (const struct InstanceData *instance,
}
}
return 0;
- }
+}
+
/**
* Tests looking up the future webhook to send for an instance.
@@ -8066,8 +6812,8 @@ test_lookup_pending_webhooks (const struct InstanceData *instance,
*/
static int
test_lookup_future_webhook (const struct InstanceData *instance,
- unsigned int pwebhooks_length,
- const struct PendingWebhookData *pwebhooks)
+ unsigned int pwebhooks_length,
+ const struct PendingWebhookData *pwebhooks)
{
unsigned int results_matching[pwebhooks_length];
struct TestLookupPendingWebhooks_Closure cls = {
@@ -8076,7 +6822,8 @@ test_lookup_future_webhook (const struct InstanceData *instance,
.results_matching = results_matching,
.results_length = 0
};
- memset (results_matching, 0, sizeof (unsigned int) * pwebhooks_length);
+
+ memset (results_matching, 0, sizeof (results_matching));
if (0 > plugin->lookup_future_webhook (plugin->cls,
&lookup_pending_webhooks_cb,
&cls))
@@ -8103,6 +6850,7 @@ test_lookup_future_webhook (const struct InstanceData *instance,
return 0;
}
+
/**
* Tests looking up all the pending webhook for an instance.
*
@@ -8116,16 +6864,19 @@ test_lookup_all_webhooks (const struct InstanceData *instance,
unsigned int pwebhooks_length,
const struct PendingWebhookData *pwebhooks)
{
- uint64_t max_results=2;
- uint64_t min_row=0;
- unsigned int results_matching[pwebhooks_length];
+ uint64_t max_results = 2;
+ uint64_t min_row = 0;
+ unsigned int results_matching[GNUNET_NZL (pwebhooks_length)];
struct TestLookupPendingWebhooks_Closure cls = {
.webhooks_to_cmp_length = pwebhooks_length,
.webhooks_to_cmp = pwebhooks,
.results_matching = results_matching,
.results_length = 0
};
- memset (results_matching, 0, sizeof (unsigned int) * pwebhooks_length);
+
+ memset (results_matching,
+ 0,
+ sizeof (results_matching));
if (0 > plugin->lookup_all_webhooks (plugin->cls,
instance->instance.id,
min_row,
@@ -8165,12 +6916,13 @@ test_lookup_all_webhooks (const struct InstanceData *instance,
* @return 0 when successful, 1 otherwise.
*/
static int
-test_delete_pending_webhook (const struct PendingWebhookData *webhook,
+test_delete_pending_webhook (uint64_t webhooks_pending_serial,
enum GNUNET_DB_QueryStatus expected_result)
{
+
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->delete_pending_webhook (plugin->cls,
- webhook->webhook_serial),
+ webhooks_pending_serial),
"Delete webhook failed\n");
return 0;
}
@@ -8252,40 +7004,39 @@ run_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
&cls->pwebhooks[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
- /* Test lookup of individual pending webhook */
- TEST_RET_ON_FAIL (test_lookup_pending_webhook (&cls->instance,
- &cls->pwebhooks[0]));
- /* Test pending webhook update */
- cls->pwebhooks[0].pwebhook.next_attempt = GNUNET_TIME_absolute_get ();
-
- TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance,
- &cls->pwebhooks[0],
- GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
- TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance,
- &cls->pwebhooks[1],
- GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
-
- /* Test collective pending webhook lookup */
TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
&cls->pwebhooks[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
- TEST_RET_ON_FAIL (test_lookup_future_webhook (&cls->instance,
- 1,
- cls->pwebhooks));
+ /* Test collective pending webhook lookup */
TEST_RET_ON_FAIL (test_lookup_pending_webhooks (&cls->instance,
2,
cls->pwebhooks));
+ /* Test pending webhook update */
+ TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance,
+ &cls->pwebhooks[0],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ TEST_RET_ON_FAIL (test_lookup_future_webhook (&cls->instance,
+ 1,
+ &cls->pwebhooks[1]));
+ TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance,
+ &cls->pwebhooks[1],
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); // ???
TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance,
2,
cls->pwebhooks));
+ uint64_t webhook_pending_serial0 = get_pending_serial (&cls->instance,
+ &cls->pwebhooks[0]);
+ uint64_t webhook_pending_serial1 = get_pending_serial (&cls->instance,
+ &cls->pwebhooks[1]);
+
/* Test webhook deletion */
- TEST_RET_ON_FAIL (test_delete_pending_webhook (&cls->pwebhooks[1],
+ TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial1,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test double deletion fails */
- TEST_RET_ON_FAIL (test_delete_pending_webhook (&cls->pwebhooks[1],
+ TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial1,
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
- TEST_RET_ON_FAIL (test_delete_pending_webhook (&cls->pwebhooks[0],
+ TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial0,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance,
0,
@@ -8323,7 +7074,6 @@ run_tests (void)
TEST_RET_ON_FAIL (test_orders ());
TEST_RET_ON_FAIL (test_deposits ());
TEST_RET_ON_FAIL (test_transfers ());
- TEST_RET_ON_FAIL (test_tips ());
TEST_RET_ON_FAIL (test_refunds ());
TEST_RET_ON_FAIL (test_lookup_orders_all_filters ());
TEST_RET_ON_FAIL (test_kyc ());
diff --git a/src/backenddb/versioning.sql b/src/backenddb/versioning.sql
index 98e7f661..444cf95e 100644
--- a/src/backenddb/versioning.sql
+++ b/src/backenddb/versioning.sql
@@ -146,12 +146,13 @@
BEGIN;
+
-- This file adds versioning support to database it will be loaded to.
-- It requires that PL/pgSQL is already loaded - will raise exception otherwise.
-- All versioning "stuff" (tables, functions) is in "_v" schema.
-- All functions are defined as 'RETURNS SETOF INT4' to be able to make them to RETURN literally nothing (0 rows).
--- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling.
+-- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling
CREATE SCHEMA IF NOT EXISTS _v;
COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.';
@@ -205,7 +206,7 @@ BEGIN
RETURN;
END;
$$ language plpgsql;
-COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[], TEXT[]) IS 'Function to register patches in database. Raises exception if there are conflicts, prerequisites are not installed or the migration has already been installed.';
+COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[], TEXT[] ) IS 'Function to register patches in database. Raises exception if there are conflicts, prerequisites are not installed or the migration has already been installed.';
CREATE OR REPLACE FUNCTION _v.register_patch( TEXT, TEXT[] ) RETURNS setof INT4 AS $$
SELECT _v.register_patch( $1, $2, NULL );
diff --git a/src/bank/Makefile.am b/src/bank/Makefile.am
new file mode 100644
index 00000000..35172268
--- /dev/null
+++ b/src/bank/Makefile.am
@@ -0,0 +1,28 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = \
+ libtalermerchantbank.la
+
+libtalermerchantbank_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+libtalermerchantbank_la_SOURCES = \
+ mb_common.c mb_common.h \
+ mb_credit.c \
+ mb_parse.c
+libtalermerchantbank_la_LIBADD = \
+ -ltalerjson \
+ -ltalercurl \
+ -ltalerutil \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ -lcurl \
+ $(XLIB)
diff --git a/src/bank/mb_common.c b/src/bank/mb_common.c
new file mode 100644
index 00000000..d113ddf9
--- /dev/null
+++ b/src/bank/mb_common.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of Taler
+ Copyright (C) 2015-2023 Taler Systems SA
+
+ Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Taler; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank/mb_common.c
+ * @brief Common functions for the bank API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "mb_common.h"
+
+
+enum GNUNET_GenericReturnValue
+TALER_MERCHANT_BANK_setup_auth_ (
+ CURL *easy,
+ const struct TALER_MERCHANT_BANK_AuthenticationData *auth)
+{
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = GNUNET_OK;
+ switch (auth->method)
+ {
+ case TALER_MERCHANT_BANK_AUTH_NONE:
+ return GNUNET_OK;
+ case TALER_MERCHANT_BANK_AUTH_BASIC:
+ {
+ char *up;
+
+ GNUNET_asprintf (&up,
+ "%s:%s",
+ auth->details.basic.username,
+ auth->details.basic.password);
+ if ( (CURLE_OK !=
+ curl_easy_setopt (easy,
+ CURLOPT_HTTPAUTH,
+ CURLAUTH_BASIC)) ||
+ (CURLE_OK !=
+ curl_easy_setopt (easy,
+ CURLOPT_USERPWD,
+ up)) )
+ ret = GNUNET_SYSERR;
+ GNUNET_free (up);
+ break;
+ }
+ }
+ return ret;
+}
+
+
+/* end of mb_common.c */
diff --git a/src/bank/mb_common.h b/src/bank/mb_common.h
new file mode 100644
index 00000000..278b7600
--- /dev/null
+++ b/src/bank/mb_common.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of Taler
+ Copyright (C) 2015-2023 Taler Systems SA
+
+ Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Taler; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank/mb_common.h
+ * @brief Common functions for the bank API
+ * @author Christian Grothoff
+ */
+#ifndef MB_COMMON_H
+#define MB_COMMON_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_bank_lib.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Set authentication data in @a easy from @a auth.
+ *
+ * @param easy curl handle to setup for authentication
+ * @param auth authentication data to use
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_MERCHANT_BANK_setup_auth_ (
+ CURL *easy,
+ const struct TALER_MERCHANT_BANK_AuthenticationData *auth);
+
+
+#endif
diff --git a/src/bank/mb_credit.c b/src/bank/mb_credit.c
new file mode 100644
index 00000000..ea5aff7b
--- /dev/null
+++ b/src/bank/mb_credit.c
@@ -0,0 +1,340 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2017--2023 Taler Systems SA
+
+ Taler is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ Taler is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with Taler; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank/mb_credit.c
+ * @brief Implementation of the /history
+ * requests of the libeufin's Taler merchant facade
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "mb_common.h"
+#include <microhttpd.h> /* just for HTTP status codes */
+
+
+/**
+ * @brief A /history Handle
+ */
+struct TALER_MERCHANT_BANK_CreditHistoryHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *request_url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_BANK_CreditHistoryCallback hcb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *hcb_cls;
+};
+
+
+/**
+ * Parse history given in JSON format and invoke the callback on each item.
+ *
+ * @param hh handle to the account history request
+ * @param history JSON array with the history
+ * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
+ * were set,
+ * #GNUNET_SYSERR if there was a protocol violation in @a history
+ */
+static enum GNUNET_GenericReturnValue
+parse_account_history (struct TALER_MERCHANT_BANK_CreditHistoryHandle *hh,
+ const json_t *history)
+{
+ const json_t *history_array;
+ const char *credit_account_uri;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("incoming_transactions",
+ &history_array),
+ GNUNET_JSON_spec_string ("credit_account",
+ &credit_account_uri),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (history,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ for (unsigned int i = 0; i<json_array_size (history_array); i++)
+ {
+ struct TALER_MERCHANT_BANK_CreditDetails td;
+ uint64_t row_id;
+ struct GNUNET_JSON_Specification hist_spec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &td.amount),
+ GNUNET_JSON_spec_timestamp ("date",
+ &td.execution_date),
+ GNUNET_JSON_spec_uint64 ("row_id",
+ &row_id),
+ GNUNET_JSON_spec_string ("subject",
+ &td.wire_subject),
+ GNUNET_JSON_spec_string ("debit_account",
+ &td.debit_account_uri),
+ GNUNET_JSON_spec_end ()
+ };
+ json_t *transaction = json_array_get (history_array,
+ i);
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ hist_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ td.credit_account_uri = credit_account_uri;
+ if (GNUNET_OK !=
+ hh->hcb (hh->hcb_cls,
+ MHD_HTTP_OK,
+ TALER_EC_NONE,
+ row_id,
+ &td))
+ {
+ hh->hcb = NULL;
+ GNUNET_JSON_parse_free (hist_spec);
+ return GNUNET_OK;
+ }
+ GNUNET_JSON_parse_free (hist_spec);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP "/history" request.
+ *
+ * @param cls the `struct TALER_MERCHANT_BANK_CreditHistoryHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_credit_history_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_BANK_CreditHistoryHandle *hh = cls;
+ const json_t *j = response;
+ enum TALER_ErrorCode ec;
+
+ hh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ parse_account_history (hh,
+ j))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ response_code = MHD_HTTP_NO_CONTENT; /* signal end of list */
+ ec = TALER_EC_NONE;
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ ec = TALER_EC_NONE;
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the bank is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break_op (0);
+ ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* Nothing really to verify, bank says the HTTP Authentication
+ failed. May happen if HTTP authentication is used and the
+ user supplied a wrong username/password combination. */
+ ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify: the bank is either unaware
+ of the endpoint (not a bank), or of the account.
+ We should pass the JSON (?) reply to the application */
+ ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ ec = TALER_JSON_get_error_code (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ ec = TALER_JSON_get_error_code (j);
+ break;
+ }
+ if (NULL != hh->hcb)
+ hh->hcb (hh->hcb_cls,
+ response_code,
+ ec,
+ 0LLU,
+ NULL);
+ TALER_MERCHANT_BANK_credit_history_cancel (hh);
+}
+
+
+struct TALER_MERCHANT_BANK_CreditHistoryHandle *
+TALER_MERCHANT_BANK_credit_history (
+ struct GNUNET_CURL_Context *ctx,
+ const struct TALER_MERCHANT_BANK_AuthenticationData *auth,
+ uint64_t start_row,
+ int64_t num_results,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_MERCHANT_BANK_CreditHistoryCallback hres_cb,
+ void *hres_cb_cls)
+{
+ char url[128];
+ struct TALER_MERCHANT_BANK_CreditHistoryHandle *hh;
+ CURL *eh;
+ unsigned long long tms;
+
+ if (0 == num_results)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ tms = (unsigned long long) (timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
+ if ( ( (UINT64_MAX == start_row) &&
+ (0 > num_results) ) ||
+ ( (0 == start_row) &&
+ (0 < num_results) ) )
+ {
+ if ( (0 < num_results) &&
+ (! GNUNET_TIME_relative_is_zero (timeout)) )
+ GNUNET_snprintf (url,
+ sizeof (url),
+ "history?delta=%lld&long_poll_ms=%llu",
+ (long long) num_results,
+ tms);
+ else
+ GNUNET_snprintf (url,
+ sizeof (url),
+ "history?delta=%lld",
+ (long long) num_results);
+ }
+ else
+ {
+ if ( (0 < num_results) &&
+ (! GNUNET_TIME_relative_is_zero (timeout)) )
+ GNUNET_snprintf (url,
+ sizeof (url),
+ "history?delta=%lld&start=%llu&long_poll_ms=%llu",
+ (long long) num_results,
+ (unsigned long long) start_row,
+ tms);
+ else
+ GNUNET_snprintf (url,
+ sizeof (url),
+ "history?delta=%lld&start=%llu",
+ (long long) num_results,
+ (unsigned long long) start_row);
+ }
+ hh = GNUNET_new (struct TALER_MERCHANT_BANK_CreditHistoryHandle);
+ hh->hcb = hres_cb;
+ hh->hcb_cls = hres_cb_cls;
+ hh->request_url = TALER_url_join (auth->wire_gateway_url,
+ url,
+ NULL);
+ if (NULL == hh->request_url)
+ {
+ GNUNET_free (hh);
+ GNUNET_break (0);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Requesting credit history at `%s'\n",
+ hh->request_url);
+ eh = curl_easy_init ();
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_MERCHANT_BANK_setup_auth_ (eh,
+ auth)) ||
+ (CURLE_OK !=
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ hh->request_url)) )
+ {
+ GNUNET_break (0);
+ TALER_MERCHANT_BANK_credit_history_cancel (hh);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ return NULL;
+ }
+ if (0 != tms)
+ {
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 100L)));
+ }
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_VERBOSE,
+ 1L));
+ hh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ NULL,
+ &handle_credit_history_finished,
+ hh);
+ return hh;
+}
+
+
+void
+TALER_MERCHANT_BANK_credit_history_cancel (
+ struct TALER_MERCHANT_BANK_CreditHistoryHandle *hh)
+{
+ if (NULL != hh->job)
+ {
+ GNUNET_CURL_job_cancel (hh->job);
+ hh->job = NULL;
+ }
+ GNUNET_free (hh->request_url);
+ GNUNET_free (hh);
+}
+
+
+/* end of mb_credit.c */
diff --git a/src/bank/mb_parse.c b/src/bank/mb_parse.c
new file mode 100644
index 00000000..c05ea133
--- /dev/null
+++ b/src/bank/mb_parse.c
@@ -0,0 +1,218 @@
+/*
+ This file is part of Taler
+ Copyright (C) 2018-2023 Taler Systems SA
+
+ Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Taler; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank/mb_parse.c
+ * @brief Convenience function to parse authentication configuration
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_merchant_bank_lib.h"
+#include <gnunet/gnunet_json_lib.h>
+
+
+/**
+ * Names of authentication methods available.
+ */
+static const struct
+{
+ const char *m;
+ enum TALER_MERCHANT_BANK_AuthenticationMethod e;
+} methods[] = {
+ { "NONE", TALER_MERCHANT_BANK_AUTH_NONE },
+ { "BASIC", TALER_MERCHANT_BANK_AUTH_BASIC },
+ { NULL, TALER_MERCHANT_BANK_AUTH_NONE }
+};
+
+
+enum GNUNET_GenericReturnValue
+TALER_MERCHANT_BANK_auth_parse_cfg (
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section,
+ struct TALER_MERCHANT_BANK_AuthenticationData *auth)
+{
+ char *method;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "WIRE_GATEWAY_URL",
+ &auth->wire_gateway_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "WIRE_GATEWAY_URL");
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "WIRE_GATEWAY_AUTH_METHOD",
+ &method))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "WIRE_GATEWAY_AUTH_METHOD");
+ GNUNET_free (auth->wire_gateway_url);
+ return GNUNET_SYSERR;
+ }
+ for (unsigned int i = 0; NULL != methods[i].m; i++)
+ {
+ if (0 == strcasecmp (method,
+ methods[i].m))
+ {
+ switch (methods[i].e)
+ {
+ case TALER_MERCHANT_BANK_AUTH_NONE:
+ auth->method = TALER_MERCHANT_BANK_AUTH_NONE;
+ GNUNET_free (method);
+ return GNUNET_OK;
+ case TALER_MERCHANT_BANK_AUTH_BASIC:
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "USERNAME",
+ &auth->details.basic.username))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "USERNAME");
+ GNUNET_free (method);
+ GNUNET_free (auth->wire_gateway_url);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "PASSWORD",
+ &auth->details.basic.password))
+ {
+ GNUNET_free (auth->details.basic.username);
+ auth->details.basic.username = NULL;
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "PASSWORD");
+ GNUNET_free (method);
+ GNUNET_free (auth->wire_gateway_url);
+ return GNUNET_SYSERR;
+ }
+ auth->method = TALER_MERCHANT_BANK_AUTH_BASIC;
+ GNUNET_free (method);
+ return GNUNET_OK;
+ }
+ }
+ }
+ GNUNET_free (method);
+ return GNUNET_SYSERR;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MERCHANT_BANK_auth_parse_json (
+ const json_t *cred,
+ const char *backend_url,
+ struct TALER_MERCHANT_BANK_AuthenticationData *auth)
+{
+ const char *method;
+
+ if (NULL == backend_url)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if ( (0 == strlen (backend_url)) ||
+ ('/' != backend_url[strlen (backend_url) - 1]) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ auth->wire_gateway_url = GNUNET_strdup (backend_url);
+ method = json_string_value (json_object_get (cred,
+ "type"));
+ if (NULL == method)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ for (unsigned int i = 0; NULL != methods[i].m; i++)
+ {
+ if (0 == strcasecmp (method,
+ methods[i].m))
+ {
+ switch (methods[i].e)
+ {
+ case TALER_MERCHANT_BANK_AUTH_NONE:
+ auth->method = TALER_MERCHANT_BANK_AUTH_NONE;
+ return GNUNET_OK;
+ case TALER_MERCHANT_BANK_AUTH_BASIC:
+ {
+ const char *username;
+ const char *password;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("username",
+ &username),
+ GNUNET_JSON_spec_string ("password",
+ &password),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+ const char *err;
+ unsigned int eline;
+
+ res = GNUNET_JSON_parse (cred,
+ spec,
+ &err,
+ &eline);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Credentials malformed: %s (%u)\n",
+ err,
+ eline);
+ GNUNET_free (auth->wire_gateway_url);
+ return GNUNET_SYSERR;
+ }
+ auth->details.basic.username = GNUNET_strdup (username);
+ auth->details.basic.password = GNUNET_strdup (password);
+ }
+ auth->method = TALER_MERCHANT_BANK_AUTH_BASIC;
+ return GNUNET_OK;
+ }
+ }
+ }
+ return GNUNET_SYSERR;
+}
+
+
+void
+TALER_MERCHANT_BANK_auth_free (
+ struct TALER_MERCHANT_BANK_AuthenticationData *auth)
+{
+ switch (auth->method)
+ {
+ case TALER_MERCHANT_BANK_AUTH_NONE:
+ break;
+ case TALER_MERCHANT_BANK_AUTH_BASIC:
+ GNUNET_free (auth->details.basic.username);
+ GNUNET_free (auth->details.basic.password);
+ break;
+ }
+ GNUNET_free (auth->wire_gateway_url);
+}
+
+
+/* end of mb_parse.c */
diff --git a/src/include/Makefile.am b/src/include/Makefile.am
index aeb65712..7f1b9292 100644
--- a/src/include/Makefile.am
+++ b/src/include/Makefile.am
@@ -5,6 +5,7 @@ EXTRA_DIST = \
talerincludedir = $(includedir)/taler
talerinclude_HEADERS = \
+ taler_merchant_bank_lib.h \
taler_merchantdb_lib.h \
taler_merchantdb_plugin.h \
taler_merchant_service.h \
diff --git a/src/include/platform.h b/src/include/platform.h
index 61d3e402..fcafb941 100644
--- a/src/include/platform.h
+++ b/src/include/platform.h
@@ -15,7 +15,7 @@
*/
/**
- * @file merchant/src/include/platform.h
+ * @file src/include/platform.h
* @brief This file contains the includes and definitions which are used by the
* rest of the modules
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
@@ -277,22 +277,42 @@ atoll (const char *nptr);
/* LSB-style exit status codes */
#ifndef EXIT_INVALIDARGUMENT
+/**
+ * Command-line arguments are invalid.
+ * Restarting useless.
+ */
#define EXIT_INVALIDARGUMENT 2
#endif
#ifndef EXIT_NOTIMPLEMENTED
+/**
+ * The requested operation is not implemented.
+ * Restarting useless.
+ */
#define EXIT_NOTIMPLEMENTED 3
#endif
#ifndef EXIT_NOPERMISSION
+/**
+ * Permissions needed to run are not available.
+ * Restarting useless.
+ */
#define EXIT_NOPERMISSION 4
#endif
#ifndef EXIT_NOTINSTALLED
+/**
+ * Key resources are not installed.
+ * Restarting useless.
+ */
#define EXIT_NOTINSTALLED 5
#endif
#ifndef EXIT_NOTCONFIGURED
+/**
+ * Key configuration settings are missing or invalid.
+ * Restarting useless.
+ */
#define EXIT_NOTCONFIGURED 6
#endif
@@ -300,6 +320,14 @@ atoll (const char *nptr);
#define EXIT_NOTRUNNING 7
#endif
+#ifndef EXIT_NO_RESTART
+/**
+ * Exit code from 'main' if we do not want to be restarted,
+ * except by manual intervention (hard failure).
+ */
+#define EXIT_NO_RESTART 9
+#endif
+
#endif /* PLATFORM_H_ */
diff --git a/src/include/taler_merchant_bank_lib.h b/src/include/taler_merchant_bank_lib.h
new file mode 100644
index 00000000..beaaa516
--- /dev/null
+++ b/src/include/taler_merchant_bank_lib.h
@@ -0,0 +1,247 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2021-2023 Taler Systems SA
+
+ Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/taler_merchant_bank_lib.h
+ * @brief C interface to access the Taler merchant facade of LibEuFin
+ * See https://docs.taler.net/TBD
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_BANK_LIB_H
+#define TALER_MERCHANT_BANK_LIB_H
+
+#include <taler/taler_error_codes.h>
+#include <jansson.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include <taler/taler_util.h>
+
+
+/**
+ * Authentication method types.
+ */
+enum TALER_MERCHANT_BANK_AuthenticationMethod
+{
+
+ /**
+ * No authentication.
+ */
+ TALER_MERCHANT_BANK_AUTH_NONE,
+
+ /**
+ * Basic authentication with cleartext username and password.
+ */
+ TALER_MERCHANT_BANK_AUTH_BASIC,
+};
+
+
+/**
+ * Information used to authenticate to the bank.
+ */
+struct TALER_MERCHANT_BANK_AuthenticationData
+{
+
+ /**
+ * Base URL we use to talk to the wire gateway,
+ * which talks to the bank for us.
+ */
+ char *wire_gateway_url;
+
+ /**
+ * Which authentication method should we use?
+ */
+ enum TALER_MERCHANT_BANK_AuthenticationMethod method;
+
+ /**
+ * Further details as per @e method.
+ */
+ union
+ {
+
+ /**
+ * Details for #TALER_MERCHANT_BANK_AUTH_BASIC.
+ */
+ struct
+ {
+ /**
+ * Username to use.
+ */
+ char *username;
+
+ /**
+ * Password to use.
+ */
+ char *password;
+ } basic;
+
+ } details;
+
+};
+
+
+/* ********************* /history/incoming *********************** */
+
+/**
+ * Handle for querying the bank for transactions
+ * made to the exchange.
+ */
+struct TALER_MERCHANT_BANK_CreditHistoryHandle;
+
+/**
+ * Details about a wire transfer to the exchange.
+ */
+struct TALER_MERCHANT_BANK_CreditDetails
+{
+ /**
+ * Amount that was transferred
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Time of the the transfer
+ */
+ struct GNUNET_TIME_Timestamp execution_date;
+
+ /**
+ * The wire transfer subject.
+ */
+ const char *wire_subject;
+
+ /**
+ * payto://-URL of the source account that
+ * send the funds.
+ */
+ const char *debit_account_uri;
+
+ /**
+ * payto://-URL of the target account that
+ * received the funds.
+ */
+ const char *credit_account_uri;
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of asking
+ * the bank for the credit transaction history.
+ *
+ * @param cls closure
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
+ * 0 if the bank's reply is bogus (fails to follow the protocol),
+ * #MHD_HTTP_NO_CONTENT if there are no more results; on success the
+ * last callback is always of this status (even if `abs(num_results)` were
+ * already returned).
+ * @param ec detailed error code
+ * @param serial_id monotonically increasing counter corresponding to the transaction
+ * @param details details about the wire transfer
+ * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_MERCHANT_BANK_CreditHistoryCallback)(
+ void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ uint64_t serial_id,
+ const struct TALER_MERCHANT_BANK_CreditDetails *details);
+
+
+/**
+ * Request the wire credit history of an exchange's bank account.
+ *
+ * @param ctx curl context for the event loop
+ * @param auth authentication data to use
+ * @param start_row from which row on do we want to get results, use UINT64_MAX for the latest; exclusive
+ * @param num_results how many results do we want; negative numbers to go into the past,
+ * positive numbers to go into the future starting at @a start_row;
+ * must not be zero.
+ * @param timeout how long the client is willing to wait for more results
+ * (only useful if @a num_results is positive)
+ * @param hres_cb the callback to call with the transaction history
+ * @param hres_cb_cls closure for the above callback
+ * @return NULL
+ * if the inputs are invalid (i.e. zero value for @e num_results).
+ * In this case, the callback is not called.
+ */
+struct TALER_MERCHANT_BANK_CreditHistoryHandle *
+TALER_MERCHANT_BANK_credit_history (
+ struct GNUNET_CURL_Context *ctx,
+ const struct TALER_MERCHANT_BANK_AuthenticationData *auth,
+ uint64_t start_row,
+ int64_t num_results,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_MERCHANT_BANK_CreditHistoryCallback hres_cb,
+ void *hres_cb_cls);
+
+
+/**
+ * Cancel an history request. This function cannot be used on a request
+ * handle if the last response (anything with a status code other than
+ * 200) is already served for it.
+ *
+ * @param hh the history request handle
+ */
+void
+TALER_MERCHANT_BANK_credit_history_cancel (
+ struct TALER_MERCHANT_BANK_CreditHistoryHandle *hh);
+
+
+/* ******************** Convenience functions **************** */
+
+
+/**
+ * Convenience method for parsing configuration section with bank
+ * authentication data.
+ *
+ * @param cfg configuration to parse
+ * @param section the section with the configuration data
+ * @param[out] auth set to the configuration data found
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_MERCHANT_BANK_auth_parse_cfg (
+ const struct
+ GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section,
+ struct TALER_MERCHANT_BANK_AuthenticationData *auth);
+
+
+/**
+ * Convenience method for parsing JSON with bank
+ * authentication data.
+ *
+ * @param cred configuration to parse
+ * @param backend_url URL of the backend (not in the JSON)
+ * @param[out] auth set to the configuration data found
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_MERCHANT_BANK_auth_parse_json (
+ const json_t *cred,
+ const char *backend_url,
+ struct TALER_MERCHANT_BANK_AuthenticationData *auth);
+
+
+/**
+ * Free memory inside of @a auth (but not @a auth itself).
+ * Dual to #TALER_MERCHANT_BANK_auth_parse_cfg().
+ *
+ * @param auth authentication data to free
+ */
+void
+TALER_MERCHANT_BANK_auth_free (
+ struct TALER_MERCHANT_BANK_AuthenticationData *auth);
+
+
+#endif
+/* _TALER_MERCHANT_BANK_LIB_H */
diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h
index e413a7c6..263a6fec 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ Copyright (C) 2014-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -16,6 +16,8 @@
/**
* @file taler_merchant_service.h
* @brief C interface of libtalermerchant, a C library to use merchant's HTTP API
+ * This library is not thread-safe, all APIs must only be used from a single thread.
+ * This library calls abort() if it runs out of memory. Be aware of these limitations.
* @author Christian Grothoff
* @author Marcello Stanisci
* @author Priscilla HUANG
@@ -29,6 +31,11 @@
#include <gnunet/gnunet_curl_lib.h>
#include <jansson.h>
+/**
+ * Library version (in hex) for compatibility tests.
+ */
+#define TALER_MERCHANT_SERVICE_VERSION 0x00090403
+
/**
* General information about the HTTP response we obtained
@@ -211,7 +218,7 @@ struct TALER_MERCHANT_RefundUriData
* @param[out] parse_data data extracted from the URI. Must be free'd.
* @return #GNUNET_SYSERR if @e refund_uri is malformed, #GNUNET_OK otherwise.
*/
-int
+enum GNUNET_GenericReturnValue
TALER_MERCHANT_parse_refund_uri (
const char *refund_uri,
struct TALER_MERCHANT_RefundUriData *parse_data);
@@ -288,7 +295,8 @@ enum TALER_MERCHANT_VersionCompatibility
struct TALER_MERCHANT_ConfigInformation
{
/**
- * Currency used/supported by the merchant.
+ * Default currency of the merchant. See cspecs
+ * for all currencies supported by the merchant.
*/
const char *currency;
@@ -304,19 +312,96 @@ struct TALER_MERCHANT_ConfigInformation
/**
+ * Information about an exchange the merchant backend trusts.
+ */
+struct TALER_MERCHANT_ExchangeConfigInfo
+{
+ /**
+ * Base URL of the exchange REST API.
+ */
+ const char *base_url;
+
+ /**
+ * Currency for which the merchant is configured to
+ * trust the exchange.
+ */
+ const char *currency;
+
+ /**
+ * Master public key of the exchange.
+ */
+ struct TALER_MasterPublicKeyP master_pub;
+
+};
+
+/**
+ * Response to /config request.
+ */
+struct TALER_MERCHANT_ConfigResponse
+{
+ /**
+ * HTTP response.
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Status-dependent details.
+ */
+ union
+ {
+ /**
+ * Information returned on #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * basic information about the merchant
+ */
+ struct TALER_MERCHANT_ConfigInformation ci;
+
+ /**
+ * protocol compatibility information
+ */
+ enum TALER_MERCHANT_VersionCompatibility compat;
+
+ /**
+ * Length of the @e cspecs array.
+ */
+ unsigned int num_cspecs;
+
+ /**
+ * Array with rendering specifications for the currencies
+ * supported by this merchant backend.
+ */
+ const struct TALER_CurrencySpecification *cspecs;
+
+ /**
+ * Length of the @e exchanges array.
+ */
+ unsigned int num_exchanges;
+
+ /**
+ * Array details about exchanges trusted
+ * by this merchant backend.
+ */
+ const struct TALER_MERCHANT_ExchangeConfigInfo *exchanges;
+
+ } ok;
+ } details;
+};
+
+
+/**
* Function called with information about the merchant.
*
* @param cls closure
- * @param hr HTTP response data
- * @param ci basic information about the merchant
- * @param compat protocol compatibility information
+ * @param cr response data
*/
typedef void
(*TALER_MERCHANT_ConfigCallback) (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_MERCHANT_ConfigInformation *ci,
- enum TALER_MERCHANT_VersionCompatibility compat);
+ const struct TALER_MERCHANT_ConfigResponse *cr);
/**
@@ -338,17 +423,18 @@ struct TALER_MERCHANT_ConfigGetHandle;
* @return the config check handle; NULL upon error
*/
struct TALER_MERCHANT_ConfigGetHandle *
-TALER_MERCHANT_config_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- TALER_MERCHANT_ConfigCallback config_cb,
- void *config_cb_cls);
+TALER_MERCHANT_config_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ TALER_MERCHANT_ConfigCallback config_cb,
+ void *config_cb_cls);
/**
* Cancel /config request. Must not be called by clients after
* the callback was invoked.
*
- * @param vgh request to cancel.
+ * @param[in] vgh request to cancel.
*/
void
TALER_MERCHANT_config_get_cancel (struct TALER_MERCHANT_ConfigGetHandle *vgh);
@@ -382,7 +468,12 @@ struct TALER_MERCHANT_InstanceInformation
* JSON array of payment targets (strings) supported by this backend
* instance.
*/
- json_t *payment_targets;
+ const json_t *payment_targets;
+
+ /**
+ * User type for the instance.
+ */
+ enum TALER_KYCLOGIC_KycUserType ut;
};
@@ -394,19 +485,47 @@ struct TALER_MERCHANT_InstancesGetHandle;
/**
+ * Response to a GET /instances request.
+ */
+struct TALER_MERCHANT_InstancesGetResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ union
+ {
+ /**
+ * Data returned on #MHD_HTTP_OK status.
+ */
+ struct
+ {
+ /**
+ * length of the @e iis array
+ */
+ unsigned int iis_length;
+
+ /**
+ * array with instance information of length @e iis_length
+ */
+ const struct TALER_MERCHANT_InstanceInformation *iis;
+ } ok;
+ } details;
+
+};
+
+
+/**
* Function called with the result of the GET /instances operation.
*
* @param cls closure
- * @param hr HTTP response data
- * @param iis_length length of the @a iis array
- * @param iis array with instance information of length @a iis_length
+ * @param igr response data
*/
typedef void
(*TALER_MERCHANT_InstancesGetCallback)(
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int iis_length,
- const struct TALER_MERCHANT_InstanceInformation iis[]);
+ const struct TALER_MERCHANT_InstancesGetResponse *igr);
/**
@@ -422,10 +541,11 @@ typedef void
* @return the instances handle; NULL upon error
*/
struct TALER_MERCHANT_InstancesGetHandle *
-TALER_MERCHANT_instances_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- TALER_MERCHANT_InstancesGetCallback instances_cb,
- void *instances_cb_cls);
+TALER_MERCHANT_instances_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ TALER_MERCHANT_InstancesGetCallback instances_cb,
+ void *instances_cb_cls);
/**
@@ -446,7 +566,7 @@ struct TALER_MERCHANT_InstancesPostHandle;
/**
- * Function called with the result of the GET /instances/$ID operation.
+ * Function called with the result of the POST /instances/$ID operation.
*
* @param cls closure
* @param hr HTTP response data
@@ -463,14 +583,11 @@ typedef void
* @param ctx the context
* @param backend_url HTTP base URL for the backend
* @param instance_id identity of the instance to get information about
- * @param accounts_length how many bank accounts this instance has
- * @param payto_uris URIs of the bank accounts of the merchant instance
* @param name name of the merchant instance
+ * @param ut user type of the merchant instance
* @param address physical address of the merchant instance
* @param jurisdiction jurisdiction of the merchant instance
- * @param default_max_wire_fee default maximum wire fee merchant is willing to fully pay
- * @param default_wire_fee_amortization default amortization factor for excess wire fees
- * @param default_max_deposit_fee default maximum deposit fee merchant is willing to pay
+ * @param use_stefan use STEFAN curve for acceptable fees
* @param default_wire_transfer_delay default wire transfer delay merchant will ask for
* @param default_pay_delay default validity period for offers merchant makes
* @param auth_token authentication token to use for access control, NULL for external auth; MUST follow RFC 8959
@@ -484,14 +601,11 @@ TALER_MERCHANT_instances_post (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
const char *instance_id,
- unsigned int accounts_length,
- const char *payto_uris[],
const char *name,
+ enum TALER_KYCLOGIC_KycUserType ut,
const json_t *address,
const json_t *jurisdiction,
- const struct TALER_Amount *default_max_wire_fee,
- uint32_t default_wire_fee_amortization,
- const struct TALER_Amount *default_max_deposit_fee,
+ bool use_stefan,
struct GNUNET_TIME_Relative default_wire_transfer_delay,
struct GNUNET_TIME_Relative default_pay_delay,
const char *auth_token,
@@ -536,14 +650,11 @@ typedef void
* or base URL of an instance if @a instance_id is NULL)
* @param instance_id identity of the instance to modify information about; NULL
* if the instance is identified as part of the @a backend_url
- * @param accounts_length length of the @a accounts array
- * @param payto_uris URIs of the bank accounts of the merchant instance
* @param name name of the merchant instance
+ * @param ut user type of the merchant instance
* @param address physical address of the merchant instance
* @param jurisdiction jurisdiction of the merchant instance
- * @param default_max_wire_fee default maximum wire fee merchant is willing to fully pay
- * @param default_wire_fee_amortization default amortization factor for excess wire fees
- * @param default_max_deposit_fee default maximum deposit fee merchant is willing to pay
+ * @param use_stefan use STEFAN curve for acceptable fees
* @param default_wire_transfer_delay default wire transfer delay merchant will ask for
* @param default_pay_delay default validity period for offers merchant makes
* @param cb function to call with the
@@ -556,14 +667,11 @@ TALER_MERCHANT_instance_patch (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
const char *instance_id,
- unsigned int accounts_length,
- const char *payto_uris[],
const char *name,
+ enum TALER_KYCLOGIC_KycUserType ut,
const json_t *address,
const json_t *jurisdiction,
- const struct TALER_Amount *default_max_wire_fee,
- uint32_t default_wire_fee_amortization,
- const struct TALER_Amount *default_max_deposit_fee,
+ bool use_stefan,
struct GNUNET_TIME_Relative default_wire_transfer_delay,
struct GNUNET_TIME_Relative default_pay_delay,
TALER_MERCHANT_InstancePatchCallback cb,
@@ -642,34 +750,6 @@ struct TALER_MERCHANT_InstanceGetHandle;
/**
- * Details about a merchant's bank account.
- */
-struct TALER_MERCHANT_Account
-{
- /**
- * salt used to compute h_wire
- */
- struct TALER_WireSaltP salt;
-
- /**
- * payto:// URI of the account.
- */
- const char *payto_uri;
-
- /**
- * Hash of @e payto_uri and @e salt.
- */
- struct TALER_MerchantWireHashP h_wire;
-
- /**
- * true if the account is active,
- * false if it is historic.
- */
- bool active;
-};
-
-
-/**
* Details about an instance.
*/
struct TALER_MERCHANT_InstanceDetails
@@ -682,7 +762,7 @@ struct TALER_MERCHANT_InstanceDetails
/**
* public key of the merchant instance
*/
- const struct TALER_MerchantPublicKeyP *merchant_pub;
+ struct TALER_MerchantPublicKeyP merchant_pub;
/**
* physical address of the merchant instance
@@ -695,29 +775,51 @@ struct TALER_MERCHANT_InstanceDetails
const json_t *jurisdiction;
/**
- * default maximum wire fee merchant is willing to fully pay
+ * Are we using STEFAN curves to determine acceptable
+ * fees?
*/
- const struct TALER_Amount *default_max_wire_fee;
+ bool use_stefan;
/**
- * default amortization factor for excess wire fees
+ * default wire transfer delay merchant will ask for
*/
- uint32_t default_wire_fee_amortization;
+ struct GNUNET_TIME_Relative default_wire_transfer_delay;
/**
- * default maximum deposit fee merchant is willing to pay
+ * default validity period for offers merchant makes
*/
- const struct TALER_Amount *default_max_deposit_fee;
+ struct GNUNET_TIME_Relative default_pay_delay;
/**
- * default wire transfer delay merchant will ask for
+ * User type for the instance.
*/
- struct GNUNET_TIME_Relative default_wire_transfer_delay;
+ enum TALER_KYCLOGIC_KycUserType ut;
+};
+
+struct TALER_MERCHANT_InstanceGetResponse
+{
/**
- * default validity period for offers merchant makes
+ * HTTP response data
*/
- struct GNUNET_TIME_Relative default_pay_delay;
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ union
+ {
+
+ /**
+ * Data returned on #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * Details about the instance.
+ */
+ struct TALER_MERCHANT_InstanceDetails details;
+
+ } ok;
+ }
+ details;
};
@@ -725,18 +827,12 @@ struct TALER_MERCHANT_InstanceDetails
* Function called with the result of the GET /instances/$ID operation.
*
* @param cls closure
- * @param hr HTTP response data
- * @param accounts_length length of the @a accounts array
- * @param accounts bank accounts of the merchant instance
- * @param details details about the instance configuration
+ * @param igr response details
*/
typedef void
(*TALER_MERCHANT_InstanceGetCallback)(
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int accounts_length,
- const struct TALER_MERCHANT_Account accounts[],
- const struct TALER_MERCHANT_InstanceDetails *details);
+ const struct TALER_MERCHANT_InstanceGetResponse *igr);
/**
@@ -753,11 +849,12 @@ typedef void
* @return the instances handle; NULL upon error
*/
struct TALER_MERCHANT_InstanceGetHandle *
-TALER_MERCHANT_instance_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const char *instance_id,
- TALER_MERCHANT_InstanceGetCallback cb,
- void *cb_cls);
+TALER_MERCHANT_instance_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *instance_id,
+ TALER_MERCHANT_InstanceGetCallback cb,
+ void *cb_cls);
/**
@@ -855,6 +952,421 @@ TALER_MERCHANT_instance_delete_cancel (
TALER_MERCHANT_instance_delete_cancel (arg)
+/* *************** Accounts **************** */
+
+/**
+ * Handle for a POST /instances/$ID/accounts operation.
+ */
+struct TALER_MERCHANT_AccountsPostHandle;
+
+
+/**
+ * Response for a POST /instances/$ID/account operation.
+ */
+struct TALER_MERCHANT_AccountsPostResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Details depending on HTTP status.
+ */
+ union
+ {
+
+ /**
+ * Details returned on #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * Hash of @e payto_uri and @e salt.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * salt used to compute h_wire
+ */
+ struct TALER_WireSaltP salt;
+ } ok;
+
+ } details;
+};
+
+
+/**
+ * Function called with the result of the POST /instances/$ID/accounts operation.
+ *
+ * @param cls closure
+ * @param par response data
+ */
+typedef void
+(*TALER_MERCHANT_AccountsPostCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_AccountsPostResponse *par);
+
+
+/**
+ * Setup an new account for an instance in the backend.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param payto_uri URI of the bank account as per RFC 8905
+ * @param credit_facade_url credit facade for the account, can be NULL
+ * @param credit_facade_credentials credentials for credit facade, can be NULL
+ * @param cb function to call with the response
+ * @param cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
+ */
+struct TALER_MERCHANT_AccountsPostHandle *
+TALER_MERCHANT_accounts_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *payto_uri,
+ const char *credit_facade_url,
+ const json_t *credit_facade_credentials,
+ TALER_MERCHANT_AccountsPostCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel POST /accounts request. Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param pah request to cancel.
+ */
+void
+TALER_MERCHANT_accounts_post_cancel (
+ struct TALER_MERCHANT_AccountsPostHandle *pah);
+
+
+/**
+ * Handle for a GET /accounts/$ID operation.
+ */
+struct TALER_MERCHANT_AccountGetHandle;
+
+
+/**
+ * Details about a merchant's bank account.
+ */
+struct TALER_MERCHANT_AccountDetails
+{
+ /**
+ * salt used to compute h_wire
+ */
+ struct TALER_WireSaltP salt;
+
+ /**
+ * payto:// URI of the account.
+ */
+ const char *payto_uri;
+
+ /**
+ * Credit facade URL of the account.
+ */
+ const char *credit_facade_url;
+
+ /**
+ * Hash of @e payto_uri and @e salt.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * true if the account is active,
+ * false if it is historic.
+ */
+ bool active;
+};
+
+
+/**
+ * Response returned with details about an account.
+ */
+struct TALER_MERCHANT_AccountGetResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ union
+ {
+
+ /**
+ * Data returned on #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * bank accounts of the merchant instance
+ */
+ struct TALER_MERCHANT_AccountDetails ad;
+
+ } ok;
+ }
+ details;
+};
+
+
+/**
+ * Function called with the result of the GET /instances/$ID/accounts/$H_WIRE operation.
+ *
+ * @param cls closure
+ * @param igr response details
+ */
+typedef void
+(*TALER_MERCHANT_AccountGetCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_AccountGetResponse *igr);
+
+
+/**
+ * Get the details on one of the accounts of an instance. Will connect to the
+ * merchant backend and obtain information about the account. The respective
+ * information will be passed to the @a cb once available.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param instance_id identity of the instance to get information about
+ * @param h_wire hash of the wire details
+ * @param cb function to call with the
+ * backend's instances information
+ * @param cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
+ */
+struct TALER_MERCHANT_AccountGetHandle *
+TALER_MERCHANT_account_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *instance_id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ TALER_MERCHANT_AccountGetCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel GET /accounts/$H_WIRE request. Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param igh request to cancel.
+ */
+void
+TALER_MERCHANT_account_get_cancel (
+ struct TALER_MERCHANT_AccountGetHandle *igh);
+
+
+/**
+ * Handle for a GET /accounts operation.
+ */
+struct TALER_MERCHANT_AccountsGetHandle;
+
+/**
+ * Individual account (minimal information
+ * returned via GET /accounts).
+ */
+struct TALER_MERCHANT_AccountEntry
+{
+ /**
+ * account payto URI.
+ */
+ const char *payto_uri;
+
+ /**
+ * Hash of @e payto_uri and salt.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+};
+
+
+/**
+ * Response to a GET /accounts operation.
+ */
+struct TALER_MERCHANT_AccountsGetResponse
+{
+ /**
+ * HTTP response details
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Details depending on status.
+ */
+ union
+ {
+ /**
+ * Details if status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * length of the @e accounts array
+ */
+ unsigned int accounts_length;
+
+ /**
+ * array of accounts the requested instance offers
+ */
+ const struct TALER_MERCHANT_AccountEntry *accounts;
+ } ok;
+ } details;
+};
+
+
+/**
+ * Function called with the result of the GET /accounts operation.
+ *
+ * @param cls closure
+ * @param tgr response details
+ */
+typedef void
+(*TALER_MERCHANT_AccountsGetCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_AccountsGetResponse *tgr);
+
+
+/**
+ * Make a GET /accounts request.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param cb function to call with the backend information
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_AccountsGetHandle *
+TALER_MERCHANT_accounts_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ TALER_MERCHANT_AccountsGetCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel GET /accounts operation.
+ *
+ * @param tgh operation to cancel
+ */
+void
+TALER_MERCHANT_accounts_get_cancel (
+ struct TALER_MERCHANT_AccountsGetHandle *tgh);
+
+
+/**
+ * Handle for a PATCH /account operation.
+ */
+struct TALER_MERCHANT_AccountPatchHandle;
+
+
+/**
+ * Function called with the result of the PATCH /account operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ */
+typedef void
+(*TALER_MERCHANT_AccountPatchCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Make a PATCH /accounts/$H_WIRE request to update account details. Cannot be used to change the payto URI or the salt.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param h_wire identifies the account to patch
+ * @param credit_facade_url credit facade for the account, can be NULL
+ * @param credit_facade_credentials credentials for credit facade, can be NULL
+ * @param cb function to call with the backend's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_AccountPatchHandle *
+TALER_MERCHANT_account_patch (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *credit_facade_url,
+ const json_t *credit_facade_credentials,
+ TALER_MERCHANT_AccountPatchCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel PATCH /accounts/$H_WIRE operation.
+ *
+ * @param[in] tph operation to cancel
+ */
+void
+TALER_MERCHANT_account_patch_cancel (
+ struct TALER_MERCHANT_AccountPatchHandle *tph);
+
+
+/**
+ * Handle for a DELETE /instances/$ID/account/$H_WIRE operation.
+ */
+struct TALER_MERCHANT_AccountDeleteHandle;
+
+
+/**
+ * Response for a DELETE /instances/$ID/account operation.
+ */
+struct TALER_MERCHANT_AccountDeleteResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+};
+
+
+/**
+ * Function called with the result of the DELETE /instances/$ID/account/$H_WIRE operation.
+ *
+ * @param cls closure
+ * @param par response data
+ */
+typedef void
+(*TALER_MERCHANT_AccountDeleteCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_AccountDeleteResponse *par);
+
+
+/**
+ * Remove bank account from an instance in the backend.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param h_wire wire hash of the bank accounts to delete
+ * @param cb function to call with the response
+ * @param cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
+ */
+struct TALER_MERCHANT_AccountDeleteHandle *
+TALER_MERCHANT_account_delete (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct TALER_MerchantWireHashP *h_wire,
+ TALER_MERCHANT_AccountDeleteCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel /account request. Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param pah request to cancel.
+ */
+void
+TALER_MERCHANT_account_delete_cancel (
+ struct TALER_MERCHANT_AccountDeleteHandle *pah);
+
+
/* ********************* /products *********************** */
@@ -874,6 +1386,40 @@ struct TALER_MERCHANT_InventoryEntry
*/
const char *product_id;
+ /**
+ * Serial ID of the product.
+ */
+ uint64_t product_serial;
+};
+
+
+/**
+ * Response to a GET /products request.
+ */
+struct TALER_MERCHANT_GetProductsResponse
+{
+ /**
+ * HTTP response details
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ union
+ {
+ struct
+ {
+
+ /**
+ * length of the @a products array
+ */
+ unsigned int products_length;
+
+ /**
+ * array of products the requested instance offers
+ */
+ const struct TALER_MERCHANT_InventoryEntry *products;
+ } ok;
+
+ } details;
};
@@ -881,16 +1427,12 @@ struct TALER_MERCHANT_InventoryEntry
* Function called with the result of the GET /products operation.
*
* @param cls closure
- * @param hr HTTP response details
- * @param products_length length of the @a products array
- * @param products array of products the requested instance offers
+ * @param gpr response details
*/
typedef void
(*TALER_MERCHANT_ProductsGetCallback)(
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int products_length,
- const struct TALER_MERCHANT_InventoryEntry products[]);
+ const struct TALER_MERCHANT_GetProductsResponse *gpr);
/**
@@ -930,44 +1472,104 @@ struct TALER_MERCHANT_ProductGetHandle;
/**
+ * Response to GET /product/$ID operation.
+ */
+struct TALER_MERCHANT_ProductGetResponse
+{
+ /**
+ * HTTP response details
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Details depending on HTTP status.
+ */
+ union
+ {
+ /**
+ * Details for #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * description of the product
+ */
+ const char *description;
+
+ /**
+ * Map from IETF BCP 47 language tags to localized descriptions
+ */
+ const json_t *description_i18n;
+
+ /**
+ * unit in which the product is measured (liters, kilograms, packages, etc.)
+ */
+ const char *unit;
+
+ /**
+ * the price for one @a unit of the product, zero is used to imply that
+ * this product is not sold separately or that the price is
+ * not fixed and must be supplied by the front-end. If
+ * non-zero, price must include applicable taxes.
+ */
+ struct TALER_Amount price;
+
+ /**
+ * base64-encoded product image
+ */
+ const char *image;
+
+ /**
+ * list of taxes paid by the merchant
+ */
+ const json_t *taxes;
+
+ /**
+ * total_stock in @e units, -1 to indicate "infinite" (i.e. electronic
+ * books), does NOT indicate remaining stocks, to get remaining stocks,
+ * subtract @e total_sold and @e total_lost. Note that this still does
+ * not then say how many of the remaining inventory are locked.
+ */
+ int64_t total_stock;
+
+ /**
+ * in @e units, total number of @e unit of product sold
+ */
+ uint64_t total_sold;
+
+ /**
+ * in @e units, total number of @e unit of product lost from inventory
+ */
+ uint64_t total_lost;
+
+ /**
+ * where the product is in stock
+ */
+ const json_t *location;
+
+ /**
+ * when the next restocking is expected to happen, 0 for unknown,
+ * #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ */
+ struct GNUNET_TIME_Timestamp next_restock;
+ } ok;
+
+ } details;
+
+};
+
+
+/**
* Function called with the result of the GET /products operation.
*
* @param cls closure
- * @param hr HTTP response details
- * @param description description of the product
- * @param description_i18n Map from IETF BCP 47 language tags to localized descriptions
- * @param unit unit in which the product is measured (liters, kilograms, packages, etc.)
- * @param price the price for one @a unit of the product, zero is used to imply that
- * this product is not sold separately or that the price is not fixed and
- * must be supplied by the front-end. If non-zero, price must include
- * applicable taxes.
- * @param image base64-encoded product image
- * @param taxes list of taxes paid by the merchant
- * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic books),
- * does NOT indicate remaining stocks, to get remaining stocks,
- * subtract @a total_sold and @a total_lost. Note that this still
- * does not then say how many of the remaining inventory are locked.
- * @param total_sold in @a units, total number of @a unit of product sold
- * @param total_lost in @a units, total number of @a unit of product lost from inventory
- * @param location where the product is in stock
- * @param next_restock when the next restocking is expected to happen, 0 for unknown,
- * #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ * @param pgr response details
*/
typedef void
(*TALER_MERCHANT_ProductGetCallback)(
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const char *description,
- const json_t *description_i18n,
- const char *unit,
- const struct TALER_Amount *price,
- const char *image,
- const json_t *taxes,
- int64_t total_stock,
- uint64_t total_sold,
- uint64_t total_lost,
- const json_t *location,
- struct GNUNET_TIME_Timestamp next_restock);
+ const struct TALER_MERCHANT_ProductGetResponse *pgr);
/**
@@ -1061,6 +1663,50 @@ TALER_MERCHANT_products_post (
/**
+ * Make a POST /products request to add a product to the
+ * inventory.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param product_id identifier to use for the product
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to imply that
+ * this product is not sold separately or that the price is not fixed and
+ * must be supplied by the front-end. If non-zero, price must include
+ * applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic books)
+ * @param address where the product is in stock
+ * @param next_restock when the next restocking is expected to happen, 0 for unknown,
+ * #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ * @param minimum_age minimum age the buyer must have
+ * @param cb function to call with the backend's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_ProductsPostHandle *
+TALER_MERCHANT_products_post2 (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *product_id,
+ const char *description,
+ const json_t *description_i18n,
+ const char *unit,
+ const struct TALER_Amount *price,
+ const char *image,
+ const json_t *taxes,
+ int64_t total_stock,
+ const json_t *address,
+ struct GNUNET_TIME_Timestamp next_restock,
+ uint32_t minimum_age,
+ TALER_MERCHANT_ProductsPostCallback cb,
+ void *cb_cls);
+
+
+/**
* Cancel POST /products operation.
*
* @param pph operation to cancel
@@ -1359,12 +2005,13 @@ typedef void
* @return a handle for this request, NULL on error
*/
struct TALER_MERCHANT_PostOrdersHandle *
-TALER_MERCHANT_orders_post (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const json_t *order,
- struct GNUNET_TIME_Relative refund_delay,
- TALER_MERCHANT_PostOrdersCallback cb,
- void *cb_cls);
+TALER_MERCHANT_orders_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const json_t *order,
+ struct GNUNET_TIME_Relative refund_delay,
+ TALER_MERCHANT_PostOrdersCallback cb,
+ void *cb_cls);
/**
* Information needed per product for constructing orders from
@@ -1414,7 +2061,44 @@ TALER_MERCHANT_orders_post2 (
unsigned int inventory_products_length,
const struct TALER_MERCHANT_InventoryProduct inventory_products[],
unsigned int uuids_length,
- const char *uuids[],
+ const char *uuids[static uuids_length],
+ bool create_token,
+ TALER_MERCHANT_PostOrdersCallback cb,
+ void *cb_cls);
+
+
+/**
+ * POST to /orders at the backend to setup an order and obtain
+ * the order ID (which may have been set by the front-end).
+ *
+ * @param ctx execution context
+ * @param backend_url URL of the backend
+ * @param order basic information about this purchase, to be extended by the backend
+ * @param session_id session ID to set for the order
+ * @param refund_delay how long can refunds happen for this order; 0 to use
+ * absolute value from contract (or not allow refunds).
+ * @param payment_target desired payment target identifier (to select merchant bank details)
+ * @param inventory_products_length length of the @a inventory_products array
+ * @param inventory_products products to add to the order from the inventory
+ * @param uuids_length length of the @a uuids array
+ * @param uuids array of UUIDs with locks on @a inventory_products
+ * @param create_token whether to create a claim token
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for @a cb
+ * @return a handle for this request, NULL on error
+ */
+struct TALER_MERCHANT_PostOrdersHandle *
+TALER_MERCHANT_orders_post3 (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const json_t *order,
+ const char *session_id,
+ struct GNUNET_TIME_Relative refund_delay,
+ const char *payment_target,
+ unsigned int inventory_products_length,
+ const struct TALER_MERCHANT_InventoryProduct inventory_products[],
+ unsigned int uuids_length,
+ const char *uuids[static uuids_length],
bool create_token,
TALER_MERCHANT_PostOrdersCallback cb,
void *cb_cls);
@@ -1424,7 +2108,7 @@ TALER_MERCHANT_orders_post2 (
* Cancel a POST /orders request. This function cannot be used
* on a request handle if a response is already served for it.
*
- * @param po the proposal operation request handle
+ * @param[in] po the proposal operation request handle
*/
void
TALER_MERCHANT_orders_post_cancel (
@@ -1482,19 +2166,51 @@ struct TALER_MERCHANT_OrderEntry
/**
+ * Response for a GET /private/orders request.
+ */
+struct TALER_MERCHANT_OrdersGetResponse
+{
+ /**
+ * HTTP response details.
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Details depending on HTTP status.
+ */
+ union
+ {
+ /**
+ * Details for #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * length of the @e orders array
+ */
+ unsigned int orders_length;
+
+ /**
+ * array of orders the requested instance has made
+ */
+ const struct TALER_MERCHANT_OrderEntry *orders;
+ } ok;
+ } details;
+
+};
+
+
+/**
* Function called with the result of the GET /orders operation.
*
* @param cls closure
- * @param hr HTTP response details
- * @param orders_length length of the @a orders array
- * @param orders array of orders the requested instance has made
+ * @param ogr response details
*/
typedef void
(*TALER_MERCHANT_OrdersGetCallback)(
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int orders_length,
- const struct TALER_MERCHANT_OrderEntry orders[]);
+ const struct TALER_MERCHANT_OrdersGetResponse *ogr);
/**
@@ -1548,9 +2264,46 @@ TALER_MERCHANT_orders_get2 (
/**
+ * Make a GET /orders request with more filters.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param paid filter on payment status
+ * @param refunded filter on refund status
+ * @param wired filter on wire transfer status
+ * @param session_id filter by session ID
+ * @param fulfillment_url filter by fulfillment URL
+ * @param date range limit by date
+ * @param start_row range limit by order table row
+ * @param delta range from which @a date and @a start_row apply, positive
+ * to return delta items after the given limit(s), negative to
+ * return delta items before the given limit(s)
+ * @param timeout how long to wait (long polling) of zero results match the query
+ * @param cb function to call with the backend's inventory information
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_OrdersGetHandle *
+TALER_MERCHANT_orders_get3 (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ enum TALER_EXCHANGE_YesNoAll paid,
+ enum TALER_EXCHANGE_YesNoAll refunded,
+ enum TALER_EXCHANGE_YesNoAll wired,
+ const char *session_id,
+ const char *fulfillment_url,
+ struct GNUNET_TIME_Timestamp date,
+ uint64_t start_row,
+ int64_t delta,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_MERCHANT_OrdersGetCallback cb,
+ void *cb_cls);
+
+
+/**
* Cancel GET /orders operation.
*
- * @param pgh operation to cancel
+ * @param[in] pgh operation to cancel
*/
void
TALER_MERCHANT_orders_get_cancel (
@@ -1602,7 +2355,7 @@ struct TALER_MERCHANT_OrderWalletGetResponse
*/
struct TALER_Amount refund_amount;
- } success;
+ } ok;
/**
* Response if a payment is required from the client.
@@ -1679,7 +2432,7 @@ TALER_MERCHANT_wallet_order_get (
/**
* Cancel a GET /orders/$ID request.
*
- * @param owgh handle to the request to be canceled
+ * @param[in] owgh handle to the request to be canceled
*/
void
TALER_MERCHANT_wallet_order_get_cancel (
@@ -1871,17 +2624,6 @@ struct TALER_MERCHANT_OrderStatusResponse
unsigned int wts_len;
/**
- * Array of wire reports about problems tracking wire transfers.
- * Of length @e wrs_len.
- */
- struct TALER_MERCHANT_WireReport *wrs;
-
- /**
- * Length of the @e wrs array.
- */
- unsigned int wrs_len;
-
- /**
* Details returned by the merchant backend about refunds.
* Of length @e refunds_len.
*/
@@ -1974,7 +2716,7 @@ struct TALER_MERCHANT_OrderStatusResponse
} details;
- } success;
+ } ok;
} details;
};
@@ -1999,10 +2741,8 @@ typedef void
* @param ctx execution context
* @param backend_url base URL of the merchant backend
* @param order_id order id to identify the payment
- * @param session_id sesion id for the payment (or NULL if the check is not
+ * @param session_id session id for the payment (or NULL if the check is not
* bound to a session)
- * @param transfer if true, obtain the wire transfer status from the exchange.
- * Otherwise, the wire transfer status MAY be returned if it is available.
* @param timeout timeout to use in long polling (how long may the server wait to reply
* before generating an unpaid response). Note that this is just provided to
* the server, we as client will block until the response comes back or until
@@ -2016,7 +2756,6 @@ TALER_MERCHANT_merchant_order_get (struct GNUNET_CURL_Context *ctx,
const char *backend_url,
const char *order_id,
const char *session_id,
- bool transfer,
struct GNUNET_TIME_Relative timeout,
TALER_MERCHANT_OrderMerchantGetCallback cb,
void *cb_cls);
@@ -2025,7 +2764,7 @@ TALER_MERCHANT_merchant_order_get (struct GNUNET_CURL_Context *ctx,
/**
* Cancel a GET /private/orders/$ID request.
*
- * @param omgh handle to the request to be canceled
+ * @param[in] omgh handle to the request to be canceled
*/
void
TALER_MERCHANT_merchant_order_get_cancel (
@@ -2075,7 +2814,7 @@ TALER_MERCHANT_order_delete (
/**
* Cancel DELETE /orders/$ID operation.
*
- * @param odh operation to cancel
+ * @param[in] odh operation to cancel
*/
void
TALER_MERCHANT_order_delete_cancel (
@@ -2089,22 +2828,54 @@ struct TALER_MERCHANT_OrderClaimHandle;
/**
+ * Response to a POST /orders/$ID/claim request.
+ */
+struct TALER_MERCHANT_OrderClaimResponse
+{
+ /**
+ * HTTP response details
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Details depending on HTTP status.
+ */
+ union
+ {
+ /**
+ * Details for #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * the details of the contract
+ */
+ const json_t *contract_terms;
+
+ /**
+ * merchant's signature over @e contract_terms (already verified)
+ */
+ struct TALER_MerchantSignatureP sig;
+
+ /**
+ * hash over @e contract_terms (computed client-side to verify @e sig)
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+ } ok;
+
+ } details;
+};
+
+/**
* Callback called to process a POST /orders/$ID/claim response.
*
* @param cls closure
- * @param hr HTTP response details
- * @param contract_terms the details of the contract
- * @param sig merchant's signature over @a contract_terms (already verified)
- * @param h_contract_terms hash over @a contract_terms (computed
- * client-side to verify @a sig)
+ * @param ocr response details
*/
typedef void
(*TALER_MERCHANT_OrderClaimCallback) (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const json_t *contract_terms,
- const struct TALER_MerchantSignatureP *sig,
- const struct TALER_PrivateContractHashP *h_contract_terms);
+ const struct TALER_MERCHANT_OrderClaimResponse *ocr);
/**
@@ -2123,19 +2894,20 @@ typedef void
* @return handle for this handle, NULL upon errors
*/
struct TALER_MERCHANT_OrderClaimHandle *
-TALER_MERCHANT_order_claim (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const char *order_id,
- const struct GNUNET_CRYPTO_EddsaPublicKey *nonce,
- const struct TALER_ClaimTokenP *claim_token,
- TALER_MERCHANT_OrderClaimCallback cb,
- void *cb_cls);
+TALER_MERCHANT_order_claim (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *order_id,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *nonce,
+ const struct TALER_ClaimTokenP *claim_token,
+ TALER_MERCHANT_OrderClaimCallback cb,
+ void *cb_cls);
/**
* Cancel a POST /order/$ID/claim request.
*
- * @param och handle to the request to be canceled
+ * @param[in] och handle to the request to be canceled
*/
void
TALER_MERCHANT_order_claim_cancel (struct TALER_MERCHANT_OrderClaimHandle *och);
@@ -2179,7 +2951,12 @@ struct TALER_MERCHANT_PayResponse
*/
struct TALER_MerchantSignatureP merchant_sig;
- } success;
+ /**
+ * Optional payment confirmation code returned by the service.
+ */
+ const char *pos_confirmation;
+
+ } ok;
// TODO: might want to return further details on errors,
// especially refund signatures on double-pay conflict.
@@ -2263,6 +3040,7 @@ struct TALER_MERCHANT_PaidCoin
* @param merchant_url base URL of the merchant
* @param order_id which order should be paid
* @param session_id session to pay for, or NULL for none
+ * @param wallet_data inputs from the wallet for the contract, NULL for none
* @param num_coins length of the @a coins array
* @param coins array of coins to pay with
* @param pay_cb the callback to call when a reply for this request is available
@@ -2275,8 +3053,9 @@ TALER_MERCHANT_order_pay_frontend (
const char *merchant_url,
const char *order_id,
const char *session_id,
+ const json_t *wallet_data,
unsigned int num_coins,
- const struct TALER_MERCHANT_PaidCoin coins[],
+ const struct TALER_MERCHANT_PaidCoin coins[static num_coins],
TALER_MERCHANT_OrderPayCallback pay_cb,
void *pay_cb_cls);
@@ -2339,6 +3118,7 @@ struct TALER_MERCHANT_PayCoin
* @param merchant_url base URL of the merchant
* @param session_id session to pay for, or NULL for none
* @param h_contract hash of the contact of the merchant with the customer
+ * @param wallet_data inputs from the wallet for the contract, NULL for none
* @param amount total value of the contract to be paid to the merchant
* @param max_fee maximum fee covered by the merchant (according to the contract)
* @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
@@ -2355,23 +3135,25 @@ struct TALER_MERCHANT_PayCoin
* @return a handle for this request
*/
struct TALER_MERCHANT_OrderPayHandle *
-TALER_MERCHANT_order_pay (struct GNUNET_CURL_Context *ctx,
- const char *merchant_url,
- const char *session_id,
- const struct TALER_PrivateContractHashP *h_contract,
- const struct TALER_Amount *amount,
- const struct TALER_Amount *max_fee,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_MerchantSignatureP *merchant_sig,
- struct GNUNET_TIME_Timestamp timestamp,
- struct GNUNET_TIME_Timestamp refund_deadline,
- struct GNUNET_TIME_Timestamp pay_deadline,
- const struct TALER_MerchantWireHashP *h_wire,
- const char *order_id,
- unsigned int num_coins,
- const struct TALER_MERCHANT_PayCoin coins[],
- TALER_MERCHANT_OrderPayCallback pay_cb,
- void *pay_cb_cls);
+TALER_MERCHANT_order_pay (
+ struct GNUNET_CURL_Context *ctx,
+ const char *merchant_url,
+ const char *session_id,
+ const struct TALER_PrivateContractHashP *h_contract,
+ const json_t *wallet_data,
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *max_fee,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantSignatureP *merchant_sig,
+ struct GNUNET_TIME_Timestamp timestamp,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *order_id,
+ unsigned int num_coins,
+ const struct TALER_MERCHANT_PayCoin coins[static num_coins],
+ TALER_MERCHANT_OrderPayCallback pay_cb,
+ void *pay_cb_cls);
/**
@@ -2393,18 +3175,46 @@ TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *oph);
*/
struct TALER_MERCHANT_OrderPaidHandle;
+/**
+ * Response to an /orders/$ID/paid request.
+ */
+struct TALER_MERCHANT_OrderPaidResponse
+{
+ /**
+ * HTTP response details.
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+ /**
+ * HTTP-status code dependent details.
+ */
+ union
+ {
+ /**
+ * Details on success.
+ */
+ struct
+ {
+ /**
+ * Set to true if the order was paid but also
+ * refunded.
+ */
+ bool refunded;
+ } ok;
+ } details;
+};
+
/**
* Callbacks of this type are used to serve the result of submitting a
* POST /orders/$ID/paid request to a merchant.
*
* @param cls closure
- * @param hr HTTP response details
+ * @param opr response details
*/
typedef void
(*TALER_MERCHANT_OrderPaidCallback) (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr);
+ const struct TALER_MERCHANT_OrderPaidResponse *opr);
/**
@@ -2418,6 +3228,7 @@ typedef void
* @param order_id which order should be paid
* @param session_id session to pay for, or NULL for none
* @param h_contract_terms hash of the contract terms
+ * @param wallet_data_hash inputs from the wallet for the contract, NULL for none
* @param merchant_sig signature from the merchant
* affirming payment, or NULL on errors
* @param paid_cb the callback to call when a reply for this request is available
@@ -2431,6 +3242,7 @@ TALER_MERCHANT_order_paid (
const char *order_id,
const char *session_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct GNUNET_HashCode *wallet_data_hash,
const struct TALER_MerchantSignatureP *merchant_sig,
TALER_MERCHANT_OrderPaidCallback paid_cb,
void *paid_cb_cls);
@@ -2470,22 +3282,57 @@ struct TALER_MERCHANT_AbortedCoin
/**
+ * Response to an /orders/$ID/abort request.
+ */
+struct TALER_MERCHANT_AbortResponse
+{
+ /**
+ * HTTP response details
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Details depending on HTTP status code.
+ */
+ union
+ {
+ /**
+ * Details for #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * public key of the merchant
+ */
+ const struct TALER_MerchantPublicKeyP *merchant_pub;
+
+ /**
+ * size of the @e aborts array
+ */
+ unsigned int num_aborts;
+
+ /**
+ * merchant signatures refunding coins
+ */
+ const struct TALER_MERCHANT_AbortedCoin *aborts;
+ } ok;
+
+ } details;
+};
+
+
+/**
* Callbacks of this type are used to serve the result of submitting a
* /orders/$ID/abort request to a merchant.
*
* @param cls closure
- * @param hr HTTP response details
- * @param merchant_pub public key of the merchant
- * @param num_aborts size of the @a res array, 0 on errors
- * @param aborts merchant signatures refunding coins, NULL on errors
+ * @param ar response details
*/
typedef void
(*TALER_MERCHANT_AbortCallback) (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- unsigned int num_aborts,
- const struct TALER_MERCHANT_AbortedCoin aborts[]);
+ const struct TALER_MERCHANT_AbortResponse *ar);
/**
@@ -2530,15 +3377,16 @@ struct TALER_MERCHANT_AbortCoin
* @return a handle for this request
*/
struct TALER_MERCHANT_OrderAbortHandle *
-TALER_MERCHANT_order_abort (struct GNUNET_CURL_Context *ctx,
- const char *merchant_url,
- const char *order_id,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_PrivateContractHashP *h_contract,
- unsigned int num_coins,
- const struct TALER_MERCHANT_AbortCoin coins[],
- TALER_MERCHANT_AbortCallback cb,
- void *cb_cls);
+TALER_MERCHANT_order_abort (
+ struct GNUNET_CURL_Context *ctx,
+ const char *merchant_url,
+ const char *order_id,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_PrivateContractHashP *h_contract,
+ unsigned int num_coins,
+ const struct TALER_MERCHANT_AbortCoin coins[static num_coins],
+ TALER_MERCHANT_AbortCallback cb,
+ void *cb_cls);
/**
@@ -2585,13 +3433,14 @@ typedef void
* @return a handle for this request.
*/
struct TALER_MERCHANT_OrderForgetHandle *
-TALER_MERCHANT_order_forget (struct GNUNET_CURL_Context *ctx,
- const char *merchant_url,
- const char *order_id,
- unsigned int fields_length,
- const char *fields[],
- TALER_MERCHANT_ForgetCallback cb,
- void *cb_cls);
+TALER_MERCHANT_order_forget (
+ struct GNUNET_CURL_Context *ctx,
+ const char *merchant_url,
+ const char *order_id,
+ unsigned int fields_length,
+ const char *fields[static fields_length],
+ TALER_MERCHANT_ForgetCallback cb,
+ void *cb_cls);
/**
@@ -2599,11 +3448,11 @@ TALER_MERCHANT_order_forget (struct GNUNET_CURL_Context *ctx,
* like this, you have no assurance that the request has not yet been
* forwarded to the merchant.
*
- * @param ofh the forget request handle
+ * @param[in] ofh the forget request handle
*/
void
-TALER_MERCHANT_order_forget_cancel (struct
- TALER_MERCHANT_OrderForgetHandle *ofh);
+TALER_MERCHANT_order_forget_cancel (
+ struct TALER_MERCHANT_OrderForgetHandle *ofh);
/**
@@ -2613,20 +3462,51 @@ struct TALER_MERCHANT_OrderRefundHandle;
/**
- * Callback to process a POST /orders/ID/refund request
+ * Response to a POST /orders/$ID/refund request
+ */
+struct TALER_MERCHANT_RefundResponse
+{
+ /**
+ * HTTP response details this request
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Details depending on HTTP status.
+ */
+ union
+ {
+ /**
+ * Details if status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * the refund uri offered to the wallet
+ */
+ const char *taler_refund_uri;
+
+ /**
+ * Hash of the contract a wallet may need to authorize obtaining the HTTP
+ * response.
+ */
+ struct TALER_PrivateContractHashP h_contract;
+ } ok;
+ } details;
+};
+
+
+/**
+ * Callback to process a POST /orders/$ID/refund request
*
* @param cls closure
- * @param hr HTTP response details this request
- * @param taler_refund_uri the refund uri offered to the wallet
- * @param h_contract hash of the contract a Browser may need to authorize
- * obtaining the HTTP response.
+ * @param rr response details this request
*/
typedef void
(*TALER_MERCHANT_RefundCallback) (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const char *taler_refund_uri,
- const struct TALER_PrivateContractHashP *h_contract);
+ const struct TALER_MERCHANT_RefundResponse *rr);
/**
@@ -2641,13 +3521,14 @@ typedef void
* @param cb_cls closure for cb
*/
struct TALER_MERCHANT_OrderRefundHandle *
-TALER_MERCHANT_post_order_refund (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const char *order_id,
- const struct TALER_Amount *refund,
- const char *reason,
- TALER_MERCHANT_RefundCallback cb,
- void *cb_cls);
+TALER_MERCHANT_post_order_refund (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *order_id,
+ const struct TALER_Amount *refund,
+ const char *reason,
+ TALER_MERCHANT_RefundCallback cb,
+ void *cb_cls);
/**
* Cancel a POST /refund request.
@@ -2693,17 +3574,74 @@ struct TALER_MERCHANT_RefundDetail
struct TALER_Amount refund_amount;
/**
- * Public key of the exchange affirming the refund,
- * only valid if the @e hr http_status is #MHD_HTTP_OK.
+ * Details depending on exchange HTTP status.
*/
- struct TALER_ExchangePublicKeyP exchange_pub;
+ union
+ {
+ /**
+ * Details if exchange status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * Public key of the exchange affirming the refund,
+ * only valid if the @e hr http_status is #MHD_HTTP_OK.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Signature of the exchange affirming the refund,
+ * only valid if the @e hr http_status is #MHD_HTTP_OK.
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+ } ok;
+ } details;
+};
+
+/**
+ * Response to a POST /orders/$ID/refund request
+ * for wallet API.
+ */
+struct TALER_MERCHANT_WalletRefundResponse
+{
/**
- * Signature of the exchange affirming the refund,
- * only valid if the @e hr http_status is #MHD_HTTP_OK.
+ * HTTP response details this request
*/
- struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_MERCHANT_HttpResponse hr;
+ /**
+ * Details depending on HTTP status.
+ */
+ union
+ {
+ /**
+ * Details if status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * Total amount of the refund that was granted
+ */
+ struct TALER_Amount refund_amount;
+
+ /**
+ * public key of the merchant signing the @e refunds
+ */
+ struct TALER_MerchantPublicKeyP merchant_pub;
+
+ /**
+ * array with details about the refunds obtained
+ */
+ const struct TALER_MERCHANT_RefundDetail *refunds;
+
+ /**
+ * length of the @e refunds array
+ */
+ unsigned int refunds_length;
+ } ok;
+ } details;
};
@@ -2711,20 +3649,12 @@ struct TALER_MERCHANT_RefundDetail
* Callback to process a (public) POST /orders/ID/refund request
*
* @param cls closure
- * @param hr HTTP response details
- * @param refund_amount what is the total amount of the refund that was granted
- * @param merchant_pub public key of the merchant signing the @a refunds
- * @param refunds array with details about the refunds obtained
- * @param refunds_length length of the @a refunds array
+ * @param wrr HTTP response details
*/
typedef void
(*TALER_MERCHANT_WalletRefundCallback) (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_Amount *refund_amount,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- struct TALER_MERCHANT_RefundDetail refunds[],
- unsigned int refunds_length);
+ const struct TALER_MERCHANT_WalletRefundResponse *wrr);
/**
@@ -2763,56 +3693,56 @@ TALER_MERCHANT_wallet_post_order_refund_cancel (
*/
struct TALER_MERCHANT_PostTransfersHandle;
+
/**
- * Information about the _total_ amount that was paid back
- * by the exchange for a given h_contract_terms, by _one_ wire
- * transfer.
+ * @brief Response to a POST /transfers operation from a merchant's backend.
*/
-struct TALER_MERCHANT_TrackTransferDetail
+struct TALER_MERCHANT_PostTransfersResponse
{
-
/**
- * Total amount paid back by the exchange.
+ * HTTP response details
*/
- struct TALER_Amount deposit_value;
+ struct TALER_MERCHANT_HttpResponse hr;
/**
- * Total amount of deposit fees.
+ * Details depending on HTTP status.
*/
- struct TALER_Amount deposit_fee;
+ union
+ {
- /**
- * Order ID associated whit this payment.
- */
- const char *order_id;
+ /**
+ * Details if we got an #MHD_HTTP_BAD_GATEWAY.
+ */
+ struct
+ {
+ /**
+ * HTTP status of the exchange (or 0 if not available).
+ */
+ unsigned int exchange_http_status;
+
+ /**
+ * Error code of the exchange (or TALER_EC_NONE if not available).
+ */
+ enum TALER_ErrorCode exchange_ec;
+
+ } bad_gateway;
+
+ } details;
};
+
/**
* Callbacks of this type are used to work the result of submitting a
* POST /transfers request to a merchant
*
* @param cls closure
- * @param hr HTTP response details
- * @param execution_time when did the transfer happen (according to the exchange),
- * #GNUNET_TIME_UNIT_FOREVER_ABS if the transfer did not yet happen or if
- * we have no data from the exchange about it
- * @param total_amount total amount of the wire transfer, or NULL if the exchange did
- * not provide any details
- * @param wire_fee how much did the exchange charge in terms of wire fees, or NULL
- * if the exchange did not provide any details
- * @param details_length length of the @a details array
- * @param details array with details about the combined transactions
+ * @param ptr response details
*/
typedef void
(*TALER_MERCHANT_PostTransfersCallback) (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- struct GNUNET_TIME_Timestamp execution_time,
- const struct TALER_Amount *total_amount,
- const struct TALER_Amount *wire_fee,
- unsigned int details_length,
- const struct TALER_MERCHANT_TrackTransferDetail details[]);
+ const struct TALER_MERCHANT_PostTransfersResponse *ptr);
/**
@@ -2964,21 +3894,54 @@ struct TALER_MERCHANT_TransferData
};
+
+/**
+ * Response from a GET /transfers request.
+ */
+struct TALER_MERCHANT_GetTransfersResponse
+{
+ /**
+ * HTTP response details
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Details depending on HTTP status.
+ */
+ union
+ {
+
+ /**
+ * Details for status #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * length of the @e transfers array
+ */
+ unsigned int transfers_length;
+
+ /**
+ * array with details about the transfers we received
+ */
+ const struct TALER_MERCHANT_TransferData *transfers;
+ } ok;
+ } details;
+};
+
+
/**
* Callbacks of this type are used to work the result of submitting a
* GET /transfers request to a merchant
*
* @param cls closure
- * @param hr HTTP response details
- * @param transfers_length length of the @a transfers array
- * @param transfers array with details about the transfers we received
+ * @param gtr HTTP response details
*/
typedef void
(*TALER_MERCHANT_GetTransfersCallback) (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int transfers_length,
- const struct TALER_MERCHANT_TransferData transfers[]);
+ const struct TALER_MERCHANT_GetTransfersResponse *gtr);
/**
@@ -3033,548 +3996,353 @@ TALER_MERCHANT_transfers_get_cancel (
struct TALER_MERCHANT_GetTransfersHandle *gth);
-/* ******************* /reserves *************** */
-
-
-/**
- * @brief Handle to a POST /reserves operation at a merchant's backend.
- */
-struct TALER_MERCHANT_PostReservesHandle;
-
-
-/**
- * Callbacks of this type are used to work the result of submitting a
- * POST /reserves request to a merchant
- *
- * @param cls closure
- * @param hr HTTP response details
- * @param reserve_pub public key of the created reserve, NULL on error
- * @param payto_uri where to make the payment to for filling the reserve, NULL on error
- */
-typedef void
-(*TALER_MERCHANT_PostReservesCallback) (
- void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const char *payto_uri);
-
-
-/**
- * Request backend to create a reserve.
- *
- * @param ctx execution context
- * @param backend_url base URL of the backend
- * @param initial_balance desired initial balance for the reserve
- * @param exchange_url what is the URL of the exchange where the reserve should be set up
- * @param wire_method desired wire method, for example "iban" or "x-taler-bank"
- * @param cb the callback to call when a reply for this request is available
- * @param cb_cls closure for @a cb
- * @return a handle for this request
- */
-struct TALER_MERCHANT_PostReservesHandle *
-TALER_MERCHANT_reserves_post (
- struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_Amount *initial_balance,
- const char *exchange_url,
- const char *wire_method,
- TALER_MERCHANT_PostReservesCallback cb,
- void *cb_cls);
-
-
-/**
- * Cancel a POST /reserves request. This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param prh the operation to cancel
- */
-void
-TALER_MERCHANT_reserves_post_cancel (
- struct TALER_MERCHANT_PostReservesHandle *prh);
-
+/* ********************* /kyc ************************** */
/**
- * Handle for a GET /reserves operation.
+ * Handle for getting the KYC status of instance(s).
*/
-struct TALER_MERCHANT_ReservesGetHandle;
+struct TALER_MERCHANT_KycGetHandle;
/**
- * Information about a reserve.
+ * Information about KYC actions the merchant still must perform.
*/
-struct TALER_MERCHANT_ReserveSummary
+struct TALER_MERCHANT_AccountKycRedirectDetail
{
- /**
- * Public key of the reserve
- */
- struct TALER_ReservePublicKeyP reserve_pub;
/**
- * Timestamp when it was established
+ * URL that the user should open in a browser to
+ * proceed with the KYC process (as returned
+ * by the exchange's /kyc-check/ endpoint). Can
+ * be NULL, specifically if KYC is satisfied but
+ * the transactions are hanging in AML.
*/
- struct GNUNET_TIME_Timestamp creation_time;
+ const char *kyc_url;
/**
- * Timestamp when it expires
+ * Base URL of the exchange this is about.
*/
- struct GNUNET_TIME_Timestamp expiration_time;
+ const char *exchange_url;
/**
- * Initial amount as per reserve creation call
+ * Our bank wire account this is about.
*/
- struct TALER_Amount merchant_initial_amount;
+ const char *payto_uri;
/**
- * Initial amount as per exchange, 0 if exchange did
- * not confirm reserve creation yet.
+ * AML state for our account.
*/
- struct TALER_Amount exchange_initial_amount;
+ enum TALER_AmlDecisionState aml_status;
+};
+
+/**
+ * Information about KYC status failures at the exchange.
+ */
+struct TALER_MERCHANT_ExchangeKycFailureDetail
+{
/**
- * Amount picked up so far.
+ * Base URL of the exchange this is about.
*/
- struct TALER_Amount pickup_amount;
+ const char *exchange_url;
/**
- * Amount approved for tips that exceeds the pickup_amount.
+ * Error code indicating errors the exchange
+ * returned, or #TALER_EC_INVALID for none.
*/
- struct TALER_Amount committed_amount;
+ enum TALER_ErrorCode exchange_code;
/**
- * Is this reserve active (false if it was deleted but not purged)
+ * HTTP status code returned by the exchange when we asked for
+ * information about the KYC status.
+ * 0 if there was no response at all.
*/
- bool active;
+ unsigned int exchange_http_status;
};
/**
- * Callback to process a GET /reserves request
- *
- * @param cls closure
- * @param hr HTTP response details
- * @param reserves_length length of the @a reserves array
- * @param reserves array with details about the reserves, NULL on error
- */
-typedef void
-(*TALER_MERCHANT_ReservesGetCallback) (
- void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int reserves_length,
- const struct TALER_MERCHANT_ReserveSummary reserves[]);
-
-
-/**
- * Issue a GET /reserves request to the backend. Informs the backend
- * that a customer wants to pick up a reserves.
- *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param after filter for reserves created after this date, use 0 for no filtering
- * @param active filter for reserves that are active
- * @param failures filter for reserves where we disagree about the balance with
- * the exchange
- * @param cb function to call with the result(s)
- * @param cb_cls closure for @a cb
- * @return handle for this operation, NULL upon errors
- */
-struct TALER_MERCHANT_ReservesGetHandle *
-TALER_MERCHANT_reserves_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- struct GNUNET_TIME_Timestamp after,
- enum TALER_EXCHANGE_YesNoAll active,
- enum TALER_EXCHANGE_YesNoAll failures,
- TALER_MERCHANT_ReservesGetCallback cb,
- void *cb_cls);
-
-
-/**
- * Cancel a GET /reserves request.
- *
- * @param rgh handle to the request to be canceled
- */
-void
-TALER_MERCHANT_reserves_get_cancel (
- struct TALER_MERCHANT_ReservesGetHandle *rgh);
-
-
-/**
- * Handle for a request to obtain details on a specific
- * (tipping) reserve.
- */
-struct TALER_MERCHANT_ReserveGetHandle;
-
-
-/**
- * Details about a tip granted by the merchant.
+ * Details in a response to a GET /kyc request.
*/
-struct TALER_MERCHANT_TipDetails
+struct TALER_MERCHANT_KycResponse
{
/**
- * Identifier for the tip.
- */
- struct TALER_TipIdentifierP tip_id;
-
- /**
- * Total value of the tip (including fees).
+ * HTTP response details.
*/
- struct TALER_Amount amount;
+ struct TALER_MERCHANT_HttpResponse hr;
/**
- * Human-readable reason for why the tip was granted.
+ * Response details depending on the HTTP status.
*/
- const char *reason;
-
-};
-
-
-/**
- * Callback to process a GET /reserve/$RESERVE_PUB request
- *
- * @param cls closure
- * @param hr HTTP response details
- * @param rs reserve summary for the reserve, NULL on error
- * @param active is this reserve active (false if it was deleted but not purged)
- * @param exchange_url URL of the exchange hosting the reserve, NULL if not @a active
- * @param payto_uri URI to fill the reserve, NULL if not @a active or already filled
- * @param tips_length length of the @a reserves array
- * @param tips array with details about the tips granted, NULL on error
- */
-typedef void
-(*TALER_MERCHANT_ReserveGetCallback) (
- void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_MERCHANT_ReserveSummary *rs,
- bool active,
- const char *exchange_url,
- const char *payto_uri,
- unsigned int tips_length,
- const struct TALER_MERCHANT_TipDetails tips[]);
+ union
+ {
+ /**
+ * Information returned if the status was #MHD_HTTP_ACCEPTED,
+ * #MHD_HTTP_BAD_GATEWAY or #MHD_HTTP_GATEWAY_TIMEOUT.
+ */
+ struct
+ {
+ /**
+ * Array with information about KYC actions the merchant still must perform.
+ */
+ struct TALER_MERCHANT_AccountKycRedirectDetail *pending_kycs;
-/**
- * Issue a GET /reserve/$RESERVE_ID request to the backend. Queries the backend
- * about the status of a reserve.
- *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param reserve_pub which reserve should be queried
- * @param fetch_tips should we return details about the tips issued from the reserve
- * @param cb function to call with the result(s)
- * @param cb_cls closure for @a cb
- * @return handle for this operation, NULL upon errors
- */
-struct TALER_MERCHANT_ReserveGetHandle *
-TALER_MERCHANT_reserve_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- bool fetch_tips,
- TALER_MERCHANT_ReserveGetCallback cb,
- void *cb_cls);
+ /**
+ * Array with information about KYC failures at the exchange.
+ */
+ struct TALER_MERCHANT_ExchangeKycFailureDetail *timeout_kycs;
+ /**
+ * Length of the @e pending_kycs array.
+ */
+ unsigned int pending_kycs_length;
-/**
- * Cancel a GET /reserve/$RESERVE_ID request.
- *
- * @param rgh handle to the request to be canceled
- */
-void
-TALER_MERCHANT_reserve_get_cancel (
- struct TALER_MERCHANT_ReserveGetHandle *rgh);
+ /**
+ * Length of the @e timeout_kycs array.
+ */
+ unsigned int timeout_kycs_length;
+ } kyc_status;
+ } details;
-/**
- * Handle for a /tip-authorize operation.
- */
-struct TALER_MERCHANT_TipAuthorizeHandle;
+};
/**
- * Callback for a /reserves/$RESERVE_PUB/tip-authorize request. Returns the result of
- * the operation.
+ * Callback to with a response from a GET [/private]/kyc request
*
* @param cls closure
- * @param hr HTTP response details
- * @param tip_id which tip ID should be used to pickup the tip
- * @param tip_uri URI for the tip
- * @param tip_expiration when does the tip expire
+ * @param kr response details
*/
typedef void
-(*TALER_MERCHANT_TipAuthorizeCallback) (
+(*TALER_MERCHANT_KycGetCallback) (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- struct TALER_TipIdentifierP *tip_id,
- const char *tip_uri,
- struct GNUNET_TIME_Timestamp tip_expiration);
+ const struct TALER_MERCHANT_KycResponse *kr);
/**
- * Issue a /tip-authorize request to the backend. Informs the backend
- * that a tip should be created.
+ * Issue a GET /private/kycs/$KYC_ID (private variant) request to the backend.
+ * Returns KYC status of bank accounts.
*
* @param ctx execution context
* @param backend_url base URL of the merchant backend
- * @param reserve_pub public key of the reserve
- * @param next_url where the browser should proceed after picking up the tip
- * @param amount amount to be handed out as a tip
- * @param justification which justification should be stored (human-readable reason for the tip)
- * @param authorize_cb callback which will work the response gotten from the backend
- * @param authorize_cb_cls closure to pass to @a authorize_cb
+ * @param h_wire which bank account to query, NULL for all
+ * @param exchange_url which exchange to query, NULL for all
+ * @param timeout how long to wait for a (positive) reply
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
* @return handle for this operation, NULL upon errors
*/
-struct TALER_MERCHANT_TipAuthorizeHandle *
-TALER_MERCHANT_tip_authorize2 (
+struct TALER_MERCHANT_KycGetHandle *
+TALER_MERCHANT_kyc_get (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const char *next_url,
- const struct TALER_Amount *amount,
- const char *justification,
- TALER_MERCHANT_TipAuthorizeCallback authorize_cb,
- void *authorize_cb_cls);
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *exchange_url,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_MERCHANT_KycGetCallback cb,
+ void *cb_cls);
/**
- * Issue a POST /tips request to the backend. Informs the backend that a tip
- * should be created. In contrast to #TALER_MERCHANT_tip_authorize2(), the
- * backend gets to pick the reserve with this API.
+ * Issue a GET /management/instances/$INSTANCE/kyc request to the backend.
+ * Returns KYC status of bank accounts.
*
* @param ctx execution context
* @param backend_url base URL of the merchant backend
- * @param next_url where the browser should proceed after picking up the tip
- * @param amount amount to be handed out as a tip
- * @param justification which justification should be stored (human-readable reason for the tip)
- * @param authorize_cb callback which will work the response gotten from the backend
- * @param authorize_cb_cls closure to pass to @a authorize_cb
+ * @param instance_id specific instance to query
+ * @param h_wire which bank account to query, NULL for all
+ * @param exchange_url which exchange to query, NULL for all
+ * @param timeout how long to wait for a (positive) reply
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
* @return handle for this operation, NULL upon errors
*/
-struct TALER_MERCHANT_TipAuthorizeHandle *
-TALER_MERCHANT_tip_authorize (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const char *next_url,
- const struct TALER_Amount *amount,
- const char *justification,
- TALER_MERCHANT_TipAuthorizeCallback authorize_cb,
- void *authorize_cb_cls);
+struct TALER_MERCHANT_KycGetHandle *
+TALER_MERCHANT_management_kyc_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *instance_id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *exchange_url,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_MERCHANT_KycGetCallback cb,
+ void *cb_cls);
/**
- * Cancel a pending /tip-authorize request
+ * Cancel a GET [/private]/kyc/$KYC_ID request.
*
- * @param ta handle from the operation to cancel
+ * @param kyc handle to the request to be canceled
*/
void
-TALER_MERCHANT_tip_authorize_cancel (
- struct TALER_MERCHANT_TipAuthorizeHandle *ta);
+TALER_MERCHANT_kyc_get_cancel (
+ struct TALER_MERCHANT_KycGetHandle *kyc);
-/**
- * Handle for a request to delete or purge a specific reserve.
- */
-struct TALER_MERCHANT_ReserveDeleteHandle;
+/* ********************* /otp-devices *********************** */
/**
- * Callback to process a DELETE /reserve/$RESERVE_PUB request
- *
- * @param cls closure
- * @param hr HTTP response details
+ * Handle for a GET /otp-devices operation.
*/
-typedef void
-(*TALER_MERCHANT_ReserveDeleteCallback) (
- void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr);
-
+struct TALER_MERCHANT_OtpDevicesGetHandle;
/**
- * Issue a DELETE /reserve/$RESERVE_ID request to the backend. Only
- * deletes the private key of the reserve, preserves tipping data.
- *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param reserve_pub which reserve should be queried
- * @param cb function to call with the result
- * @param cb_cls closure for @a cb
- * @return handle for this operation, NULL upon errors
+ * Individual OTP device (minimal information
+ * returned via GET /otp-devices).
*/
-struct TALER_MERCHANT_ReserveDeleteHandle *
-TALER_MERCHANT_reserve_delete (
- struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- TALER_MERCHANT_ReserveDeleteCallback cb,
- void *cb_cls);
+struct TALER_MERCHANT_OtpDeviceEntry
+{
+ /**
+ * OTP device identifier.
+ */
+ const char *otp_device_id;
+ /**
+ * OTP device description.
+ */
+ const char *device_description;
-/**
- * Issue a DELETE /reserve/$RESERVE_ID request to the backend.
- * Purges the reserve, deleting all associated data. DANGEROUS.
- *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param reserve_pub which reserve should be queried
- * @param cb function to call with the result
- * @param cb_cls closure for @a cb
- * @return handle for this operation, NULL upon errors
- */
-struct TALER_MERCHANT_ReserveDeleteHandle *
-TALER_MERCHANT_reserve_purge (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- TALER_MERCHANT_ReserveDeleteCallback cb,
- void *cb_cls);
+};
/**
- * Cancel a DELETE (or purge) /reserve/$RESERVE_ID request.
- *
- * @param rdh handle to the request to be canceled
+ * Response to a GET /otp-devices operation.
*/
-void
-TALER_MERCHANT_reserve_delete_cancel (
- struct TALER_MERCHANT_ReserveDeleteHandle *rdh);
-
-
-/* ********************* /tips ************************** */
+struct TALER_MERCHANT_OtpDevicesGetResponse
+{
+ /**
+ * HTTP response details
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+ /**
+ * Details depending on status.
+ */
+ union
+ {
+ /**
+ * Details if status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * length of the @e otp_devices array
+ */
+ unsigned int otp_devices_length;
-/**
- * Handle for a GET /tips/$TIP_ID (public variant) operation.
- */
-struct TALER_MERCHANT_TipWalletGetHandle;
+ /**
+ * array of otp_devices the requested instance offers
+ */
+ const struct TALER_MERCHANT_OtpDeviceEntry *otp_devices;
+ } ok;
+ } details;
+};
/**
- * Callback to process a GET /tips/$TIP_ID request
+ * Function called with the result of the GET /otp-devices operation.
*
* @param cls closure
- * @param hr HTTP response details
- * @param expiration when the tip will expire
- * @param exchange_url exchange from which the coins should be withdrawn
- * @param amount_remaining total amount still available for the tip
+ * @param tgr response details
*/
typedef void
-(*TALER_MERCHANT_TipWalletGetCallback) (
+(*TALER_MERCHANT_OtpDevicesGetCallback)(
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- struct GNUNET_TIME_Timestamp expiration,
- const char *exchange_url,
- const struct TALER_Amount *amount_remaining);
+ const struct TALER_MERCHANT_OtpDevicesGetResponse *tgr);
/**
- * Issue a GET /tips/$TIP_ID (public variant) request to the backend. Returns
- * information needed to pick up a tip.
+ * Make a GET /otp-devices request.
*
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param tip_id which tip should we query
- * @param cb function to call with the result
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param cb function to call with the backend information
* @param cb_cls closure for @a cb
- * @return handle for this operation, NULL upon errors
+ * @return the request handle; NULL upon error
*/
-struct TALER_MERCHANT_TipWalletGetHandle *
-TALER_MERCHANT_wallet_tip_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_TipIdentifierP *tip_id,
- TALER_MERCHANT_TipWalletGetCallback cb,
- void *cb_cls);
+struct TALER_MERCHANT_OtpDevicesGetHandle *
+TALER_MERCHANT_otp_devices_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ TALER_MERCHANT_OtpDevicesGetCallback cb,
+ void *cb_cls);
/**
- * Cancel a GET /tips/$TIP_ID request.
+ * Cancel GET /otp-devices operation.
*
- * @param tgh handle to the request to be canceled
+ * @param tgh operation to cancel
*/
void
-TALER_MERCHANT_wallet_tip_get_cancel (
- struct TALER_MERCHANT_TipWalletGetHandle *tgh);
-
-
-/**
- * Handle for a GET /private/tips/$TIP_ID (private variant) operation.
- */
-struct TALER_MERCHANT_TipMerchantGetHandle;
+TALER_MERCHANT_otp_devices_get_cancel (
+ struct TALER_MERCHANT_OtpDevicesGetHandle *tgh);
/**
- * Summary information for a tip pickup.
+ * Handle for a GET /otp-device/$ID operation. Gets details
+ * about a single otp_device. Do not confused with a
+ * `struct TALER_MERCHANT_OtpDevicesGetHandle`, which
+ * obtains a list of all otp_devices.
*/
-struct TALER_MERCHANT_PickupDetail
-{
- /**
- * Identifier of the pickup.
- */
- struct TALER_PickupIdentifierP pickup_id;
-
- /**
- * Number of planchets involved.
- */
- uint64_t num_planchets;
-
- /**
- * Total amount requested for this pickup.
- */
- struct TALER_Amount requested_amount;
-};
+struct TALER_MERCHANT_OtpDeviceGetHandle;
/**
- * Details returned about a tip by the merchant.
+ * Details in a response to a GET /otp-devices request.
*/
-struct TALER_MERCHANT_TipStatusResponse
+struct TALER_MERCHANT_OtpDeviceGetResponse
{
/**
- * HTTP status of the response.
+ * HTTP response details.
*/
struct TALER_MERCHANT_HttpResponse hr;
/**
- * Details depending on the HTTP status.
+ * Response details depending on the HTTP status.
*/
union
{
-
/**
- * Details on #MHD_HTTP_OK.
+ * Information returned if the status was #MHD_HTTP_OK.
*/
struct
{
/**
- * Amount that was authorized under this tip
- */
- struct TALER_Amount total_authorized;
-
- /**
- * Amount that has been picked up
+ * description of the otp_device
*/
- struct TALER_Amount total_picked_up;
+ const char *otp_device_description;
/**
- * The reason given for the tip
+ * POS confirmation text with OTP codes that
+ * would be returned for a purchase over the
+ * amount given in the query for the respective
+ * time and algorithm. NULL if the confirmation
+ * could not be computed based on the query and
+ * OTP algorithm.
*/
- const char *reason;
+ const char *otp_code;
/**
- * Time when the tip will expire
+ * current counter.
*/
- struct GNUNET_TIME_Timestamp expiration;
+ uint64_t otp_ctr;
/**
- * reserve which is funding this tip
+ * OTP algorithm used.
*/
- struct TALER_ReservePublicKeyP reserve_pub;
+ enum TALER_MerchantConfirmationAlgorithm otp_alg;
/**
- * Length of the @e pickups array
+ * Timestamp in second used to compute
+ * @e otp_code.
*/
- unsigned int pickups_length;
+ uint64_t otp_timestamp_s;
- /**
- * array of pickup operations performed for this tip
- */
- struct TALER_MERCHANT_PickupDetail *pickups;
- } success;
+ } ok;
} details;
@@ -3582,577 +4350,446 @@ struct TALER_MERCHANT_TipStatusResponse
/**
- * Callback to process a GET /private/tips/$TIP_ID request
+ * Function called with the result of the GET /otp-device/$ID operation.
*
* @param cls closure
- * @param tsr response details
+ * @param tgr HTTP response details
*/
typedef void
-(*TALER_MERCHANT_TipMerchantGetCallback) (
+(*TALER_MERCHANT_OtpDeviceGetCallback)(
void *cls,
- const struct TALER_MERCHANT_TipStatusResponse *tsr);
+ const struct TALER_MERCHANT_OtpDeviceGetResponse *tgr);
/**
- * Issue a GET /private/tips/$TIP_ID (private variant) request to the backend.
- * Returns information needed to pick up a tip.
- *
- * FIXME: lp_timeout/min_pick_up not implemented in backend!
+ * Make a GET /otp-device/$ID request to get details about an
+ * individual OTP device.
*
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param tip_id which tip should we query
- * @param min_pick_up minimum amount picked up to notify about
- * @param lp_timeout how long to wait for @a min_pick_up to be exceeded
- * @param pickups whether to fetch associated pickups
- * @param cb function to call with the result
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param otp_device_id identifier of the otp_device to inquire about
+ * @param cb function to call with the backend's otp_device information
* @param cb_cls closure for @a cb
- * @return handle for this operation, NULL upon errors
+ * @return the request handle; NULL upon error
*/
-struct TALER_MERCHANT_TipMerchantGetHandle *
-TALER_MERCHANT_merchant_tip_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_TipIdentifierP *tip_id,
- const struct TALER_Amount *min_pick_up,
- struct GNUNET_TIME_Relative lp_timeout,
- bool pickups,
- TALER_MERCHANT_TipMerchantGetCallback cb,
- void *cb_cls);
+struct TALER_MERCHANT_OtpDeviceGetHandle *
+TALER_MERCHANT_otp_device_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *otp_device_id,
+ TALER_MERCHANT_OtpDeviceGetCallback cb,
+ void *cb_cls);
/**
- * Cancel a GET /private/tips/$TIP_ID request.
+ * Cancel GET /otp-devices/$ID operation.
*
- * @param tgh handle to the request to be canceled
+ * @param tgh operation to cancel
*/
void
-TALER_MERCHANT_merchant_tip_get_cancel (
- struct TALER_MERCHANT_TipMerchantGetHandle *tgh);
+TALER_MERCHANT_otp_device_get_cancel (
+ struct TALER_MERCHANT_OtpDeviceGetHandle *tgh);
/**
- * Handle for a GET /private/tips request.
+ * Handle for a POST /otp-devices operation.
*/
-struct TALER_MERCHANT_TipsGetHandle;
-
-
-/**
- * Database entry information of a tip.
- */
-struct TALER_MERCHANT_TipEntry
-{
- /**
- * Row number of the tip in the database.
- */
- uint64_t row_id;
-
- /**
- * Identifier for the tip.
- */
- struct TALER_TipIdentifierP tip_id;
-
- /**
- * Total value of the tip (including fees).
- */
- struct TALER_Amount tip_amount;
-
-};
+struct TALER_MERCHANT_OtpDevicesPostHandle;
/**
- * Callback to process a GET /private/tips request.
+ * Function called with the result of the POST /otp-devices operation.
*
* @param cls closure
* @param hr HTTP response details
- * @param tips_length length of the @a tips array
- * @param tips the array of tips, NULL on error
*/
typedef void
-(*TALER_MERCHANT_TipsGetCallback) (
+(*TALER_MERCHANT_OtpDevicesPostCallback)(
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int tips_length,
- const struct TALER_MERCHANT_TipEntry tips[]);
-
-
-/**
- * Issue a GET /private/tips request to the backend.
- *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param cb function to call with the result
- * @param cb_cls closure for @a cb
- * @return handle for this operation, NULL upon errors
- */
-struct TALER_MERCHANT_TipsGetHandle *
-TALER_MERCHANT_tips_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- TALER_MERCHANT_TipsGetCallback cb,
- void *cb_cls);
+ const struct TALER_MERCHANT_HttpResponse *hr);
/**
- * Issue a GET /private/tips request with filters to the backend.
+ * Make a POST /otp-devices request to add an OTP device
*
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param expired yes for expired tips, no for unexpired tips, all for all tips
- * @param limit number of results to return, negative for descending row id, positive for ascending
- * @param offset row id to start returning results from
- * @param cb function to call with the result
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param otp_id identifier to use for the OTP device
+ * @param otp_device_description description of the OTP device
+ * @param otp_key key of the OTP device
+ * @param otp_alg OTP algorithm used
+ * @param otp_ctr counter for counter-based OTP
+ * @param cb function to call with the backend's result
* @param cb_cls closure for @a cb
- * @return handle for this operation, NULL upon errors
+ * @return the request handle; NULL upon error
*/
-struct TALER_MERCHANT_TipsGetHandle *
-TALER_MERCHANT_tips_get2 (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- enum TALER_EXCHANGE_YesNoAll expired,
- int64_t limit,
- uint64_t offset,
- TALER_MERCHANT_TipsGetCallback cb,
- void *cb_cls);
+struct TALER_MERCHANT_OtpDevicesPostHandle *
+TALER_MERCHANT_otp_devices_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *otp_id,
+ const char *otp_device_description,
+ const char *otp_key,
+ enum TALER_MerchantConfirmationAlgorithm otp_alg,
+ uint64_t otp_ctr,
+ TALER_MERCHANT_OtpDevicesPostCallback cb,
+ void *cb_cls);
/**
- * Cancel a GET /private/tips request.
+ * Cancel POST /otp-devices operation.
*
- * @param tgh the operation to cancel
+ * @param[in] tph operation to cancel
*/
void
-TALER_MERCHANT_tips_get_cancel (struct TALER_MERCHANT_TipsGetHandle *tgh);
+TALER_MERCHANT_otp_devices_post_cancel (
+ struct TALER_MERCHANT_OtpDevicesPostHandle *tph);
/**
- * Handle for a POST /tips/$TIP_ID/pickup operation.
+ * Handle for a PATCH /otp-device operation.
*/
-struct TALER_MERCHANT_TipPickupHandle;
+struct TALER_MERCHANT_OtpDevicePatchHandle;
/**
- * Details about a pickup operation, as returned to the application.
- */
-struct TALER_MERCHANT_PickupDetails
-{
- /**
- * HTTP response data.
- */
- struct TALER_MERCHANT_HttpResponse hr;
-
- /**
- * Details about the response.
- */
- union
- {
- /**
- * Details if the status is #MHD_HTTP_OK.
- */
- struct
- {
-
- /**
- * Array of length @e num_sigs with details about each of the coins that
- * were picked up.
- */
- struct TALER_EXCHANGE_PrivateCoinDetails *pcds;
-
- /**
- * Length of the @e pcds array.
- */
- unsigned int num_sigs;
- } success;
-
- } details;
-
-};
-
-
-/**
- * Callback for a POST /tips/$TIP_ID/pickup request. Returns the result of
- * the operation.
+ * Function called with the result of the PATCH /otp-device operation.
*
* @param cls closure
- * @param pd HTTP response details
+ * @param hr HTTP response details
*/
typedef void
-(*TALER_MERCHANT_TipPickupCallback) (
+(*TALER_MERCHANT_OtpDevicePatchCallback)(
void *cls,
- const struct TALER_MERCHANT_PickupDetails *pd);
-
-
-/**
- * Information per planchet.
- */
-struct TALER_MERCHANT_PlanchetData
-{
- /**
- * Planchet secrets.
- */
- struct TALER_PlanchetMasterSecretP ps;
+ const struct TALER_MERCHANT_HttpResponse *hr);
- /**
- * Denomination key desired.
- */
- const struct TALER_EXCHANGE_DenomPublicKey *pk;
-
-};
/**
- * Issue a POST /tips/$TIP_ID/pickup request to the backend. Informs the
- * backend that a customer wants to pick up a tip.
+ * Make a PATCH /otp-device request to update OTP device details
*
- * @param ctx execution context
- * @param exchange handle to the exchange we are picking up the tip from
- * @param backend_url base URL of the merchant backend
- * @param tip_id unique identifier for the tip
- * @param num_planchets number of planchets provided in @a pds
- * @param planchets array of planchet secrets to be signed into existence for the tip
- * @param pickup_cb callback which will work the response gotten from the backend
- * @param pickup_cb_cls closure to pass to @a pickup_cb
- * @return handle for this operation, NULL upon errors
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param otp_id identifier to use for the OTP device; the OTP device must exist,
+ * or the transaction will fail with a #MHD_HTTP_NOT_FOUND
+ * HTTP status code
+ * @param otp_device_description description of the otp_device
+ * @param otp_key key of the OTP device
+ * @param otp_alg OTP algorithm used
+ * @param otp_ctr counter for counter-based OTP
+ * @param cb function to call with the backend's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
*/
-struct TALER_MERCHANT_TipPickupHandle *
-TALER_MERCHANT_tip_pickup (struct GNUNET_CURL_Context *ctx,
- struct TALER_EXCHANGE_Handle *exchange,
- const char *backend_url,
- const struct TALER_TipIdentifierP *tip_id,
- unsigned int num_planchets,
- const struct TALER_MERCHANT_PlanchetData planchets[],
- TALER_MERCHANT_TipPickupCallback pickup_cb,
- void *pickup_cb_cls);
+struct TALER_MERCHANT_OtpDevicePatchHandle *
+TALER_MERCHANT_otp_device_patch (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *otp_id,
+ const char *otp_device_description,
+ const char *otp_key,
+ enum TALER_MerchantConfirmationAlgorithm otp_alg,
+ uint64_t otp_ctr,
+ TALER_MERCHANT_OtpDevicePatchCallback cb,
+ void *cb_cls);
/**
- * Cancel a pending /tips/$TIP_ID/pickup request
+ * Cancel PATCH /otp-device operation.
*
- * @param tph handle from the operation to cancel
+ * @param[in] tph operation to cancel
*/
void
-TALER_MERCHANT_tip_pickup_cancel (struct TALER_MERCHANT_TipPickupHandle *tph);
+TALER_MERCHANT_otp_device_patch_cancel (
+ struct TALER_MERCHANT_OtpDevicePatchHandle *tph);
/**
- * Handle for a low-level /tip-pickup operation (without unblinding).
+ * Handle for a DELETE /otp-device/$ID operation.
*/
-struct TALER_MERCHANT_TipPickup2Handle;
+struct TALER_MERCHANT_OtpDeviceDeleteHandle;
/**
- * Callback for a POST /tips/$TIP_ID/pickup request. Returns the result of
- * the operation. Note that the client MUST still do the unblinding of the @a
- * blind_sigs.
+ * Function called with the result of the DELETE /otp-device/$ID operation.
*
* @param cls closure
* @param hr HTTP response details
- * @param num_blind_sigs length of the @a blind_sigs array, 0 on error
- * @param blind_sigs array of blind signatures over the planchets, NULL on error
*/
typedef void
-(*TALER_MERCHANT_TipPickup2Callback) (
+(*TALER_MERCHANT_OtpDeviceDeleteCallback)(
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int num_blind_sigs,
- const struct TALER_BlindedDenominationSignature blind_sigs[]);
+ const struct TALER_MERCHANT_HttpResponse *hr);
/**
- * Issue a POST /tips/$TIP_ID/pickup request to the backend. Informs the
- * backend that a customer wants to pick up a tip.
+ * Make a DELETE /otp-device/$ID request to delete an OTP device.
*
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param tip_id unique identifier for the tip
- * @param num_planchets number of planchets provided in @a planchets
- * @param planchets array of planchets to be signed into existence for the tip
- * @param pickup_cb callback which will work the response gotten from the backend
- * @param pickup_cb_cls closure to pass to @a pickup_cb
- * @return handle for this operation, NULL upon errors
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param otp_device_id identifier of the OTP device
+ * @param cb function to call with the backend's deletion status
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
*/
-struct TALER_MERCHANT_TipPickup2Handle *
-TALER_MERCHANT_tip_pickup2 (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_TipIdentifierP *tip_id,
- unsigned int num_planchets,
- const struct TALER_PlanchetDetail planchets[],
- TALER_MERCHANT_TipPickup2Callback pickup_cb,
- void *pickup_cb_cls);
+struct TALER_MERCHANT_OtpDeviceDeleteHandle *
+TALER_MERCHANT_otp_device_delete (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *otp_device_id,
+ TALER_MERCHANT_OtpDeviceDeleteCallback cb,
+ void *cb_cls);
/**
- * Cancel a pending /tip-pickup request.
+ * Cancel DELETE /otp-device/$ID operation.
*
- * @param tp handle from the operation to cancel
+ * @param[in] tdh operation to cancel
*/
void
-TALER_MERCHANT_tip_pickup2_cancel (
- struct TALER_MERCHANT_TipPickup2Handle *tp);
-
+TALER_MERCHANT_otp_device_delete_cancel (
+ struct TALER_MERCHANT_OtpDeviceDeleteHandle *tdh);
-/* ********************* /kyc ************************** */
-/**
- * Handle for getting the KYC status of instance(s).
- */
-struct TALER_MERCHANT_KycGetHandle;
+/* ********************* /templates *********************** */
/**
- * Information about KYC actions the merchant still must perform.
+ * Handle for a GET /templates operation.
*/
-struct TALER_MERCHANT_AccountKycRedirectDetail
-{
-
- /**
- * URL that the user should open in a browser to
- * proceed with the KYC process (as returned
- * by the exchange's /kyc-check/ endpoint).
- */
- const char *kyc_url;
-
- /**
- * Base URL of the exchange this is about.
- */
- const char *exchange_url;
-
- /**
- * Our bank wire account this is about.
- */
- const char *payto_uri;
-};
-
+struct TALER_MERCHANT_TemplatesGetHandle;
/**
- * Information about KYC status failures at the exchange.
+ * Individual template (minimal information
+ * returned via GET /templates).
*/
-struct TALER_MERCHANT_ExchangeKycFailureDetail
+struct TALER_MERCHANT_TemplateEntry
{
/**
- * Base URL of the exchange this is about.
- */
- const char *exchange_url;
-
- /**
- * Error code indicating errors the exchange
- * returned, or #TALER_EC_INVALID for none.
+ * template identifier.
*/
- enum TALER_ErrorCode exchange_code;
+ const char *template_id;
- /**
- * HTTP status code returned by the exchange when we asked for
- * information about the KYC status.
- * 0 if there was no response at all.
- */
- unsigned int exchange_http_status;
};
/**
- * Details in a response to a GET /kyc request.
+ * Response to a GET /templates operation.
*/
-struct TALER_MERCHANT_KycResponse
+struct TALER_MERCHANT_TemplatesGetResponse
{
/**
- * HTTP response details.
+ * HTTP response details
*/
struct TALER_MERCHANT_HttpResponse hr;
/**
- * Response details depending on the HTTP status.
+ * Details depending on status.
*/
union
{
/**
- * Information returned if the status was #MHD_HTTP_ACCEPTED,
- * #MHD_HTTP_BAD_GATEWAY or #MHD_HTTP_GATEWAY_TIMEOUT.
+ * Details if status is #MHD_HTTP_OK.
*/
struct
{
-
/**
- * Array with information about KYC actions the merchant still must perform.
+ * length of the @e templates array
*/
- struct TALER_MERCHANT_AccountKycRedirectDetail *pending_kycs;
-
- /**
- * Array with information about KYC failures at the exchange.
- */
- struct TALER_MERCHANT_ExchangeKycFailureDetail *timeout_kycs;
+ unsigned int templates_length;
/**
- * Length of the @e pending_kycs array.
- */
- unsigned int pending_kycs_length;
-
- /**
- * Length of the @e timeout_kycs array.
+ * array of templates the requested instance offers
*/
- unsigned int timeout_kycs_length;
- } kyc_status;
-
+ const struct TALER_MERCHANT_TemplateEntry *templates;
+ } ok;
} details;
-
};
/**
- * Callback to with a response from a GET [/private]/kyc request
+ * Function called with the result of the GET /templates operation.
*
* @param cls closure
- * @param kr response details
+ * @param tgr response details
*/
typedef void
-(*TALER_MERCHANT_KycGetCallback) (
+(*TALER_MERCHANT_TemplatesGetCallback)(
void *cls,
- const struct TALER_MERCHANT_KycResponse *kr);
+ const struct TALER_MERCHANT_TemplatesGetResponse *tgr);
/**
- * Issue a GET /private/kycs/$KYC_ID (private variant) request to the backend.
- * Returns KYC status of bank accounts.
- *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param h_wire which bank account to query, NULL for all
- * @param exchange_url which exchange to query, NULL for all
- * @param timeout how long to wait for a (positive) reply
- * @param cb function to call with the result
- * @param cb_cls closure for @a cb
- * @return handle for this operation, NULL upon errors
- */
-struct TALER_MERCHANT_KycGetHandle *
-TALER_MERCHANT_kyc_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_MerchantWireHashP *h_wire,
- const char *exchange_url,
- struct GNUNET_TIME_Relative timeout,
- TALER_MERCHANT_KycGetCallback cb,
- void *cb_cls);
-
-
-/**
- * Issue a GET /management/instances/$INSTANCE/kyc request to the backend.
- * Returns KYC status of bank accounts.
+ * Make a GET /templates request.
*
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param instance_id specific instance to query
- * @param h_wire which bank account to query, NULL for all
- * @param exchange_url which exchange to query, NULL for all
- * @param timeout how long to wait for a (positive) reply
- * @param cb function to call with the result
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param cb function to call with the backend information
* @param cb_cls closure for @a cb
- * @return handle for this operation, NULL upon errors
+ * @return the request handle; NULL upon error
*/
-struct TALER_MERCHANT_KycGetHandle *
-TALER_MERCHANT_management_kyc_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const char *instance_id,
- const struct TALER_MerchantWireHashP *h_wire,
- const char *exchange_url,
- struct GNUNET_TIME_Relative timeout,
- TALER_MERCHANT_KycGetCallback cb,
- void *cb_cls);
+struct TALER_MERCHANT_TemplatesGetHandle *
+TALER_MERCHANT_templates_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ TALER_MERCHANT_TemplatesGetCallback cb,
+ void *cb_cls);
/**
- * Cancel a GET [/private]/kyc/$KYC_ID request.
+ * Cancel GET /templates operation.
*
- * @param kyc handle to the request to be canceled
+ * @param tgh operation to cancel
*/
void
-TALER_MERCHANT_kyc_get_cancel (
- struct TALER_MERCHANT_KycGetHandle *kyc);
-
-
-/* ********************* /templates *********************** */
+TALER_MERCHANT_templates_get_cancel (
+ struct TALER_MERCHANT_TemplatesGetHandle *tgh);
/**
- * Handle for a GET /templates operation.
+ * Handle for a GET /private/template/$ID operation. Gets details
+ * about a single template. Do not confused with a
+ * `struct TALER_MERCHANT_TemplatesGetHandle`, which
+ * obtains a list of all templates.
*/
-struct TALER_MERCHANT_TemplatesGetHandle;
+struct TALER_MERCHANT_TemplateGetHandle;
+
/**
- * Individual template (minimal information
- * returned via GET /templates).
+ * Details in a response to a GET /private/templates/$ID request.
*/
-struct TALER_MERCHANT_TemplateEntry
+struct TALER_MERCHANT_TemplateGetResponse
{
/**
- * template identifier.
+ * HTTP response details.
*/
- const char *template_id;
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Response details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Information returned if the status was #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * description of the template
+ */
+ const char *template_description;
+
+ /**
+ * OTP device ID used by the POS, NULL if none.
+ */
+ const char *otp_id;
+
+ /**
+ * Template for the contract.
+ */
+ const json_t *template_contract;
+
+ } ok;
+
+ } details;
};
/**
- * Function called with the result of the GET /templates operation.
+ * Function called with the result of the GET /private/template/$ID operation.
*
* @param cls closure
- * @param hr HTTP response details
- * @param templates_length length of the @a templates array
- * @param templates array of templates the requested instance offers
+ * @param tgr HTTP response details
*/
typedef void
-(*TALER_MERCHANT_TemplatesGetCallback)(
+(*TALER_MERCHANT_TemplateGetCallback)(
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int templates_length,
- const struct TALER_MERCHANT_TemplateEntry templates[]);
+ const struct TALER_MERCHANT_TemplateGetResponse *tgr);
/**
- * Make a GET /templates request.
+ * Make a GET /private/template/$ID request to get details about an
+ * individual template.
*
* @param ctx the context
* @param backend_url HTTP base URL for the backend
- * @param cb function to call with the backend information
+ * @param template_id identifier of the template to inquire about
+ * @param cb function to call with the backend's template information
* @param cb_cls closure for @a cb
* @return the request handle; NULL upon error
*/
-struct TALER_MERCHANT_TemplatesGetHandle *
-TALER_MERCHANT_templates_get (
+struct TALER_MERCHANT_TemplateGetHandle *
+TALER_MERCHANT_template_get (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- TALER_MERCHANT_TemplatesGetCallback cb,
+ const char *template_id,
+ TALER_MERCHANT_TemplateGetCallback cb,
void *cb_cls);
/**
- * Cancel GET /templates operation.
+ * Cancel GET /private/templates/$ID operation.
*
* @param tgh operation to cancel
*/
void
-TALER_MERCHANT_templates_get_cancel (
- struct TALER_MERCHANT_TemplatesGetHandle *tgh);
+TALER_MERCHANT_template_get_cancel (
+ struct TALER_MERCHANT_TemplateGetHandle *tgh);
/**
- * Handle for a GET /template/$ID operation. Gets details
- * about a single template. Do not confused with a
- * `struct TALER_MERCHANT_TemplatesGetHandle`, which
- * obtains a list of all templates.
+ * Handle for a (public) GET /template/$ID operation. Gets details about a
+ * single template. Do not confused with a `struct
+ * TALER_MERCHANT_TemplateGetHandle`, which is for the private API.
*/
-struct TALER_MERCHANT_TemplateGetHandle;
+struct TALER_MERCHANT_WalletTemplateGetHandle;
/**
- * Function called with the result of the GET /templates operation.
+ * Details in a response to a GET /templates request.
+ */
+struct TALER_MERCHANT_WalletTemplateGetResponse
+{
+ /**
+ * HTTP response details.
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Response details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Information returned if the status was #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * Template for the contract.
+ */
+ const json_t *template_contract;
+
+ } ok;
+
+ } details;
+
+};
+
+
+/**
+ * Function called with the result of the GET /template/$ID operation.
*
* @param cls closure
- * @param hr HTTP response details
- * @param template_description description of the template
- * @param image base64-encoded product image
- * @param template_contract is the contract of the company
+ * @param tgr HTTP response details
*/
typedef void
-(*TALER_MERCHANT_TemplateGetCallback)(
+(*TALER_MERCHANT_WalletTemplateGetCallback)(
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const char *template_description,
- const char *image,
- const json_t *template_contract);
+ const struct TALER_MERCHANT_WalletTemplateGetResponse *tgr);
/**
@@ -4166,12 +4803,12 @@ typedef void
* @param cb_cls closure for @a cb
* @return the request handle; NULL upon error
*/
-struct TALER_MERCHANT_TemplateGetHandle *
-TALER_MERCHANT_template_get (
+struct TALER_MERCHANT_WalletTemplateGetHandle *
+TALER_MERCHANT_wallet_template_get (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
const char *template_id,
- TALER_MERCHANT_TemplateGetCallback cb,
+ TALER_MERCHANT_WalletTemplateGetCallback cb,
void *cb_cls);
@@ -4181,8 +4818,8 @@ TALER_MERCHANT_template_get (
* @param tgh operation to cancel
*/
void
-TALER_MERCHANT_template_get_cancel (
- struct TALER_MERCHANT_TemplateGetHandle *tgh);
+TALER_MERCHANT_wallet_template_get_cancel (
+ struct TALER_MERCHANT_WalletTemplateGetHandle *tgh);
/**
@@ -4210,7 +4847,7 @@ typedef void
* @param backend_url HTTP base URL for the backend
* @param template_id identifier to use for the template
* @param template_description description of the template
- * @param image base64-encoded product image
+ * @param otp_id ID of the OTP device, or NULL if OTP is not used
* @param template_contract is the contract of the company
* @param cb function to call with the backend's result
* @param cb_cls closure for @a cb
@@ -4222,7 +4859,7 @@ TALER_MERCHANT_templates_post (
const char *backend_url,
const char *template_id,
const char *template_description,
- const char *image,
+ const char *otp_id,
const json_t *template_contract,
TALER_MERCHANT_TemplatesPostCallback cb,
void *cb_cls);
@@ -4231,7 +4868,7 @@ TALER_MERCHANT_templates_post (
/**
* Cancel POST /templates operation.
*
- * @param tph operation to cancel
+ * @param[in] tph operation to cancel
*/
void
TALER_MERCHANT_templates_post_cancel (
@@ -4265,7 +4902,7 @@ typedef void
* or the transaction will fail with a #MHD_HTTP_NOT_FOUND
* HTTP status code
* @param template_description description of the template
- * @param image base64-encoded product image
+ * @param otp_id device ID of the OTP device, or NULL if OTP is not used
* @param template_contract is the contract of the company
* @param cb function to call with the backend's result
* @param cb_cls closure for @a cb
@@ -4277,7 +4914,7 @@ TALER_MERCHANT_template_patch (
const char *backend_url,
const char *template_id,
const char *template_description,
- const char *image,
+ const char *otp_id,
json_t *template_contract,
TALER_MERCHANT_TemplatePatchCallback cb,
void *cb_cls);
@@ -4286,7 +4923,7 @@ TALER_MERCHANT_template_patch (
/**
* Cancel PATCH /template operation.
*
- * @param tph operation to cancel
+ * @param[in] tph operation to cancel
*/
void
TALER_MERCHANT_template_patch_cancel (
@@ -4333,7 +4970,7 @@ TALER_MERCHANT_template_delete (
/**
* Cancel DELETE /template/$ID operation.
*
- * @param tdh operation to cancel
+ * @param[in] tdh operation to cancel
*/
void
TALER_MERCHANT_template_delete_cancel (
@@ -4366,7 +5003,7 @@ TALER_MERCHANT_using_templates_post (
/**
* Cancel POST /using-templates operation.
*
- * @param utph operation to cancel
+ * @param[in] utph operation to cancel
*/
void
TALER_MERCHANT_using_templates_post_cancel (
@@ -4396,19 +5033,51 @@ struct TALER_MERCHANT_WebhookEntry
/**
+ * Response to a GET /webhooks operation.
+ */
+struct TALER_MERCHANT_WebhooksGetResponse
+{
+ /**
+ * HTTP response details
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Details depending on status.
+ */
+ union
+ {
+ /**
+ * Details if status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * length of the @e webhooks array
+ */
+ unsigned int webhooks_length;
+
+ /**
+ * array of templates the requested instance offers
+ */
+ const struct TALER_MERCHANT_WebhookEntry *webhooks;
+
+ } ok;
+
+ } details;
+};
+
+
+/**
* Function called with the result of the GET /webhooks operation.
*
* @param cls closure
- * @param hr HTTP response details
- * @param webhooks_length length of the @a webhooks array
- * @param webhooks array of webhooks the requested instance offers
+ * @param wgr response details
*/
typedef void
(*TALER_MERCHANT_WebhooksGetCallback)(
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int webhooks_length,
- const struct TALER_MERCHANT_WebhookEntry webhooks[]);
+ const struct TALER_MERCHANT_WebhooksGetResponse *wgr);
/**
@@ -4431,7 +5100,7 @@ TALER_MERCHANT_webhooks_get (
/**
* Cancel GET /webhooks operation.
*
- * @param tgh operation to cancel
+ * @param[in] tgh operation to cancel
*/
void
TALER_MERCHANT_webhooks_get_cancel (
@@ -4492,7 +5161,7 @@ TALER_MERCHANT_webhook_get (
/**
* Cancel GET /webhooks/$ID operation.
*
- * @param tgh operation to cancel
+ * @param[in] tgh operation to cancel
*/
void
TALER_MERCHANT_webhook_get_cancel (
@@ -4549,7 +5218,7 @@ TALER_MERCHANT_webhooks_post (
/**
* Cancel POST /webhooks operation.
*
- * @param wph operation to cancel
+ * @param[in] wph operation to cancel
*/
void
TALER_MERCHANT_webhooks_post_cancel (
@@ -4608,7 +5277,7 @@ TALER_MERCHANT_webhook_patch (
/**
* Cancel PATCH /webhook operation.
*
- * @param wph operation to cancel
+ * @param[in] wph operation to cancel
*/
void
TALER_MERCHANT_webhook_patch_cancel (
@@ -4655,7 +5324,7 @@ TALER_MERCHANT_webhook_delete (
/**
* Cancel DELETE /webhook/$ID operation.
*
- * @param wdh operation to cancel
+ * @param[in] wdh operation to cancel
*/
void
TALER_MERCHANT_webhook_delete_cancel (
diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h
index c805afba..b1de5292 100644
--- a/src/include/taler_merchant_testing_lib.h
+++ b/src/include/taler_merchant_testing_lib.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2018-2022 Taler Systems SA
+ (C) 2018-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -36,33 +36,17 @@
#define MERCHANT_FAIL() \
do {GNUNET_break (0); return NULL; } while (0)
+
/**
- * Prepare the merchant execution. Create tables and check if
- * the port is available.
+ * Extract hostname (and port) from merchant base URL.
*
- * @param config_filename configuration filename.
- * @return the base url, or NULL upon errors. Must be freed
- * by the caller.
+ * @param merchant_url full merchant URL (e.g. "http://host:8080/foo/bar/")
+ * @return just the hostname and port ("hostname:8080")
*/
char *
-TALER_TESTING_prepare_merchant (const char *config_filename);
+TALER_MERCHANT_TESTING_extract_host (const char *merchant_url);
-/**
- * Start the merchant backend process. Assume the port
- * is available and the database is clean. Use the "prepare
- * merchant" function to do such tasks.
- *
- * @param config_filename configuration filename.
- * @param merchant_url merchant base URL, used to check
- * if the merchant was started right.
- * @return the process, or NULL if the process could not
- * be started.
- */
-struct GNUNET_OS_Process *
-TALER_TESTING_run_merchant (const char *config_filename,
- const char *merchant_url);
-
/* ************** Specific interpreter commands ************ */
@@ -107,8 +91,6 @@ TALER_TESTING_cmd_merchant_get_instances (const char *label,
* @param merchant_url base URL of the merchant serving the
* POST /instances request.
* @param instance_id the ID of the instance to create
- * @param payto_uri payment URI to use
- * @param currency currency to use for default fees
* @param http_status expected HTTP response code.
* @return the command.
*/
@@ -116,8 +98,6 @@ struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_instances (const char *label,
const char *merchant_url,
const char *instance_id,
- const char *payto_uri,
- const char *currency,
unsigned int http_status);
@@ -147,14 +127,10 @@ TALER_TESTING_cmd_merchant_post_instance_auth (const char *label,
* @param merchant_url base URL of the merchant serving the
* POST /instances request.
* @param instance_id the ID of the instance to query
- * @param accounts_length length of the @a accounts array
- * @param payto_uris URIs of the bank accounts of the merchant instance
* @param name name of the merchant instance
* @param address physical address of the merchant instance
* @param jurisdiction jurisdiction of the merchant instance
- * @param default_max_wire_fee default maximum wire fee merchant is willing to fully pay
- * @param default_wire_fee_amortization default amortization factor for excess wire fees
- * @param default_max_deposit_fee default maximum deposit fee merchant is willing to pay
+ * @param use_stefan enable STEFAN curve
* @param default_wire_transfer_delay default wire transfer delay merchant will ask for
* @param default_pay_delay default validity period for offers merchant makes
* @param auth_token authorization token needed to access the instance, can be NULL
@@ -166,14 +142,10 @@ TALER_TESTING_cmd_merchant_post_instances2 (
const char *label,
const char *merchant_url,
const char *instance_id,
- unsigned int accounts_length,
- const char *payto_uris[],
const char *name,
json_t *address,
json_t *jurisdiction,
- const char *default_max_wire_fee,
- uint32_t default_wire_fee_amortization,
- const char *default_max_deposit_fee,
+ bool use_stefan,
struct GNUNET_TIME_Relative default_wire_transfer_delay,
struct GNUNET_TIME_Relative default_pay_delay,
const char *auth_token,
@@ -181,20 +153,75 @@ TALER_TESTING_cmd_merchant_post_instances2 (
/**
+ * Define a "POST /account" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * POST /instances request.
+ * @param payto_uri URIs of the bank account to add to the merchant instance
+ * @param credit_facade_url credit facade URL to configure, can be NULL
+ * @param credit_facade_credentials credit facade credentials to use, can be NULL
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_account (
+ const char *label,
+ const char *merchant_url,
+ const char *payto_uri,
+ const char *credit_facade_url,
+ const json_t *credit_facade_credentials,
+ unsigned int http_status);
+
+
+/**
+ * Define a "PATCH /account" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * POST /instances request.
+ * @param create_account_ref reference to account setup command
+ * @param credit_facade_url credit facade URL to configure, can be NULL
+ * @param credit_facade_credentials credit facade credentials to use, can be NULL
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_patch_account (
+ const char *label,
+ const char *merchant_url,
+ const char *create_account_ref,
+ const char *credit_facade_url,
+ const json_t *credit_facade_credentials,
+ unsigned int http_status);
+
+
+/**
+ * Define a "DELETE /account" CMD.
+ *
+ * @param label command label.
+ * @param create_account_ref reference to account setup command
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_account (
+ const char *label,
+ const char *create_account_ref,
+ unsigned int http_status);
+
+
+/**
* Define a "PATCH /instances/$ID" CMD.
*
* @param label command label.
* @param merchant_url base URL of the merchant serving the
* PATCH /instance request.
* @param instance_id the ID of the instance to query
- * @param payto_uris_length length of the @a accounts array
- * @param payto_uris URIs of the bank accounts of the merchant instance
* @param name name of the merchant instance
* @param address physical address of the merchant instance
* @param jurisdiction jurisdiction of the merchant instance
- * @param default_max_wire_fee default maximum wire fee merchant is willing to fully pay
- * @param default_wire_fee_amortization default amortization factor for excess wire fees
- * @param default_max_deposit_fee default maximum deposit fee merchant is willing to pay
+ * @param use_stefan use STEFAN curve
* @param default_wire_transfer_delay default wire transfer delay merchant will ask for
* @param default_pay_delay default validity period for offers merchant makes
* @param http_status expected HTTP response code.
@@ -205,14 +232,10 @@ TALER_TESTING_cmd_merchant_patch_instance (
const char *label,
const char *merchant_url,
const char *instance_id,
- unsigned int payto_uris_length,
- const char *payto_uris[],
const char *name,
json_t *address,
json_t *jurisdiction,
- const char *default_max_wire_fee,
- uint32_t default_wire_fee_amortization,
- const char *default_max_deposit_fee,
+ bool use_stefan,
struct GNUNET_TIME_Relative default_wire_transfer_delay,
struct GNUNET_TIME_Relative default_pay_delay,
unsigned int http_status);
@@ -239,35 +262,6 @@ TALER_TESTING_cmd_merchant_get_instance (const char *label,
/**
- * Define a "GET instance" CMD that compares accounts returned.
- *
- * @param label command label.
- * @param merchant_url base URL of the merchant serving the
- * GET /instances/$ID request.
- * @param instance_id the ID of the instance to query
- * @param http_status expected HTTP response code.
- * @param instance_reference reference to a "POST /instances" or "PATCH /instances/$ID" CMD
- * that will provide what we expect the backend to return to us
- * @param active_accounts the accounts the merchant is actively using.
- * @param active_accounts_length length of @e active_accounts.
- * @param inactive_accounts the accounts the merchant is no longer using.
- * @param inactive_accounts_length length of @e inactive_accounts.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_instance2 (const char *label,
- const char *merchant_url,
- const char *instance_id,
- unsigned int http_status,
- const char *instance_reference,
- const char *active_accounts[],
- unsigned int active_accounts_length,
- const char *inactive_accounts[],
- unsigned int
- inactive_accounts_length);
-
-
-/**
* Define a "PURGE instance" CMD.
*
* @param label command label.
@@ -321,9 +315,10 @@ TALER_TESTING_cmd_merchant_delete_instance (const char *label,
* @param image base64-encoded product image
* @param taxes list of taxes paid by the merchant
* @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic books)
+ * @param minimum_age minimum age required for buying this product
* @param address where the product is in stock
* @param next_restock when the next restocking is expected to happen, 0 for unknown,
- * #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ * #GNUNET_TIME_UNIT_FOREVER_TS for 'never'.
* @param http_status expected HTTP response code.
* @return the command.
*/
@@ -339,6 +334,7 @@ TALER_TESTING_cmd_merchant_post_products2 (
const char *image,
json_t *taxes,
int64_t total_stock,
+ uint32_t minimum_age,
json_t *address,
struct GNUNET_TIME_Timestamp next_restock,
unsigned int http_status);
@@ -493,6 +489,7 @@ TALER_TESTING_cmd_merchant_delete_product (const char *label,
* Make the "proposal" command.
*
* @param label command label
+ * @param cfg configuration to use
* @param merchant_url base URL of the merchant serving
* the proposal request.
* @param http_status expected HTTP status.
@@ -505,6 +502,7 @@ TALER_TESTING_cmd_merchant_delete_product (const char *label,
struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_orders (
const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *merchant_url,
unsigned int http_status,
const char *order_id,
@@ -512,6 +510,7 @@ TALER_TESTING_cmd_merchant_post_orders (
struct GNUNET_TIME_Timestamp pay_deadline,
const char *amount);
+
/**
* Make the "proposal" command AVOIDING claiming the order.
*
@@ -535,10 +534,12 @@ TALER_TESTING_cmd_merchant_post_orders_no_claim (
struct GNUNET_TIME_Timestamp pay_deadline,
const char *amount);
+
/**
* Make the "proposal" command.
*
* @param label command label
+ * @param cfg configuration to use
* @param merchant_url base URL of the merchant serving
* the proposal request.
* @param http_status expected HTTP status.
@@ -560,6 +561,7 @@ TALER_TESTING_cmd_merchant_post_orders_no_claim (
struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_orders2 (
const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *merchant_url,
unsigned int http_status,
const char *order_id,
@@ -574,6 +576,35 @@ TALER_TESTING_cmd_merchant_post_orders2 (
/**
+ * Create an order with a specific fulfillment URL.
+ * Does not claim the order.
+ *
+ * @param label command label
+ * @param cfg configuration to use
+ * @param merchant_url base URL of the merchant serving
+ * the proposal request
+ * @param http_status expected HTTP status
+ * @param order_id ID of the order to create
+ * @param refund_deadline the deadline for refunds on this order
+ * @param pay_deadline the deadline for payment on this order
+ * @param fulfillment_url the fulfillment URL to use
+ * @param amount the amount this order is for
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_orders3 (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *order_id,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ const char *fulfillment_url,
+ const char *amount);
+
+
+/**
* Define a "GET /orders" CMD.
*
* @param label command label.
@@ -581,7 +612,7 @@ TALER_TESTING_cmd_merchant_post_orders2 (
* GET /orders request.
* @param http_status expected HTTP response code.
* @param ... NULL-terminated list of labels (const char *) of
- * reserve (commands) we expect to be returned in the list
+ * order (commands) we expect to be returned in the list
* (assuming @a http_code is #MHD_HTTP_OK)
* @return the command.
*/
@@ -616,9 +647,10 @@ TALER_TESTING_cmd_poll_orders_start (const char *label,
* @param poll_start_reference reference to the #TALER_TESTING_cmd_poll_orders_start command
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_poll_orders_conclude (const char *label,
- unsigned int http_status,
- const char *poll_start_reference);
+TALER_TESTING_cmd_poll_orders_conclude (
+ const char *label,
+ unsigned int http_status,
+ const char *poll_start_reference);
/**
@@ -634,13 +666,41 @@ TALER_TESTING_cmd_poll_orders_conclude (const char *label,
* @param http_status expected HTTP response code for the request.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_wallet_get_order (const char *label,
- const char *merchant_url,
- const char *order_reference,
- bool paid,
- bool refunded,
- bool refund_pending,
- unsigned int http_status);
+TALER_TESTING_cmd_wallet_get_order (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ bool paid,
+ bool refunded,
+ bool refund_pending,
+ unsigned int http_status);
+
+
+/**
+ * Define a GET /orders/$ORDER_ID CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ * serve the request.
+ * @param order_reference reference to a command that created an order.
+ * @param session_id session ID to check for
+ * @param paid whether the order has been paid for or not.
+ * @param refunded whether the order has been refunded.
+ * @param refund_pending whether the order has refunds that haven't been obtained.
+ * @param repurchase_order_ref command of a paid equivalent order the merchant should be referring us to, or NULL
+ * @param http_status expected HTTP response code for the request.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wallet_get_order2 (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ const char *session_id,
+ bool paid,
+ bool refunded,
+ bool refund_pending,
+ const char *repurchase_order_ref,
+ unsigned int http_status);
/**
@@ -737,13 +797,14 @@ TALER_TESTING_cmd_wallet_poll_order_conclude2 (
* this parameter is ignored.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_order (const char *label,
- const char *merchant_url,
- const char *order_reference,
- enum TALER_MERCHANT_OrderStatusCode osc,
- bool refunded,
- unsigned int http_status,
- ...);
+TALER_TESTING_cmd_merchant_get_order (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ enum TALER_MERCHANT_OrderStatusCode osc,
+ bool refunded,
+ unsigned int http_status,
+ ...);
/**
@@ -770,16 +831,61 @@ TALER_TESTING_cmd_merchant_get_order (const char *label,
* @param http_status expected HTTP response code for the request.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_order2 (const char *label,
- const char *merchant_url,
- const char *order_reference,
- enum TALER_MERCHANT_OrderStatusCode osc,
- bool wired,
- const char **transfers,
- bool refunded,
- const char **refunds,
- const char **forgets,
- unsigned int http_status);
+TALER_TESTING_cmd_merchant_get_order2 (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ enum TALER_MERCHANT_OrderStatusCode osc,
+ bool wired,
+ const char **transfers,
+ bool refunded,
+ const char **refunds,
+ const char **forgets,
+ unsigned int http_status);
+
+
+/**
+ * Define a GET /private/orders/$ORDER_ID CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ * serve the request.
+ * @param order_reference reference to a command that created an order.
+ * @param osc expected order status
+ * @param session_id session ID the payment must be bound to
+ * @param repurchase_order_ref command of a paid equivalent order the merchant should be referring us to, or NULL
+ * @param expected_http_status expected HTTP response code for the request.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_order3 (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ enum TALER_MERCHANT_OrderStatusCode osc,
+ const char *session_id,
+ const char *repurchase_order_ref,
+ unsigned int expected_http_status);
+
+
+/**
+ * Define a GET /private/orders/$ORDER_ID CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ * serve the request.
+ * @param order_reference reference to a command that created an order.
+ * @param osc expected order status
+ * @param expected_min_age expected minimum age for the contract
+ * @param expected_http_status expected HTTP response code for the request.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_order4 (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ enum TALER_MERCHANT_OrderStatusCode osc,
+ uint32_t expected_min_age,
+ unsigned int expected_http_status);
/**
@@ -836,14 +942,15 @@ TALER_TESTING_cmd_merchant_claim_order (const char *label,
* @return the command
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_pay_order (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- const char *proposal_reference,
- const char *coin_reference,
- const char *amount_with_fee,
- const char *amount_without_fee,
- const char *session_id);
+TALER_TESTING_cmd_merchant_pay_order (
+ const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *proposal_reference,
+ const char *coin_reference,
+ const char *amount_with_fee,
+ const char *amount_without_fee,
+ const char *session_id);
/**
@@ -857,11 +964,12 @@ TALER_TESTING_cmd_merchant_pay_order (const char *label,
* @return the command
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_post_orders_paid (const char *label,
- const char *merchant_url,
- const char *pay_reference,
- const char *session_id,
- unsigned int http_status);
+TALER_TESTING_cmd_merchant_post_orders_paid (
+ const char *label,
+ const char *merchant_url,
+ const char *pay_reference,
+ const char *session_id,
+ unsigned int http_status);
/**
@@ -874,10 +982,11 @@ TALER_TESTING_cmd_merchant_post_orders_paid (const char *label,
* @return the command
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_order_abort (const char *label,
- const char *merchant_url,
- const char *pay_reference,
- unsigned int http_status);
+TALER_TESTING_cmd_merchant_order_abort (
+ const char *label,
+ const char *merchant_url,
+ const char *pay_reference,
+ unsigned int http_status);
/**
@@ -916,12 +1025,13 @@ TALER_TESTING_cmd_merchant_forget_order (
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_order_refund (const char *label,
- const char *merchant_url,
- const char *reason,
- const char *order_id,
- const char *refund_amount,
- unsigned int http_code);
+TALER_TESTING_cmd_merchant_order_refund (
+ const char *label,
+ const char *merchant_url,
+ const char *reason,
+ const char *order_id,
+ const char *refund_amount,
+ unsigned int http_code);
/**
@@ -939,11 +1049,12 @@ TALER_TESTING_cmd_merchant_order_refund (const char *label,
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_wallet_order_refund (const char *label,
- const char *merchant_url,
- const char *order_ref,
- unsigned int http_code,
- ...);
+TALER_TESTING_cmd_wallet_order_refund (
+ const char *label,
+ const char *merchant_url,
+ const char *order_ref,
+ unsigned int http_code,
+ ...);
/**
@@ -957,10 +1068,11 @@ TALER_TESTING_cmd_wallet_order_refund (const char *label,
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_delete_order (const char *label,
- const char *merchant_url,
- const char *order_id,
- unsigned int http_status);
+TALER_TESTING_cmd_merchant_delete_order (
+ const char *label,
+ const char *merchant_url,
+ const char *order_id,
+ unsigned int http_status);
/* ******************* /transfers *************** */
@@ -1071,421 +1183,139 @@ TALER_TESTING_cmd_merchant_delete_transfer (const char *label,
unsigned int http_status);
-/* ******************* /reserves *************** */
-
-
/**
- * Define a "POST /reserves" CMD
+ * Run a command to fetch the KYC status of a merchant.
*
- * @param label command label.
- * @param merchant_url url to the murchant.
- * @param initial_balance initial amount in the reserve.
- * @param exchange_url url to the exchange
- * @param wire_method wire transfer method to use for this reserve
- * @param http_status expected HTTP response code.
- * @return the command.
+ * @param label the command label
+ * @param merchant_url base URL of the merchant
+ * @param instance_id instance to use, NULL if instance is part of @a merchant_url
+ * @param h_wire_ref label of command with a merchant wire hash trait
+ * of the bank account to check KYC for; NULL to check all accounts
+ * @param exchange_url base URL of the exchange to check KYC status for
+ * @param expected_http_status expected HTTP status
+ * @param expected_aml_state expected AML state (only effective if @e expected_http_status is #MHD_HTTP_OK)
+ * @return the command
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_post_reserves (const char *label,
- const char *merchant_url,
- const char *initial_balance,
- const char *exchange_url,
- const char *wire_method,
- unsigned int http_status);
+TALER_TESTING_cmd_merchant_kyc_get (
+ const char *label,
+ const char *merchant_url,
+ const char *instance_id,
+ const char *h_wire_ref,
+ const char *exchange_url,
+ unsigned int expected_http_status,
+ enum TALER_AmlDecisionState expected_aml_state);
-/**
- * This commands does not query the backend at all,
- * but just makes up a fake reserve.
- *
- * @param label command label.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_post_reserves_fake (const char *label);
+/* ****** OTP devices ******* */
/**
- * Define a "GET reserve" CMD.
+ * Define a "POST /otp-devices" CMD.
*
* @param label command label.
- * @param merchant_url base URL of the merchant serving the request.
+ * @param merchant_url base URL of the merchant serving the
+ * POST /otps request.
+ * @param otp_id the ID of the otp device to modify
+ * @param otp_description description of the otp device
+ * @param otp_key base32-encoded key to verify the payment
+ * @param otp_alg is an option that show the amount of the order. it is linked with the @a otp_key
+ * @param otp_ctr counter to use (if in counter mode)
* @param http_status expected HTTP response code.
- * @param reserve_reference reference to a "POST /reserves" that provides the
- * information we are expecting.
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_reserve (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- const char *reserve_reference);
+TALER_TESTING_cmd_merchant_post_otp_devices (
+ const char *label,
+ const char *merchant_url,
+ const char *otp_id,
+ const char *otp_description,
+ const char *otp_key,
+ enum TALER_MerchantConfirmationAlgorithm otp_alg,
+ uint64_t otp_ctr,
+ unsigned int http_status);
/**
- * Define a "GET reserve" CMD.
+ * Define a "PATCH /otp-devices/$ID" CMD.
*
* @param label command label.
- * @param merchant_url base URL of the merchant serving the request.
+ * @param merchant_url base URL of the merchant serving the
+ * PATCH /otp-devices request.
+ * @param otp_id the ID of the otp device to modify
+ * @param otp_description description of the otp device
+ * @param otp_key base32-encoded key to verify the payment
+ * @param otp_alg is an option that show the amount of the order. it is linked with the @a otp_key
+ * @param otp_ctr counter to use (if in counter mode)
* @param http_status expected HTTP response code.
- * @param reserve_reference reference to a "POST /reserves" that provides the
- * information we are expecting.
- * @param ... NULL-terminated list of labels (const char *) of
- * tip (commands) we expect to be returned in the list
- * (assuming @a http_code is #MHD_HTTP_OK)
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_reserve_with_tips (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- const char *reserve_reference,
- ...);
+TALER_TESTING_cmd_merchant_patch_otp_device (
+ const char *label,
+ const char *merchant_url,
+ const char *otp_id,
+ const char *otp_description,
+ const char *otp_key,
+ enum TALER_MerchantConfirmationAlgorithm otp_alg,
+ uint64_t otp_ctr,
+ unsigned int http_status);
/**
- * Define a "GET /reserves" CMD
+ * Define a "GET /otp-devices" CMD.
*
* @param label command label.
- * @param merchant_url url to the merchant.
+ * @param merchant_url base URL of the merchant serving the
+ * GET /otp-devices request.
* @param http_status expected HTTP response code.
* @param ... NULL-terminated list of labels (const char *) of
- * reserve (commands) we expect to be returned in the list
+ * otp (commands) we expect to be returned in the list
* (assuming @a http_code is #MHD_HTTP_OK)
+ * @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_reserves (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- ...);
+TALER_TESTING_cmd_merchant_get_otp_devices (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ ...);
/**
- * Define a "DELETE reserve" CMD.
+ * Define a "GET otp device" CMD.
*
* @param label command label.
* @param merchant_url base URL of the merchant serving the
- * DELETE /reserves/$RESERVE_PUB request.
- * @param reserve_reference command label of a command providing a reserve
+ * GET /otp-devices/$ID request.
+ * @param otp_id the ID of the otp to query
* @param http_status expected HTTP response code.
+ * @param otp_reference reference to a "POST /otp-devices" or "PATCH /otp-devices/$ID" CMD
+ * that will provide what we expect the backend to return to us
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_delete_reserve (const char *label,
+TALER_TESTING_cmd_merchant_get_otp_device (const char *label,
const char *merchant_url,
- const char *reserve_reference,
- unsigned int http_status);
+ const char *otp_id,
+ unsigned int http_status,
+ const char *otp_reference);
/**
- * Define a "PURGE reserve" CMD.
+ * Define a "DELETE otp device" CMD.
*
* @param label command label.
* @param merchant_url base URL of the merchant serving the
- * DELETE /reserves/$RESERVE_PUB request.
- * @param reserve_reference command label of a command providing a reserve
+ * DELETE /otp-devices/$ID request.
+ * @param otp_id the ID of the otp to query
* @param http_status expected HTTP response code.
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_purge_reserve (const char *label,
- const char *merchant_url,
- const char *reserve_reference,
- unsigned int http_status);
-
-
-/**
- * Define a get tips CMD.
- *
- * @param label the command label
- * @param merchant_url base URL of the merchant which will
- * server the /tip-query request.
- * @param http_status expected HTTP response code for the
- * /tip-query request.
- * @param ... NULL-terminated list of labels (const char *) of
- * tip (commands) we expect to be returned in the list
- * (assuming @a http_code is #MHD_HTTP_OK)
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_get_tips (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- ...);
-
-
-/**
- * Define a get tips CMD.
- *
- * @param label the command label
- * @param merchant_url base URL of the merchant which will
- * server the /tip-query request.
- * @param http_status expected HTTP response code for the
- * /tip-query request.
- * @param offset row to start querying the database from.
- * @param limit how many rows to return (with direction).
- * @param ... NULL-terminated list of labels (const char *) of
- * tip (commands) we expect to be returned in the list
- * (assuming @a http_code is #MHD_HTTP_OK)
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_get_tips2 (const char *label,
- const char *merchant_url,
- uint64_t offset,
- int64_t limit,
- unsigned int http_status,
- ...);
-
-
-/**
- * Define a GET /private/tips/$TIP_ID CMD.
- *
- * @param label the command label
- * @param merchant_url base URL of the merchant which will
- * serve the request.
- * @param tip_reference reference to a command that created a tip.
- * @param http_status expected HTTP response code for the request.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_tip (const char *label,
- const char *merchant_url,
- const char *tip_reference,
- unsigned int http_status);
-
-
-/**
- * Define a GET /private/tips/$TIP_ID CMD.
- *
- * @param label the command label
- * @param merchant_url base URL of the merchant which will
- * serve the request.
- * @param tip_reference reference to a command that created a tip.
- * @param http_status expected HTTP response code for the request.
- * @param ... NULL-terminated list of labels (const char *) of
- * pickup (commands) we expect to be returned in the list
- * (assuming @a http_code is #MHD_HTTP_OK)
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_tip_with_pickups (const char *label,
- const char *merchant_url,
- const char *tip_reference,
- unsigned int http_status,
- ...);
-
-/**
- * Define a GET /tips/$TIP_ID CMD.
- *
- * @param label the command label
- * @param merchant_url base URL of the merchant which will
- * serve the request.
- * @param tip_reference reference to a command that created a tip.
- * @param http_status expected HTTP response code for the request.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_wallet_get_tip (const char *label,
- const char *merchant_url,
- const char *tip_reference,
- unsigned int http_status);
-
-
-/**
- * Define a GET /tips/$TIP_ID CMD.
- *
- * @param label the command label
- * @param merchant_url base URL of the merchant which will
- * serve the request.
- * @param tip_reference reference to a command that created a tip.
- * @param amount_remaining the balance remaining after pickups.
- * @param http_status expected HTTP response code for the request.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_wallet_get_tip2 (const char *label,
- const char *merchant_url,
- const char *tip_reference,
- const char *amount_remaining,
- unsigned int http_status);
-
-
-/**
- * Create a /tip-authorize CMD.
- *
- * @param label this command label
- * @param merchant_url the base URL of the merchant that will
- * serve the /tip-authorize request.
- * @param exchange_url the base URL of the exchange that owns
- * the reserve from which the tip is going to be gotten.
- * @param http_status the HTTP response code which is expected
- * for this operation.
- * @param justification human-readable justification for this
- * tip authorization.
- * @param amount the amount to authorize for tipping.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_authorize (const char *label,
- const char *merchant_url,
- const char *exchange_url,
- unsigned int http_status,
- const char *justification,
- const char *amount);
-
-
-/**
- * Create a /tip-authorize CMD.
- *
- * @param label this command label
- * @param merchant_url the base URL of the merchant that will
- * serve the /tip-authorize request.
- * @param exchange_url the base URL of the exchange that owns
- * the reserve from which the tip is going to be gotten.
- * @param reserve_reference reference to a command that created
- * a reserve.
- * @param http_status the HTTP response code which is expected
- * for this operation.
- * @param justification human-readable justification for this
- * tip authorization.
- * @param amount the amount to authorize for tipping.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_authorize_from_reserve (const char *label,
+TALER_TESTING_cmd_merchant_delete_otp_device (const char *label,
const char *merchant_url,
- const char *exchange_url,
- const char *reserve_reference,
- unsigned int http_status,
- const char *justification,
- const char *amount);
-
-
-/**
- * Create a /tip-authorize CMD, specifying the Taler error code
- * that is expected to be returned by the backend.
- *
- * @param label this command label
- * @param merchant_url the base URL of the merchant that will
- * serve the /tip-authorize request.
- * @param exchange_url the base URL of the exchange that owns
- * the reserve from which the tip is going to be gotten.
- * @param http_status the HTTP response code which is expected
- * for this operation.
- * @param justification human-readable justification for this
- * tip authorization.
- * @param amount the amount to authorize for tipping.
- * @param ec expected Taler-defined error code.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_authorize_with_ec (const char *label,
- const char *merchant_url,
- const char *exchange_url,
- unsigned int http_status,
- const char *justification,
- const char *amount,
- enum TALER_ErrorCode ec);
-
-
-/**
- * Create a /tip-authorize CMD, specifying the Taler error code
- * that is expected to be returned by the backend.
- *
- * @param label this command label
- * @param merchant_url the base URL of the merchant that will
- * serve the /tip-authorize request.
- * @param exchange_url the base URL of the exchange that owns
- * the reserve from which the tip is going to be gotten.
- * @param reserve_reference reference to a command that created
- * a reserve.
- * @param http_status the HTTP response code which is expected
- * for this operation.
- * @param justification human-readable justification for this
- * tip authorization.
- * @param amount the amount to authorize for tipping.
- * @param ec expected Taler-defined error code.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_authorize_from_reserve_with_ec (
- const char *label,
- const char *merchant_url,
- const char *exchange_url,
- const char *reserve_reference,
- unsigned int http_status,
- const char *justification,
- const char *amount,
- enum TALER_ErrorCode ec);
-
-
-/**
- * This commands does not query the backend at all,
- * but just makes up a fake authorization id that will
- * be subsequently used by the "pick up" CMD in order
- * to test against such a case.
- *
- * @param label command label.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_authorize_fake (const char *label);
-
-
-/**
- * Define a /tip-pickup CMD, equipped with the expected error
- * code.
- *
- * @param label the command label
- * @param merchant_url base URL of the backend which will serve
- * the /tip-pickup request.
- * @param http_status expected HTTP response code.
- * @param authorize_reference reference to a /tip-autorize CMD
- * that offers a tip id to pick up.
- * @param amounts array of string-defined amounts that specifies
- * which denominations will be accepted for tipping.
- * @param ec expected Taler error code.
- * @return the command
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_pickup_with_ec (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- const char *authorize_reference,
- const char **amounts,
- enum TALER_ErrorCode ec);
-
-/**
- * Define a /tip-pickup CMD.
- *
- * @param label the command label
- * @param merchant_url base URL of the backend which will serve
- * the /tip-pickup request.
- * @param http_status expected HTTP response code.
- * @param authorize_reference reference to a /tip-autorize CMD
- * that offers a tip id to pick up.
- * @param amounts array of string-defined amounts that specifies
- * which denominations will be accepted for tipping.
- * @return the command
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_pickup (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- const char *authorize_reference,
- const char **amounts);
-
-
-/**
- * Run a command to fetch the KYC status of a merchant.
- *
- * @param label the command label
- * @param merchant_url base URL of the merchant
- * @param instance_id instance to use, NULL if instance is part of @a merchant_url
- * @param h_wire_ref label of command with a merchant wire hash trait
- * of the bank account to check KYC for; NULL to check all accounts
- * @param exchange_url base URL of the exchange to check KYC status for
- * @param expected_http_status expected HTTP status
- * @return the command
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_kyc_get (const char *label,
- const char *merchant_url,
- const char *instance_id,
- const char *h_wire_ref,
- const char *exchange_url,
- unsigned int expected_http_status);
+ const char *otp_id,
+ unsigned int http_status);
/* ****** Templates ******* */
@@ -1498,7 +1328,7 @@ TALER_TESTING_cmd_merchant_kyc_get (const char *label,
* POST /templates request.
* @param template_id the ID of the template to query
* @param template_description description of the template
- * @param image base64-encoded template image
+ * @param otp_id OTP device ID, NULL for none
* @param template_contract where the contract of the company is
* @param http_status expected HTTP response code.
* @return the command.
@@ -1509,7 +1339,7 @@ TALER_TESTING_cmd_merchant_post_templates2 (
const char *merchant_url,
const char *template_id,
const char *template_description,
- const char *image,
+ const char *otp_id,
json_t *template_contract,
unsigned int http_status);
@@ -1541,7 +1371,7 @@ TALER_TESTING_cmd_merchant_post_templates (const char *label,
* PATCH /template request.
* @param template_id the ID of the template to query
* @param template_description description of the template
- * @param image base64-encoded template image
+ * @param otp_id OTP device to use
* @param template_contract contract of the company
* @param http_status expected HTTP response code.
* @return the command.
@@ -1552,7 +1382,7 @@ TALER_TESTING_cmd_merchant_patch_template (
const char *merchant_url,
const char *template_id,
const char *template_description,
- const char *image,
+ const char *otp_id,
json_t *template_contract,
unsigned int http_status);
@@ -1618,20 +1448,29 @@ TALER_TESTING_cmd_merchant_delete_template (const char *label,
*
* @param label command label.
* @param template_ref label of command that created the template to use
+ * @param otp_ref label of command that created OTP device we use (or NULL for no OTP)
* @param merchant_url base URL of the merchant serving the
* POST /using-templates request.
+ * @param using_template_id template ID to use
* @param summary given by the customer to know what they did pay
* @param amount given by the customer to pay
+ * @param refund_deadline refund deadline to use for the contract
+ * @param pay_deadline pay deadline to use for the contract
* @param http_status expected HTTP response code.
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_post_using_templates (const char *label,
- const char *template_ref,
- const char *merchant_url,
- const char *summary,
- const char *amount,
- unsigned int http_status);
+TALER_TESTING_cmd_merchant_post_using_templates (
+ const char *label,
+ const char *template_ref,
+ const char *otp_ref,
+ const char *merchant_url,
+ const char *using_template_id,
+ const char *summary,
+ const char *amount,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ unsigned int http_status);
/* ****** Webhooks ******* */
@@ -1767,6 +1606,84 @@ TALER_TESTING_cmd_merchant_delete_webhook (const char *label,
const char *webhook_id,
unsigned int http_status);
+/**
+ * Command to run the 'taler-merchant-webhook' program.
+ *
+ * @param label command label.
+ * @param config_filename configuration file used by the webhook.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_webhook (const char *label,
+ const char *config_filename);
+
+
+/**
+ * Command to run the 'taler-merchant-depositcheck' program.
+ *
+ * @param label command label.
+ * @param config_filename configuration file used by the deposit check helper.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_depositcheck (const char *label,
+ const char *config_filename);
+
+
+/**
+ * Command to run the 'taler-merchant-exchange' program.
+ *
+ * @param label command label.
+ * @param config_filename configuration file used by the webhook.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_run_tme (const char *label,
+ const char *config_filename);
+
+
+/**
+ * This function is used to start the web server.
+ *
+ * @param label command label
+ * @param port is the port of the web server
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_testserver (const char *label,
+ uint16_t port);
+
+
+/**
+ * This function is used to check the web server got the
+ * expected request from the web hook.
+ *
+ * @param label command label
+ * @param ref_operation reference to command to the previous set server status operation.
+ * @param index index to know which web server we check.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_checkserver (const char *label,
+ const char *ref_operation,
+ unsigned int index);
+
+
+/**
+ * This function is used to check that the web server
+ * got the expected request from the web hook.
+ *
+ * @param label command label
+ * @param ref_operation reference to command to the previous set server status operation.
+ * @param index index to know which web server we check.
+ * @param expected_url url of the webhook
+ * @param expected_method method of the webhook
+ * @param expected_header header of the webhook
+ * @param expected_body body of the webhook
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_checkserver2 (const char *label,
+ const char *ref_operation,
+ unsigned int index,
+ const char *expected_url,
+ const char *expected_method,
+ const char *expected_header,
+ const char *expected_body);
/* ****** Specific traits supported by this component ******* */
@@ -1779,28 +1696,25 @@ TALER_TESTING_cmd_merchant_delete_webhook (const char *label,
// FIXME: rename: refund_entry->refund_detail
#define TALER_MERCHANT_TESTING_SIMPLE_TRAITS(op) \
op (claim_nonce, const struct GNUNET_CRYPTO_EddsaPublicKey) \
- op (tip_id, const struct TALER_TipIdentifierP) \
op (pickup_id, const struct TALER_PickupIdentifierP) \
- op (instance_name, const char *) \
- op (instance_id, const char *) \
+ op (instance_name, const char) \
+ op (instance_id, const char) \
op (address, const json_t) \
- op (product_description, const char *) \
- op (product_image, const char *) \
+ op (product_description, const char) \
+ op (product_image, const char) \
op (product_stock, const int64_t) \
- op (product_unit, const char *) \
- op (product_id, const char *) \
- op (reason, const char *) \
- op (lock_uuid, const char *) \
- op (auth_token, const char *) \
+ op (product_unit, const char) \
+ op (product_id, const char) \
+ op (reason, const char) \
+ op (lock_uuid, const char) \
+ op (auth_token, const char) \
op (paths_length, const uint32_t) \
op (payto_length, const uint32_t) \
op (num_planchets, const uint32_t) \
op (i18n_description, const json_t) \
op (taxes, const json_t) \
op (fee, const struct TALER_Amount) \
- op (max_wire_fee, const struct TALER_Amount) \
- op (max_deposit_fee, const struct TALER_Amount) \
- op (wire_fee_amortization, const uint32_t) \
+ op (use_stefan, const bool) \
op (jurisdiction, const json_t) \
op (wire_delay, const struct GNUNET_TIME_Relative) \
op (pay_delay, const struct GNUNET_TIME_Relative) \
@@ -1808,18 +1722,22 @@ TALER_TESTING_cmd_merchant_delete_webhook (const char *label,
op (order_terms, const json_t) \
op (h_contract_terms, const struct TALER_PrivateContractHashP) \
op (h_wire, const struct TALER_MerchantWireHashP) \
- op (proposal_reference, const char *) \
- op (template_description, const char *) \
- op (template_image, const char *) \
- op (template_id, const char *) \
+ op (proposal_reference, const char) \
+ op (template_description, const char) \
+ op (otp_device_description, const char) \
+ op (otp_id, const char) \
+ op (otp_key, const char) \
+ op (otp_alg, const enum TALER_MerchantConfirmationAlgorithm) \
+ op (template_id, const char) \
op (template_contract, const json_t) \
- op (event_type, const char *) \
- op (url, const char *) \
- op (webhook_id, const char *) \
- op (http_method, const char *) \
- op (header_template, const char *) \
- op (body_template, const char *) \
- op (summary, const char *)
+ op (event_type, const char) \
+ op (webhook_id, const char) \
+ op (merchant_base_url, const char) \
+ op (url, const char) \
+ op (http_method, const char) \
+ op (header_template, const char) \
+ op (body_template, const char) \
+ op (summary, const char)
/**
@@ -1828,10 +1746,16 @@ TALER_TESTING_cmd_merchant_delete_webhook (const char *label,
* @param op macro to call
*/
#define TALER_MERCHANT_TESTING_INDEXED_TRAITS(op) \
- op (coin_reference, const char *) \
- op (paths, const char *) \
- op (payto_uris, const char *) \
- op (amounts, const struct TALER_Amount) \
+ op (coin_reference, const char) \
+ op (paths, const char) \
+ op (payto_uris, const char) \
+ op (h_wires, const struct TALER_MerchantWireHashP) \
+ op (amounts, const struct TALER_Amount) \
+ op (urls, const char) \
+ op (http_methods, const char) \
+ op (http_header, const char) \
+ op (http_body, const void) \
+ op (http_body_size, const size_t) \
op (planchet_secrets, const struct TALER_PlanchetMasterSecretP)
diff --git a/src/include/taler_merchantdb_lib.h b/src/include/taler_merchantdb_lib.h
index 1120b864..3a641a54 100644
--- a/src/include/taler_merchantdb_lib.h
+++ b/src/include/taler_merchantdb_lib.h
@@ -87,6 +87,16 @@ void
TALER_MERCHANTDB_pending_webhook_details_free (
struct TALER_MERCHANTDB_PendingWebhookDetails *pwb);
+
+/**
+ * Free members of @a tf, but not @a tf itself.
+ *
+ * @param[in] tf token family details to clean up
+ */
+void
+TALER_MERCHANTDB_token_family_details_free (
+ struct TALER_MERCHANTDB_TokenFamilyDetails *tf);
+
#endif /* MERCHANT_DB_H */
/* end of taler_merchantdb_lib.h */
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
index d9fe0d24..e7eb2d0f 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -96,7 +96,20 @@ struct TALER_MERCHANTDB_AccountDetails
/**
* Actual account address as a payto://-URI.
*/
- const char *payto_uri;
+ char *payto_uri;
+
+ /**
+ * Where can the taler-merchant-wirewatch helper
+ * download information about incoming transfers?
+ * NULL if not available.
+ */
+ char *credit_facade_url;
+
+ /**
+ * JSON with credentials to use to access the
+ * @e credit_facade_url.
+ */
+ json_t *credit_facade_credentials;
/**
* Is the account set for active use in new contracts?
@@ -105,6 +118,19 @@ struct TALER_MERCHANTDB_AccountDetails
};
+
+/**
+ * Binary login token. Just a vanilla token made out
+ * of random bits.
+ */
+struct TALER_MERCHANTDB_LoginTokenP
+{
+ /**
+ * 32 bytes of entropy.
+ */
+ uint64_t data[32 / 8];
+};
+
/**
* Authentication settings for an instance.
*/
@@ -151,6 +177,7 @@ struct TALER_MERCHANTDB_InstanceSettings
* merchant's logo data uri
*/
char *logo;
+
/**
* Address of the business
*/
@@ -162,22 +189,10 @@ struct TALER_MERCHANTDB_InstanceSettings
json_t *jurisdiction;
/**
- * Default max deposit fee that the merchant is willing to
- * pay; if deposit costs more, then the customer will cover
- * the difference.
+ * Use STEFAN curves to determine acceptable
+ * fees by default (otherwise: accept no fees by default).
*/
- struct TALER_Amount default_max_deposit_fee;
-
- /**
- * Default maximum wire fee to assume, unless stated differently in the
- * proposal already.
- */
- struct TALER_Amount default_max_wire_fee;
-
- /**
- * Default factor for wire fee amortization.
- */
- uint32_t default_wire_fee_amortization;
+ bool use_stefan;
/**
* If the frontend does NOT specify an execution date, how long should
@@ -193,6 +208,10 @@ struct TALER_MERCHANTDB_InstanceSettings
*/
struct GNUNET_TIME_Relative default_pay_delay;
+ /**
+ * Type of user this merchant represents.
+ */
+ enum TALER_KYCLOGIC_KycUserType ut;
};
@@ -204,8 +223,6 @@ struct TALER_MERCHANTDB_InstanceSettings
* @param merchant_priv private key of the instance, NULL if not available
* @param is general instance settings
* @param ias instance authentication settings
- * @param accounts_length length of the @a accounts array
- * @param accounts list of accounts of the merchant
*/
typedef void
(*TALER_MERCHANTDB_InstanceCallback)(
@@ -213,18 +230,31 @@ typedef void
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_MerchantPrivateKeyP *merchant_priv,
const struct TALER_MERCHANTDB_InstanceSettings *is,
- const struct TALER_MERCHANTDB_InstanceAuthSettings *ias,
- unsigned int accounts_length,
- const struct TALER_MERCHANTDB_AccountDetails accounts[]);
+ const struct TALER_MERCHANTDB_InstanceAuthSettings *ias);
+
+
+/**
+ * Callback invoked with information about a bank account.
+ *
+ * @param cls closure
+ * @param ad details about the account
+ */
+typedef void
+(*TALER_MERCHANTDB_AccountCallback)(
+ void *cls,
+ const struct TALER_MERCHANTDB_AccountDetails *ad);
+
/**
* Typically called by `lookup_products`.
*
* @param cls a `json_t *` JSON array to build
+ * @param product_serial row ID of the product
* @param product_id ID of the product
*/
typedef void
(*TALER_MERCHANTDB_ProductsCallback)(void *cls,
+ uint64_t product_serial,
const char *product_id);
@@ -305,7 +335,7 @@ struct TALER_MERCHANTDB_ProductDetails
/**
* Typically called by `lookup_templates`.
*
- * @param cls a `json_t *` JSON array to build
+ * @param cls closure
* @param template_id ID of the template
* @param template_description description of the template
*/
@@ -316,6 +346,19 @@ typedef void
/**
+ * Typically called by `lookup_otp_devices`.
+ *
+ * @param cls closure
+ * @param otp_id ID of the OTP device
+ * @param otp_description description of the OTP device
+ */
+typedef void
+(*TALER_MERCHANTDB_OtpDeviceCallback)(void *cls,
+ const char *otp_id,
+ const char *otp_description);
+
+
+/**
* Details about a template.
*/
struct TALER_MERCHANTDB_TemplateDetails
@@ -326,14 +369,56 @@ struct TALER_MERCHANTDB_TemplateDetails
char *template_description;
/**
- * Base64-encoded image, or NULL.
+ * In this template contract, we can have additional information.
*/
- char *image;
+ json_t *template_contract;
/**
- * In this template contract, we can have additional information.
+ * ID of the OTP device linked to the template, or NULL.
*/
- json_t *template_contract;
+ char *otp_id;
+
+ /**
+ * Currency the payment must be in, NULL to allow any
+ * supported currency.
+ */
+ char *required_currency;
+
+ /**
+ * Editable default values for fields not specified
+ * in the @e template_contract. NULL if the user
+ * cannot edit anything.
+ */
+ json_t *editable_defaults;
+
+};
+
+
+/**
+ * Details about an OTP device.
+ */
+struct TALER_MERCHANTDB_OtpDeviceDetails
+{
+
+ /**
+ * Description of the device.
+ */
+ char *otp_description;
+
+ /**
+ * Current usage counter value.
+ */
+ uint64_t otp_ctr;
+
+ /**
+ * Base64-encoded key.
+ */
+ char *otp_key;
+
+ /**
+ * Algorithm used to compute purchase confirmations.
+ */
+ enum TALER_MerchantConfirmationAlgorithm otp_algorithm;
};
@@ -409,7 +494,7 @@ typedef void
* Typically called by `lookup_pending_webhooks`.
*
* @param cls a `json_t *` JSON array to build
- * @param webhook_serial reference to the configured webhook template.
+ * @param webhook_pending_serial reference to the configured webhook template.
* @param next_attempt is the time we should make the next request to the webhook.
* @param retries how often have we tried this request to the webhook.
* @param url to make request to
@@ -419,7 +504,7 @@ typedef void
*/
typedef void
(*TALER_MERCHANTDB_PendingWebhooksCallback)(void *cls,
- uint64_t webhook_serial,
+ uint64_t webhook_pending_serial,
struct GNUNET_TIME_Absolute
next_attempt,
uint32_t retries,
@@ -434,6 +519,7 @@ typedef void
*/
struct TALER_MERCHANTDB_PendingWebhookDetails
{
+
/**
* Identifies when we should make the next request to the webhook. 0 for unknown,
* #GNUNET_TIME_UNIT_FOREVER_ABS for never.
@@ -455,13 +541,11 @@ struct TALER_MERCHANTDB_PendingWebhookDetails
*/
char *http_method;
-
/**
* Header of the webhook.
*/
char *header;
-
/**
* Body of the webhook.
*/
@@ -476,6 +560,16 @@ struct TALER_MERCHANTDB_PendingWebhookDetails
struct TALER_MERCHANTDB_OrderFilter
{
/**
+ * Filter orders by this fulfillment URL.
+ */
+ const char *fulfillment_url;
+
+ /**
+ * Filter orders by this session ID.
+ */
+ const char *session_id;
+
+ /**
* Filter by payment status.
*/
enum TALER_EXCHANGE_YesNoAll paid;
@@ -538,7 +632,6 @@ typedef void
* @param amount_with_fee amount the exchange will deposit for this coin
* @param deposit_fee fee the exchange will charge for this coin
* @param refund_fee fee the exchange will charge for refunding this coin
- * @param wire_fee wire fee the exchange charges
*/
typedef void
(*TALER_MERCHANTDB_DepositsCallback)(
@@ -547,8 +640,7 @@ typedef void
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
- const struct TALER_Amount *refund_fee,
- const struct TALER_Amount *wire_fee);
+ const struct TALER_Amount *refund_fee);
/**
@@ -625,6 +717,7 @@ typedef void
* @param exchange_url base URL of the exchange for which this is a status
* @param last_check when did we last get an update on our KYC status from the exchange
* @param kyc_ok true if we satisfied the KYC requirements
+ * @param aml_decision current AML decision state at the exchange
*/
typedef void
(*TALER_MERCHANTDB_KycCallback)(
@@ -634,7 +727,8 @@ typedef void
const char *payto_uri,
const char *exchange_url,
struct GNUNET_TIME_Timestamp last_check,
- bool kyc_ok);
+ bool kyc_ok,
+ enum TALER_AmlDecisionState aml_decision);
/**
@@ -644,6 +738,11 @@ enum TALER_MERCHANTDB_RefundStatus
{
/**
+ * Refund amount currency does not match original payment.
+ */
+ TALER_MERCHANTDB_RS_BAD_CURRENCY = -4,
+
+ /**
* Refund amount exceeds original payment.
*/
TALER_MERCHANTDB_RS_TOO_HIGH = -3,
@@ -688,6 +787,60 @@ typedef void
/**
+ * Function called with information about wire transfers
+ * that taler-merchant-exchange still needs to process.
+ *
+ * @param cls closure
+ * @param rowid row of the transfer in the merchant database
+ * @param instance_id instance that received the transfer
+ * @param exchange_url base URL of the exchange that initiated the transfer
+ * @param payto_uri account of the merchant that received the transfer
+ * @param wtid wire transfer subject identifying the aggregation
+ * @param total total amount that was wired
+ * @param next_attempt when should we next try to interact with the exchange
+ */
+typedef void
+(*TALER_MERCHANTDB_OpenTransferCallback)(
+ void *cls,
+ uint64_t rowid,
+ const char *instance_id,
+ const char *exchange_url,
+ const char *payto_uri,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *total,
+ struct GNUNET_TIME_Absolute next_attempt);
+
+
+/**
+ * Callback for results from `lookup_pending_deposits`.
+ *
+ * @param cls NULL
+ * @param deposit_serial identifies the deposit operation
+ * @param wire_deadline when is the wire due
+ * @param retry_backoff current value of the retry backoff
+ * @param h_contract_terms hash of the contract terms
+ * @param merchant_priv private key of the merchant
+ * @param instance_id name of the instance
+ * @param h_wire hash of the merchant's wire account into * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin which the deposit was made
+ * @param coin_pub public key of the deposited coin
+ */
+typedef void
+(*TALER_MERCHANTDB_PendingDepositsCallback) (
+ void *cls,
+ uint64_t deposit_serial,
+ struct GNUNET_TIME_Absolute wire_deadline,
+ struct GNUNET_TIME_Relative retry_backoff,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ const char *instance_id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub);
+
+
+/**
* Function called with detailed information about a wire transfer and
* the underlying deposits that are being aggregated.
*
@@ -703,6 +856,27 @@ typedef void
/**
+ * Function called with information about a accounts
+ * the wirewatcher should monitor.
+ *
+ * @param cls closure
+ * @param instance instance that owns the account
+ * @param payto_uri account URI
+ * @param credit_facade_url URL for the credit facade
+ * @param credit_facade_credentials account access credentials
+ * @param last_serial last transaction serial (inclusive) we have seen from this account
+ */
+typedef void
+(*TALER_MERCHANTDB_WirewatchWorkCallback)(
+ void *cls,
+ const char *instance,
+ const char *payto_uri,
+ const char *credit_facade_url,
+ const json_t *credit_facade_credentials,
+ uint64_t last_serial);
+
+
+/**
* Function called with information about a wire transfer.
*
* @param cls closure with a `json_t *` array to build up the response
@@ -732,71 +906,24 @@ typedef void
/**
- * Callback with reserve details.
+ * If the given account is feasible, add it to the array
+ * of accounts we return.
*
* @param cls closure
- * @param reserve_pub public key of the reserve
- * @param creation_time time when the reserve was setup
- * @param expiration_time time when the reserve will be closed by the exchange
- * @param merchant_initial_amount initial amount that the merchant claims to have filled the
- * reserve with
- * @param exchange_initial_amount initial amount that the exchange claims to have received
- * @param pickup_amount total of tips that were picked up from this reserve
- * @param committed_amount total of tips that the merchant committed to, but that were not
- * picked up yet
- * @param active true if the reserve is still active (we have the private key)
+ * @param payto_uri URI of the account
+ * @param conversion_url URL of a conversion service
+ * @param debit_restrictions restrictions for debits from account
+ * @param credit_restrictions restrictions for credits to account
+ * @param master_sig signature affirming the account
*/
typedef void
-(*TALER_MERCHANTDB_ReservesCallback)(
+(*TALER_MERCHANTDB_ExchangeAccountCallback) (
void *cls,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- struct GNUNET_TIME_Timestamp creation_time,
- struct GNUNET_TIME_Timestamp expiration_time,
- const struct TALER_Amount *merchant_initial_amount,
- const struct TALER_Amount *exchange_initial_amount,
- const struct TALER_Amount *pickup_amount,
- const struct TALER_Amount *committed_amount,
- bool active);
-
-
-/**
- * Callback with details about a reserve pending exchange confirmation.
- *
- * @param cls closure
- * @param instance_id for which instance is this reserve
- * @param exchange_url base URL of the exchange
- * @param reserve_pub public key of the reserve
- * @param expected_amount how much do we expect to see in the reserve
- */
-typedef void
-(*TALER_MERCHANTDB_PendingReservesCallback)(
- void *cls,
- const char *instance_id,
- const char *exchange_url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_Amount *expected_amount);
-
-
-/**
- * Details about a tip.
- */
-struct TALER_MERCHANTDB_TipDetails
-{
- /**
- * ID of the tip.
- */
- struct TALER_TipIdentifierP tip_id;
-
- /**
- * Total amount of the tip.
- */
- struct TALER_Amount total_amount;
-
- /**
- * Reason given for granting the tip.
- */
- char *reason;
-};
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ const struct TALER_MasterSignatureP *master_sig);
/**
@@ -822,55 +949,6 @@ typedef void
/**
- * Callback with reserve details.
- *
- * @param cls closure
- * @param creation_time time when the reserve was setup
- * @param expiration_time time when the reserve will be closed by the exchange
- * @param merchant_initial_amount initial amount that the merchant claims to have filled the
- * reserve with
- * @param exchange_initial_amount initial amount that the exchange claims to have received
- * @param picked_up_amount total of tips that were picked up from this reserve
- * @param committed_amount total of tips that the merchant committed to, but that were not
- * picked up yet
- * @param active true if the reserve is still active (we have the private key)
- * @param exchange_url base URL of the exchange hosting the reserve, NULL if not @a active
- * @param payto_uri URI to use to fund the reserve, NULL if not @a active
- * @param tips_length length of the @a tips array
- * @param tips information about the tips created by this reserve
- */
-typedef void
-(*TALER_MERCHANTDB_ReserveDetailsCallback)(
- void *cls,
- struct GNUNET_TIME_Timestamp creation_time,
- struct GNUNET_TIME_Timestamp expiration_time,
- const struct TALER_Amount *merchant_initial_amount,
- const struct TALER_Amount *exchange_initial_amount,
- const struct TALER_Amount *picked_up_amount,
- const struct TALER_Amount *committed_amount,
- bool active,
- const char *exchange_url,
- const char *payto_uri,
- unsigned int tips_length,
- const struct TALER_MERCHANTDB_TipDetails *tips);
-
-
-/**
- * Typically called by `lookup_tips`.
- *
- * @param cls closure
- * @param row_id row of the tip in the database
- * @param tip_id id of the tip
- * @param amount amount of the tip
- */
-typedef void
-(*TALER_MERCHANTDB_TipsCallback)(void *cls,
- uint64_t row_id,
- struct TALER_TipIdentifierP tip_id,
- struct TALER_Amount amount);
-
-
-/**
* Function called with information about a coin that was deposited.
*
* @param cls closure
@@ -901,25 +979,151 @@ typedef void
/**
- * Details about a pickup operation executed by the merchant.
+ * Possible token family kinds.
+ */
+enum TALER_MERCHANTDB_TokenFamilyKind
+{
+
+ /**
+ * Token family representing a discount token
+ */
+ TALER_MERCHANTDB_TFK_Discount = 0,
+
+ /**
+ * Token family representing a subscription token
+ */
+ TALER_MERCHANTDB_TFK_Subscription = 1,
+
+};
+
+
+/**
+ * Typically called by `lookup_token_families`.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param slug slug of the token family
+ * @param name name of the token family
+ * @param start_time start time of the token family's validity period
+ * @param expiration end time of the token family's validity period
+ * @param kind kind of the token family
+ */
+typedef void
+(*TALER_MERCHANTDB_TokenFamiliesCallback)(
+ void *cls,
+ const char *slug,
+ const char *name,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp expiration,
+ const char *kind);
+
+
+/**
+ * Details about a token family.
+ */
+struct TALER_MERCHANTDB_TokenFamilyDetails
+{
+ /**
+ * Token family slug used for identification.
+ */
+ char *slug;
+
+ /**
+ * User readable name of the token family.
+ */
+ char *name;
+
+ /**
+ * Description of the token family.
+ */
+ char *description;
+
+ /**
+ * Internationalized token family description.
+ */
+ json_t *description_i18n;
+
+ /**
+ * Start time of the token family duration.
+ */
+ struct GNUNET_TIME_Timestamp valid_after;
+
+ /**
+ * End time of the token family duration.
+ */
+ struct GNUNET_TIME_Timestamp valid_before;
+
+ /**
+ * Validity duration of the token family.
+ */
+ struct GNUNET_TIME_Relative duration;
+
+ /**
+ * Token family kind.
+ */
+ enum TALER_MERCHANTDB_TokenFamilyKind kind;
+
+ /**
+ * Counter for each issued token of this family.
+ */
+ uint64_t issued;
+
+ /**
+ * Counter for each redeemed token of this family.
+ */
+ uint64_t redeemed;
+};
+
+
+/**
+ * Details about a token key.
*/
-struct TALER_MERCHANTDB_PickupDetails
+struct TALER_MERCHANTDB_TokenFamilyKeyDetails
{
/**
- * Identifier for the pickup operation.
+ * Tokens signed with this key are valid from this date on.
+ */
+ struct GNUNET_TIME_Timestamp valid_after;
+
+ /**
+ * Tokens signed with this key are valid until this date.
*/
- struct TALER_PickupIdentifierP pickup_id;
+ struct GNUNET_TIME_Timestamp valid_before;
/**
- * Total amount requested for this @e pickup_id.
+ * Token family public key.
*/
- struct TALER_Amount requested_amount;
+ struct TALER_TokenFamilyPublicKey pub;
/**
- * Number of planchets involved in the request.
+ * Hash of the token family public key.
*/
- unsigned int num_planchets;
+ struct TALER_TokenFamilyPublicKeyHash pub_h;
+
+ /**
+ * Token family private key.
+ */
+ struct TALER_TokenFamilyPrivateKey priv;
+};
+
+/**
+ * Details about a spent token.
+*/
+struct TALER_MERCHANTDB_SpentTokenDetails
+{
+ /**
+ * Public key of the spent token.
+ */
+ struct TALER_TokenPublicKey pub;
+ /**
+ * Signature that this token was spent on the specified order.
+ */
+ struct TALER_TokenSignature sig;
+
+ /**
+ * Blind signature for the spent token to prove validity of it.
+ */
+ struct TALER_TokenBlindSignature blind_sig;
};
@@ -1052,7 +1256,6 @@ struct TALER_MERCHANTDB_Plugin
* Roll back the current transaction of a database connection.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
- * @return #GNUNET_OK on success
*/
void
(*rollback) (void *cls);
@@ -1061,7 +1264,7 @@ struct TALER_MERCHANTDB_Plugin
* Commit the current transaction of a database connection.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
- * @return transaction status code
+ * @return transaction status
*/
enum GNUNET_DB_QueryStatus
(*commit)(void *cls);
@@ -1139,6 +1342,132 @@ struct TALER_MERCHANTDB_Plugin
const char *id,
const struct TALER_MERCHANTDB_AccountDetails *account_details);
+
+ /**
+ * Insert instance login token into our database.
+ *
+ * @param cls closure
+ * @param id identifier of the instance
+ * @param token value of the token
+ * @param creation_time the current time
+ * @param expiration_time when does the token expire
+ * @param validity_scope scope of the token
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_login_token)(
+ void *cls,
+ const char *id,
+ const struct TALER_MERCHANTDB_LoginTokenP *token,
+ struct GNUNET_TIME_Timestamp creation_time,
+ struct GNUNET_TIME_Timestamp expiration_time,
+ uint32_t validity_scope);
+
+
+ /**
+ * Lookup information about a login token from database.
+ *
+ * @param cls closure
+ * @param id identifier of the instance
+ * @param token value of the token
+ * @param[out] expiration_time set to expiration time
+ * @param[out] validity_scope set to scope of the token
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_login_token)(
+ void *cls,
+ const char *id,
+ const struct TALER_MERCHANTDB_LoginTokenP *token,
+ struct GNUNET_TIME_Timestamp *expiration_time,
+ uint32_t *validity_scope);
+
+
+ /**
+ * Delete login token from database.
+ *
+ * @param cls closure
+ * @param id identifier of the instance
+ * @param token value of the token
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*delete_login_token)(
+ void *cls,
+ const char *id,
+ const struct TALER_MERCHANTDB_LoginTokenP *token);
+
+
+ /**
+ * Update information about an instance's account into our database.
+ *
+ * @param cls closure
+ * @param id identifier of the instance
+ * @param h_wire which account to update
+ * @param credit_facade_url new facade URL, can be NULL
+ * @param credit_facade_credentials new credentials, can be NULL
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_account)(
+ void *cls,
+ const char *id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *credit_facade_url,
+ const json_t *credit_facade_credentials);
+
+
+ /**
+ * Obtain information about an instance's accounts.
+ *
+ * @param cls closure
+ * @param id identifier of the instance
+ * @param cb function to call on each account
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_accounts)(
+ void *cls,
+ const char *id,
+ TALER_MERCHANTDB_AccountCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Obtain detailed information about an instance's account.
+ *
+ * @param cls closure
+ * @param id identifier of the instance
+ * @param h_wire wire hash of the account
+ * @param[out] ad account details returned
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_account)(
+ void *cls,
+ const char *id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ struct TALER_MERCHANTDB_AccountDetails *ad);
+
+
+ /**
+ * Obtain detailed information about an instance's account.
+ *
+ * @param cls closure
+ * @param id identifier of the instance
+ * @param payto_uri URI of the account
+ * @param[out] ad account details returned
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_account_by_uri)(
+ void *cls,
+ const char *id,
+ const char *payto_uri,
+ struct TALER_MERCHANTDB_AccountDetails *ad);
+
+
/**
* Delete private key of an instance from our database.
*
@@ -1250,6 +1579,7 @@ struct TALER_MERCHANTDB_Plugin
* @param exchange_pub public key of the exchange, or NULL for none
* @param timestamp timestamp to store
* @param kyc_ok current KYC status (true for satisfied)
+ * @param aml_decision current AML decision state at the exchange
* @return database result code
*/
enum GNUNET_DB_QueryStatus
@@ -1261,7 +1591,8 @@ struct TALER_MERCHANTDB_Plugin
const struct TALER_ExchangeSignatureP *exchange_sig,
const struct TALER_ExchangePublicKeyP *exchange_pub,
struct GNUNET_TIME_Timestamp timestamp,
- bool kyc_ok);
+ bool kyc_ok,
+ enum TALER_AmlDecisionState aml_decision);
/**
@@ -1269,6 +1600,9 @@ struct TALER_MERCHANTDB_Plugin
*
* @param cls closure
* @param instance_id instance to lookup products for
+ * @param offset transfer_serial number of the transfer we want to offset from
+ * @param limit number of entries to return, negative for descending,
+ * positive for ascending
* @param cb function to call on all products found
* @param cb_cls closure for @a cb
* @return database result code
@@ -1276,6 +1610,8 @@ struct TALER_MERCHANTDB_Plugin
enum GNUNET_DB_QueryStatus
(*lookup_products)(void *cls,
const char *instance_id,
+ uint64_t offset,
+ int64_t limit,
TALER_MERCHANTDB_ProductsCallback cb,
void *cb_cls);
@@ -1378,8 +1714,9 @@ struct TALER_MERCHANTDB_Plugin
* instances.
*
* @param cls closure
+ * @return database result code
*/
- void
+ enum GNUNET_DB_QueryStatus
(*expire_locks)(void *cls);
@@ -1465,20 +1802,26 @@ struct TALER_MERCHANTDB_Plugin
* @param cls closure
* @param instance_id identifies the instance responsible for the order
* @param order_id alphanumeric string that uniquely identifies the order
+ * @param session_id session ID for the order
* @param h_post_data hash of the POST data for idempotency checks
* @param pay_deadline how long does the customer have to pay for the order
* @param claim_token token to use for access control
* @param contract_terms proposal data to store
+ * @param pos_key encoded key for payment verification
+ * @param pos_algorithm algorithm to compute the payment verification
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
(*insert_order)(void *cls,
const char *instance_id,
const char *order_id,
+ const char *session_id,
const struct TALER_MerchantPostDataHashP *h_post_data,
struct GNUNET_TIME_Timestamp pay_deadline,
const struct TALER_ClaimTokenP *claim_token,
- const json_t *contract_terms);
+ const json_t *contract_terms,
+ const char *pos_key,
+ enum TALER_MerchantConfirmationAlgorithm pos_algorithm);
/**
@@ -1526,16 +1869,71 @@ struct TALER_MERCHANTDB_Plugin
* @param[out] order_serial set to the order's serial number
* @param[out] paid set to true if the order is fully paid
* @param[out] claim_token set to the claim token, NULL to only check for existence
+ * @param[out] pos_key encoded key for payment verification
+ * @param[out] pos_algorithm set to algorithm to compute the payment verification
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_contract_terms)(void *cls,
- const char *instance_id,
- const char *order_id,
- json_t **contract_terms,
- uint64_t *order_serial,
- bool *paid,
- struct TALER_ClaimTokenP *claim_token);
+ (*lookup_contract_terms2)(
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ json_t **contract_terms,
+ uint64_t *order_serial,
+ bool *paid,
+ struct TALER_ClaimTokenP *claim_token,
+ char **pos_key,
+ enum TALER_MerchantConfirmationAlgorithm *pricing_algorithm);
+
+
+ /**
+ * Retrieve contract terms given its @a order_id
+ *
+ * @param cls closure
+ * @param instance_id instance's identifier
+ * @param order_id order_id used to lookup
+ * @param session_id session_id to compare, can be NULL
+ * @param[out] contract_terms where to store the result, NULL to only check for existence
+ * @param[out] order_serial set to the order's serial number
+ * @param[out] paid set to true if the order is fully paid
+ * @param[out] wired set to true if the exchange wired the funds
+ * @param[out] session_matches set to true if @a session_id matches session stored for this contract
+ * @param[out] claim_token set to the claim token, NULL to only check for existence
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_contract_terms3)(
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const char *session_id,
+ json_t **contract_terms,
+ uint64_t *order_serial,
+ bool *paid,
+ bool *wired,
+ bool *session_matches,
+ struct TALER_ClaimTokenP *claim_token);
+
+
+ /**
+ * Retrieve contract terms given its @a order_id
+ *
+ * @param cls closure
+ * @param instance_id instance's identifier
+ * @param order_id order_id used to lookup.
+ * @param[out] contract_terms where to store the result, NULL to only check for existence
+ * @param[out] order_serial set to the order's serial number
+ * @param[out] claim_token set to the claim token, NULL to only check for existence
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_contract_terms)(
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ json_t **contract_terms,
+ uint64_t *order_serial,
+ struct TALER_ClaimTokenP *claim_token);
/**
@@ -1555,11 +1953,12 @@ struct TALER_MERCHANTDB_Plugin
* is malformed
*/
enum GNUNET_DB_QueryStatus
- (*insert_contract_terms)(void *cls,
- const char *instance_id,
- const char *order_id,
- json_t *contract_terms,
- uint64_t *order_serial);
+ (*insert_contract_terms)(
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ json_t *contract_terms,
+ uint64_t *order_serial);
/**
@@ -1646,36 +2045,62 @@ struct TALER_MERCHANTDB_Plugin
/**
- * Insert payment confirmation from the exchange into the database.
+ * Insert deposit confirmation from the exchange into the database.
*
* @param cls closure
* @param instance_id instance to lookup deposits for
* @param deposit_timestamp time when the exchange generated the deposit confirmation
* @param h_contract_terms proposal data's hashcode
- * @param coin_pub public key of the coin
* @param exchange_url URL of the exchange that issued @a coin_pub
- * @param amount_with_fee amount the exchange will deposit for this coin
- * @param deposit_fee fee the exchange will charge for this coin
+ * @param wire_transfer_deadline when do we expect the wire transfer from the exchange
+ * @param total_without_fees deposited total in the batch without fees
* @param wire_fee wire fee the exchange charges
* @param h_wire hash of the wire details of the target account of the merchant
* @param exchange_sig signature from exchange that coin was accepted
* @param exchange_pub signing key that was used for @a exchange_sig
+ * @param[out] batch_deposit_serial_id set to the table row
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*insert_deposit)(void *cls,
- const char *instance_id,
- struct GNUNET_TIME_Timestamp deposit_timestamp,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const char *exchange_url,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct TALER_Amount *refund_fee,
- const struct TALER_Amount *wire_fee,
- const struct TALER_MerchantWireHashP *h_wire,
- const struct TALER_ExchangeSignatureP *exchange_sig,
- const struct TALER_ExchangePublicKeyP *exchange_pub);
+ (*insert_deposit_confirmation)(
+ void *cls,
+ const char *instance_id,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const char *exchange_url,
+ struct GNUNET_TIME_Timestamp wire_transfer_deadline,
+ const struct TALER_Amount *total_without_fees,
+ const struct TALER_Amount *wire_fee,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ uint64_t *batch_deposit_serial_id);
+
+
+ /**
+ * Insert information about coin deposited as part of
+ * a batch into the database.
+ *
+ * @param cls closure
+ * @param offset offset of the coin in the batch
+ * @param deposit_confirmation_serial_id deposit confirmation for the batch the coin is part of
+ * @param coin_pub public key of the coin
+ * @param coin_sig deposit signature of the coin
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param refund_fee fee the exchange will charge for refunds of coin
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_deposit)(
+ void *cls,
+ uint32_t offset,
+ uint64_t deposit_confirmation_serial_id,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_Amount *refund_fee);
/**
@@ -1778,25 +2203,6 @@ struct TALER_MERCHANTDB_Plugin
/**
- * Retrieve payment and wire status for a given @a order_serial and
- * session ID.
- *
- * @param cls closure
- * @param order_serial identifies the order
- * @param session_id session for which to check the payment status, NULL for any
- * @param[out] paid set to the payment status of the contract
- * @param[out] wired set to the wire transfer status of the exchange payment
- * @return transaction status
- */
- enum GNUNET_DB_QueryStatus
- (*lookup_payment_status)(void *cls,
- uint64_t order_serial,
- const char *session_id,
- bool *paid,
- bool *wired);
-
-
- /**
* Retrieve details about coins that were deposited for an order.
*
* @param cls closure
@@ -1831,6 +2237,47 @@ struct TALER_MERCHANTDB_Plugin
/**
+ * Update transfer status.
+ *
+ * @param cls closure
+ * @param exchange_url the exchange that made the transfer
+ * @param wtid wire transfer subject
+ * @param next_attempt when should we try again (if ever)
+ * @param ec current error state of checking the transfer
+ * @param failed true if validation has failed for good
+ * @param verified true if validation has succeeded for good
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_transfer_status)(
+ void *cls,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct GNUNET_TIME_Absolute next_attempt,
+ enum TALER_ErrorCode ec,
+ bool failed,
+ bool verified);
+
+ /**
+ * Retrieve wire transfer details of wire details
+ * that taler-merchant-exchange still needs to
+ * investigate.
+ *
+ * @param cls closure
+ * @param limit maximum number of results to return
+ * @param cb function called with the wire transfer data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_open_transfers)(
+ void *cls,
+ uint64_t limit,
+ TALER_MERCHANTDB_OpenTransferCallback cb,
+ void *cb_cls);
+
+
+ /**
* Insert wire transfer details for a deposit.
*
* @param cls closure
@@ -1941,6 +2388,7 @@ struct TALER_MERCHANTDB_Plugin
* @param fulfillment_url URL that canonically identifies the resource
* being paid for
* @param session_id session id
+ * @param allow_refunded_for_repurchase true to include refunded orders in repurchase detection
* @param[out] order_id location to store the order ID that was used when
* paying for the resource URL
* @return transaction status
@@ -1950,9 +2398,42 @@ struct TALER_MERCHANTDB_Plugin
const char *instance_id,
const char *fulfillment_url,
const char *session_id,
+ bool allow_refunded_for_repurchase,
char **order_id);
/**
+ * Update information about progress made by taler-merchant-wirewatch.
+ *
+ * @param cls closure
+ * @param instance which instance does the account belong to
+ * @param payto_uri which account is this about
+ * @param last_serial last serial imported from the bank
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_wirewatch_progress)(
+ void *cls,
+ const char *instance,
+ const char *payto_uri,
+ uint64_t last_serial);
+
+
+ /**
+ * Select information about accounts which taler-merchant-wirewatch should work on.
+ *
+ * @param cls closure
+ * @param cb function to call with results
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_wirewatch_accounts)(
+ void *cls,
+ TALER_MERCHANTDB_WirewatchWorkCallback cb,
+ void *cb_cls);
+
+
+ /**
* Insert information about a wire transfer the merchant has received.
*
* @param cls closure
@@ -2127,20 +2608,22 @@ struct TALER_MERCHANTDB_Plugin
/**
- * Set transfer status to verified.
+ * Set transfer status to confirmed.
*
* @param cls closure
* @param instance_id instance to lookup payments for
* @param exchange_url the exchange that made the transfer
- * @param payto_uri account that received the transfer
* @param wtid wire transfer subject
+ * @param amount confirmed amount of the wire transfer
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*set_transfer_status_to_verified)(
+ (*set_transfer_status_to_confirmed)(
void *cls,
+ const char *instance_id,
const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid);
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *amount);
/**
@@ -2219,402 +2702,236 @@ struct TALER_MERCHANTDB_Plugin
* including signature (so we have proof).
*
* @param cls closure
- * @param exchange_pub public key of the exchange
+ * @param master_pub master public key of the exchange
* @param h_wire_method hash of wire method
* @param fees wire fees charged
* @param start_date start of fee being used
* @param end_date end of fee being used
- * @param exchange_sig signature of exchange over fee structure
+ * @param master_sig signature of exchange over fee structure
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
(*store_wire_fee_by_exchange)(
void *cls,
- const struct TALER_MasterPublicKeyP *exchange_pub,
+ const struct TALER_MasterPublicKeyP *master_pub,
const struct GNUNET_HashCode *h_wire_method,
const struct TALER_WireFeeSet *fees,
struct GNUNET_TIME_Timestamp start_date,
struct GNUNET_TIME_Timestamp end_date,
- const struct TALER_MasterSignatureP *exchange_sig);
-
-
- /**
- * Add @a credit to a reserve to be used for tipping. Note that
- * this function does not actually perform any wire transfers to
- * credit the reserve, it merely tells the merchant backend that
- * a reserve now exists. This has to happen before tips can be
- * authorized.
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance is the reserve tied to
- * @param reserve_priv which reserve is topped up or created
- * @param reserve_pub which reserve is topped up or created
- * @param exchange_url what URL is the exchange reachable at where the reserve is located
- * @param payto_uri URI to fund the reserve
- * @param initial_balance how much money will be added to the reserve
- * @param expiration when does the reserve expire?
- * @return transaction status, usually
- * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
- */
- enum TALER_ErrorCode
- (*insert_reserve)(void *cls,
- const char *instance_id,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const char *exchange_url,
- const char *payto_uri,
- const struct TALER_Amount *initial_balance,
- struct GNUNET_TIME_Timestamp expiration);
+ const struct TALER_MasterSignatureP *master_sig);
/**
- * Confirms @a credit as the amount the exchange claims to have received and
- * thus really 'activates' the reserve. This has to happen before tips can
- * be authorized.
+ * Delete information about wire accounts of an exchange. (Used when we got new account data.)
*
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance is the reserve tied to
- * @param reserve_pub which reserve is topped up or created
- * @param initial_exchange_balance how much money was be added to the reserve
- * according to the exchange
- * @return transaction status, usually
- * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
+ * @param cls closure
+ * @param master_pub public key of the exchange
+ * @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*activate_reserve)(void *cls,
- const char *instance_id,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_Amount *initial_exchange_balance);
+ (*delete_exchange_accounts)(
+ void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub);
/**
- * Lookup reserves.
+ * Return information about wire accounts of an exchange.
*
* @param cls closure
- * @param instance_id instance to lookup payments for
- * @param created_after filter by reserves created after this date
- * @param active filter by active reserves
- * @param failures filter by reserves with a disagreement on the initial balance
- * @param cb function to call with reserve summary data
+ * @param master_pub public key of the exchange
+ * @param cb function to call on each account
* @param cb_cls closure for @a cb
- * @return transaction status
+ * @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_reserves)(void *cls,
- const char *instance_id,
- struct GNUNET_TIME_Timestamp created_after,
- enum TALER_EXCHANGE_YesNoAll active,
- enum TALER_EXCHANGE_YesNoAll failures,
- TALER_MERCHANTDB_ReservesCallback cb,
- void *cb_cls);
+ (*select_accounts_by_exchange)(
+ void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ TALER_MERCHANTDB_ExchangeAccountCallback cb,
+ void *cb_cls);
/**
- * Lookup reserves pending activation across all instances.
+ * Insert information about a wire account of an exchange.
*
* @param cls closure
- * @param cb function to call with reserve data
- * @param cb_cls closure for @a cb
- * @return transaction status
+ * @param master_pub public key of the exchange
+ * @param payto_uri URI of the bank account
+ * @param conversion_url conversion service, NULL if there is no conversion required
+ * @param debit_restrictions JSON array of debit restrictions on the account
+ * @param credit_restrictions JSON array of debit restrictions on the account
+ * @param master_sig signature affirming the account of the exchange
+ * @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_pending_reserves)(void *cls,
- TALER_MERCHANTDB_PendingReservesCallback cb,
- void *cb_cls);
+ (*insert_exchange_account)(
+ void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ const struct TALER_MasterSignatureP *master_sig);
/**
- * Lookup reserve details.
+ * Lookup all of the templates the given instance has configured.
*
* @param cls closure
- * @param instance_id instance to lookup payments for
- * @param reserve_pub public key of the reserve to inspect
- * @param fetch_tips if true, also return information about tips
- * @param cb function to call with reserve summary data
+ * @param instance_id instance to lookup template for
+ * @param cb function to call on all template found
* @param cb_cls closure for @a cb
- * @return transaction status
+ * @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_reserve)(void *cls,
- const char *instance_id,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- bool fetch_tips,
- TALER_MERCHANTDB_ReserveDetailsCallback cb,
- void *cb_cls);
+ (*lookup_templates)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_TemplatesCallback cb,
+ void *cb_cls);
/**
- * Delete private key of a reserve.
+ * Lookup details about a particular template.
*
* @param cls closure
- * @param instance_id instance to lookup payments for
- * @param reserve_pub public key of the reserve to delete
- * @return transaction status
+ * @param instance_id instance to lookup template for
+ * @param template_id template to lookup
+ * @param[out] td set to the template details on success, can be NULL
+ * (in that case we only want to check if the template exists)
+ * @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*delete_reserve)(void *cls,
- const char *instance_id,
- const struct TALER_ReservePublicKeyP *reserve_pub);
+ (*lookup_template)(void *cls,
+ const char *instance_id,
+ const char *template_id,
+ struct TALER_MERCHANTDB_TemplateDetails *td);
/**
- * Purge all information about a reserve (including tips from it).
+ * Delete information about a template.
*
* @param cls closure
- * @param instance_id instance to lookup payments for
- * @param reserve_pub public key of the reserve to purge
- * @return transaction status
+ * @param instance_id instance to delete template of
+ * @param template_id template to delete
+ * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ * if template unknown.
*/
enum GNUNET_DB_QueryStatus
- (*purge_reserve)(void *cls,
- const char *instance_id,
- const struct TALER_ReservePublicKeyP *reserve_pub);
-
-
- /**
- * Authorize a tip over @a amount from reserve @a reserve_pub. Remember
- * the authorization under @a tip_id for later, together with the
- * @a justification.
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance should generate the tip
- * @param reserve_pub which reserve is debited, NULL to pick one in the DB
- * @param amount how high is the tip (with fees)
- * @param justification why was the tip approved
- * @param next_url where to send the URL post tip pickup
- * @param[out] tip_id set to the unique ID for the tip
- * @param[out] expiration set to when the tip expires
- * @return transaction status,
- * #TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but has expired
- * #TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_NOT_FOUND if the reserve is not known
- * #TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has insufficient funds left
- * #TALER_EC_GENERIC_DB_START_FAILED on hard DB errors
- * #TALER_EC_GENERIC_DB_FETCH_FAILED on hard DB errors
- * #TALER_EC_GENERIC_DB_STORE_FAILED on hard DB errors
- * #TALER_EC_GENERIC_DB_INVARIANT_FAILURE on hard DB errors
- * #TALER_EC_GENERIC_DB_SOFT_FAILURE on soft DB errors (client should retry)
- * #TALER_EC_NONE upon success
- */
- enum TALER_ErrorCode
- (*authorize_tip)(void *cls,
- const char *instance_id,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_Amount *amount,
- const char *justification,
- const char *next_url,
- struct TALER_TipIdentifierP *tip_id,
- struct GNUNET_TIME_Timestamp *expiration);
+ (*delete_template)(void *cls,
+ const char *instance_id,
+ const char *template_id);
/**
- * Lookup pickup details for pickup @a pickup_id.
+ * Insert details about a particular template.
*
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance should we lookup tip details for
- * @param tip_id which tip should we lookup details on
- * @param pickup_id which pickup should we lookup details on
- * @param[out] exchange_url which exchange is the tip withdrawn from
- * @param[out] reserve_priv private key the tip is withdrawn from (set if still available!)
- * @param sigs_length length of the @a sigs array
- * @param[out] sigs set to the (blind) signatures we have for this @a pickup_id,
- * those that are unavailable are left at NULL
- * @return transaction status
+ * @param cls closure
+ * @param instance_id instance to insert template for
+ * @param template_id template identifier of template to insert
+ * @param otp_serial_id 0 if no OTP device is associated
+ * @param td the template details to insert
+ * @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_pickup)(void *cls,
- const char *instance_id,
- const struct TALER_TipIdentifierP *tip_id,
- const struct TALER_PickupIdentifierP *pickup_id,
- char **exchange_url,
- struct TALER_ReservePrivateKeyP *reserve_priv,
- unsigned int sigs_length,
- struct TALER_BlindedDenominationSignature sigs[]);
+ (*insert_template)(void *cls,
+ const char *instance_id,
+ const char *template_id,
+ uint64_t otp_serial_id,
+ const struct TALER_MERCHANTDB_TemplateDetails *td);
/**
- * Lookup tip details for tip @a tip_id.
+ * Delete information about an OTP device.
*
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance should we lookup tip details for
- * @param tip_id which tip should we lookup details on
- * @param[out] total_authorized amount how high is the tip (with fees)
- * @param[out] total_picked_up how much of the tip was so far picked up (with fees)
- * @param[out] expiration set to when the tip expires
- * @param[out] exchange_url set to the exchange URL where the reserve is
- * @param[out] reserve_priv set to private key of reserve to be debited
- * @return transaction status
+ * @param cls closure
+ * @param instance_id instance to delete OTP device of
+ * @param otp_id otp device to delete
+ * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ * if template unknown.
*/
enum GNUNET_DB_QueryStatus
- (*lookup_tip)(void *cls,
+ (*delete_otp)(void *cls,
const char *instance_id,
- const struct TALER_TipIdentifierP *tip_id,
- struct TALER_Amount *total_authorized,
- struct TALER_Amount *total_picked_up,
- struct GNUNET_TIME_Timestamp *expiration,
- char **exchange_url,
- struct TALER_ReservePrivateKeyP *reserve_priv);
-
-
- /**
- * Lookup tips
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance should we lookup tips for
- * @param expired should we include expired tips?
- * @param limit maximum number of results to return, positive for
- * ascending row id, negative for descending
- * @param offset row id to start returning results from
- * @param cb function to call with tip data
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
- enum GNUNET_DB_QueryStatus
- (*lookup_tips)(void *cls,
- const char *instance_id,
- enum TALER_EXCHANGE_YesNoAll expired,
- int64_t limit,
- uint64_t offset,
- TALER_MERCHANTDB_TipsCallback cb,
- void *cb_cls);
-
-
- /**
- * Lookup tip details for tip @a tip_id.
- *
- * @param cls closure, typically a connection to the db
- * @param instance_id which instance should we lookup tip details for
- * @param tip_id which tip should we lookup details on
- * @param fpu should we fetch details about individual pickups
- * @param[out] total_authorized amount how high is the tip (with fees)
- * @param[out] total_picked_up how much of the tip was so far picked up (with fees)
- * @param[out] justification why was the tip approved
- * @param[out] expiration set to when the tip expires
- * @param[out] reserve_pub set to which reserve is debited
- * @param[out] pickups_length set to the length of @e pickups
- * @param[out] pickups if @a fpu is true, set to details about the pickup operations
- * @return transaction status
- */
- enum GNUNET_DB_QueryStatus
- (*lookup_tip_details)(void *cls,
- const char *instance_id,
- const struct TALER_TipIdentifierP *tip_id,
- bool fpu,
- struct TALER_Amount *total_authorized,
- struct TALER_Amount *total_picked_up,
- char **justification,
- struct GNUNET_TIME_Timestamp *expiration,
- struct TALER_ReservePublicKeyP *reserve_pub,
- unsigned int *pickups_length,
- struct TALER_MERCHANTDB_PickupDetails **pickups);
-
+ const char *otp_id);
/**
- * Insert details about a tip pickup operation. The @a total_picked_up
- * UPDATES the total amount under the @a tip_id, while the @a total_requested
- * is the amount to be associated with this @a pickup_id.
- * While there is usually only one pickup event that picks up the entire
- * amount, our schema allows for wallets to pick up the amount incrementally
- * over multiple pick up operations.
+ * Insert details about a particular OTP device.
*
- * @param cls closure, typically a connection to the db
- * @param tip_id the unique ID for the tip
- * @param total_picked_up how much was picked up overall at this
- * point (includes @a total_requested)
- * @param pickup_id unique ID for the operation
- * @param total_requested how much is being picked up in this operation
- * @return transaction status, usually
- * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
- * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a credit_uuid already known
+ * @param cls closure
+ * @param instance_id instance to insert OTP device for
+ * @param otp_id otp identifier of OTP device to insert
+ * @param td the OTP device details to insert
+ * @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_pickup)(void *cls,
- const char *instance_id,
- const struct TALER_TipIdentifierP *tip_id,
- const struct TALER_Amount *total_picked_up,
- const struct TALER_PickupIdentifierP *pickup_id,
- const struct TALER_Amount *total_requested);
+ (*insert_otp)(void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ const struct TALER_MERCHANTDB_OtpDeviceDetails *td);
/**
- * Insert blind signature obtained from the exchange during a
- * tip pickup operation.
+ * Update details about a particular OTP device.
*
- * @param cls closure, typically a connection to the db
- * @param pickup_id unique ID for the operation
- * @param offset offset of the blind signature for the pickup
- * @param blind_sig the blind signature
- * @return transaction status, usually
- * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
- * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a credit_uuid already known
+ * @param cls closure
+ * @param instance_id instance to update OTP device for
+ * @param otp_id OTP device to update
+ * @param td update to the OTP device details on success, can be NULL
+ * (in that case we only want to check if the template exists)
+ * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the template
+ * does not yet exist.
*/
enum GNUNET_DB_QueryStatus
- (*insert_pickup_blind_signature)(
- void *cls,
- const struct TALER_PickupIdentifierP *pickup_id,
- uint32_t offset,
- const struct TALER_BlindedDenominationSignature *blind_sig);
-
+ (*update_otp)(void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ const struct TALER_MERCHANTDB_OtpDeviceDetails *td);
/**
- * Lookup all of the templates the given instance has configured.
+ * Lookup all of the OTP devices the given instance has configured.
*
* @param cls closure
- * @param instance_id instance to lookup template for
- * @param cb function to call on all template found
+ * @param instance_id instance to lookup OTP devices for
+ * @param cb function to call on all OTP devices found
* @param cb_cls closure for @a cb
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_templates)(void *cls,
- const char *instance_id,
- TALER_MERCHANTDB_TemplatesCallback cb,
- void *cb_cls);
+ (*lookup_otp_devices)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_OtpDeviceCallback cb,
+ void *cb_cls);
/**
- * Lookup details about a particular template.
+ * Lookup details about an OTP device.
*
* @param cls closure
* @param instance_id instance to lookup template for
- * @param template_id template to lookup
- * @param[out] td set to the template details on success, can be NULL
+ * @param otp_id OTP device to lookup
+ * @param[out] td set to the OTP device details on success, can be NULL
* (in that case we only want to check if the template exists)
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_template)(void *cls,
- const char *instance_id,
- const char *template_id,
- struct TALER_MERCHANTDB_TemplateDetails *td);
-
- /**
- * Delete information about a template.
- *
- * @param cls closure
- * @param instance_id instance to delete template of
- * @param template_id template to delete
- * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
- * if template unknown.
- */
- enum GNUNET_DB_QueryStatus
- (*delete_template)(void *cls,
- const char *instance_id,
- const char *template_id);
+ (*select_otp)(void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ struct TALER_MERCHANTDB_OtpDeviceDetails *td);
/**
- * Insert details about a particular template.
+ * Lookup serial number of an OTP device.
*
* @param cls closure
- * @param instance_id instance to insert template for
- * @param template_id template identifier of template to insert
- * @param td the template details to insert
- * @return database result code
+ * @param instance_id instance to lookup template for
+ * @param otp_id OTP device to lookup
+ * @param[out] serial set to the OTP device serial number * @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_template)(void *cls,
- const char *instance_id,
- const char *template_id,
- const struct TALER_MERCHANTDB_TemplateDetails *td);
+ (*select_otp_serial)(void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ uint64_t *serial);
/**
@@ -2752,22 +3069,6 @@ struct TALER_MERCHANTDB_Plugin
const char *http_method,
const char *header,
const char *body);
-
- /**
- * Lookup details about a particular pending webhook.
- *
- * @param cls closure
- * @param instance_id instance to lookup webhook for
- * @param webhook_serial webhook to lookup
- * @param[out] pwb set to the pending webhook details on success, can be NULL
- * (in that case we only want to check if the webhook exists)
- * @return database result code
- */
- enum GNUNET_DB_QueryStatus
- (*lookup_pending_webhook)(void *cls,
- const char *instance_id,
- uint64_t *webhook_serial,
- struct TALER_MERCHANTDB_PendingWebhookDetails *pwb);
/**
* Lookup the webhook that need to be send in priority. These webhooks are not successfully
* send.
@@ -2827,9 +3128,9 @@ struct TALER_MERCHANTDB_Plugin
*/
enum GNUNET_DB_QueryStatus
(*update_pending_webhook)(void *cls,
- uint64_t webhook_serial,
+ uint64_t webhook_pending_serial,
struct GNUNET_TIME_Absolute next_attempt);
- // maybe add: http status of failure?
+ // maybe add: http status of failure?
/**
@@ -2842,8 +3143,154 @@ struct TALER_MERCHANTDB_Plugin
*/
enum GNUNET_DB_QueryStatus
(*delete_pending_webhook)(void *cls,
- uint64_t webhook_serial);
+ uint64_t webhook_pending_serial);
+
+
+ /**
+ * Retrieve exchange's keys from the database.
+ *
+ * @param cls plugin closure
+ * @param exchange_url base URL of the exchange
+ * @param[out] keys set to the keys of the exchange
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_exchange_keys)(void *cls,
+ const char *exchange_url,
+ struct TALER_EXCHANGE_Keys **keys);
+
+
+ /**
+ * Insert or update @a keys into the database.
+ *
+ * @param cls plugin closure
+ * @param keys data to store
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_exchange_keys)(void *cls,
+ const struct TALER_EXCHANGE_Keys *keys);
+
+ /**
+ * Lookup all of the token families the given instance has configured.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup token families for
+ * @param cb function to call on all token families found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_token_families)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_TokenFamiliesCallback cb,
+ void *cb_cls);
+
+ /**
+ * Lookup details about a particular token family.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup token family for
+ * @param token_family_slug token family to lookup
+ * @param[out] details set to the token family details on success, can be NULL
+ * (in that case we only want to check if the token family exists)
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_token_family)(void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ struct TALER_MERCHANTDB_TokenFamilyDetails *details);
+
+ /**
+ * Delete information about a token family.
+ *
+ * @param cls closure
+ * @param instance_id instance to delete token family of
+ * @param token_family_slug slug of token family to delete
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*delete_token_family)(void *cls,
+ const char *instance_id,
+ const char *token_family_slug);
+
+ /**
+ * Update details about a particular token family.
+ *
+ * @param cls closure
+ * @param instance_id instance to update token family for
+ * @param token_family_slug slug of token family to update
+ * @param details set to the updated token family on success, can be NULL
+ * (in that case we only want to check if the token family exists)
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_token_family)(
+ void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ const struct TALER_MERCHANTDB_TokenFamilyDetails *details);
+
+
+ /**
+ * Insert details about a particular token family.
+ *
+ * @param cls closure
+ * @param instance_id instance to insert product for
+ * @param token_family_slug slug of token family to insert
+ * @param details the token family details to insert
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_token_family)(
+ void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ const struct TALER_MERCHANTDB_TokenFamilyDetails *details);
+
+ /**
+ * Lookup deposits that are finished and awaiting a wire transfer.
+ *
+ * @param cls closure
+ * @param exchange_url exchange to filter deposits by
+ * @param limit maximum number of deposits to return
+ * @param allow_future true to allow deposits with wire deadline in the future
+ * @param cb function to call with deposit data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_pending_deposits)(
+ void *cls,
+ const char *exchange_url,
+ uint64_t limit,
+ bool allow_future,
+ TALER_MERCHANTDB_PendingDepositsCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Update the deposit confirmation status associated with
+ * the given @a deposit_serial.
+ *
+ * @param cls closure
+ * @param deposit_serial deposit to update status for
+ * @param wire_pending should we keep checking for the wire status with the exchange?
+ * @param future_retry when should we ask the exchange again
+ * @param retry_backoff current value for the retry backoff
+ * @param emsg error message to record
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_deposit_confirmation_status)(
+ void *cls,
+ uint64_t deposit_serial,
+ bool wire_pending,
+ struct GNUNET_TIME_Timestamp future_retry,
+ struct GNUNET_TIME_Relative retry_backoff,
+ const char *emsg);
};
#endif
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 36e249ed..04b2b089 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -10,29 +10,31 @@ lib_LTLIBRARIES = \
libtalermerchant.la
libtalermerchant_la_LDFLAGS = \
- -version-info 3:0:0 \
+ -version-info 5:2:0 \
-no-undefined
libtalermerchant_la_SOURCES = \
merchant_api_curl_defaults.c merchant_api_curl_defaults.h \
merchant_api_common.c merchant_api_common.h \
+ merchant_api_delete_account.c \
merchant_api_delete_instance.c \
merchant_api_delete_order.c \
+ merchant_api_delete_otp_device.c \
merchant_api_delete_product.c \
- merchant_api_delete_reserve.c \
- merchant_api_delete_transfer.c \
merchant_api_delete_template.c \
+ merchant_api_delete_transfer.c \
merchant_api_delete_webhook.c \
+ merchant_api_get_account.c \
+ merchant_api_get_accounts.c \
merchant_api_get_config.c \
merchant_api_get_instance.c \
merchant_api_get_instances.c \
merchant_api_get_kyc.c \
merchant_api_get_orders.c \
+ merchant_api_get_otp_device.c \
+ merchant_api_get_otp_devices.c \
merchant_api_get_product.c \
merchant_api_get_products.c \
- merchant_api_get_reserve.c \
- merchant_api_get_reserves.c \
- merchant_api_get_tips.c \
merchant_api_get_transfers.c \
merchant_api_get_template.c \
merchant_api_get_templates.c \
@@ -40,12 +42,14 @@ libtalermerchant_la_SOURCES = \
merchant_api_get_webhooks.c \
merchant_api_lock_product.c \
merchant_api_merchant_get_order.c \
- merchant_api_merchant_get_tip.c \
+ merchant_api_patch_account.c \
merchant_api_patch_instance.c \
merchant_api_patch_order_forget.c \
+ merchant_api_patch_otp_device.c \
merchant_api_patch_product.c \
merchant_api_patch_template.c \
merchant_api_patch_webhook.c \
+ merchant_api_post_account.c \
merchant_api_post_instance_auth.c \
merchant_api_post_instances.c \
merchant_api_post_orders.c \
@@ -54,23 +58,21 @@ libtalermerchant_la_SOURCES = \
merchant_api_post_order_paid.c \
merchant_api_post_order_pay.c \
merchant_api_post_order_refund.c \
+ merchant_api_post_otp_devices.c \
merchant_api_post_products.c \
- merchant_api_post_reserves.c \
merchant_api_post_transfers.c \
merchant_api_post_templates.c \
merchant_api_post_using_templates.c \
merchant_api_post_webhooks.c \
- merchant_api_tip_authorize.c \
- merchant_api_tip_pickup.c \
- merchant_api_tip_pickup2.c \
- merchant_api_wallet_get_tip.c \
merchant_api_wallet_get_order.c \
+ merchant_api_wallet_get_template.c \
merchant_api_wallet_post_order_refund.c
libtalermerchant_la_LIBADD = \
-ltalerexchange \
-ltalercurl \
-ltalerjson \
+ -ltalerkyclogic \
-ltalerutil \
-lgnunetcurl \
-lgnunetjson \
diff --git a/src/lib/merchant_api_common.c b/src/lib/merchant_api_common.c
index 00e0358d..f5569ce3 100644
--- a/src/lib/merchant_api_common.c
+++ b/src/lib/merchant_api_common.c
@@ -245,7 +245,7 @@ TALER_MERCHANT_parse_pay_uri_free (
}
-int
+enum GNUNET_GenericReturnValue
TALER_MERCHANT_parse_refund_uri (
const char *refund_uri,
struct TALER_MERCHANT_RefundUriData *parse_data)
diff --git a/src/lib/merchant_api_curl_defaults.c b/src/lib/merchant_api_curl_defaults.c
index 34e4aad8..f3c4ee18 100644
--- a/src/lib/merchant_api_curl_defaults.c
+++ b/src/lib/merchant_api_curl_defaults.c
@@ -19,7 +19,8 @@
* @brief curl easy handle defaults
* @author Florian Dold
*/
-
+#include "platform.h"
+#include <taler/taler_curl_lib.h>
#include "merchant_api_curl_defaults.h"
@@ -38,22 +39,14 @@ TALER_MERCHANT_curl_easy_get_ (const char *url)
curl_easy_setopt (eh,
CURLOPT_URL,
url));
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_FOLLOWLOCATION,
- 1L));
+ TALER_curl_set_secure_redirect_policy (eh,
+ url);
/* Enable compression (using whatever curl likes), see
https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html */
GNUNET_break (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_ACCEPT_ENCODING,
""));
- /* limit MAXREDIRS to 5 as a simple security measure against
- a potential infinite loop caused by a malicious target */
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_MAXREDIRS,
- 5L));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_TCP_FASTOPEN,
diff --git a/src/lib/merchant_api_delete_account.c b/src/lib/merchant_api_delete_account.c
new file mode 100644
index 00000000..42d8bc5d
--- /dev/null
+++ b/src/lib/merchant_api_delete_account.c
@@ -0,0 +1,185 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 2.1, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING.LGPL. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant_api_delete_account.c
+ * @brief Implementation of the DELETE /private/account/$H_WIRE request of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_curl_defaults.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Handle for a DELETE /accounts/$ID operation.
+ */
+struct TALER_MERCHANT_AccountDeleteHandle
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_AccountDeleteCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP DELETE /accounts/$H_WIRE request.
+ *
+ * @param cls the `struct TALER_MERCHANT_AccountDeleteHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_delete_account_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_AccountDeleteHandle *adh = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_AccountDeleteResponse adr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ adh->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got /accounts/$H_WIRE response with status code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we need to authenticate. */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ break;
+ default:
+ /* unexpected response code */
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for DELETE /account/ID\n",
+ (unsigned int) response_code,
+ (int) adr.hr.ec);
+ break;
+ }
+ adh->cb (adh->cb_cls,
+ &adr);
+ TALER_MERCHANT_account_delete_cancel (adh);
+}
+
+
+struct TALER_MERCHANT_AccountDeleteHandle *
+TALER_MERCHANT_account_delete (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct TALER_MerchantWireHashP *h_wire,
+ TALER_MERCHANT_AccountDeleteCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_AccountDeleteHandle *adh;
+
+ adh = GNUNET_new (struct TALER_MERCHANT_AccountDeleteHandle);
+ adh->ctx = ctx;
+ adh->cb = cb;
+ adh->cb_cls = cb_cls;
+ {
+ char h_wire_str[sizeof (*h_wire) * 2];
+ char *path;
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (h_wire,
+ sizeof (*h_wire),
+ h_wire_str,
+ sizeof (h_wire_str));
+ *end = '\0';
+ GNUNET_asprintf (&path,
+ "private/account/%s",
+ h_wire_str);
+ adh->url = TALER_url_join (backend_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == adh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (adh);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ adh->url);
+ {
+ CURL *eh;
+
+ eh = TALER_MERCHANT_curl_easy_get_ (adh->url);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_CUSTOMREQUEST,
+ MHD_HTTP_METHOD_DELETE));
+ adh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_delete_account_finished,
+ adh);
+ }
+ return adh;
+}
+
+
+void
+TALER_MERCHANT_account_delete_cancel (
+ struct TALER_MERCHANT_AccountDeleteHandle *adh)
+{
+ if (NULL != adh->job)
+ GNUNET_CURL_job_cancel (adh->job);
+ GNUNET_free (adh->url);
+ GNUNET_free (adh);
+}
diff --git a/src/lib/merchant_api_delete_otp_device.c b/src/lib/merchant_api_delete_otp_device.c
new file mode 100644
index 00000000..5397606c
--- /dev/null
+++ b/src/lib/merchant_api_delete_otp_device.c
@@ -0,0 +1,184 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 2.1, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING.LGPL. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant_api_delete_otp_device.c
+ * @brief Implementation of the DELETE /otp-devices/$ID request of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_curl_defaults.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Handle for a DELETE /otp-devices/$ID operation.
+ */
+struct TALER_MERCHANT_OtpDeviceDeleteHandle
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_OtpDeviceDeleteCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /otp-devices/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_OtpDeviceDeleteHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_delete_otp_device_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_OtpDeviceDeleteHandle *tdh = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_HttpResponse hr = {
+ .http_status = (unsigned int) response_code,
+ .reply = json
+ };
+
+ tdh->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got DELETE /otp-devices/$ID response with status code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we need to authenticate. */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_CONFLICT:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) hr.ec);
+ break;
+ }
+ tdh->cb (tdh->cb_cls,
+ &hr);
+ TALER_MERCHANT_otp_device_delete_cancel (tdh);
+}
+
+
+struct TALER_MERCHANT_OtpDeviceDeleteHandle *
+TALER_MERCHANT_otp_device_delete (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *otp_device_id,
+ TALER_MERCHANT_OtpDeviceDeleteCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_OtpDeviceDeleteHandle *tdh;
+
+ tdh = GNUNET_new (struct TALER_MERCHANT_OtpDeviceDeleteHandle);
+ tdh->ctx = ctx;
+ tdh->cb = cb;
+ tdh->cb_cls = cb_cls;
+ {
+ char *path;
+
+ GNUNET_asprintf (&path,
+ "private/otp-devices/%s",
+ otp_device_id);
+ tdh->url = TALER_url_join (backend_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == tdh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (tdh);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ tdh->url);
+ {
+ CURL *eh;
+
+ eh = TALER_MERCHANT_curl_easy_get_ (tdh->url);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_CUSTOMREQUEST,
+ MHD_HTTP_METHOD_DELETE));
+ tdh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_delete_otp_device_finished,
+ tdh);
+ }
+ return tdh;
+}
+
+
+void
+TALER_MERCHANT_otp_device_delete_cancel (
+ struct TALER_MERCHANT_OtpDeviceDeleteHandle *tdh)
+{
+ if (NULL != tdh->job)
+ GNUNET_CURL_job_cancel (tdh->job);
+ GNUNET_free (tdh->url);
+ GNUNET_free (tdh);
+}
diff --git a/src/lib/merchant_api_delete_reserve.c b/src/lib/merchant_api_delete_reserve.c
deleted file mode 100644
index 8062d040..00000000
--- a/src/lib/merchant_api_delete_reserve.c
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
- Foundation; either version 2.1, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along with
- TALER; see the file COPYING.LGPL. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file merchant_api_delete_reserve.c
- * @brief Implementation of the DELETE /reserves/$RESERVE_PUB request of the merchant's HTTP API
- * @author Jonathan Buchanan
- */
-#include "platform.h"
-#include <curl/curl.h>
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_merchant_service.h"
-#include "merchant_api_curl_defaults.h"
-#include <taler/taler_json_lib.h>
-#include <taler/taler_signatures.h>
-
-
-/**
- * Handle for a DELETE /reserves/$RESERVE_PUB operation.
- */
-struct TALER_MERCHANT_ReserveDeleteHandle
-{
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_MERCHANT_ReserveDeleteCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Reference to the execution context.
- */
- struct GNUNET_CURL_Context *ctx;
-
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP DELETE /reserves/$RESERVE_PUB request.
- *
- * @param cls the `struct TALER_MERCHANT_InstanceDeleteHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response response body, NULL if not in JSON
- */
-static void
-handle_delete_reserve_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_MERCHANT_ReserveDeleteHandle *rdh = cls;
- const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
- };
-
- rdh->job = NULL;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Got /reserves/$ID response with status code %u\n",
- (unsigned int) response_code);
- switch (response_code)
- {
- case MHD_HTTP_NO_CONTENT:
- break;
- case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- /* Nothing really to verify, merchant says we need to authenticate. */
- break;
- case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- default:
- /* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- (int) hr.ec);
- break;
- }
- rdh->cb (rdh->cb_cls,
- &hr);
- TALER_MERCHANT_reserve_delete_cancel (rdh);
-}
-
-
-/**
- * Delete the private key of a reserve.
- *
- * @param ctx the context
- * @param backend_url HTTP base URL for the backend
- * @param reserve_pub which reserve should be deleted
- * @param purge purge instead of just deleting
- * @param cb function to call with the backend's return
- * @param cb_cls closure for @a config_cb
- * @return the instances handle; NULL upon error
- */
-static struct TALER_MERCHANT_ReserveDeleteHandle *
-reserve_delete (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- bool purge,
- TALER_MERCHANT_ReserveDeleteCallback cb,
- void *cb_cls)
-{
- struct TALER_MERCHANT_ReserveDeleteHandle *rdh;
-
- rdh = GNUNET_new (struct TALER_MERCHANT_ReserveDeleteHandle);
- rdh->ctx = ctx;
- rdh->cb = cb;
- rdh->cb_cls = cb_cls;
- {
- char res_str[sizeof (*reserve_pub) * 2];
- char arg_str[sizeof (res_str) + 32];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (reserve_pub,
- sizeof (*reserve_pub),
- res_str,
- sizeof (res_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "private/reserves/%s",
- res_str);
- if (purge)
- rdh->url = TALER_url_join (backend_url,
- arg_str,
- "purge",
- "yes",
- NULL);
- else
- rdh->url = TALER_url_join (backend_url,
- arg_str,
- NULL);
- }
- if (NULL == rdh->url)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not construct request URL.\n");
- GNUNET_free (rdh);
- return NULL;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Requesting URL '%s'\n",
- rdh->url);
- {
- CURL *eh;
-
- eh = TALER_MERCHANT_curl_easy_get_ (rdh->url);
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_CUSTOMREQUEST,
- MHD_HTTP_METHOD_DELETE));
- rdh->job = GNUNET_CURL_job_add (ctx,
- eh,
- &handle_delete_reserve_finished,
- rdh);
- }
- return rdh;
-}
-
-
-struct TALER_MERCHANT_ReserveDeleteHandle *
-TALER_MERCHANT_reserve_delete (
- struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- TALER_MERCHANT_ReserveDeleteCallback cb,
- void *cb_cls)
-{
- return reserve_delete (ctx,
- backend_url,
- reserve_pub,
- false,
- cb,
- cb_cls);
-}
-
-
-struct TALER_MERCHANT_ReserveDeleteHandle *
-TALER_MERCHANT_reserve_purge (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- TALER_MERCHANT_ReserveDeleteCallback cb,
- void *cb_cls)
-{
- return reserve_delete (ctx,
- backend_url,
- reserve_pub,
- true,
- cb,
- cb_cls);
-}
-
-
-void
-TALER_MERCHANT_reserve_delete_cancel (
- struct TALER_MERCHANT_ReserveDeleteHandle *rdh)
-{
- if (NULL != rdh->job)
- GNUNET_CURL_job_cancel (rdh->job);
- GNUNET_free (rdh->url);
- GNUNET_free (rdh);
-}
diff --git a/src/lib/merchant_api_delete_template.c b/src/lib/merchant_api_delete_template.c
index 0a5924aa..b2083cc9 100644
--- a/src/lib/merchant_api_delete_template.c
+++ b/src/lib/merchant_api_delete_template.c
@@ -74,8 +74,8 @@ struct TALER_MERCHANT_TemplateDeleteHandle
*/
static void
handle_delete_template_finished (void *cls,
- long response_code,
- const void *response)
+ long response_code,
+ const void *response)
{
struct TALER_MERCHANT_TemplateDeleteHandle *tdh = cls;
const json_t *json = response;
diff --git a/src/lib/merchant_api_get_account.c b/src/lib/merchant_api_get_account.c
new file mode 100644
index 00000000..84595e80
--- /dev/null
+++ b/src/lib/merchant_api_get_account.c
@@ -0,0 +1,211 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 2.1, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING.LGPL. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant_api_get_account.c
+ * @brief Implementation of the GET /accounts/$ID request of the merchant's HTTP API
+ * @author Priscilla HUANG
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_curl_defaults.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Handle for a GET /accounts/$ID operation.
+ */
+struct TALER_MERCHANT_AccountGetHandle
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_AccountGetCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /accounts/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_AccountGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_get_account_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_AccountGetHandle *tgh = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_AccountGetResponse tgr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ tgh->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got /accounts/$ID response with status code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &tgr.details.ok.ad.salt),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("credit_facade_url",
+ &tgr.details.ok.ad.credit_facade_url),
+ NULL),
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &tgr.details.ok.ad.payto_uri),
+ GNUNET_JSON_spec_fixed_auto ("h_wire",
+ &tgr.details.ok.ad.h_wire),
+ GNUNET_JSON_spec_bool ("active",
+ &tgr.details.ok.ad.active),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK ==
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ tgh->cb (tgh->cb_cls,
+ &tgr);
+ TALER_MERCHANT_account_get_cancel (tgh);
+ return;
+ }
+ tgr.hr.http_status = 0;
+ tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ case MHD_HTTP_UNAUTHORIZED:
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we need to authenticate. */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) tgr.hr.ec);
+ break;
+ }
+ tgh->cb (tgh->cb_cls,
+ &tgr);
+ TALER_MERCHANT_account_get_cancel (tgh);
+}
+
+
+struct TALER_MERCHANT_AccountGetHandle *
+TALER_MERCHANT_account_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *instance_id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ TALER_MERCHANT_AccountGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_AccountGetHandle *tgh;
+ CURL *eh;
+
+ tgh = GNUNET_new (struct TALER_MERCHANT_AccountGetHandle);
+ tgh->ctx = ctx;
+ tgh->cb = cb;
+ tgh->cb_cls = cb_cls;
+ {
+ char w_str[sizeof (*h_wire) * 2];
+ char *path;
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (h_wire,
+ sizeof (*h_wire),
+ w_str,
+ sizeof (w_str));
+ *end = '\0';
+ GNUNET_asprintf (&path,
+ "private/accounts/%s",
+ w_str);
+ tgh->url = TALER_url_join (backend_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == tgh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (tgh);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ tgh->url);
+ eh = TALER_MERCHANT_curl_easy_get_ (tgh->url);
+ tgh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_get_account_finished,
+ tgh);
+ return tgh;
+}
+
+
+void
+TALER_MERCHANT_account_get_cancel (
+ struct TALER_MERCHANT_AccountGetHandle *tgh)
+{
+ if (NULL != tgh->job)
+ GNUNET_CURL_job_cancel (tgh->job);
+ GNUNET_free (tgh->url);
+ GNUNET_free (tgh);
+}
diff --git a/src/lib/merchant_api_get_accounts.c b/src/lib/merchant_api_get_accounts.c
new file mode 100644
index 00000000..c08cd92d
--- /dev/null
+++ b/src/lib/merchant_api_get_accounts.c
@@ -0,0 +1,247 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 2.1, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING.LGPL. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant_api_get_accounts.c
+ * @brief Implementation of the GET /accounts request of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_curl_defaults.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+/**
+ * Maximum number of accounts permitted.
+ */
+#define MAX_ACCOUNTS 1024
+
+/**
+ * Handle for a GET /accounts operation.
+ */
+struct TALER_MERCHANT_AccountsGetHandle
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_AccountsGetCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Parse account information from @a ia.
+ *
+ * @param ia JSON array (or NULL!) with account data
+ * @param[in] tgr partially filled response
+ * @param tgh operation handle
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_accounts (const json_t *ia,
+ struct TALER_MERCHANT_AccountsGetResponse *tgr,
+ struct TALER_MERCHANT_AccountsGetHandle *tgh)
+{
+ unsigned int tmpl_len = (unsigned int) json_array_size (ia);
+
+ if ( (json_array_size (ia) != (size_t) tmpl_len) ||
+ (tmpl_len > MAX_ACCOUNTS) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ {
+ struct TALER_MERCHANT_AccountEntry tmpl[GNUNET_NZL (tmpl_len)];
+ size_t index;
+ json_t *value;
+
+ json_array_foreach (ia, index, value) {
+ struct TALER_MERCHANT_AccountEntry *ie = &tmpl[index];
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &ie->payto_uri),
+ GNUNET_JSON_spec_fixed_auto ("h_wire",
+ &ie->h_wire),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ tgr->details.ok.accounts_length = tmpl_len;
+ tgr->details.ok.accounts = tmpl;
+ tgh->cb (tgh->cb_cls,
+ tgr);
+ tgh->cb = NULL; /* just to be sure */
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /accounts request.
+ *
+ * @param cls the `struct TALER_MERCHANT_AccountsGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_get_accounts_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_AccountsGetHandle *tgh = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_AccountsGetResponse tgr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ tgh->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got /accounts response with status code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ const json_t *accounts;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("accounts",
+ &accounts),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ tgr.hr.http_status = 0;
+ tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ if (GNUNET_OK ==
+ parse_accounts (accounts,
+ &tgr,
+ tgh))
+ {
+ TALER_MERCHANT_accounts_get_cancel (tgh);
+ return;
+ }
+ tgr.hr.http_status = 0;
+ tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ case MHD_HTTP_UNAUTHORIZED:
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we need to authenticate. */
+ break;
+ default:
+ /* unexpected response code */
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) tgr.hr.ec);
+ break;
+ }
+ tgh->cb (tgh->cb_cls,
+ &tgr);
+ TALER_MERCHANT_accounts_get_cancel (tgh);
+}
+
+
+struct TALER_MERCHANT_AccountsGetHandle *
+TALER_MERCHANT_accounts_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ TALER_MERCHANT_AccountsGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_AccountsGetHandle *tgh;
+ CURL *eh;
+
+ tgh = GNUNET_new (struct TALER_MERCHANT_AccountsGetHandle);
+ tgh->ctx = ctx;
+ tgh->cb = cb;
+ tgh->cb_cls = cb_cls;
+ tgh->url = TALER_url_join (backend_url,
+ "private/accounts",
+ NULL);
+ if (NULL == tgh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (tgh);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ tgh->url);
+ eh = TALER_MERCHANT_curl_easy_get_ (tgh->url);
+ tgh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_get_accounts_finished,
+ tgh);
+ return tgh;
+}
+
+
+void
+TALER_MERCHANT_accounts_get_cancel (
+ struct TALER_MERCHANT_AccountsGetHandle *tgh)
+{
+ if (NULL != tgh->job)
+ GNUNET_CURL_job_cancel (tgh->job);
+ GNUNET_free (tgh->url);
+ GNUNET_free (tgh);
+}
diff --git a/src/lib/merchant_api_get_config.c b/src/lib/merchant_api_get_config.c
index 088434c5..ddbc20a3 100644
--- a/src/lib/merchant_api_get_config.c
+++ b/src/lib/merchant_api_get_config.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018, 2020 Taler Systems SA
+ Copyright (C) 2014-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -34,13 +34,22 @@
* Which version of the Taler protocol is implemented
* by this library? Used to determine compatibility.
*/
-#define MERCHANT_PROTOCOL_CURRENT 3
+#define MERCHANT_PROTOCOL_CURRENT 13
/**
- * How many configs are we backwards compatible with?
+ * How many configs are we backwards-compatible with?
*/
#define MERCHANT_PROTOCOL_AGE 1
+/**
+ * How many exchanges do we allow at most per merchant?
+ */
+#define MAX_EXCHANGES 1024
+
+/**
+ * How many currency specs do we allow at most per merchant?
+ */
+#define MAX_CURRENCIES 1024
/**
* @brief A handle for /config operations
@@ -90,9 +99,9 @@ handle_config_finished (void *cls,
{
struct TALER_MERCHANT_ConfigGetHandle *vgh = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_ConfigResponse cr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -104,78 +113,162 @@ handle_config_finished (void *cls,
{
case MHD_HTTP_OK:
{
- struct TALER_MERCHANT_ConfigInformation vi;
- enum TALER_MERCHANT_VersionCompatibility vc =
- TALER_MERCHANT_VC_PROTOCOL_ERROR;
+ const json_t *jcs;
+ const json_t *exchanges = NULL;
+ struct TALER_MERCHANT_ExchangeConfigInfo *eci = NULL;
+ unsigned int num_eci = 0;
+ unsigned int nspec;
+ struct TALER_JSON_ProtocolVersion pv;
struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_object_const ("currencies",
+ &jcs),
+ /* Optional for v5 compatibility, remove
+ once we reduce age! */
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("exchanges",
+ &exchanges),
+ NULL),
GNUNET_JSON_spec_string ("currency",
- &vi.currency),
+ &cr.details.ok.ci.currency),
+ TALER_JSON_spec_version ("version",
+ &pv),
GNUNET_JSON_spec_string ("version",
- &vi.version),
+ &cr.details.ok.ci.version),
GNUNET_JSON_spec_end ()
};
+ cr.details.ok.compat = TALER_MERCHANT_VC_PROTOCOL_ERROR;
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
spec,
NULL, NULL))
{
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ GNUNET_break_op (0);
+ cr.hr.http_status = 0;
+ cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
}
- else
+ cr.details.ok.compat = TALER_MERCHANT_VC_MATCH;
+ if (MERCHANT_PROTOCOL_CURRENT < pv.current)
+ {
+ cr.details.ok.compat |= TALER_MERCHANT_VC_NEWER;
+ if (MERCHANT_PROTOCOL_CURRENT < pv.current - pv.age)
+ cr.details.ok.compat |= TALER_MERCHANT_VC_INCOMPATIBLE;
+ }
+ if (MERCHANT_PROTOCOL_CURRENT > pv.current)
+ {
+ cr.details.ok.compat |= TALER_MERCHANT_VC_OLDER;
+ if (MERCHANT_PROTOCOL_CURRENT - MERCHANT_PROTOCOL_AGE > pv.current)
+ cr.details.ok.compat |= TALER_MERCHANT_VC_INCOMPATIBLE;
+ }
+
+ nspec = (unsigned int) json_object_size (jcs);
+ if ( (nspec > MAX_CURRENCIES) ||
+ (json_object_size (jcs) != (size_t) nspec) )
{
- unsigned int age;
- unsigned int revision;
- unsigned int current;
-
- if (3 != sscanf (vi.version,
- "%u:%u:%u",
- &current,
- &revision,
- &age))
+ GNUNET_break_op (0);
+ cr.hr.http_status = 0;
+ cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ if (NULL != exchanges)
+ {
+ num_eci = (unsigned int) json_object_size (exchanges);
+ if ( (num_eci > MAX_EXCHANGES) ||
+ (json_object_size (exchanges) != (size_t) num_eci) )
{
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ GNUNET_break_op (0);
+ cr.hr.http_status = 0;
+ cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
}
- else
+ eci = GNUNET_new_array (num_eci,
+ struct TALER_MERCHANT_ExchangeConfigInfo);
+ for (unsigned int i = 0; i<num_eci; i++)
{
- vc = TALER_MERCHANT_VC_MATCH;
- if (MERCHANT_PROTOCOL_CURRENT < current)
+ struct TALER_MERCHANT_ExchangeConfigInfo *ei = &eci[i];
+ const json_t *ej = json_array_get (exchanges,
+ i);
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("currency",
+ &ei->currency),
+ GNUNET_JSON_spec_string ("base_url",
+ &ei->base_url),
+ GNUNET_JSON_spec_fixed_auto ("master_pub",
+ &ei->master_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (ej,
+ ispec,
+ NULL, NULL))
{
- vc |= TALER_MERCHANT_VC_NEWER;
- if (MERCHANT_PROTOCOL_CURRENT < current - age)
- vc |= TALER_MERCHANT_VC_INCOMPATIBLE;
+ GNUNET_break_op (0);
+ cr.hr.http_status = 0;
+ cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ GNUNET_free (eci);
+ break;
}
- if (MERCHANT_PROTOCOL_CURRENT > current)
+ }
+ }
+ {
+ struct TALER_CurrencySpecification *cspecs;
+ unsigned int off = 0;
+ json_t *obj;
+ const char *curr;
+
+ cspecs = GNUNET_new_array (nspec,
+ struct TALER_CurrencySpecification);
+ cr.details.ok.num_cspecs = nspec;
+ cr.details.ok.cspecs = cspecs;
+ cr.details.ok.num_exchanges = (unsigned int) num_eci;
+ cr.details.ok.exchanges = eci;
+ json_object_foreach ((json_t *) jcs, curr, obj)
+ {
+ struct TALER_CurrencySpecification *cs = &cspecs[off++];
+ struct GNUNET_JSON_Specification cspec[] = {
+ TALER_JSON_spec_currency_specification (curr,
+ curr,
+ cs),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jcs,
+ cspec,
+ NULL, NULL))
{
- vc |= TALER_MERCHANT_VC_OLDER;
- if (MERCHANT_PROTOCOL_CURRENT - MERCHANT_PROTOCOL_AGE > current)
- vc |= TALER_MERCHANT_VC_INCOMPATIBLE;
+ GNUNET_break_op (0);
+ cr.hr.http_status = 0;
+ cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ GNUNET_free (eci);
+ TALER_CONFIG_free_currencies (off - 1,
+ cspecs);
+ break;
}
}
+ vgh->cb (vgh->cb_cls,
+ &cr);
+ GNUNET_free (eci);
+ TALER_CONFIG_free_currencies (nspec,
+ cspecs);
}
- vgh->cb (vgh->cb_cls,
- &hr,
- &vi,
- vc);
TALER_MERCHANT_config_get_cancel (vgh);
return;
}
default:
/* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ cr.hr.ec = TALER_JSON_get_error_code (json);
+ cr.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
- vgh->cb (vgh->cb_cls,
- &hr,
- NULL,
- TALER_MERCHANT_VC_PROTOCOL_ERROR);
+ (int) cr.hr.ec);
break;
}
+ vgh->cb (vgh->cb_cls,
+ &cr);
TALER_MERCHANT_config_get_cancel (vgh);
}
diff --git a/src/lib/merchant_api_get_instance.c b/src/lib/merchant_api_get_instance.c
index bb71a1ed..eef95b84 100644
--- a/src/lib/merchant_api_get_instance.c
+++ b/src/lib/merchant_api_get_instance.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018, 2020 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -28,6 +28,7 @@
#include "taler_merchant_service.h"
#include "merchant_api_curl_defaults.h"
#include <taler/taler_json_lib.h>
+#include <taler/taler_kyclogic_lib.h>
#include <taler/taler_signatures.h>
@@ -79,9 +80,9 @@ handle_get_instance_finished (void *cls,
{
struct TALER_MERCHANT_InstanceGetHandle *igh = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_InstanceGetResponse igr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
igh->job = NULL;
@@ -92,135 +93,86 @@ handle_get_instance_finished (void *cls,
{
case MHD_HTTP_OK:
{
- json_t *accounts;
- const char *name;
- struct TALER_MerchantPublicKeyP merchant_pub;
- json_t *address;
- json_t *jurisdiction;
- struct TALER_Amount default_max_wire_fee;
- uint32_t default_wire_fee_amortization;
- struct TALER_Amount default_max_deposit_fee;
- struct GNUNET_TIME_Relative default_wire_transfer_delay;
- struct GNUNET_TIME_Relative default_pay_delay;
+ const char *uts;
+ const json_t *address;
+ const json_t *jurisdiction;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("accounts",
- &accounts),
- GNUNET_JSON_spec_string ("name",
- &name),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &merchant_pub),
- GNUNET_JSON_spec_json ("address",
- &address),
- GNUNET_JSON_spec_json ("jurisdiction",
- &jurisdiction),
- TALER_JSON_spec_amount_any ("default_max_wire_fee",
- &default_max_wire_fee),
- GNUNET_JSON_spec_uint32 ("default_wire_fee_amortization",
- &default_wire_fee_amortization),
- TALER_JSON_spec_amount_any ("default_max_deposit_fee",
- &default_max_deposit_fee),
- GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
- &default_wire_transfer_delay),
- GNUNET_JSON_spec_relative_time ("default_pay_delay",
- &default_pay_delay),
+ GNUNET_JSON_spec_string (
+ "name",
+ &igr.details.ok.details.name),
+ GNUNET_JSON_spec_string (
+ "user_type",
+ &uts),
+ GNUNET_JSON_spec_fixed_auto (
+ "merchant_pub",
+ &igr.details.ok.details.merchant_pub),
+ GNUNET_JSON_spec_object_const (
+ "address",
+ &address),
+ GNUNET_JSON_spec_object_const (
+ "jurisdiction",
+ &jurisdiction),
+ GNUNET_JSON_spec_bool (
+ "use_stefan",
+ &igr.details.ok.details.use_stefan),
+ GNUNET_JSON_spec_relative_time (
+ "default_wire_transfer_delay",
+ &igr.details.ok.details.default_wire_transfer_delay),
+ GNUNET_JSON_spec_relative_time (
+ "default_pay_delay",
+ &igr.details.ok.details.default_pay_delay),
GNUNET_JSON_spec_end ()
};
- if ( (GNUNET_OK ==
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL)) &&
- (json_is_array (accounts)) )
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
{
- unsigned int accounts_length = json_array_size (accounts);
- struct TALER_MERCHANT_Account aa[accounts_length];
- const char *payto_uris[accounts_length];
- size_t index;
- json_t *value;
- int ret = GNUNET_OK;
-
- memset (payto_uris, 0, sizeof (payto_uris));
- json_array_foreach (accounts, index, value)
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("salt",
- &aa[index].salt),
- GNUNET_JSON_spec_string ("payto_uri",
- &payto_uris[index]),
- GNUNET_JSON_spec_fixed_auto ("h_wire",
- &aa[index].h_wire),
- GNUNET_JSON_spec_bool ("active",
- &aa[index].active),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (value,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- ret = GNUNET_SYSERR;
- break;
- }
- aa[index].payto_uri = payto_uris[index];
- }
-
- if (GNUNET_OK == ret)
- {
- struct TALER_MERCHANT_InstanceDetails details = {
- .name = name,
- .merchant_pub = &merchant_pub,
- .address = address,
- .jurisdiction = jurisdiction,
- .default_max_wire_fee = &default_max_wire_fee,
- .default_wire_fee_amortization = default_wire_fee_amortization,
- .default_max_deposit_fee = &default_max_deposit_fee,
- .default_wire_transfer_delay = default_wire_transfer_delay,
- .default_pay_delay = default_pay_delay
- };
-
- igh->cb (igh->cb_cls,
- &hr,
- accounts_length,
- aa,
- &details);
- GNUNET_JSON_parse_free (spec);
- TALER_MERCHANT_instance_get_cancel (igh);
- return;
- }
+ GNUNET_break_op (0);
+ igr.hr.http_status = 0;
+ igr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ igr.details.ok.details.address = address;
+ igr.details.ok.details.jurisdiction = jurisdiction;
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_user_type_from_string (
+ uts,
+ &igr.details.ok.details.ut))
+ {
+ GNUNET_break_op (0);
+ igr.hr.http_status = 0;
+ igr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
}
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- GNUNET_JSON_parse_free (spec);
- break;
+ igh->cb (igh->cb_cls,
+ &igr);
+ TALER_MERCHANT_instance_get_cancel (igh);
+ return;
}
case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ igr.hr.ec = TALER_JSON_get_error_code (json);
+ igr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, merchant says we need to authenticate. */
break;
case MHD_HTTP_NOT_FOUND:
/* instance does not exist */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ igr.hr.ec = TALER_JSON_get_error_code (json);
+ igr.hr.hint = TALER_JSON_get_error_hint (json);
break;
default:
/* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ igr.hr.ec = TALER_JSON_get_error_code (json);
+ igr.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) igr.hr.ec);
break;
}
igh->cb (igh->cb_cls,
- &hr,
- 0,
- NULL,
- NULL);
+ &igr);
TALER_MERCHANT_instance_get_cancel (igh);
}
diff --git a/src/lib/merchant_api_get_instances.c b/src/lib/merchant_api_get_instances.c
index 52a462b9..b2f99853 100644
--- a/src/lib/merchant_api_get_instances.c
+++ b/src/lib/merchant_api_get_instances.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018, 2020 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -32,6 +32,11 @@
/**
+ * Maximum number of instances permitted.
+ */
+#define MAX_INSTANCES 1024
+
+/**
* Handle for a GET /instances operation.
*/
struct TALER_MERCHANT_InstancesGetHandle
@@ -67,79 +72,82 @@ struct TALER_MERCHANT_InstancesGetHandle
/**
* Parse instance information from @a ia.
*
+ * @param json overall reply body
* @param ia JSON array (or NULL!) with instance data
* @param igh operation handle
* @return #GNUNET_OK on success
*/
-static int
-parse_instances (const json_t *ia,
+static enum GNUNET_GenericReturnValue
+parse_instances (const json_t *json,
+ const json_t *ia,
struct TALER_MERCHANT_InstancesGetHandle *igh)
{
- unsigned int iis_len = json_array_size (ia);
- struct TALER_MERCHANT_InstanceInformation iis[iis_len];
- size_t index;
- json_t *value;
- int ret;
+ unsigned int iis_len = (unsigned int) json_array_size (ia);
- ret = GNUNET_OK;
- json_array_foreach (ia, index, value) {
- struct TALER_MERCHANT_InstanceInformation *ii = &iis[index];
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("name",
- &ii->name),
- GNUNET_JSON_spec_string ("id",
- &ii->id),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &ii->merchant_pub),
- GNUNET_JSON_spec_json ("payment_targets",
- &ii->payment_targets),
- GNUNET_JSON_spec_end ()
+ if ( (json_array_size (ia) != (size_t) iis_len) ||
+ (iis_len > MAX_INSTANCES) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ {
+ struct TALER_MERCHANT_InstanceInformation iis[GNUNET_NZL (iis_len)];
+ size_t index;
+ json_t *value;
+ struct TALER_MERCHANT_InstancesGetResponse igr = {
+ .hr.http_status = MHD_HTTP_OK,
+ .hr.reply = json,
+ .details.ok.iis_length = iis_len,
+ .details.ok.iis = iis
};
- if (GNUNET_OK !=
- GNUNET_JSON_parse (value,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- ret = GNUNET_SYSERR;
- continue;
- }
- if (! json_is_array (ii->payment_targets))
- {
- GNUNET_break_op (0);
- ret = GNUNET_SYSERR;
- break;
- }
- for (unsigned int i = 0; i<json_array_size (ii->payment_targets); i++)
- {
- if (! json_is_string (json_array_get (ii->payment_targets,
- i)))
+ json_array_foreach (ia, index, value) {
+ struct TALER_MERCHANT_InstanceInformation *ii = &iis[index];
+ const char *uts;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("name",
+ &ii->name),
+ GNUNET_JSON_spec_string ("user_type",
+ &uts),
+ GNUNET_JSON_spec_string ("id",
+ &ii->id),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &ii->merchant_pub),
+ GNUNET_JSON_spec_array_const ("payment_targets",
+ &ii->payment_targets),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ NULL, NULL))
{
GNUNET_break_op (0);
- ret = GNUNET_SYSERR;
- break;
+ return GNUNET_SYSERR;
}
- }
- if (GNUNET_SYSERR == ret)
- break;
- }
- if (GNUNET_OK == ret)
- {
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = MHD_HTTP_OK
- };
-
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_user_type_from_string (uts,
+ &ii->ut))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ for (size_t i = 0; i<json_array_size (ii->payment_targets); i++)
+ {
+ if (! json_is_string (json_array_get (ii->payment_targets,
+ i)))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ } /* for all instances */
igh->cb (igh->cb_cls,
- &hr,
- iis_len,
- iis);
+ &igr);
igh->cb = NULL; /* just to be sure */
}
- for (unsigned int i = 0; i<iis_len; i++)
- if (NULL != iis[i].payment_targets)
- json_decref (iis[i].payment_targets);
- return ret;
+ return GNUNET_OK;
}
@@ -158,9 +166,9 @@ handle_instances_finished (void *cls,
{
struct TALER_MERCHANT_InstancesGetHandle *igh = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_InstancesGetResponse igr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
igh->job = NULL;
@@ -171,10 +179,10 @@ handle_instances_finished (void *cls,
{
case MHD_HTTP_OK:
{
- json_t *instances;
+ const json_t *instances;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("instances",
- &instances),
+ GNUNET_JSON_spec_array_const ("instances",
+ &instances),
GNUNET_JSON_spec_end ()
};
@@ -183,47 +191,38 @@ handle_instances_finished (void *cls,
spec,
NULL, NULL))
{
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ igr.hr.http_status = 0;
+ igr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
}
- else
+ if (GNUNET_OK ==
+ parse_instances (json,
+ instances,
+ igh))
{
- if ( (! json_is_array (instances)) ||
- (GNUNET_OK ==
- parse_instances (instances,
- igh)) )
- {
- GNUNET_JSON_parse_free (spec);
- TALER_MERCHANT_instances_get_cancel (igh);
- return;
- }
- else
- {
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- }
+ TALER_MERCHANT_instances_get_cancel (igh);
+ return;
}
- GNUNET_JSON_parse_free (spec);
+ igr.hr.http_status = 0;
+ igr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ igr.hr.ec = TALER_JSON_get_error_code (json);
+ igr.hr.hint = TALER_JSON_get_error_hint (json);
break;
default:
/* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ igr.hr.ec = TALER_JSON_get_error_code (json);
+ igr.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) igr.hr.ec);
break;
}
igh->cb (igh->cb_cls,
- &hr,
- 0,
- NULL);
+ &igr);
TALER_MERCHANT_instances_get_cancel (igh);
}
diff --git a/src/lib/merchant_api_get_kyc.c b/src/lib/merchant_api_get_kyc.c
index bc688eed..d2a819ea 100644
--- a/src/lib/merchant_api_get_kyc.c
+++ b/src/lib/merchant_api_get_kyc.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -32,6 +32,11 @@
/**
+ * Maximum length of the KYC arrays supported.
+ */
+#define MAX_KYC 1024
+
+/**
* Handle for a GET /kyc operation.
*/
struct TALER_MERCHANT_KycGetHandle
@@ -76,70 +81,91 @@ struct TALER_MERCHANT_KycGetHandle
static enum GNUNET_GenericReturnValue
parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc,
struct TALER_MERCHANT_KycResponse *kr,
- json_t *pends,
- json_t *touts)
+ const json_t *pends,
+ const json_t *touts)
{
- unsigned int num_pends = json_array_size (pends);
- unsigned int num_touts = json_array_size (touts);
- struct TALER_MERCHANT_AccountKycRedirectDetail pending_kycs[GNUNET_NZL (
- num_pends)];
- struct TALER_MERCHANT_ExchangeKycFailureDetail timeout_kycs[GNUNET_NZL (
- num_touts)];
-
- for (unsigned int i = 0; i<num_pends; i++)
+ unsigned int num_pends = (unsigned int) json_array_size (pends);
+ unsigned int num_touts = (unsigned int) json_array_size (touts);
+
+ if ( (json_array_size (pends) != (size_t) num_pends) ||
+ (num_pends > MAX_KYC) )
{
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("kyc_url",
- &pending_kycs[i].kyc_url),
- GNUNET_JSON_spec_string ("exchange_url",
- &pending_kycs[i].exchange_url),
- GNUNET_JSON_spec_string ("payto_uri",
- &pending_kycs[i].payto_uri),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json_array_get (pends,
- i),
- spec,
- NULL, NULL))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if ( (json_array_size (touts) != (size_t) num_touts) ||
+ (num_touts > MAX_KYC) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
- for (unsigned int i = 0; i<num_touts; i++)
+
{
- uint32_t hs;
- uint32_t ec;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("exchange_url",
- &timeout_kycs[i].exchange_url),
- GNUNET_JSON_spec_uint32 ("exchange_code",
- &ec),
- GNUNET_JSON_spec_uint32 ("exchange_http_status",
- &hs),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json_array_get (touts,
- i),
- spec,
- NULL, NULL))
+ struct TALER_MERCHANT_AccountKycRedirectDetail pending_kycs[
+ GNUNET_NZL (num_pends)];
+ struct TALER_MERCHANT_ExchangeKycFailureDetail timeout_kycs[
+ GNUNET_NZL (num_touts)];
+
+ memset (pending_kycs,
+ 0,
+ sizeof (pending_kycs));
+ for (unsigned int i = 0; i<num_pends; i++)
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("kyc_url",
+ &pending_kycs[i].kyc_url),
+ NULL),
+ TALER_JSON_spec_aml_decision ("aml_status",
+ &pending_kycs[i].aml_status),
+ TALER_JSON_spec_web_url ("exchange_url",
+ &pending_kycs[i].exchange_url),
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &pending_kycs[i].payto_uri),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (pends,
+ i),
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ for (unsigned int i = 0; i<num_touts; i++)
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
+ uint32_t hs;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_web_url ("exchange_url",
+ &timeout_kycs[i].exchange_url),
+ TALER_JSON_spec_ec ("exchange_code",
+ &timeout_kycs[i].exchange_code),
+ GNUNET_JSON_spec_uint32 ("exchange_http_status",
+ &hs),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (touts,
+ i),
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ timeout_kycs[i].exchange_http_status = (unsigned int) hs;
}
- timeout_kycs[i].exchange_http_status = (unsigned int) hs;
- timeout_kycs[i].exchange_code = (enum TALER_ErrorCode) ec;
+ kr->details.kyc_status.pending_kycs = pending_kycs;
+ kr->details.kyc_status.timeout_kycs = timeout_kycs;
+ kr->details.kyc_status.pending_kycs_length = num_pends;
+ kr->details.kyc_status.timeout_kycs_length = num_touts;
+ kyc->cb (kyc->cb_cls,
+ kr);
}
- kr->details.kyc_status.pending_kycs = pending_kycs;
- kr->details.kyc_status.timeout_kycs = timeout_kycs;
- kr->details.kyc_status.pending_kycs_length = num_pends;
- kr->details.kyc_status.timeout_kycs_length = num_touts;
- kyc->cb (kyc->cb_cls,
- kr);
return GNUNET_OK;
}
@@ -176,13 +202,13 @@ handle_get_kyc_finished (void *cls,
case MHD_HTTP_BAD_GATEWAY:
case MHD_HTTP_GATEWAY_TIMEOUT:
{
- json_t *pends;
- json_t *touts;
+ const json_t *pends;
+ const json_t *touts;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("pending_kycs",
- &pends),
- GNUNET_JSON_spec_json ("timeout_kycs",
- &touts),
+ GNUNET_JSON_spec_array_const ("pending_kycs",
+ &pends),
+ GNUNET_JSON_spec_array_const ("timeout_kycs",
+ &touts),
GNUNET_JSON_spec_end ()
};
@@ -195,20 +221,17 @@ handle_get_kyc_finished (void *cls,
kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
- if ( (! json_is_array (pends)) ||
- (! json_is_array (touts)) ||
- (GNUNET_OK !=
- parse_kyc (kyc,
- &kr,
- pends,
- touts)) )
+ if (GNUNET_OK !=
+ parse_kyc (kyc,
+ &kr,
+ pends,
+ touts))
{
kr.hr.http_status = 0;
kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
/* parse_kyc called the continuation already */
- GNUNET_JSON_parse_free (spec);
TALER_MERCHANT_kyc_get_cancel (kyc);
return;
}
@@ -260,17 +283,19 @@ kyc_get (struct GNUNET_CURL_Context *ctx,
struct TALER_MERCHANT_KycGetHandle *kyc;
CURL *eh;
char timeout_ms[32];
+ unsigned int tms;
kyc = GNUNET_new (struct TALER_MERCHANT_KycGetHandle);
kyc->ctx = ctx;
kyc->cb = cb;
kyc->cb_cls = cb_cls;
+ tms = (unsigned int) (timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.
+ rel_value_us);
GNUNET_snprintf (timeout_ms,
sizeof (timeout_ms),
- "%llu",
- (unsigned long long) (timeout.rel_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.
- rel_value_us));
+ "%u",
+ tms);
kyc->url = TALER_url_join (url,
"kyc",
"h_wire",
@@ -298,6 +323,13 @@ kyc_get (struct GNUNET_CURL_Context *ctx,
"Requesting URL '%s'\n",
kyc->url);
eh = TALER_MERCHANT_curl_easy_get_ (kyc->url);
+ if (0 != tms)
+ {
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 100L)));
+ }
kyc->job = GNUNET_CURL_job_add (ctx,
eh,
&handle_get_kyc_finished,
@@ -321,7 +353,7 @@ TALER_MERCHANT_kyc_get (struct GNUNET_CURL_Context *ctx,
"%sprivate/",
backend_url);
return kyc_get (ctx,
- url,
+ url, /* consumed! */
h_wire,
exchange_url,
timeout,
@@ -347,7 +379,7 @@ TALER_MERCHANT_management_kyc_get (struct GNUNET_CURL_Context *ctx,
backend_url,
instance_id);
return kyc_get (ctx,
- url,
+ url, /* consumed! */
h_wire,
exchange_url,
timeout,
diff --git a/src/lib/merchant_api_get_orders.c b/src/lib/merchant_api_get_orders.c
index 7f08acb6..459409fd 100644
--- a/src/lib/merchant_api_get_orders.c
+++ b/src/lib/merchant_api_get_orders.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018, 2020, 2021 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -30,6 +30,10 @@
#include <taler/taler_json_lib.h>
#include <taler/taler_signatures.h>
+/**
+ * Maximum number of orders we return.
+ */
+#define MAX_ORDERS 1024
/**
* Handle for a GET /orders operation.
@@ -68,65 +72,64 @@ struct TALER_MERCHANT_OrdersGetHandle
* Parse order information from @a ia.
*
* @param ia JSON array (or NULL!) with order data
+ * @param[in] ogr response to fill
* @param ogh operation handle
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
parse_orders (const json_t *ia,
+ struct TALER_MERCHANT_OrdersGetResponse *ogr,
struct TALER_MERCHANT_OrdersGetHandle *ogh)
{
- unsigned int oes_len = json_array_size (ia);
- struct TALER_MERCHANT_OrderEntry oes[oes_len];
- size_t index;
- json_t *value;
- int ret;
-
- ret = GNUNET_OK;
- json_array_foreach (ia, index, value) {
- struct TALER_MERCHANT_OrderEntry *ie = &oes[index];
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("order_id",
- &ie->order_id),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &ie->timestamp),
- GNUNET_JSON_spec_uint64 ("row_id",
- &ie->order_serial),
- TALER_JSON_spec_amount_any ("amount",
- &ie->amount),
- GNUNET_JSON_spec_string ("summary",
- &ie->summary),
- GNUNET_JSON_spec_bool ("refundable",
- &ie->refundable),
- GNUNET_JSON_spec_bool ("paid",
- &ie->paid),
- GNUNET_JSON_spec_end ()
- };
+ unsigned int oes_len = (unsigned int) json_array_size (ia);
- if (GNUNET_OK !=
- GNUNET_JSON_parse (value,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- ret = GNUNET_SYSERR;
- continue;
- }
- if (GNUNET_SYSERR == ret)
- break;
+ if ( (json_array_size (ia) != (size_t) oes_len) ||
+ (oes_len > MAX_ORDERS) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
- if (GNUNET_OK == ret)
{
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = MHD_HTTP_OK
- };
+ struct TALER_MERCHANT_OrderEntry oes[GNUNET_NZL (oes_len)];
+ size_t index;
+ json_t *value;
+
+ json_array_foreach (ia, index, value) {
+ struct TALER_MERCHANT_OrderEntry *ie = &oes[index];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("order_id",
+ &ie->order_id),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &ie->timestamp),
+ GNUNET_JSON_spec_uint64 ("row_id",
+ &ie->order_serial),
+ TALER_JSON_spec_amount_any ("amount",
+ &ie->amount),
+ GNUNET_JSON_spec_string ("summary",
+ &ie->summary),
+ GNUNET_JSON_spec_bool ("refundable",
+ &ie->refundable),
+ GNUNET_JSON_spec_bool ("paid",
+ &ie->paid),
+ GNUNET_JSON_spec_end ()
+ };
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ ogr->details.ok.orders_length = oes_len;
+ ogr->details.ok.orders = oes;
ogh->cb (ogh->cb_cls,
- &hr,
- oes_len,
- oes);
+ ogr);
ogh->cb = NULL; /* just to be sure */
}
- return ret;
+ return GNUNET_OK;
}
@@ -145,9 +148,9 @@ handle_get_orders_finished (void *cls,
{
struct TALER_MERCHANT_OrdersGetHandle *ogh = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_OrdersGetResponse ogr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
ogh->job = NULL;
@@ -158,10 +161,10 @@ handle_get_orders_finished (void *cls,
{
case MHD_HTTP_OK:
{
- json_t *orders;
+ const json_t *orders;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("orders",
- &orders),
+ GNUNET_JSON_spec_array_const ("orders",
+ &orders),
GNUNET_JSON_spec_end ()
};
@@ -170,52 +173,43 @@ handle_get_orders_finished (void *cls,
spec,
NULL, NULL))
{
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ ogr.hr.http_status = 0;
+ ogr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
}
- else
+ if (GNUNET_OK ==
+ parse_orders (orders,
+ &ogr,
+ ogh))
{
- if ( (! json_is_array (orders)) ||
- (GNUNET_OK ==
- parse_orders (orders,
- ogh)) )
- {
- GNUNET_JSON_parse_free (spec);
- TALER_MERCHANT_orders_get_cancel (ogh);
- return;
- }
- else
- {
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- }
+ TALER_MERCHANT_orders_get_cancel (ogh);
+ return;
}
- GNUNET_JSON_parse_free (spec);
+ ogr.hr.http_status = 0;
+ ogr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ ogr.hr.ec = TALER_JSON_get_error_code (json);
+ ogr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, merchant says we need to authenticate. */
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ ogr.hr.ec = TALER_JSON_get_error_code (json);
+ ogr.hr.hint = TALER_JSON_get_error_hint (json);
break;
default:
/* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ ogr.hr.ec = TALER_JSON_get_error_code (json);
+ ogr.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) ogr.hr.ec);
break;
}
ogh->cb (ogh->cb_cls,
- &hr,
- 0,
- NULL);
+ &ogr);
TALER_MERCHANT_orders_get_cancel (ogh);
}
@@ -255,12 +249,51 @@ TALER_MERCHANT_orders_get2 (
TALER_MERCHANT_OrdersGetCallback cb,
void *cb_cls)
{
+ return TALER_MERCHANT_orders_get3 (
+ ctx,
+ backend_url,
+ paid,
+ refunded,
+ wired,
+ NULL,
+ NULL,
+ date,
+ start_row,
+ delta,
+ timeout,
+ cb,
+ cb_cls);
+}
+
+
+struct TALER_MERCHANT_OrdersGetHandle *
+TALER_MERCHANT_orders_get3 (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ enum TALER_EXCHANGE_YesNoAll paid,
+ enum TALER_EXCHANGE_YesNoAll refunded,
+ enum TALER_EXCHANGE_YesNoAll wired,
+ const char *session_id,
+ const char *fulfillment_url,
+ struct GNUNET_TIME_Timestamp date,
+ uint64_t start_row,
+ int64_t delta,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_MERCHANT_OrdersGetCallback cb,
+ void *cb_cls)
+{
struct TALER_MERCHANT_OrdersGetHandle *ogh;
CURL *eh;
- unsigned int timeout_ms = timeout.rel_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
+ unsigned int tms = timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
GNUNET_assert (NULL != backend_url);
+ if ( (delta > MAX_ORDERS) ||
+ (delta < -MAX_ORDERS) )
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
if (0 == delta)
{
GNUNET_break (0);
@@ -274,6 +307,8 @@ TALER_MERCHANT_orders_get2 (
/* build ogh->url with the various optional arguments */
{
char *dstr;
+ char *fec = NULL;
+ char *sid = NULL;
bool have_date;
bool have_srow;
char cbuf[30];
@@ -282,8 +317,8 @@ TALER_MERCHANT_orders_get2 (
GNUNET_snprintf (tbuf,
sizeof (tbuf),
- "%llu",
- (unsigned long long) timeout_ms);
+ "%u",
+ tms);
GNUNET_snprintf (dbuf,
sizeof (dbuf),
"%lld",
@@ -292,6 +327,14 @@ TALER_MERCHANT_orders_get2 (
sizeof (cbuf),
"%llu",
(unsigned long long) start_row);
+ if (NULL != session_id)
+ (void) GNUNET_STRINGS_urlencode (strlen (session_id),
+ session_id,
+ &sid);
+ if (NULL != fulfillment_url)
+ (void) GNUNET_STRINGS_urlencode (strlen (fulfillment_url),
+ fulfillment_url,
+ &fec);
dstr = GNUNET_strdup (GNUNET_TIME_timestamp2s (date));
if (delta > 0)
{
@@ -330,11 +373,17 @@ TALER_MERCHANT_orders_get2 (
? dbuf
: NULL,
"timeout_ms",
- (0 != timeout_ms)
+ (0 != tms)
? tbuf
: NULL,
+ "session_id",
+ sid,
+ "fulfillment_url",
+ fec,
NULL);
GNUNET_free (dstr);
+ GNUNET_free (sid);
+ GNUNET_free (fec);
}
if (NULL == ogh->url)
{
@@ -347,6 +396,20 @@ TALER_MERCHANT_orders_get2 (
"Requesting URL '%s'\n",
ogh->url);
eh = TALER_MERCHANT_curl_easy_get_ (ogh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (ogh->url);
+ GNUNET_free (ogh);
+ return NULL;
+ }
+ if (0 != tms)
+ {
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 100L)));
+ }
ogh->job = GNUNET_CURL_job_add (ctx,
eh,
&handle_get_orders_finished,
diff --git a/src/lib/merchant_api_get_otp_device.c b/src/lib/merchant_api_get_otp_device.c
new file mode 100644
index 00000000..65950af8
--- /dev/null
+++ b/src/lib/merchant_api_get_otp_device.c
@@ -0,0 +1,210 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 2.1, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING.LGPL. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant_api_get_otp_device.c
+ * @brief Implementation of the GET /otp-devices/$ID request of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_curl_defaults.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Handle for a GET /otp-devices/$ID operation.
+ */
+struct TALER_MERCHANT_OtpDeviceGetHandle
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_OtpDeviceGetCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /otp-devices/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_OtpDeviceGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_get_otp_device_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_OtpDeviceGetHandle *tgh = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_OtpDeviceGetResponse tgr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ tgh->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got /otp-devices/$ID response with status code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ uint32_t alg32;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("otp_device_description",
+ &tgr.details.ok.otp_device_description),
+ GNUNET_JSON_spec_uint32 ("otp_algorithm",
+ &alg32),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint64 ("otp_ctr",
+ &tgr.details.ok.otp_ctr),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint64 ("otp_timestamp",
+ &tgr.details.ok.otp_timestamp_s),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("otp_code",
+ &tgr.details.ok.otp_code),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK ==
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ tgr.details.ok.otp_alg =
+ (enum TALER_MerchantConfirmationAlgorithm) alg32;
+ tgh->cb (tgh->cb_cls,
+ &tgr);
+ TALER_MERCHANT_otp_device_get_cancel (tgh);
+ return;
+ }
+ tgr.hr.http_status = 0;
+ tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ case MHD_HTTP_UNAUTHORIZED:
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we need to authenticate. */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) tgr.hr.ec);
+ break;
+ }
+ tgh->cb (tgh->cb_cls,
+ &tgr);
+ TALER_MERCHANT_otp_device_get_cancel (tgh);
+}
+
+
+struct TALER_MERCHANT_OtpDeviceGetHandle *
+TALER_MERCHANT_otp_device_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *otp_device_id,
+ TALER_MERCHANT_OtpDeviceGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_OtpDeviceGetHandle *tgh;
+ CURL *eh;
+
+ tgh = GNUNET_new (struct TALER_MERCHANT_OtpDeviceGetHandle);
+ tgh->ctx = ctx;
+ tgh->cb = cb;
+ tgh->cb_cls = cb_cls;
+ {
+ char *path;
+
+ GNUNET_asprintf (&path,
+ "private/otp-devices/%s",
+ otp_device_id);
+ tgh->url = TALER_url_join (backend_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == tgh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (tgh);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ tgh->url);
+ eh = TALER_MERCHANT_curl_easy_get_ (tgh->url);
+ tgh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_get_otp_device_finished,
+ tgh);
+ return tgh;
+}
+
+
+void
+TALER_MERCHANT_otp_device_get_cancel (
+ struct TALER_MERCHANT_OtpDeviceGetHandle *tgh)
+{
+ if (NULL != tgh->job)
+ GNUNET_CURL_job_cancel (tgh->job);
+ GNUNET_free (tgh->url);
+ GNUNET_free (tgh);
+}
diff --git a/src/lib/merchant_api_get_otp_devices.c b/src/lib/merchant_api_get_otp_devices.c
new file mode 100644
index 00000000..4737944c
--- /dev/null
+++ b/src/lib/merchant_api_get_otp_devices.c
@@ -0,0 +1,248 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 2.1, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING.LGPL. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant_api_get_otp_devices.c
+ * @brief Implementation of the GET /otp-devices request of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_curl_defaults.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+/**
+ * Maximum number of OTP devices we return.
+ */
+#define MAX_OTP 1024
+
+
+/**
+ * Handle for a GET /otp-devices operation.
+ */
+struct TALER_MERCHANT_OtpDevicesGetHandle
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_OtpDevicesGetCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Parse OTP device information from @a ia.
+ *
+ * @param ia JSON array (or NULL!) with otp_device data
+ * @param[in] tgr partially filled response
+ * @param tgh operation handle
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_otp_devices (const json_t *ia,
+ struct TALER_MERCHANT_OtpDevicesGetResponse *tgr,
+ struct TALER_MERCHANT_OtpDevicesGetHandle *tgh)
+{
+ unsigned int otp_len = (unsigned int) json_array_size (ia);
+
+ if ( (json_array_size (ia) != (size_t) otp_len) ||
+ (otp_len > MAX_OTP) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ {
+ struct TALER_MERCHANT_OtpDeviceEntry otp[GNUNET_NZL (otp_len)];
+ size_t index;
+ json_t *value;
+
+ json_array_foreach (ia, index, value) {
+ struct TALER_MERCHANT_OtpDeviceEntry *ie = &otp[index];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("otp_device_id",
+ &ie->otp_device_id),
+ GNUNET_JSON_spec_string ("device_description",
+ &ie->device_description),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ tgr->details.ok.otp_devices_length = otp_len;
+ tgr->details.ok.otp_devices = otp;
+ tgh->cb (tgh->cb_cls,
+ tgr);
+ tgh->cb = NULL; /* just to be sure */
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /otp-devices request.
+ *
+ * @param cls the `struct TALER_MERCHANT_OtpDevicesGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_get_otp_devices_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_OtpDevicesGetHandle *tgh = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_OtpDevicesGetResponse tgr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ tgh->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got /otp-devices response with status code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ const json_t *otp_devices;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("otp_devices",
+ &otp_devices),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ tgr.hr.http_status = 0;
+ tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ if (GNUNET_OK ==
+ parse_otp_devices (otp_devices,
+ &tgr,
+ tgh))
+ {
+ TALER_MERCHANT_otp_devices_get_cancel (tgh);
+ return;
+ }
+ tgr.hr.http_status = 0;
+ tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ case MHD_HTTP_UNAUTHORIZED:
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we need to authenticate. */
+ break;
+ default:
+ /* unexpected response code */
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) tgr.hr.ec);
+ break;
+ }
+ tgh->cb (tgh->cb_cls,
+ &tgr);
+ TALER_MERCHANT_otp_devices_get_cancel (tgh);
+}
+
+
+struct TALER_MERCHANT_OtpDevicesGetHandle *
+TALER_MERCHANT_otp_devices_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ TALER_MERCHANT_OtpDevicesGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_OtpDevicesGetHandle *tgh;
+ CURL *eh;
+
+ tgh = GNUNET_new (struct TALER_MERCHANT_OtpDevicesGetHandle);
+ tgh->ctx = ctx;
+ tgh->cb = cb;
+ tgh->cb_cls = cb_cls;
+ tgh->url = TALER_url_join (backend_url,
+ "private/otp-devices",
+ NULL);
+ if (NULL == tgh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (tgh);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ tgh->url);
+ eh = TALER_MERCHANT_curl_easy_get_ (tgh->url);
+ tgh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_get_otp_devices_finished,
+ tgh);
+ return tgh;
+}
+
+
+void
+TALER_MERCHANT_otp_devices_get_cancel (
+ struct TALER_MERCHANT_OtpDevicesGetHandle *tgh)
+{
+ if (NULL != tgh->job)
+ GNUNET_CURL_job_cancel (tgh->job);
+ GNUNET_free (tgh->url);
+ GNUNET_free (tgh);
+}
diff --git a/src/lib/merchant_api_get_product.c b/src/lib/merchant_api_get_product.c
index 94d38ec7..3f026bf3 100644
--- a/src/lib/merchant_api_get_product.c
+++ b/src/lib/merchant_api_get_product.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018, 2020 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -79,9 +79,9 @@ handle_get_product_finished (void *cls,
{
struct TALER_MERCHANT_ProductGetHandle *pgh = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_ProductGetResponse pgr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
pgh->job = NULL;
@@ -92,121 +92,80 @@ handle_get_product_finished (void *cls,
{
case MHD_HTTP_OK:
{
- const char *description;
- json_t *description_i18n;
- const char *unit;
- struct TALER_Amount price;
- const char *image;
- json_t *taxes;
- int64_t total_stock;
- uint64_t total_sold;
- uint64_t total_lost;
- json_t *address;
- bool rst_ok = true;
- struct GNUNET_TIME_Timestamp next_restock
- = GNUNET_TIME_UNIT_ZERO_TS;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("description",
- &description),
- GNUNET_JSON_spec_json ("description_i18n",
- &description_i18n),
- GNUNET_JSON_spec_string ("unit",
- &unit),
- TALER_JSON_spec_amount_any ("price",
- &price),
- GNUNET_JSON_spec_string ("image",
- &image),
- GNUNET_JSON_spec_json ("taxes",
- &taxes),
- GNUNET_JSON_spec_int64 ("total_stock",
- &total_stock),
- GNUNET_JSON_spec_uint64 ("total_sold",
- &total_sold),
- GNUNET_JSON_spec_uint64 ("total_lost",
- &total_lost),
- GNUNET_JSON_spec_json ("address",
- &address),
+ GNUNET_JSON_spec_string (
+ "description",
+ &pgr.details.ok.description),
+ GNUNET_JSON_spec_object_const (
+ "description_i18n",
+ &pgr.details.ok.description_i18n),
+ GNUNET_JSON_spec_string (
+ "unit",
+ &pgr.details.ok.unit),
+ TALER_JSON_spec_amount_any (
+ "price",
+ &pgr.details.ok.price),
+ GNUNET_JSON_spec_string (
+ "image",
+ &pgr.details.ok.image),
+ GNUNET_JSON_spec_array_const (
+ "taxes",
+ &pgr.details.ok.taxes),
+ GNUNET_JSON_spec_int64 (
+ "total_stock",
+ &pgr.details.ok.total_stock),
+ GNUNET_JSON_spec_uint64 (
+ "total_sold",
+ &pgr.details.ok.total_sold),
+ GNUNET_JSON_spec_uint64 (
+ "total_lost",
+ &pgr.details.ok.total_lost),
+ GNUNET_JSON_spec_object_const (
+ "address",
+ &pgr.details.ok.location),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("next_restock",
+ &pgr.details.ok.next_restock),
+ NULL),
GNUNET_JSON_spec_end ()
};
- if (NULL !=
- json_object_get (json,
- "next_restock"))
- {
- struct GNUNET_JSON_Specification spect[] = {
- GNUNET_JSON_spec_timestamp ("next_restock",
- &next_restock),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spect,
- NULL, NULL))
- rst_ok = false;
- }
-
-
- if ( (rst_ok) &&
- (GNUNET_OK ==
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL)) )
+ if (GNUNET_OK ==
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
{
pgh->cb (pgh->cb_cls,
- &hr,
- description,
- description_i18n,
- unit,
- &price,
- image,
- taxes,
- total_stock,
- total_sold,
- total_lost,
- address,
- next_restock);
+ &pgr);
GNUNET_JSON_parse_free (spec);
TALER_MERCHANT_product_get_cancel (pgh);
return;
}
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- GNUNET_JSON_parse_free (spec);
+ pgr.hr.http_status = 0;
+ pgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ pgr.hr.ec = TALER_JSON_get_error_code (json);
+ pgr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, merchant says we need to authenticate. */
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ pgr.hr.ec = TALER_JSON_get_error_code (json);
+ pgr.hr.hint = TALER_JSON_get_error_hint (json);
break;
default:
/* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ pgr.hr.ec = TALER_JSON_get_error_code (json);
+ pgr.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) pgr.hr.ec);
break;
}
pgh->cb (pgh->cb_cls,
- &hr,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- 0,
- 0,
- 0,
- NULL,
- GNUNET_TIME_UNIT_FOREVER_TS);
+ &pgr);
TALER_MERCHANT_product_get_cancel (pgh);
}
diff --git a/src/lib/merchant_api_get_products.c b/src/lib/merchant_api_get_products.c
index c3cc30e7..c33e24c9 100644
--- a/src/lib/merchant_api_get_products.c
+++ b/src/lib/merchant_api_get_products.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018, 2020 Taler Systems SA
+ Copyright (C) 2014-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -32,6 +32,12 @@
/**
+ * Maximum number of products we return.
+ */
+#define MAX_PRODUCTS 1024
+
+
+/**
* Handle for a GET /products operation.
*/
struct TALER_MERCHANT_ProductsGetHandle
@@ -67,54 +73,68 @@ struct TALER_MERCHANT_ProductsGetHandle
/**
* Parse product information from @a ia.
*
+ * @param json overall JSON reply
* @param ia JSON array (or NULL!) with product data
* @param pgh operation handle
* @return #GNUNET_OK on success
*/
-static int
-parse_products (const json_t *ia,
+static enum GNUNET_GenericReturnValue
+parse_products (const json_t *json,
+ const json_t *ia,
struct TALER_MERCHANT_ProductsGetHandle *pgh)
{
unsigned int ies_len = json_array_size (ia);
- struct TALER_MERCHANT_InventoryEntry ies[ies_len];
- size_t index;
- json_t *value;
- int ret;
-
- ret = GNUNET_OK;
- json_array_foreach (ia, index, value) {
- struct TALER_MERCHANT_InventoryEntry *ie = &ies[index];
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("product_id",
- &ie->product_id),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (value,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- ret = GNUNET_SYSERR;
- continue;
- }
- if (GNUNET_SYSERR == ret)
- break;
+
+ if ( (json_array_size (ia) != (size_t) ies_len) ||
+ (ies_len > MAX_PRODUCTS) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
- if (GNUNET_OK == ret)
{
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = MHD_HTTP_OK
- };
-
- pgh->cb (pgh->cb_cls,
- &hr,
- ies_len,
- ies);
- pgh->cb = NULL; /* just to be sure */
+ struct TALER_MERCHANT_InventoryEntry ies[GNUNET_NZL (ies_len)];
+ size_t index;
+ json_t *value;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = GNUNET_OK;
+ json_array_foreach (ia, index, value) {
+ struct TALER_MERCHANT_InventoryEntry *ie = &ies[index];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("product_id",
+ &ie->product_id),
+ GNUNET_JSON_spec_uint64 ("product_serial",
+ &ie->product_serial),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ret = GNUNET_SYSERR;
+ continue;
+ }
+ if (GNUNET_SYSERR == ret)
+ break;
+ }
+ if (GNUNET_OK == ret)
+ {
+ struct TALER_MERCHANT_GetProductsResponse gpr = {
+ .hr.http_status = MHD_HTTP_OK,
+ .hr.reply = json,
+ .details.ok.products_length = ies_len,
+ .details.ok.products = ies
+ };
+
+ pgh->cb (pgh->cb_cls,
+ &gpr);
+ pgh->cb = NULL; /* just to be sure */
+ }
+ return ret;
}
- return ret;
}
@@ -133,9 +153,9 @@ handle_get_products_finished (void *cls,
{
struct TALER_MERCHANT_ProductsGetHandle *pgh = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_GetProductsResponse gpr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
pgh->job = NULL;
@@ -146,10 +166,10 @@ handle_get_products_finished (void *cls,
{
case MHD_HTTP_OK:
{
- json_t *products;
+ const json_t *products;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("products",
- &products),
+ GNUNET_JSON_spec_array_const ("products",
+ &products),
GNUNET_JSON_spec_end ()
};
@@ -158,48 +178,39 @@ handle_get_products_finished (void *cls,
spec,
NULL, NULL))
{
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ gpr.hr.http_status = 0;
+ gpr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
}
- else
+ if (GNUNET_OK ==
+ parse_products (json,
+ products,
+ pgh))
{
- if ( (! json_is_array (products)) ||
- (GNUNET_OK ==
- parse_products (products,
- pgh)) )
- {
- GNUNET_JSON_parse_free (spec);
- TALER_MERCHANT_products_get_cancel (pgh);
- return;
- }
- else
- {
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- }
+ TALER_MERCHANT_products_get_cancel (pgh);
+ return;
}
- GNUNET_JSON_parse_free (spec);
+ gpr.hr.http_status = 0;
+ gpr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ gpr.hr.ec = TALER_JSON_get_error_code (json);
+ gpr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, merchant says we need to authenticate. */
break;
default:
/* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ gpr.hr.ec = TALER_JSON_get_error_code (json);
+ gpr.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) gpr.hr.ec);
break;
}
pgh->cb (pgh->cb_cls,
- &hr,
- 0,
- NULL);
+ &gpr);
TALER_MERCHANT_products_get_cancel (pgh);
}
diff --git a/src/lib/merchant_api_get_reserve.c b/src/lib/merchant_api_get_reserve.c
deleted file mode 100644
index 6c970db8..00000000
--- a/src/lib/merchant_api_get_reserve.c
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
- Foundation; either version 2.1, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along with
- TALER; see the file COPYING.LGPL. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file merchant_api_get_reserve.c
- * @brief Implementation of the GET /reserve request of the merchant's HTTP API
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <curl/curl.h>
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_merchant_service.h"
-#include "merchant_api_common.h"
-#include "merchant_api_curl_defaults.h"
-#include <taler/taler_json_lib.h>
-#include <taler/taler_signatures.h>
-
-
-/**
- * @brief A Handle for tracking wire reserve.
- */
-struct TALER_MERCHANT_ReserveGetHandle
-{
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_MERCHANT_ReserveGetCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Reference to the execution context.
- */
- struct GNUNET_CURL_Context *ctx;
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP GET /reserve request.
- *
- * @param cls the `struct TALER_MERCHANT_ReserveGetHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response response body, NULL if not in JSON
- */
-static void
-handle_reserve_get_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_MERCHANT_ReserveGetHandle *rgh = cls;
- const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
- };
- bool active;
-
- rgh->job = NULL;
- switch (response_code)
- {
- case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- struct TALER_MERCHANT_ReserveSummary rs;
- const json_t *tips;
- const char *exchange_url = NULL;
- const char *payto_uri = NULL;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_timestamp ("creation_time",
- &rs.creation_time),
- GNUNET_JSON_spec_timestamp ("expiration_time",
- &rs.expiration_time),
- GNUNET_JSON_spec_bool ("active",
- &active),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("exchange_url",
- &exchange_url),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("payto_uri",
- &payto_uri),
- NULL),
- TALER_JSON_spec_amount_any ("merchant_initial_amount",
- &rs.merchant_initial_amount),
- TALER_JSON_spec_amount_any ("exchange_initial_amount",
- &rs.exchange_initial_amount),
- TALER_JSON_spec_amount_any ("pickup_amount",
- &rs.pickup_amount),
- TALER_JSON_spec_amount_any ("committed_amount",
- &rs.committed_amount),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
-
- tips = json_object_get (json,
- "tips");
- if ((NULL == tips) ||
- json_is_null (tips))
- {
- rgh->cb (rgh->cb_cls,
- &hr,
- &rs,
- false,
- NULL,
- NULL,
- 0,
- NULL);
- TALER_MERCHANT_reserve_get_cancel (rgh);
- return;
- }
- if (! json_is_array (tips))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- {
- size_t tds_length;
- json_t *tip;
- struct TALER_MERCHANT_TipDetails *tds;
- unsigned int i;
- bool ok;
-
- tds_length = json_array_size (tips);
- tds = GNUNET_new_array (tds_length,
- struct TALER_MERCHANT_TipDetails);
- ok = true;
- json_array_foreach (tips, i, tip) {
- struct TALER_MERCHANT_TipDetails *td = &tds[i];
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_fixed_auto ("tip_id",
- &td->tip_id),
- TALER_JSON_spec_amount_any ("total_amount",
- &td->amount),
- GNUNET_JSON_spec_string ("reason",
- &td->reason),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (tip,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- ok = false;
- break;
- }
- }
-
- if (! ok)
- {
- GNUNET_break_op (0);
- GNUNET_free (tds);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- rgh->cb (rgh->cb_cls,
- &hr,
- &rs,
- active,
- exchange_url,
- payto_uri,
- tds_length,
- tds);
- GNUNET_free (tds);
- TALER_MERCHANT_reserve_get_cancel (rgh);
- return;
- }
- }
- case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- /* Nothing really to verify, merchant says we need to authenticate. */
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &hr);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- (int) hr.ec);
- response_code = 0;
- break;
- }
- rgh->cb (rgh->cb_cls,
- &hr,
- NULL,
- false,
- NULL,
- NULL,
- 0,
- NULL);
- TALER_MERCHANT_reserve_get_cancel (rgh);
-}
-
-
-struct TALER_MERCHANT_ReserveGetHandle *
-TALER_MERCHANT_reserve_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- bool fetch_tips,
- TALER_MERCHANT_ReserveGetCallback cb,
- void *cb_cls)
-{
- struct TALER_MERCHANT_ReserveGetHandle *rgh;
- CURL *eh;
-
- rgh = GNUNET_new (struct TALER_MERCHANT_ReserveGetHandle);
- rgh->ctx = ctx;
- rgh->cb = cb;
- rgh->cb_cls = cb_cls;
- {
- char res_str[sizeof (*reserve_pub) * 2];
- char arg_str[sizeof (res_str) + 32];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (reserve_pub,
- sizeof (*reserve_pub),
- res_str,
- sizeof (res_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "private/reserves/%s",
- res_str);
- rgh->url = TALER_url_join (backend_url,
- arg_str,
- "tips",
- fetch_tips ? "yes" : "no",
- NULL);
- }
- if (NULL == rgh->url)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not construct request URL.\n");
- GNUNET_free (rgh);
- return NULL;
- }
- eh = TALER_MERCHANT_curl_easy_get_ (rgh->url);
- rgh->job = GNUNET_CURL_job_add (ctx,
- eh,
- &handle_reserve_get_finished,
- rgh);
- return rgh;
-}
-
-
-void
-TALER_MERCHANT_reserve_get_cancel (
- struct TALER_MERCHANT_ReserveGetHandle *rgh)
-{
- if (NULL != rgh->job)
- {
- GNUNET_CURL_job_cancel (rgh->job);
- rgh->job = NULL;
- }
- GNUNET_free (rgh->url);
- GNUNET_free (rgh);
-}
-
-
-/* end of merchant_api_get_reserve.c */
diff --git a/src/lib/merchant_api_get_reserves.c b/src/lib/merchant_api_get_reserves.c
deleted file mode 100644
index 9b3b45e9..00000000
--- a/src/lib/merchant_api_get_reserves.c
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2017, 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
- Foundation; either version 2.1, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along with
- TALER; see the file COPYING.LGPL. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file merchant_api_get_reserves.c
- * @brief Implementation of the GET /reserves request of the merchant's HTTP API
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <curl/curl.h>
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_merchant_service.h"
-#include "merchant_api_common.h"
-#include "merchant_api_curl_defaults.h"
-#include <taler/taler_json_lib.h>
-#include <taler/taler_signatures.h>
-
-
-/**
- * @brief A Handle for tracking wire reserves.
- */
-struct TALER_MERCHANT_ReservesGetHandle
-{
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_MERCHANT_ReservesGetCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Reference to the execution context.
- */
- struct GNUNET_CURL_Context *ctx;
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP GET /reserves request.
- *
- * @param cls the `struct TALER_MERCHANT_ReservesGetHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response response body, NULL if not in JSON
- */
-static void
-handle_reserves_get_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_MERCHANT_ReservesGetHandle *rgh = cls;
- const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
- };
-
- rgh->job = NULL;
- switch (response_code)
- {
- case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- json_t *reserves;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("reserves",
- &reserves),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- else
- {
- size_t rds_length;
- struct TALER_MERCHANT_ReserveSummary *rds;
- json_t *reserve;
- unsigned int i;
- bool ok;
-
- if (! json_is_array (reserves))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- rds_length = json_array_size (reserves);
- rds = GNUNET_new_array (rds_length,
- struct TALER_MERCHANT_ReserveSummary);
- ok = true;
- json_array_foreach (reserves, i, reserve) {
- struct TALER_MERCHANT_ReserveSummary *rd = &rds[i];
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_fixed_auto ("reserve_pub",
- &rd->reserve_pub),
- GNUNET_JSON_spec_timestamp ("creation_time",
- &rd->creation_time),
- GNUNET_JSON_spec_timestamp ("expiration_time",
- &rd->expiration_time),
- TALER_JSON_spec_amount_any ("merchant_initial_amount",
- &rd->merchant_initial_amount),
- TALER_JSON_spec_amount_any ("exchange_initial_amount",
- &rd->exchange_initial_amount),
- TALER_JSON_spec_amount_any ("pickup_amount",
- &rd->pickup_amount),
- TALER_JSON_spec_amount_any ("committed_amount",
- &rd->committed_amount),
- GNUNET_JSON_spec_bool ("active",
- &rd->active),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (reserve,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- ok = false;
- break;
- }
- }
-
- if (! ok)
- {
- GNUNET_break_op (0);
- GNUNET_free (rds);
- GNUNET_JSON_parse_free (spec);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- rgh->cb (rgh->cb_cls,
- &hr,
- rds_length,
- rds);
- GNUNET_free (rds);
- GNUNET_JSON_parse_free (spec);
- TALER_MERCHANT_reserves_get_cancel (rgh);
- return;
- }
- }
- case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- /* Nothing really to verify, merchant says we need to authenticate. */
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &hr);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- (int) hr.ec);
- response_code = 0;
- break;
- }
- rgh->cb (rgh->cb_cls,
- &hr,
- 0,
- NULL);
- TALER_MERCHANT_reserves_get_cancel (rgh);
-}
-
-
-struct TALER_MERCHANT_ReservesGetHandle *
-TALER_MERCHANT_reserves_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- struct GNUNET_TIME_Timestamp after,
- enum TALER_EXCHANGE_YesNoAll active,
- enum TALER_EXCHANGE_YesNoAll failures,
- TALER_MERCHANT_ReservesGetCallback cb,
- void *cb_cls)
-{
- struct TALER_MERCHANT_ReservesGetHandle *rgh;
- CURL *eh;
- const char *active_s = NULL;
- const char *failures_s = NULL;
- char *after_s;
-
- rgh = GNUNET_new (struct TALER_MERCHANT_ReservesGetHandle);
- rgh->ctx = ctx;
- rgh->cb = cb;
- rgh->cb_cls = cb_cls;
- active_s = TALER_yna_to_string (active);
- failures_s = TALER_yna_to_string (failures);
- after_s = GNUNET_strdup (GNUNET_TIME_timestamp2s (after));
- rgh->url = TALER_url_join (backend_url,
- "private/reserves",
- "active",
- active_s,
- "failures",
- failures_s,
- "after",
- GNUNET_TIME_absolute_is_zero (after.abs_time)
- ? NULL
- : after_s,
- NULL);
- GNUNET_free (after_s);
- if (NULL == rgh->url)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not construct request URL.\n");
- GNUNET_free (rgh);
- return NULL;
- }
- eh = TALER_MERCHANT_curl_easy_get_ (rgh->url);
- rgh->job = GNUNET_CURL_job_add (ctx,
- eh,
- &handle_reserves_get_finished,
- rgh);
- return rgh;
-}
-
-
-void
-TALER_MERCHANT_reserves_get_cancel (
- struct TALER_MERCHANT_ReservesGetHandle *rgh)
-{
- if (NULL != rgh->job)
- {
- GNUNET_CURL_job_cancel (rgh->job);
- rgh->job = NULL;
- }
- GNUNET_free (rgh->url);
- GNUNET_free (rgh);
-}
-
-
-/* end of merchant_api_get_reserves.c */
diff --git a/src/lib/merchant_api_get_template.c b/src/lib/merchant_api_get_template.c
index 920333b6..9bbcc93a 100644
--- a/src/lib/merchant_api_get_template.c
+++ b/src/lib/merchant_api_get_template.c
@@ -79,9 +79,9 @@ handle_get_template_finished (void *cls,
{
struct TALER_MERCHANT_TemplateGetHandle *tgh = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_TemplateGetResponse tgr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
tgh->job = NULL;
@@ -92,67 +92,55 @@ handle_get_template_finished (void *cls,
{
case MHD_HTTP_OK:
{
- const char *template_description;
- const char *image;
- json_t *template_contract;
- bool rst_ok = true;
+ const json_t *contract;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("template_description",
- &template_description),
+ &tgr.details.ok.template_description),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("image",
- &image),
+ GNUNET_JSON_spec_string ("otp_id",
+ &tgr.details.ok.otp_id),
NULL),
- GNUNET_JSON_spec_json ("template_contract",
- &template_contract),
+ GNUNET_JSON_spec_object_const ("template_contract",
+ &contract),
GNUNET_JSON_spec_end ()
};
-
- if ( (rst_ok) &&
- (GNUNET_OK ==
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL)) )
+ if (GNUNET_OK ==
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
{
+ tgr.details.ok.template_contract = contract;
tgh->cb (tgh->cb_cls,
- &hr,
- template_description,
- image,
- template_contract);
- GNUNET_JSON_parse_free (spec);
+ &tgr);
TALER_MERCHANT_template_get_cancel (tgh);
return;
}
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- GNUNET_JSON_parse_free (spec);
+ tgr.hr.http_status = 0;
+ tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, merchant says we need to authenticate. */
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
break;
default:
/* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) tgr.hr.ec);
break;
}
tgh->cb (tgh->cb_cls,
- &hr,
- NULL,
- NULL,
- NULL);
+ &tgr);
TALER_MERCHANT_template_get_cancel (tgh);
}
diff --git a/src/lib/merchant_api_get_templates.c b/src/lib/merchant_api_get_templates.c
index 030d80cb..f1f973b5 100644
--- a/src/lib/merchant_api_get_templates.c
+++ b/src/lib/merchant_api_get_templates.c
@@ -32,6 +32,12 @@
/**
+ * Maximum number of templates we return.
+ */
+#define MAX_TEMPLATES 1024
+
+
+/**
* Handle for a GET /templates operation.
*/
struct TALER_MERCHANT_TemplatesGetHandle
@@ -68,53 +74,52 @@ struct TALER_MERCHANT_TemplatesGetHandle
* Parse template information from @a ia.
*
* @param ia JSON array (or NULL!) with template data
+ * @param[in] tgr partially filled response
* @param tgh operation handle
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
parse_templates (const json_t *ia,
+ struct TALER_MERCHANT_TemplatesGetResponse *tgr,
struct TALER_MERCHANT_TemplatesGetHandle *tgh)
{
- unsigned int ies_len = json_array_size (ia);
- struct TALER_MERCHANT_TemplateEntry ies[ies_len];
- size_t index;
- json_t *value;
- int ret;
-
- ret = GNUNET_OK;
- json_array_foreach (ia, index, value) {
- struct TALER_MERCHANT_TemplateEntry *ie = &ies[index];
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("template_id",
- &ie->template_id),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (value,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- ret = GNUNET_SYSERR;
- continue;
- }
- if (GNUNET_SYSERR == ret)
- break;
+ unsigned int tmpl_len = (unsigned int) json_array_size (ia);
+
+ if ( (json_array_size (ia) != (size_t) tmpl_len) ||
+ (tmpl_len > MAX_TEMPLATES) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
- if (GNUNET_OK == ret)
{
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = MHD_HTTP_OK
- };
+ struct TALER_MERCHANT_TemplateEntry tmpl[GNUNET_NZL (tmpl_len)];
+ size_t index;
+ json_t *value;
+ json_array_foreach (ia, index, value) {
+ struct TALER_MERCHANT_TemplateEntry *ie = &tmpl[index];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("template_id",
+ &ie->template_id),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ tgr->details.ok.templates_length = tmpl_len;
+ tgr->details.ok.templates = tmpl;
tgh->cb (tgh->cb_cls,
- &hr,
- ies_len,
- ies);
+ tgr);
tgh->cb = NULL; /* just to be sure */
}
- return ret;
+ return GNUNET_OK;
}
@@ -133,9 +138,9 @@ handle_get_templates_finished (void *cls,
{
struct TALER_MERCHANT_TemplatesGetHandle *tgh = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_TemplatesGetResponse tgr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
tgh->job = NULL;
@@ -146,10 +151,10 @@ handle_get_templates_finished (void *cls,
{
case MHD_HTTP_OK:
{
- json_t *templates;
+ const json_t *templates;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("templates",
- &templates),
+ GNUNET_JSON_spec_array_const ("templates",
+ &templates),
GNUNET_JSON_spec_end ()
};
@@ -158,48 +163,39 @@ handle_get_templates_finished (void *cls,
spec,
NULL, NULL))
{
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ tgr.hr.http_status = 0;
+ tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
}
- else
+ if (GNUNET_OK ==
+ parse_templates (templates,
+ &tgr,
+ tgh))
{
- if ( (! json_is_array (templates)) ||
- (GNUNET_OK ==
- parse_templates (templates,
- tgh)) )
- {
- GNUNET_JSON_parse_free (spec);
- TALER_MERCHANT_templates_get_cancel (tgh);
- return;
- }
- else
- {
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- }
+ TALER_MERCHANT_templates_get_cancel (tgh);
+ return;
}
- GNUNET_JSON_parse_free (spec);
+ tgr.hr.http_status = 0;
+ tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, merchant says we need to authenticate. */
break;
default:
/* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) tgr.hr.ec);
break;
}
tgh->cb (tgh->cb_cls,
- &hr,
- 0,
- NULL);
+ &tgr);
TALER_MERCHANT_templates_get_cancel (tgh);
}
diff --git a/src/lib/merchant_api_get_tips.c b/src/lib/merchant_api_get_tips.c
deleted file mode 100644
index 7af6936b..00000000
--- a/src/lib/merchant_api_get_tips.c
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
- Foundation; either version 2.1, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along with
- TALER; see the file COPYING.LGPL. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file merchant_api_get_tips.c
- * @brief Implementation of the GET /private/tips request of the merchant's HTTP API
- * @author Jonathan Buchanan
- */
-#include "platform.h"
-#include <curl/curl.h>
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_merchant_service.h"
-#include "merchant_api_curl_defaults.h"
-#include <taler/taler_json_lib.h>
-#include <taler/taler_signatures.h>
-
-
-/**
- * Handle for a GET /private/tips operation.
- */
-struct TALER_MERCHANT_TipsGetHandle
-{
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_MERCHANT_TipsGetCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Reference to the execution context.
- */
- struct GNUNET_CURL_Context *ctx;
-
-};
-
-
-/**
- * Parse tip information from @a ia.
- *
- * @param ia JSON array (or NULL!) tip order data
- * @param tgh operation handle
- * @return #GNUNET_OK on success
- */
-static int
-parse_tips (const json_t *ia,
- struct TALER_MERCHANT_TipsGetHandle *tgh)
-{
- unsigned int tes_len = json_array_size (ia);
- struct TALER_MERCHANT_TipEntry tes[tes_len];
- size_t index;
- json_t *value;
- int ret;
-
- ret = GNUNET_OK;
- json_array_foreach (ia, index, value) {
- struct TALER_MERCHANT_TipEntry *ie = &tes[index];
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint64 ("row_id",
- &ie->row_id),
- GNUNET_JSON_spec_fixed_auto ("tip_id",
- &ie->tip_id),
- TALER_JSON_spec_amount_any ("tip_amount",
- &ie->tip_amount),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (value,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- ret = GNUNET_SYSERR;
- continue;
- }
- if (GNUNET_SYSERR == ret)
- break;
- }
- if (GNUNET_OK == ret)
- {
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = MHD_HTTP_OK
- };
-
- tgh->cb (tgh->cb_cls,
- &hr,
- tes_len,
- tes);
- tgh->cb = NULL; /* just to be sure */
- }
- return ret;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP GET /private/tips request.
- *
- * @param cls the `struct TALER_MERCHANT_TipsGetHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response response body, NULL if not in JSON
- */
-static void
-handle_get_tips_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_MERCHANT_TipsGetHandle *tgh = cls;
- const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
- };
-
- tgh->job = NULL;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Got /private/tips response with status code %u\n",
- (unsigned int) response_code);
- switch (response_code)
- {
- case MHD_HTTP_OK:
- {
- json_t *tips;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("tips",
- &tips),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- }
- else
- {
- if ( (! json_is_array (tips)) ||
- (GNUNET_OK ==
- parse_tips (tips,
- tgh)) )
- {
- GNUNET_JSON_parse_free (spec);
- TALER_MERCHANT_tips_get_cancel (tgh);
- return;
- }
- else
- {
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- }
- }
- GNUNET_JSON_parse_free (spec);
- break;
- }
- case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- /* Nothing really to verify, merchant says we need to authenticate. */
- break;
- default:
- /* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- (int) hr.ec);
- break;
- }
- tgh->cb (tgh->cb_cls,
- &hr,
- 0,
- NULL);
- TALER_MERCHANT_tips_get_cancel (tgh);
-}
-
-
-struct TALER_MERCHANT_TipsGetHandle *
-TALER_MERCHANT_tips_get (
- struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- TALER_MERCHANT_TipsGetCallback cb,
- void *cb_cls)
-{
- return TALER_MERCHANT_tips_get2 (ctx,
- backend_url,
- TALER_EXCHANGE_YNA_NO,
- -20,
- UINT64_MAX,
- cb,
- cb_cls);
-}
-
-
-struct TALER_MERCHANT_TipsGetHandle *
-TALER_MERCHANT_tips_get2 (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- enum TALER_EXCHANGE_YesNoAll expired,
- int64_t limit,
- uint64_t offset,
- TALER_MERCHANT_TipsGetCallback cb,
- void *cb_cls)
-{
- struct TALER_MERCHANT_TipsGetHandle *tgh;
- CURL *eh;
-
- GNUNET_assert (NULL != backend_url);
- if (0 == limit)
- {
- GNUNET_break (0);
- return NULL;
- }
- tgh = GNUNET_new (struct TALER_MERCHANT_TipsGetHandle);
- tgh->ctx = ctx;
- tgh->cb = cb;
- tgh->cb_cls = cb_cls;
-
- /* build tgh->url with the various optional arguments */
- {
- char cbuf[30];
- char lbuf[30];
- bool have_offset;
-
- GNUNET_snprintf (lbuf,
- sizeof (lbuf),
- "%lld",
- (long long) limit);
-
- if (limit > 0)
- have_offset = (0 != offset);
- else
- have_offset = (UINT64_MAX != offset);
- GNUNET_snprintf (cbuf,
- sizeof (cbuf),
- "%llu",
- (unsigned long long) offset);
- tgh->url = TALER_url_join (backend_url,
- "private/tips",
- "expired",
- (TALER_EXCHANGE_YNA_ALL != expired)
- ? TALER_yna_to_string (expired)
- : NULL,
- "offset", (have_offset) ? cbuf : NULL,
- "limit", (-20 != limit) ? lbuf : NULL,
- NULL);
- }
- if (NULL == tgh->url)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not construct request URL.\n");
- GNUNET_free (tgh);
- return NULL;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Requesting URL '%s'\n",
- tgh->url);
- eh = TALER_MERCHANT_curl_easy_get_ (tgh->url);
- tgh->job = GNUNET_CURL_job_add (ctx,
- eh,
- &handle_get_tips_finished,
- tgh);
- return tgh;
-}
-
-
-void
-TALER_MERCHANT_tips_get_cancel (
- struct TALER_MERCHANT_TipsGetHandle *tgh)
-{
- if (NULL != tgh->job)
- GNUNET_CURL_job_cancel (tgh->job);
- GNUNET_free (tgh->url);
- GNUNET_free (tgh);
-}
diff --git a/src/lib/merchant_api_get_transfers.c b/src/lib/merchant_api_get_transfers.c
index f2f1186d..6116a54f 100644
--- a/src/lib/merchant_api_get_transfers.c
+++ b/src/lib/merchant_api_get_transfers.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2017, 2020 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -81,23 +81,23 @@ handle_transfers_get_finished (void *cls,
{
struct TALER_MERCHANT_GetTransfersHandle *gth = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_GetTransfersResponse gtr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
gth->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ gtr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
{
- json_t *transfers;
+ const json_t *transfers;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("transfers",
- &transfers),
+ GNUNET_JSON_spec_array_const ("transfers",
+ &transfers),
GNUNET_JSON_spec_end ()
};
@@ -107,26 +107,18 @@ handle_transfers_get_finished (void *cls,
NULL, NULL))
{
GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ gtr.hr.http_status = 0;
+ gtr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
- else
+
{
size_t tds_length;
struct TALER_MERCHANT_TransferData *tds;
json_t *transfer;
- unsigned int i;
+ size_t i;
bool ok;
- if (! json_is_array (transfers))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
tds_length = json_array_size (transfers);
tds = GNUNET_new_array (tds_length,
struct TALER_MERCHANT_TransferData);
@@ -138,9 +130,9 @@ handle_transfers_get_finished (void *cls,
&td->credit_amount),
GNUNET_JSON_spec_fixed_auto ("wtid",
&td->wtid),
- GNUNET_JSON_spec_string ("payto_uri",
- &td->payto_uri),
- GNUNET_JSON_spec_string ("exchange_url",
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &td->payto_uri),
+ TALER_JSON_spec_web_url ("exchange_url",
&td->exchange_url),
GNUNET_JSON_spec_uint64 ("transfer_serial_id",
&td->credit_serial),
@@ -174,55 +166,51 @@ handle_transfers_get_finished (void *cls,
{
GNUNET_break_op (0);
GNUNET_free (tds);
- GNUNET_JSON_parse_free (spec);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ gtr.hr.http_status = 0;
+ gtr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
+ gtr.details.ok.transfers = tds;
+ gtr.details.ok.transfers_length = tds_length;
gth->cb (gth->cb_cls,
- &hr,
- tds_length,
- tds);
+ &gtr);
GNUNET_free (tds);
- GNUNET_JSON_parse_free (spec);
TALER_MERCHANT_transfers_get_cancel (gth);
return;
}
}
case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ gtr.hr.ec = TALER_JSON_get_error_code (json);
+ gtr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, merchant says we need to authenticate. */
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, this should never
happen, we should pass the JSON reply to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ gtr.hr.ec = TALER_JSON_get_error_code (json);
+ gtr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ gtr.hr.ec = TALER_JSON_get_error_code (json);
+ gtr.hr.hint = TALER_JSON_get_error_hint (json);
break;
default:
/* unexpected response code */
GNUNET_break_op (0);
TALER_MERCHANT_parse_error_details_ (json,
response_code,
- &hr);
+ &gtr.hr);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
- response_code = 0;
+ (int) gtr.hr.ec);
+ gtr.hr.http_status = 0;
break;
}
gth->cb (gth->cb_cls,
- &hr,
- 0,
- NULL);
+ &gtr);
TALER_MERCHANT_transfers_get_cancel (gth);
}
diff --git a/src/lib/merchant_api_get_webhook.c b/src/lib/merchant_api_get_webhook.c
index 0e9abc6f..551aa915 100644
--- a/src/lib/merchant_api_get_webhook.c
+++ b/src/lib/merchant_api_get_webhook.c
@@ -101,7 +101,7 @@ handle_get_webhook_finished (void *cls,
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("event_type",
&event_type),
- GNUNET_JSON_spec_string ("url",
+ TALER_JSON_spec_web_url ("url",
&url),
GNUNET_JSON_spec_string ("http_method",
&http_method),
diff --git a/src/lib/merchant_api_get_webhooks.c b/src/lib/merchant_api_get_webhooks.c
index f5ba6f41..e702baac 100644
--- a/src/lib/merchant_api_get_webhooks.c
+++ b/src/lib/merchant_api_get_webhooks.c
@@ -32,6 +32,11 @@
/**
+ * Maximum number of webhooks we return.
+ */
+#define MAX_WEBHOOKS 1024
+
+/**
* Handle for a GET /webhooks operation.
*/
struct TALER_MERCHANT_WebhooksGetHandle
@@ -68,53 +73,52 @@ struct TALER_MERCHANT_WebhooksGetHandle
* Parse webhook information from @a ia.
*
* @param ia JSON array (or NULL!) with webhook data
+ * @param[in] wgr partially filled webhook response
* @param wgh operation handle
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
parse_webhooks (const json_t *ia,
+ struct TALER_MERCHANT_WebhooksGetResponse *wgr,
struct TALER_MERCHANT_WebhooksGetHandle *wgh)
{
- unsigned int ies_len = json_array_size (ia);
- struct TALER_MERCHANT_WebhookEntry ies[ies_len];
- size_t index;
- json_t *value;
- int ret;
-
- ret = GNUNET_OK;
- json_array_foreach (ia, index, value) {
- struct TALER_MERCHANT_WebhookEntry *ie = &ies[index];
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("webhook_id",
- &ie->webhook_id),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (value,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- ret = GNUNET_SYSERR;
- continue;
- }
- if (GNUNET_SYSERR == ret)
- break;
+ unsigned int whook_len = (unsigned int) json_array_size (ia);
+
+ if ( (json_array_size (ia) != (size_t) whook_len) ||
+ (whook_len > MAX_WEBHOOKS) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
- if (GNUNET_OK == ret)
{
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = MHD_HTTP_OK
- };
+ struct TALER_MERCHANT_WebhookEntry whook[GNUNET_NZL (whook_len)];
+ size_t index;
+ json_t *value;
+
+ json_array_foreach (ia, index, value) {
+ struct TALER_MERCHANT_WebhookEntry *ie = &whook[index];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("webhook_id",
+ &ie->webhook_id),
+ GNUNET_JSON_spec_end ()
+ };
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ wgr->details.ok.webhooks_length = whook_len;
+ wgr->details.ok.webhooks = whook;
wgh->cb (wgh->cb_cls,
- &hr,
- ies_len,
- ies);
+ wgr);
wgh->cb = NULL; /* just to be sure */
}
- return ret;
+ return GNUNET_OK;
}
@@ -133,9 +137,9 @@ handle_get_webhooks_finished (void *cls,
{
struct TALER_MERCHANT_WebhooksGetHandle *wgh = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_WebhooksGetResponse wgr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
wgh->job = NULL;
@@ -146,10 +150,10 @@ handle_get_webhooks_finished (void *cls,
{
case MHD_HTTP_OK:
{
- json_t *webhooks;
+ const json_t *webhooks;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("webhooks",
- &webhooks),
+ GNUNET_JSON_spec_array_const ("webhooks",
+ &webhooks),
GNUNET_JSON_spec_end ()
};
@@ -158,48 +162,39 @@ handle_get_webhooks_finished (void *cls,
spec,
NULL, NULL))
{
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ wgr.hr.http_status = 0;
+ wgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
}
- else
+ if (GNUNET_OK ==
+ parse_webhooks (webhooks,
+ &wgr,
+ wgh))
{
- if ( (! json_is_array (webhooks)) ||
- (GNUNET_OK ==
- parse_webhooks (webhooks,
- wgh)) )
- {
- GNUNET_JSON_parse_free (spec);
- TALER_MERCHANT_webhooks_get_cancel (wgh);
- return;
- }
- else
- {
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- }
+ TALER_MERCHANT_webhooks_get_cancel (wgh);
+ return;
}
- GNUNET_JSON_parse_free (spec);
+ wgr.hr.http_status = 0;
+ wgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ wgr.hr.ec = TALER_JSON_get_error_code (json);
+ wgr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, merchant says we need to authenticate. */
break;
default:
/* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ wgr.hr.ec = TALER_JSON_get_error_code (json);
+ wgr.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) wgr.hr.ec);
break;
}
wgh->cb (wgh->cb_cls,
- &hr,
- 0,
- NULL);
+ &wgr);
TALER_MERCHANT_webhooks_get_cancel (wgh);
}
diff --git a/src/lib/merchant_api_merchant_get_order.c b/src/lib/merchant_api_merchant_get_order.c
index 0e6b53bf..3a49db34 100644
--- a/src/lib/merchant_api_merchant_get_order.c
+++ b/src/lib/merchant_api_merchant_get_order.c
@@ -34,6 +34,17 @@
/**
+ * Maximum number of refund details we return.
+ */
+#define MAX_REFUND_DETAILS 1024
+
+/**
+ * Maximum number of wire details we return.
+ */
+#define MAX_WIRE_DETAILS 1024
+
+
+/**
* @brief A GET /private/orders/$ORDER handle
*/
struct TALER_MERCHANT_OrderMerchantGetHandle
@@ -81,21 +92,21 @@ handle_unpaid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount_any (
"total_amount",
- &osr->details.success.details.unpaid.contract_amount),
+ &osr->details.ok.details.unpaid.contract_amount),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string (
"already_paid_order_id",
- &osr->details.success.details.unpaid.already_paid_order_id),
+ &osr->details.ok.details.unpaid.already_paid_order_id),
NULL),
GNUNET_JSON_spec_string (
"taler_pay_uri",
- &osr->details.success.details.unpaid.taler_pay_uri),
+ &osr->details.ok.details.unpaid.taler_pay_uri),
GNUNET_JSON_spec_string (
"summary",
- &osr->details.success.details.unpaid.summary),
+ &osr->details.ok.details.unpaid.summary),
GNUNET_JSON_spec_timestamp (
"creation_time",
- &osr->details.success.details.unpaid.creation_time),
+ &osr->details.ok.details.unpaid.creation_time),
GNUNET_JSON_spec_end ()
};
@@ -111,7 +122,7 @@ handle_unpaid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
osr);
return;
}
- osr->details.success.status = TALER_MERCHANT_OSC_UNPAID;
+ osr->details.ok.status = TALER_MERCHANT_OSC_UNPAID;
omgh->cb (omgh->cb_cls,
osr);
}
@@ -130,9 +141,9 @@ handle_claimed (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
struct TALER_MERCHANT_OrderStatusResponse *osr)
{
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("contract_terms",
- (json_t **) &osr->details.success.details.claimed.
- contract_terms),
+ GNUNET_JSON_spec_object_const (
+ "contract_terms",
+ &osr->details.ok.details.claimed.contract_terms),
GNUNET_JSON_spec_end ()
};
@@ -148,10 +159,9 @@ handle_claimed (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
osr);
return;
}
- osr->details.success.status = TALER_MERCHANT_OSC_CLAIMED;
+ osr->details.ok.status = TALER_MERCHANT_OSC_CLAIMED;
omgh->cb (omgh->cb_cls,
osr);
- GNUNET_JSON_parse_free (spec);
}
@@ -167,35 +177,31 @@ static void
handle_paid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
struct TALER_MERCHANT_OrderStatusResponse *osr)
{
- uint32_t ec32;
uint32_t hc32;
- json_t *wire_details;
- json_t *wire_reports;
- json_t *refund_details;
+ const json_t *wire_details;
+ const json_t *refund_details;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_bool ("refunded",
- &osr->details.success.details.paid.refunded),
+ &osr->details.ok.details.paid.refunded),
GNUNET_JSON_spec_bool ("refund_pending",
- &osr->details.success.details.paid.refund_pending),
+ &osr->details.ok.details.paid.refund_pending),
GNUNET_JSON_spec_bool ("wired",
- &osr->details.success.details.paid.wired),
+ &osr->details.ok.details.paid.wired),
TALER_JSON_spec_amount_any ("deposit_total",
- &osr->details.success.details.paid.deposit_total),
- GNUNET_JSON_spec_uint32 ("exchange_code",
- &ec32),
+ &osr->details.ok.details.paid.deposit_total),
+ TALER_JSON_spec_ec ("exchange_code",
+ &osr->details.ok.details.paid.exchange_ec),
GNUNET_JSON_spec_uint32 ("exchange_http_status",
&hc32),
TALER_JSON_spec_amount_any ("refund_amount",
- &osr->details.success.details.paid.refund_amount),
- GNUNET_JSON_spec_json (
+ &osr->details.ok.details.paid.refund_amount),
+ GNUNET_JSON_spec_object_const (
"contract_terms",
- (json_t **) &osr->details.success.details.paid.contract_terms),
- GNUNET_JSON_spec_json ("wire_details",
- &wire_details),
- GNUNET_JSON_spec_json ("wire_reports",
- &wire_reports),
- GNUNET_JSON_spec_json ("refund_details",
- &refund_details),
+ &osr->details.ok.details.paid.contract_terms),
+ GNUNET_JSON_spec_array_const ("wire_details",
+ &wire_details),
+ GNUNET_JSON_spec_array_const ("refund_details",
+ &refund_details),
GNUNET_JSON_spec_end ()
};
@@ -211,143 +217,107 @@ handle_paid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
osr);
return;
}
- if (! (json_is_array (wire_details) &&
- json_is_array (wire_reports) &&
- json_is_array (refund_details) &&
- json_is_object (osr->details.success.details.paid.contract_terms)) )
- {
- GNUNET_break_op (0);
- osr->hr.http_status = 0;
- osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- omgh->cb (omgh->cb_cls,
- osr);
- GNUNET_JSON_parse_free (spec);
- return;
- }
- osr->details.success.status = TALER_MERCHANT_OSC_PAID;
- osr->details.success.details.paid.exchange_ec = (enum TALER_ErrorCode) ec32;
- osr->details.success.details.paid.exchange_hc = (unsigned int) hc32;
+ osr->details.ok.status = TALER_MERCHANT_OSC_PAID;
+
+ osr->details.ok.details.paid.exchange_hc = (unsigned int) hc32;
{
- unsigned int wts_len = json_array_size (wire_details);
- unsigned int wrs_len = json_array_size (wire_reports);
- unsigned int ref_len = json_array_size (refund_details);
- struct TALER_MERCHANT_WireTransfer wts[wts_len];
- struct TALER_MERCHANT_WireReport wrs[wrs_len];
- struct TALER_MERCHANT_RefundOrderDetail ref[ref_len];
-
- for (unsigned int i = 0; i<wts_len; i++)
+ unsigned int wts_len = (unsigned int) json_array_size (wire_details);
+ unsigned int ref_len = (unsigned int) json_array_size (refund_details);
+
+ if ( (json_array_size (wire_details) != (size_t) wts_len) ||
+ (wts_len > MAX_WIRE_DETAILS) )
{
- struct TALER_MERCHANT_WireTransfer *wt = &wts[i];
- const json_t *w = json_array_get (wire_details,
- i);
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_string ("exchange_url",
- &wt->exchange_url),
- GNUNET_JSON_spec_fixed_auto ("wtid",
- &wt->wtid),
- GNUNET_JSON_spec_timestamp ("execution_time",
- &wt->execution_time),
- TALER_JSON_spec_amount_any ("amount",
- &wt->total_amount),
- GNUNET_JSON_spec_bool ("confirmed",
- &wt->confirmed),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (w,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- osr->hr.http_status = 0;
- osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- omgh->cb (omgh->cb_cls,
- osr);
- GNUNET_JSON_parse_free (spec);
- return;
- }
+ GNUNET_break (0);
+ osr->hr.http_status = 0;
+ osr->hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
+ omgh->cb (omgh->cb_cls,
+ osr);
+ return;
}
-
- for (unsigned int i = 0; i<wrs_len; i++)
+ if ( (json_array_size (refund_details) != (size_t) ref_len) ||
+ (ref_len > MAX_REFUND_DETAILS) )
{
- struct TALER_MERCHANT_WireReport *wr = &wrs[i];
- const json_t *w = json_array_get (wire_reports, i);
- uint32_t c32;
- uint32_t eec32;
- uint32_t ehs32;
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_uint32 ("code",
- &c32),
- GNUNET_JSON_spec_string ("hint",
- &wr->hint),
- GNUNET_JSON_spec_uint32 ("exchange_code",
- &eec32),
- GNUNET_JSON_spec_uint32 ("exchange_http_status",
- &ehs32),
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &wr->coin_pub),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (w,
- ispec,
- NULL, NULL))
+ GNUNET_break (0);
+ osr->hr.http_status = 0;
+ osr->hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
+ omgh->cb (omgh->cb_cls,
+ osr);
+ return;
+ }
+ {
+ struct TALER_MERCHANT_WireTransfer wts[GNUNET_NZL (wts_len)];
+ struct TALER_MERCHANT_RefundOrderDetail ref[GNUNET_NZL (ref_len)];
+
+ for (unsigned int i = 0; i<wts_len; i++)
{
- GNUNET_break_op (0);
- osr->hr.http_status = 0;
- osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- omgh->cb (omgh->cb_cls,
- osr);
- GNUNET_JSON_parse_free (spec);
- return;
+ struct TALER_MERCHANT_WireTransfer *wt = &wts[i];
+ const json_t *w = json_array_get (wire_details,
+ i);
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_web_url ("exchange_url",
+ &wt->exchange_url),
+ GNUNET_JSON_spec_fixed_auto ("wtid",
+ &wt->wtid),
+ GNUNET_JSON_spec_timestamp ("execution_time",
+ &wt->execution_time),
+ TALER_JSON_spec_amount_any ("amount",
+ &wt->total_amount),
+ GNUNET_JSON_spec_bool ("confirmed",
+ &wt->confirmed),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (w,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ osr->hr.http_status = 0;
+ osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ omgh->cb (omgh->cb_cls,
+ osr);
+ return;
+ }
}
- wr->code = (enum TALER_ErrorCode) c32;
- wr->hr.ec = (enum TALER_ErrorCode) eec32;
- wr->hr.http_status = (unsigned int) ehs32;
- }
- for (unsigned int i = 0; i<ref_len; i++)
- {
- struct TALER_MERCHANT_RefundOrderDetail *ro = &ref[i];
- const json_t *w = json_array_get (refund_details,
- i);
- struct GNUNET_JSON_Specification ispec[] = {
- TALER_JSON_spec_amount_any ("amount",
- &ro->refund_amount),
- GNUNET_JSON_spec_string ("reason",
- &ro->reason),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &ro->refund_time),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (w,
- ispec,
- NULL, NULL))
+ for (unsigned int i = 0; i<ref_len; i++)
{
- GNUNET_break_op (0);
- osr->hr.http_status = 0;
- osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- omgh->cb (omgh->cb_cls,
- osr);
- GNUNET_JSON_parse_free (spec);
- return;
+ struct TALER_MERCHANT_RefundOrderDetail *ro = &ref[i];
+ const json_t *w = json_array_get (refund_details,
+ i);
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &ro->refund_amount),
+ GNUNET_JSON_spec_string ("reason",
+ &ro->reason),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &ro->refund_time),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (w,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ osr->hr.http_status = 0;
+ osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ omgh->cb (omgh->cb_cls,
+ osr);
+ return;
+ }
}
- }
- osr->details.success.details.paid.wts = wts;
- osr->details.success.details.paid.wts_len = wts_len;
- osr->details.success.details.paid.wrs = wrs;
- osr->details.success.details.paid.wrs_len = wrs_len;
- osr->details.success.details.paid.refunds = ref;
- osr->details.success.details.paid.refunds_len = ref_len;
- omgh->cb (omgh->cb_cls,
- osr);
+ osr->details.ok.details.paid.wts = wts;
+ osr->details.ok.details.paid.wts_len = wts_len;
+ osr->details.ok.details.paid.refunds = ref;
+ osr->details.ok.details.paid.refunds_len = ref_len;
+ omgh->cb (omgh->cb_cls,
+ osr);
+ }
}
- GNUNET_JSON_parse_free (spec);
}
@@ -464,27 +434,20 @@ handle_merchant_order_get_finished (void *cls,
struct TALER_MERCHANT_OrderMerchantGetHandle *
-TALER_MERCHANT_merchant_order_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const char *order_id,
- const char *session_id,
- bool transfer,
- struct GNUNET_TIME_Relative timeout,
- TALER_MERCHANT_OrderMerchantGetCallback cb,
- void *cb_cls)
+TALER_MERCHANT_merchant_order_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *order_id,
+ const char *session_id,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_MERCHANT_OrderMerchantGetCallback cb,
+ void *cb_cls)
{
struct TALER_MERCHANT_OrderMerchantGetHandle *omgh;
- unsigned long long tms;
- long tlong;
-
- tms = (unsigned long long) (timeout.rel_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
- /* set curl timeout to *our* long poll timeout plus one minute
- (for network latency and processing delays) */
- tlong = (long) (GNUNET_TIME_relative_add (timeout,
- GNUNET_TIME_UNIT_MINUTES).
- rel_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
+ unsigned int tms;
+
+ tms = (unsigned int) (timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
omgh = GNUNET_new (struct TALER_MERCHANT_OrderMerchantGetHandle);
omgh->ctx = ctx;
omgh->cb = cb;
@@ -495,7 +458,7 @@ TALER_MERCHANT_merchant_order_get (struct GNUNET_CURL_Context *ctx,
GNUNET_snprintf (timeout_ms,
sizeof (timeout_ms),
- "%llu",
+ "%u",
tms);
GNUNET_asprintf (&path,
"private/orders/%s",
@@ -503,7 +466,6 @@ TALER_MERCHANT_merchant_order_get (struct GNUNET_CURL_Context *ctx,
omgh->url = TALER_url_join (backend_url,
path,
"session_id", session_id,
- "transfer", transfer ? "YES" : "NO",
"timeout_ms", (0 != tms) ? timeout_ms : NULL,
NULL);
GNUNET_free (path);
@@ -527,16 +489,12 @@ TALER_MERCHANT_merchant_order_get (struct GNUNET_CURL_Context *ctx,
GNUNET_free (omgh);
return NULL;
}
- if (CURLE_OK !=
- curl_easy_setopt (eh,
- CURLOPT_TIMEOUT_MS,
- tlong))
+ if (0 != tms)
{
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- GNUNET_free (omgh->url);
- GNUNET_free (omgh);
- return NULL;
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 100L)));
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
diff --git a/src/lib/merchant_api_merchant_get_tip.c b/src/lib/merchant_api_merchant_get_tip.c
deleted file mode 100644
index 7466fccc..00000000
--- a/src/lib/merchant_api_merchant_get_tip.c
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
- Foundation; either version 2.1, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along with
- TALER; see the file COPYING.LGPL. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file merchant_api_merchant_get_tip.c
- * @brief Implementation of the GET /private/tips/$TIP_ID request of the merchant's HTTP API
- * @author Jonathan Buchanan
- */
-#include "platform.h"
-#include <curl/curl.h>
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_merchant_service.h"
-#include "merchant_api_common.h"
-#include "merchant_api_curl_defaults.h"
-#include <taler/taler_json_lib.h>
-#include <taler/taler_signatures.h>
-
-
-struct TALER_MERCHANT_TipMerchantGetHandle
-{
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_MERCHANT_TipMerchantGetCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Reference to the execution context.
- */
- struct GNUNET_CURL_Context *ctx;
-};
-
-
-static enum GNUNET_GenericReturnValue
-parse_pickups (const json_t *pa,
- struct TALER_MERCHANT_TipStatusResponse *tsr,
- struct TALER_MERCHANT_TipMerchantGetHandle *tgh)
-{
- unsigned int pa_len = json_array_size (pa);
- struct TALER_MERCHANT_PickupDetail pickups[pa_len];
- size_t index;
- json_t *value;
-
- json_array_foreach (pa, index, value)
- {
- struct TALER_MERCHANT_PickupDetail *pickup = &pickups[index];
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("pickup_id",
- &pickup->pickup_id),
- GNUNET_JSON_spec_uint64 ("num_planchets",
- &pickup->num_planchets),
- TALER_JSON_spec_amount_any ("requested_amount",
- &pickup->requested_amount),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (value,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- tsr->details.success.pickups_length = pa_len;
- tsr->details.success.pickups = pickups;
- tgh->cb (tgh->cb_cls,
- tsr);
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * GET /private/tips/$TIP_ID request.
- *
- * @param cls the `struct TALER_MERCHANT_TipMerchantGetHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response response body, NULL if not in JSON
- */
-static void
-handle_merchant_tip_get_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_MERCHANT_TipMerchantGetHandle *tgh = cls;
- const json_t *json = response;
- struct TALER_MERCHANT_TipStatusResponse tsr = {
- .hr.http_status = (unsigned int) response_code,
- .hr.reply = json
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Got /private/tips/$TIP_ID response with status code %u\n",
- (unsigned int) response_code);
- tgh->job = NULL;
- switch (response_code)
- {
- case MHD_HTTP_OK:
- {
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("total_authorized",
- &tsr.details.success.total_authorized),
- TALER_JSON_spec_amount_any ("total_picked_up",
- &tsr.details.success.total_picked_up),
- GNUNET_JSON_spec_string ("reason",
- &tsr.details.success.reason),
- GNUNET_JSON_spec_timestamp ("expiration",
- &tsr.details.success.expiration),
- GNUNET_JSON_spec_fixed_auto ("reserve_pub",
- &tsr.details.success.reserve_pub),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- tsr.hr.http_status = 0;
- tsr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- }
- else
- {
- json_t *pickups = json_object_get (json,
- "pickups");
- if (! json_is_array (pickups))
- {
- tgh->cb (tgh->cb_cls,
- &tsr);
- TALER_MERCHANT_merchant_tip_get_cancel (tgh);
- return;
- }
- if (GNUNET_OK ==
- parse_pickups (pickups,
- &tsr,
- tgh))
- {
- GNUNET_JSON_parse_free (spec);
- TALER_MERCHANT_merchant_tip_get_cancel (tgh);
- return;
- }
- tsr.hr.http_status = 0;
- tsr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- }
- GNUNET_JSON_parse_free (spec);
- break;
- }
- case MHD_HTTP_UNAUTHORIZED:
- tsr.hr.ec = TALER_JSON_get_error_code (json);
- tsr.hr.hint = TALER_JSON_get_error_hint (json);
- /* Nothing really to verify, merchant says we need to authenticate. */
- break;
- case MHD_HTTP_NOT_FOUND:
- /* legal, can happen if instance or tip reserve is unknown */
- tsr.hr.ec = TALER_JSON_get_error_code (json);
- tsr.hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- tsr.hr.ec = TALER_JSON_get_error_code (json);
- tsr.hr.hint = TALER_JSON_get_error_hint (json);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &tsr.hr);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- (int) tsr.hr.ec);
- break;
- }
- tgh->cb (tgh->cb_cls,
- &tsr);
- TALER_MERCHANT_merchant_tip_get_cancel (tgh);
-}
-
-
-struct TALER_MERCHANT_TipMerchantGetHandle *
-TALER_MERCHANT_merchant_tip_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_TipIdentifierP *tip_id,
- const struct TALER_Amount *min_pick_up,
- struct GNUNET_TIME_Relative lp_timeout,
- bool pickups,
- TALER_MERCHANT_TipMerchantGetCallback cb,
- void *cb_cls)
-{
- struct TALER_MERCHANT_TipMerchantGetHandle *tgh;
- CURL *eh;
-
- GNUNET_assert (NULL != backend_url);
- tgh = GNUNET_new (struct TALER_MERCHANT_TipMerchantGetHandle);
- tgh->ctx = ctx;
- tgh->cb = cb;
- tgh->cb_cls = cb_cls;
-
- {
- char res_str[sizeof (*tip_id) * 2];
- char arg_str[sizeof (res_str) + 48];
- char timeout_str[32];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (tip_id,
- sizeof (*tip_id),
- res_str,
- sizeof (res_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "private/tips/%s",
- res_str);
- GNUNET_snprintf (timeout_str,
- sizeof (timeout_str),
- "%llu",
- ((unsigned long long)
- lp_timeout.rel_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us));
- tgh->url = TALER_url_join (backend_url,
- arg_str,
- "pickups",
- pickups
- ? "yes"
- : NULL,
- "min_amount",
- min_pick_up
- ? TALER_amount2s (min_pick_up)
- : NULL,
- "timeout_ms",
- GNUNET_TIME_relative_is_zero (lp_timeout)
- ? NULL
- : timeout_str,
- NULL);
- }
- if (NULL == tgh->url)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not construct request URL.\n");
- GNUNET_free (tgh);
- return NULL;
- }
-
- eh = TALER_MERCHANT_curl_easy_get_ (tgh->url);
- tgh->job = GNUNET_CURL_job_add (ctx,
- eh,
- &handle_merchant_tip_get_finished,
- tgh);
- return tgh;
-}
-
-
-void
-TALER_MERCHANT_merchant_tip_get_cancel (
- struct TALER_MERCHANT_TipMerchantGetHandle *tgh)
-{
- if (NULL != tgh->job)
- {
- GNUNET_CURL_job_cancel (tgh->job);
- tgh->job = NULL;
- }
- GNUNET_free (tgh->url);
- GNUNET_free (tgh);
-}
-
-
-/* end of merchant_api_merchant_get_tip.c */
diff --git a/src/lib/merchant_api_patch_account.c b/src/lib/merchant_api_patch_account.c
new file mode 100644
index 00000000..ce0e74d4
--- /dev/null
+++ b/src/lib/merchant_api_patch_account.c
@@ -0,0 +1,254 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with TALER; see the file COPYING.LGPL.
+ If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant_api_patch_account.c
+ * @brief Implementation of the PATCH /accounts/$ID request
+ * of the merchant's HTTP API
+ * @author Priscilla HUANG
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_common.h"
+#include "merchant_api_curl_defaults.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_curl_lib.h>
+
+
+/**
+ * Handle for a PATCH /accounts/$ID operation.
+ */
+struct TALER_MERCHANT_AccountPatchHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_AccountPatchCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP PATCH /accounts/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_AccountPatchHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_patch_account_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_AccountPatchHandle *tph = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_HttpResponse hr = {
+ .http_status = (unsigned int) response_code,
+ .reply = json
+ };
+
+ tph->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "PATCH /accounts/$ID completed with response code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case 0:
+ hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_break_op (0);
+ /* This should never happen, either us
+ * or the merchant is buggy (or API version conflict);
+ * just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we need to authenticate. */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we tried to abort the payment
+ * after it was successful. We should pass the JSON reply to the
+ * application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_CONFLICT:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* Server had an internal issue; we should retry,
+ but this API leaves this to the application */
+ break;
+ default:
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &hr);
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ tph->cb (tph->cb_cls,
+ &hr);
+ TALER_MERCHANT_account_patch_cancel (tph);
+}
+
+
+struct TALER_MERCHANT_AccountPatchHandle *
+TALER_MERCHANT_account_patch (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *credit_facade_url,
+ const json_t *credit_facade_credentials,
+ TALER_MERCHANT_AccountPatchCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_AccountPatchHandle *tph;
+ json_t *req_obj;
+
+ req_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("credit_facade_url",
+ credit_facade_url)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("credit_facade_credentials",
+ (json_t *) credit_facade_credentials)));
+ tph = GNUNET_new (struct TALER_MERCHANT_AccountPatchHandle);
+ tph->ctx = ctx;
+ tph->cb = cb;
+ tph->cb_cls = cb_cls;
+ {
+ char w_str[sizeof (*h_wire) * 2];
+ char *path;
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (h_wire,
+ sizeof (*h_wire),
+ w_str,
+ sizeof (w_str));
+ *end = '\0';
+ GNUNET_asprintf (&path,
+ "private/accounts/%s",
+ w_str);
+ tph->url = TALER_url_join (backend_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == tph->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ json_decref (req_obj);
+ GNUNET_free (tph);
+ return NULL;
+ }
+ {
+ CURL *eh;
+
+ eh = TALER_MERCHANT_curl_easy_get_ (tph->url);
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&tph->post_ctx,
+ eh,
+ req_obj))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ json_decref (req_obj);
+ GNUNET_free (tph);
+ return NULL;
+ }
+ json_decref (req_obj);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_CUSTOMREQUEST,
+ MHD_HTTP_METHOD_PATCH));
+ tph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ tph->post_ctx.headers,
+ &handle_patch_account_finished,
+ tph);
+ }
+ return tph;
+}
+
+
+void
+TALER_MERCHANT_account_patch_cancel (
+ struct TALER_MERCHANT_AccountPatchHandle *tph)
+{
+ if (NULL != tph->job)
+ {
+ GNUNET_CURL_job_cancel (tph->job);
+ tph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&tph->post_ctx);
+ GNUNET_free (tph->url);
+ GNUNET_free (tph);
+}
+
+
+/* end of merchant_api_patch_account.c */
diff --git a/src/lib/merchant_api_patch_instance.c b/src/lib/merchant_api_patch_instance.c
index 0613a852..420cd549 100644
--- a/src/lib/merchant_api_patch_instance.c
+++ b/src/lib/merchant_api_patch_instance.c
@@ -31,6 +31,7 @@
#include "merchant_api_curl_defaults.h"
#include "merchant_api_common.h"
#include <taler/taler_json_lib.h>
+#include <taler/taler_kyclogic_lib.h>
#include <taler/taler_curl_lib.h>
@@ -157,55 +158,37 @@ TALER_MERCHANT_instance_patch (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
const char *instance_id,
- unsigned int accounts_length,
- const char *payto_uris[],
const char *name,
+ enum TALER_KYCLOGIC_KycUserType ut,
const json_t *address,
const json_t *jurisdiction,
- const struct TALER_Amount *default_max_wire_fee,
- uint32_t default_wire_fee_amortization,
- const struct TALER_Amount *default_max_deposit_fee,
+ bool use_stefan,
struct GNUNET_TIME_Relative default_wire_transfer_delay,
struct GNUNET_TIME_Relative default_pay_delay,
TALER_MERCHANT_InstancePatchCallback cb,
void *cb_cls)
{
struct TALER_MERCHANT_InstancePatchHandle *iph;
- json_t *jpayto_uris;
json_t *req_obj;
+ const char *uts;
- jpayto_uris = json_array ();
- if (NULL == jpayto_uris)
+ uts = TALER_KYCLOGIC_kyc_user_type2s (ut);
+ if (NULL == uts)
{
GNUNET_break (0);
return NULL;
}
- for (unsigned int i = 0; i<accounts_length; i++)
- {
- if (0 !=
- json_array_append_new (jpayto_uris,
- json_string (payto_uris[i])))
- {
- GNUNET_break (0);
- json_decref (jpayto_uris);
- return NULL;
- }
- }
req_obj = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_array_steal ("payto_uris",
- jpayto_uris),
GNUNET_JSON_pack_string ("name",
name),
+ GNUNET_JSON_pack_string ("user_type",
+ uts),
GNUNET_JSON_pack_object_incref ("address",
(json_t *) address),
GNUNET_JSON_pack_object_incref ("jurisdiction",
(json_t *) jurisdiction),
- TALER_JSON_pack_amount ("default_max_wire_fee",
- default_max_wire_fee),
- GNUNET_JSON_pack_uint64 ("default_wire_fee_amortization",
- default_wire_fee_amortization),
- TALER_JSON_pack_amount ("default_max_deposit_fee",
- default_max_deposit_fee),
+ GNUNET_JSON_pack_bool ("use_stefan",
+ use_stefan),
GNUNET_JSON_pack_time_rel ("default_wire_transfer_delay",
default_wire_transfer_delay),
GNUNET_JSON_pack_time_rel ("default_pay_delay",
diff --git a/src/lib/merchant_api_patch_order_forget.c b/src/lib/merchant_api_patch_order_forget.c
index 572f00e6..118655db 100644
--- a/src/lib/merchant_api_patch_order_forget.c
+++ b/src/lib/merchant_api_patch_order_forget.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020, 2021 Taler Systems SA
+ Copyright (C) 2020, 2021, 2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -150,13 +150,14 @@ handle_forget_finished (void *cls,
struct TALER_MERCHANT_OrderForgetHandle *
-TALER_MERCHANT_order_forget (struct GNUNET_CURL_Context *ctx,
- const char *merchant_url,
- const char *order_id,
- unsigned int fields_length,
- const char *fields[],
- TALER_MERCHANT_ForgetCallback cb,
- void *cb_cls)
+TALER_MERCHANT_order_forget (
+ struct GNUNET_CURL_Context *ctx,
+ const char *merchant_url,
+ const char *order_id,
+ unsigned int fields_length,
+ const char *fields[static fields_length],
+ TALER_MERCHANT_ForgetCallback cb,
+ void *cb_cls)
{
struct TALER_MERCHANT_OrderForgetHandle *ofh;
json_t *req_fields;
@@ -236,8 +237,8 @@ TALER_MERCHANT_order_forget (struct GNUNET_CURL_Context *ctx,
void
-TALER_MERCHANT_order_forget_cancel (struct
- TALER_MERCHANT_OrderForgetHandle *ofh)
+TALER_MERCHANT_order_forget_cancel (
+ struct TALER_MERCHANT_OrderForgetHandle *ofh)
{
if (NULL != ofh->job)
{
diff --git a/src/lib/merchant_api_patch_otp_device.c b/src/lib/merchant_api_patch_otp_device.c
new file mode 100644
index 00000000..322efe7b
--- /dev/null
+++ b/src/lib/merchant_api_patch_otp_device.c
@@ -0,0 +1,252 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with TALER; see the file COPYING.LGPL.
+ If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant_api_patch_otp_device.c
+ * @brief Implementation of the PATCH /otp-devices/$ID request
+ * of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_common.h"
+#include "merchant_api_curl_defaults.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_curl_lib.h>
+
+
+/**
+ * Handle for a PATCH /otp-devices/$ID operation.
+ */
+struct TALER_MERCHANT_OtpDevicePatchHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_OtpDevicePatchCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP PATCH /otp-devices/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_OtpDevicePatchHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_patch_otp_device_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_OtpDevicePatchHandle *tph = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_HttpResponse hr = {
+ .http_status = (unsigned int) response_code,
+ .reply = json
+ };
+
+ tph->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "PATCH /otp-devices/$ID completed with response code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case 0:
+ hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_break_op (0);
+ /* This should never happen, either us
+ * or the merchant is buggy (or API version conflict);
+ * just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we need to authenticate. */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we tried to abort the payment
+ * after it was successful. We should pass the JSON reply to the
+ * application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_CONFLICT:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* Server had an internal issue; we should retry,
+ but this API leaves this to the application */
+ break;
+ default:
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &hr);
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ tph->cb (tph->cb_cls,
+ &hr);
+ TALER_MERCHANT_otp_device_patch_cancel (tph);
+}
+
+
+struct TALER_MERCHANT_OtpDevicePatchHandle *
+TALER_MERCHANT_otp_device_patch (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *otp_device_id,
+ const char *otp_device_description,
+ const char *otp_key,
+ enum TALER_MerchantConfirmationAlgorithm mca,
+ uint64_t otp_ctr,
+ TALER_MERCHANT_OtpDevicePatchCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_OtpDevicePatchHandle *tph;
+ json_t *req_obj;
+
+ req_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("otp_device_description",
+ otp_device_description),
+ GNUNET_JSON_pack_uint64 ("otp_algorithm",
+ (uint32_t) mca),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("otp_key",
+ otp_key)),
+ GNUNET_JSON_pack_uint64 ("otp_ctr",
+ otp_ctr));
+ tph = GNUNET_new (struct TALER_MERCHANT_OtpDevicePatchHandle);
+ tph->ctx = ctx;
+ tph->cb = cb;
+ tph->cb_cls = cb_cls;
+ {
+ char *path;
+
+ GNUNET_asprintf (&path,
+ "private/otp-devices/%s",
+ otp_device_id);
+ tph->url = TALER_url_join (backend_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == tph->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ json_decref (req_obj);
+ GNUNET_free (tph);
+ return NULL;
+ }
+ {
+ CURL *eh;
+
+ eh = TALER_MERCHANT_curl_easy_get_ (tph->url);
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&tph->post_ctx,
+ eh,
+ req_obj))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ json_decref (req_obj);
+ GNUNET_free (tph);
+ return NULL;
+ }
+ json_decref (req_obj);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_CUSTOMREQUEST,
+ MHD_HTTP_METHOD_PATCH));
+ tph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ tph->post_ctx.headers,
+ &handle_patch_otp_device_finished,
+ tph);
+ }
+ return tph;
+}
+
+
+void
+TALER_MERCHANT_otp_device_patch_cancel (
+ struct TALER_MERCHANT_OtpDevicePatchHandle *tph)
+{
+ if (NULL != tph->job)
+ {
+ GNUNET_CURL_job_cancel (tph->job);
+ tph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&tph->post_ctx);
+ GNUNET_free (tph->url);
+ GNUNET_free (tph);
+}
+
+
+/* end of merchant_api_patch_otp_device.c */
diff --git a/src/lib/merchant_api_patch_template.c b/src/lib/merchant_api_patch_template.c
index 44357cc0..7dfebf9c 100644
--- a/src/lib/merchant_api_patch_template.c
+++ b/src/lib/merchant_api_patch_template.c
@@ -162,7 +162,7 @@ TALER_MERCHANT_template_patch (
const char *backend_url,
const char *template_id,
const char *template_description,
- const char *image,
+ const char *otp_id,
json_t *template_contract,
TALER_MERCHANT_TemplatePatchCallback cb,
void *cb_cls)
@@ -174,8 +174,8 @@ TALER_MERCHANT_template_patch (
GNUNET_JSON_pack_string ("template_description",
template_description),
GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("image",
- image)),
+ GNUNET_JSON_pack_string ("otp_id",
+ otp_id)),
GNUNET_JSON_pack_object_incref ("template_contract",
(json_t *) template_contract));
tph = GNUNET_new (struct TALER_MERCHANT_TemplatePatchHandle);
diff --git a/src/lib/merchant_api_post_account.c b/src/lib/merchant_api_post_account.c
new file mode 100644
index 00000000..690aef17
--- /dev/null
+++ b/src/lib/merchant_api_post_account.c
@@ -0,0 +1,250 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with TALER; see the file COPYING.LGPL.
+ If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant_api_post_account.c
+ * @brief Implementation of the POST /account request
+ * of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_curl_defaults.h"
+#include "merchant_api_common.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_curl_lib.h>
+
+
+/**
+ * Handle for a POST /private/accounts operation.
+ */
+struct TALER_MERCHANT_AccountsPostHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_AccountsPostCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /account request.
+ *
+ * @param cls the `struct TALER_MERCHANT_AccountPostHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_post_account_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_AccountsPostHandle *aph = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_AccountsPostResponse apr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ aph->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "POST /accounts completed with response code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case 0:
+ apr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_wire",
+ &apr.details.ok.h_wire),
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &apr.details.ok.salt),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ apr.hr.http_status = 0;
+ apr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ GNUNET_break_op (0);
+ apr.hr.ec = TALER_JSON_get_error_code (json);
+ apr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* This should never happen, either us
+ * or the merchant is buggy (or API version conflict);
+ * just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ apr.hr.ec = TALER_JSON_get_error_code (json);
+ apr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ apr.hr.ec = TALER_JSON_get_error_code (json);
+ apr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the
+ application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ apr.hr.ec = TALER_JSON_get_error_code (json);
+ apr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ apr.hr.ec = TALER_JSON_get_error_code (json);
+ apr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Server had an internal issue; we should retry,
+ but this API leaves this to the application */
+ break;
+ default:
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &apr.hr);
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) apr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ aph->cb (aph->cb_cls,
+ &apr);
+ TALER_MERCHANT_accounts_post_cancel (aph);
+}
+
+
+struct TALER_MERCHANT_AccountsPostHandle *
+TALER_MERCHANT_accounts_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *payto_uri,
+ const char *credit_facade_url,
+ const json_t *credit_facade_credentials,
+ TALER_MERCHANT_AccountsPostCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_AccountsPostHandle *aph;
+ json_t *req_obj;
+
+ req_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string (
+ "payto_uri",
+ payto_uri),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string (
+ "credit_facade_url",
+ credit_facade_url)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref (
+ "credit_facade_credentials",
+ (json_t *) credit_facade_credentials))
+ );
+ aph = GNUNET_new (struct TALER_MERCHANT_AccountsPostHandle);
+ aph->ctx = ctx;
+ aph->cb = cb;
+ aph->cb_cls = cb_cls;
+ aph->url = TALER_url_join (backend_url,
+ "private/accounts",
+ NULL);
+ if (NULL == aph->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ json_decref (req_obj);
+ GNUNET_free (aph);
+ return NULL;
+ }
+ {
+ CURL *eh;
+
+ eh = TALER_MERCHANT_curl_easy_get_ (aph->url);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_curl_easy_post (&aph->post_ctx,
+ eh,
+ req_obj));
+ json_decref (req_obj);
+ aph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ aph->post_ctx.headers,
+ &handle_post_account_finished,
+ aph);
+ GNUNET_assert (NULL != aph->job);
+ }
+ return aph;
+}
+
+
+void
+TALER_MERCHANT_accounts_post_cancel (
+ struct TALER_MERCHANT_AccountsPostHandle *aph)
+{
+ if (NULL != aph->job)
+ {
+ GNUNET_CURL_job_cancel (aph->job);
+ aph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&aph->post_ctx);
+ GNUNET_free (aph->url);
+ GNUNET_free (aph);
+}
+
+
+/* end of merchant_api_post_account.c */
diff --git a/src/lib/merchant_api_post_instances.c b/src/lib/merchant_api_post_instances.c
index 183b3400..73d36369 100644
--- a/src/lib/merchant_api_post_instances.c
+++ b/src/lib/merchant_api_post_instances.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -32,6 +32,7 @@
#include "merchant_api_common.h"
#include <taler/taler_json_lib.h>
#include <taler/taler_curl_lib.h>
+#include <taler/taler_kyclogic_lib.h>
/**
@@ -163,14 +164,11 @@ TALER_MERCHANT_instances_post (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
const char *instance_id,
- unsigned int accounts_length,
- const char *payto_uris[],
const char *name,
+ enum TALER_KYCLOGIC_KycUserType ut,
const json_t *address,
const json_t *jurisdiction,
- const struct TALER_Amount *default_max_wire_fee,
- uint32_t default_wire_fee_amortization,
- const struct TALER_Amount *default_max_deposit_fee,
+ bool use_stefan,
struct GNUNET_TIME_Relative default_wire_transfer_delay,
struct GNUNET_TIME_Relative default_pay_delay,
const char *auth_token,
@@ -178,10 +176,16 @@ TALER_MERCHANT_instances_post (
void *cb_cls)
{
struct TALER_MERCHANT_InstancesPostHandle *iph;
- json_t *jpayto_uris;
json_t *req_obj;
json_t *auth_obj;
+ const char *uts;
+ uts = TALER_KYCLOGIC_kyc_user_type2s (ut);
+ if (NULL == uts)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
if (NULL != auth_token)
{
if (0 != strncasecmp (RFC_8959_PREFIX,
@@ -210,42 +214,19 @@ TALER_MERCHANT_instances_post (
GNUNET_break (0);
return NULL;
}
- jpayto_uris = json_array ();
- if (NULL == jpayto_uris)
- {
- json_decref (auth_obj);
- GNUNET_break (0);
- return NULL;
- }
- for (unsigned int i = 0; i<accounts_length; i++)
- {
- if (0 !=
- json_array_append_new (jpayto_uris,
- json_string (payto_uris[i])))
- {
- GNUNET_break (0);
- json_decref (auth_obj);
- json_decref (jpayto_uris);
- return NULL;
- }
- }
req_obj = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_array_steal ("payto_uris",
- jpayto_uris),
GNUNET_JSON_pack_string ("id",
instance_id),
GNUNET_JSON_pack_string ("name",
name),
+ GNUNET_JSON_pack_string ("user_type",
+ uts),
GNUNET_JSON_pack_object_incref ("address",
(json_t *) address),
GNUNET_JSON_pack_object_incref ("jurisdiction",
(json_t *) jurisdiction),
- TALER_JSON_pack_amount ("default_max_wire_fee",
- default_max_wire_fee),
- GNUNET_JSON_pack_uint64 ("default_wire_fee_amortization",
- default_wire_fee_amortization),
- TALER_JSON_pack_amount ("default_max_deposit_fee",
- default_max_deposit_fee),
+ GNUNET_JSON_pack_bool ("use_stefan",
+ use_stefan),
GNUNET_JSON_pack_time_rel ("default_wire_transfer_delay",
default_wire_transfer_delay),
GNUNET_JSON_pack_time_rel ("default_pay_delay",
diff --git a/src/lib/merchant_api_post_order_abort.c b/src/lib/merchant_api_post_order_abort.c
index b5a7a00b..270ceb7e 100644
--- a/src/lib/merchant_api_post_order_abort.c
+++ b/src/lib/merchant_api_post_order_abort.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -39,6 +39,12 @@
/**
+ * Maximum number of refunds we return.
+ */
+#define MAX_REFUNDS 1024
+
+
+/**
* @brief An abort Handle
*/
struct TALER_MERCHANT_OrderAbortHandle
@@ -102,17 +108,20 @@ struct TALER_MERCHANT_OrderAbortHandle
* OK. Otherwise returns #GNUNET_SYSERR.
*
* @param oah handle to operation that created the reply
+ * @param[in] ar abort response, partially initialized
* @param json the reply to parse
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah,
+ struct TALER_MERCHANT_AbortResponse *ar,
const json_t *json)
{
- json_t *refunds;
+ const json_t *refunds;
unsigned int num_refunds;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("refunds", &refunds),
+ GNUNET_JSON_spec_array_const ("refunds",
+ &refunds),
GNUNET_JSON_spec_end ()
};
@@ -124,13 +133,14 @@ check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if (! json_is_array (refunds))
+ num_refunds = (unsigned int) json_array_size (refunds);
+ if ( (json_array_size (refunds) != (size_t) num_refunds) ||
+ (num_refunds > MAX_REFUNDS) )
{
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
+ GNUNET_break (0);
return GNUNET_SYSERR;
}
- num_refunds = json_array_size (refunds);
+
{
struct TALER_MERCHANT_AbortedCoin res[GNUNET_NZL (num_refunds)];
@@ -150,7 +160,6 @@ check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah,
NULL, NULL))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
if (MHD_HTTP_OK == exchange_status)
@@ -169,7 +178,6 @@ check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah,
NULL, NULL))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
@@ -184,26 +192,17 @@ check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah,
&res[i].exchange_sig))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
}
}
- {
- struct TALER_MERCHANT_HttpResponse hr = {
- .reply = json,
- .http_status = MHD_HTTP_OK
- };
-
- oah->abort_cb (oah->abort_cb_cls,
- &hr,
- &oah->merchant_pub,
- num_refunds,
- res);
- }
+ ar->details.ok.merchant_pub = &oah->merchant_pub;
+ ar->details.ok.num_aborts = num_refunds;
+ ar->details.ok.aborts = res;
+ oah->abort_cb (oah->abort_cb_cls,
+ ar);
oah->abort_cb = NULL;
}
- GNUNET_JSON_parse_free (spec);
return GNUNET_OK;
}
@@ -223,9 +222,9 @@ handle_abort_finished (void *cls,
{
struct TALER_MERCHANT_OrderAbortHandle *oah = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_AbortResponse ar = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
oah->job = NULL;
@@ -235,40 +234,41 @@ handle_abort_finished (void *cls,
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK ==
check_abort_refund (oah,
+ &ar,
json))
{
TALER_MERCHANT_order_abort_cancel (oah);
return;
}
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ ar.hr.http_status = 0;
+ ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_BAD_REQUEST:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ ar.hr.ec = TALER_JSON_get_error_code (json);
+ ar.hr.hint = TALER_JSON_get_error_hint (json);
/* This should never happen, either us or the
merchant is buggy (or API version conflict); just
pass JSON reply to the application */
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ ar.hr.ec = TALER_JSON_get_error_code (json);
+ ar.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ ar.hr.ec = TALER_JSON_get_error_code (json);
+ ar.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, this should never
happen, we should pass the JSON reply to the
application */
break;
case MHD_HTTP_REQUEST_TIMEOUT:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ ar.hr.ec = TALER_JSON_get_error_code (json);
+ ar.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, merchant says one of
the signatures is invalid; as we checked them,
this should never happen, we should pass the JSON
@@ -276,19 +276,19 @@ handle_abort_finished (void *cls,
break;
case MHD_HTTP_PRECONDITION_FAILED:
/* Our *payment* already succeeded fully. */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ ar.hr.ec = TALER_JSON_get_error_code (json);
+ ar.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ ar.hr.ec = TALER_JSON_get_error_code (json);
+ ar.hr.hint = TALER_JSON_get_error_hint (json);
/* Server had an internal issue; we should retry,
but this API leaves this to the application */
break;
case MHD_HTTP_BAD_GATEWAY:
TALER_MERCHANT_parse_error_details_ (json,
response_code,
- &hr);
+ &ar.hr);
/* Nothing really to verify, the merchant is blaming the exchange.
We should pass the JSON reply to the application */
break;
@@ -296,33 +296,31 @@ handle_abort_finished (void *cls,
/* unexpected response code */
TALER_MERCHANT_parse_error_details_ (json,
response_code,
- &hr);
+ &ar.hr);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) ar.hr.ec);
GNUNET_break_op (0);
break;
}
oah->abort_cb (oah->abort_cb_cls,
- &hr,
- NULL,
- 0,
- NULL);
+ &ar);
TALER_MERCHANT_order_abort_cancel (oah);
}
struct TALER_MERCHANT_OrderAbortHandle *
-TALER_MERCHANT_order_abort (struct GNUNET_CURL_Context *ctx,
- const char *merchant_url,
- const char *order_id,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_PrivateContractHashP *h_contract,
- unsigned int num_coins,
- const struct TALER_MERCHANT_AbortCoin coins[],
- TALER_MERCHANT_AbortCallback cb,
- void *cb_cls)
+TALER_MERCHANT_order_abort (
+ struct GNUNET_CURL_Context *ctx,
+ const char *merchant_url,
+ const char *order_id,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_PrivateContractHashP *h_contract,
+ unsigned int num_coins,
+ const struct TALER_MERCHANT_AbortCoin coins[static num_coins],
+ TALER_MERCHANT_AbortCallback cb,
+ void *cb_cls)
{
struct TALER_MERCHANT_OrderAbortHandle *oah;
json_t *abort_obj;
@@ -389,9 +387,9 @@ TALER_MERCHANT_order_abort (struct GNUNET_CURL_Context *ctx,
oah->num_coins = num_coins;
oah->coins = GNUNET_new_array (num_coins,
struct TALER_MERCHANT_AbortCoin);
- memcpy (oah->coins,
- coins,
- num_coins * sizeof (struct TALER_MERCHANT_AbortCoin));
+ GNUNET_memcpy (oah->coins,
+ coins,
+ num_coins * sizeof (struct TALER_MERCHANT_AbortCoin));
{
CURL *eh;
diff --git a/src/lib/merchant_api_post_order_claim.c b/src/lib/merchant_api_post_order_claim.c
index 66e58c2a..76802ea5 100644
--- a/src/lib/merchant_api_post_order_claim.c
+++ b/src/lib/merchant_api_post_order_claim.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -86,40 +86,36 @@ handle_post_order_claim_finished (void *cls,
const void *response)
{
struct TALER_MERCHANT_OrderClaimHandle *och = cls;
- json_t *contract_terms;
- struct TALER_MerchantSignatureP sig;
- struct TALER_PrivateContractHashP hash;
const json_t *json = response;
+ struct TALER_MERCHANT_OrderClaimResponse ocr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("contract_terms",
- &contract_terms),
- GNUNET_JSON_spec_fixed_auto ("sig",
- &sig),
+ GNUNET_JSON_spec_object_const (
+ "contract_terms",
+ &ocr.details.ok.contract_terms),
+ GNUNET_JSON_spec_fixed_auto (
+ "sig",
+ &ocr.details.ok.sig),
GNUNET_JSON_spec_end ()
};
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
- };
och->job = NULL;
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Order claimed with status %u\n",
(unsigned int) response_code);
if (MHD_HTTP_OK != response_code)
{
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ ocr.hr.ec = TALER_JSON_get_error_code (json);
+ ocr.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Proposal lookup failed with HTTP status code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) ocr.hr.ec);
och->cb (och->cb_cls,
- &hr,
- NULL,
- NULL,
- NULL);
+ &ocr);
TALER_MERCHANT_order_claim_cancel (och);
return;
}
@@ -132,39 +128,29 @@ handle_post_order_claim_finished (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Claiming order failed: could not parse JSON response\n");
GNUNET_break_op (0);
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.http_status = 0;
+ ocr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ ocr.hr.http_status = 0;
och->cb (och->cb_cls,
- &hr,
- NULL,
- NULL,
- NULL);
+ &ocr);
TALER_MERCHANT_order_claim_cancel (och);
return;
}
if (GNUNET_OK !=
- TALER_JSON_contract_hash (contract_terms,
- &hash))
+ TALER_JSON_contract_hash (ocr.details.ok.contract_terms,
+ &ocr.details.ok.h_contract_terms))
{
GNUNET_break (0);
- hr.ec = TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_CLIENT_INTERNAL_FAILURE;
- hr.http_status = 0;
+ ocr.hr.ec = TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_CLIENT_INTERNAL_FAILURE;
+ ocr.hr.http_status = 0;
GNUNET_JSON_parse_free (spec);
och->cb (och->cb_cls,
- &hr,
- NULL,
- NULL,
- NULL);
+ &ocr);
TALER_MERCHANT_order_claim_cancel (och);
return;
}
-
och->cb (och->cb_cls,
- &hr,
- contract_terms,
- &sig,
- &hash);
+ &ocr);
GNUNET_JSON_parse_free (spec);
TALER_MERCHANT_order_claim_cancel (och);
}
diff --git a/src/lib/merchant_api_post_order_paid.c b/src/lib/merchant_api_post_order_paid.c
index a42b1255..785d956f 100644
--- a/src/lib/merchant_api_post_order_paid.c
+++ b/src/lib/merchant_api_post_order_paid.c
@@ -90,9 +90,9 @@ handle_paid_finished (void *cls,
{
struct TALER_MERCHANT_OrderPaidHandle *oph = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_OrderPaidResponse opr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
oph->job = NULL;
@@ -102,61 +102,81 @@ handle_paid_finished (void *cls,
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ opr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
- case MHD_HTTP_NO_CONTENT:
+ case MHD_HTTP_OK:
+ {
+ bool refunded;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_bool ("refunded",
+ &refunded),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (opr.hr.reply,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ opr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ break;
+ }
break;
case MHD_HTTP_BAD_REQUEST:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ opr.hr.ec = TALER_JSON_get_error_code (json);
+ opr.hr.hint = TALER_JSON_get_error_hint (json);
/* This should never happen, either us
* or the merchant is buggy (or API version conflict);
* just pass JSON reply to the application */
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ opr.hr.ec = TALER_JSON_get_error_code (json);
+ opr.hr.hint = TALER_JSON_get_error_hint (json);
/* The signature provided was invalid */
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ opr.hr.ec = TALER_JSON_get_error_code (json);
+ opr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, this should never
happen, we should pass the JSON reply to the
application */
break;
case MHD_HTTP_CONFLICT:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ opr.hr.ec = TALER_JSON_get_error_code (json);
+ opr.hr.hint = TALER_JSON_get_error_hint (json);
/* The hashed contract terms don't match with the order_id. */
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ opr.hr.ec = TALER_JSON_get_error_code (json);
+ opr.hr.hint = TALER_JSON_get_error_hint (json);
/* Server had an internal issue; we should retry,
but this API leaves this to the application */
break;
case MHD_HTTP_SERVICE_UNAVAILABLE:
TALER_MERCHANT_parse_error_details_ (json,
response_code,
- &hr);
+ &opr.hr);
/* Exchange couldn't respond properly; the retry is
left to the application */
break;
default:
TALER_MERCHANT_parse_error_details_ (json,
response_code,
- &hr);
+ &opr.hr);
/* unexpected response code */
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) opr.hr.ec);
GNUNET_break_op (0);
break;
}
oph->paid_cb (oph->paid_cb_cls,
- &hr);
+ &opr);
TALER_MERCHANT_order_paid_cancel (oph);
}
@@ -168,6 +188,7 @@ TALER_MERCHANT_order_paid (
const char *order_id,
const char *session_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct GNUNET_HashCode *wallet_data_hash,
const struct TALER_MerchantSignatureP *merchant_sig,
TALER_MERCHANT_OrderPaidCallback paid_cb,
void *paid_cb_cls)
@@ -180,6 +201,9 @@ TALER_MERCHANT_order_paid (
merchant_sig),
GNUNET_JSON_pack_data_auto ("h_contract",
h_contract_terms),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("wallet_data_hash",
+ wallet_data_hash)),
GNUNET_JSON_pack_string ("session_id",
session_id));
oph = GNUNET_new (struct TALER_MERCHANT_OrderPaidHandle);
diff --git a/src/lib/merchant_api_post_order_pay.c b/src/lib/merchant_api_post_order_pay.c
index 765ca1a6..57c85565 100644
--- a/src/lib/merchant_api_post_order_pay.c
+++ b/src/lib/merchant_api_post_order_pay.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -109,12 +109,6 @@ struct TALER_MERCHANT_OrderPayHandle
json_t *error_history;
/**
- * Handle to the exchange that issued a problematic
- * coin (if any).
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
* Number of @e coins we are paying with.
*/
unsigned int num_coins;
@@ -129,248 +123,6 @@ struct TALER_MERCHANT_OrderPayHandle
/**
- * We got a 409 response back from the exchange (or the merchant).
- * Now we need to check the provided cryptographic proof that the
- * coin was actually already spent!
- *
- * @param oph operation handle
- * @param keys key data from the exchange
- * @return #GNUNET_OK if conflict is valid
- */
-static enum GNUNET_GenericReturnValue
-check_conflict (struct TALER_MERCHANT_OrderPayHandle *oph,
- const struct TALER_EXCHANGE_Keys *keys)
-{
- struct TALER_Amount spent;
- struct TALER_Amount spent_plus_contrib;
- struct TALER_DenominationHashP h_denom_pub_pc;
- const struct TALER_EXCHANGE_DenomPublicKey *dpk;
-
- TALER_denom_pub_hash (&oph->error_pc->denom_pub,
- &h_denom_pub_pc);
- dpk = TALER_EXCHANGE_get_denomination_key_by_hash (
- keys,
- &h_denom_pub_pc);
- if (GNUNET_OK !=
- TALER_EXCHANGE_verify_coin_history (dpk,
- &oph->error_pc->coin_pub,
- oph->error_history,
- &spent))
- {
- /* Exchange's history fails to verify */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (0 >
- TALER_amount_add (&spent_plus_contrib,
- &spent,
- &oph->error_pc->amount_with_fee))
- {
- /* We got an integer overflow? Bad application! */
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (-1 != TALER_amount_cmp (&oph->error_pc->denom_value,
- &spent_plus_contrib))
- {
- /* according to our calculations, the transaction should
- have still worked, AND we did not get any proof of
- coin public key re-use; hence: exchange error! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Accepting proof of double-spending (or coin public key re-use)\n");
- return GNUNET_OK;
-}
-
-
-/**
- * We got the fee structure from the exchange. Now
- * validate the conflict error.
- *
- * @param cls a `struct TALER_MERCHANT_OrderPayHandle`
- * @param ehr reply from the exchange
- * @param keys the key structure
- * @param compat protocol compatibility indication
- */
-static void
-cert_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *ehr,
- const struct TALER_EXCHANGE_Keys *keys,
- enum TALER_EXCHANGE_VersionCompatibility compat)
-{
- struct TALER_MERCHANT_OrderPayHandle *oph = cls;
-
- if (TALER_EXCHANGE_VC_INCOMPATIBLE & compat)
- {
- struct TALER_MERCHANT_PayResponse pr = {
- .hr.http_status = MHD_HTTP_CONFLICT,
- .hr.exchange_http_status = 0,
- .hr.ec = TALER_EC_WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE,
- .hr.reply = oph->full_reply,
- .hr.exchange_reply = ehr->reply,
- .hr.hint = "could not check error: incompatible exchange version"
- };
-
- oph->pay_cb (oph->pay_cb_cls,
- &pr);
- TALER_MERCHANT_order_pay_cancel (oph);
- return;
- }
- if ( (MHD_HTTP_OK != ehr->http_status) ||
- (NULL == keys) )
- {
- struct TALER_MERCHANT_PayResponse pr = {
- .hr.http_status = MHD_HTTP_CONFLICT,
- .hr.exchange_http_status = ehr->http_status,
- .hr.ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- .hr.reply = oph->full_reply,
- .hr.exchange_reply = ehr->reply,
- .hr.hint = "failed to download /keys from the exchange"
- };
-
- oph->pay_cb (oph->pay_cb_cls,
- &pr);
- TALER_MERCHANT_order_pay_cancel (oph);
- return;
- }
-
- if (GNUNET_OK !=
- check_conflict (oph,
- keys))
- {
- struct TALER_MERCHANT_PayResponse pr = {
- .hr.http_status = 0,
- .hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE,
- .hr.reply = oph->full_reply
- };
-
- oph->pay_cb (oph->pay_cb_cls,
- &pr);
- TALER_MERCHANT_order_pay_cancel (oph);
- return;
- }
-
- {
- struct TALER_MERCHANT_PayResponse pr = {
- .hr.http_status = MHD_HTTP_CONFLICT,
- .hr.ec = TALER_JSON_get_error_code (oph->full_reply),
- .hr.reply = oph->full_reply
- };
-
- oph->pay_cb (oph->pay_cb_cls,
- &pr);
- TALER_MERCHANT_order_pay_cancel (oph);
- }
-}
-
-
-/**
- * We got a 409 response back from the exchange (or the merchant).
- * Now we need to check the provided cryptograophic proof that the
- * coin was actually already spent!
- *
- * @param[in,out] oph handle of the original pay operation
- * @param[in,out] pr response to modify if #GNUNET_OK is returned
- * @param json cryptograophic proof returned by the
- * exchange/merchant
- * @return #GNUNET_OK if proof checks out,
- * #GNUNET_SYSERR if it is wrong,
- * #GNUNET_NO if checking continues asynchronously
- */
-static enum GNUNET_GenericReturnValue
-parse_conflict (struct TALER_MERCHANT_OrderPayHandle *oph,
- struct TALER_MERCHANT_PayResponse *pr,
- const json_t *json)
-{
- json_t *ereply;
- const char *exchange_url;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("exchange_reply",
- &ereply),
- GNUNET_JSON_spec_string ("exchange_url",
- &exchange_url),
- GNUNET_JSON_spec_end ()
- };
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct GNUNET_JSON_Specification hspec[] = {
- GNUNET_JSON_spec_json ("history",
- &oph->error_history),
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &coin_pub),
- GNUNET_JSON_spec_end ()
- };
- enum TALER_ErrorCode ec = TALER_JSON_get_error_code (json);
-
- switch (ec)
- {
- case TALER_EC_GENERIC_CURRENCY_MISMATCH:
- /* no proof to check, still very strange, as we
- should have checked that the currency matches */
- GNUNET_break_op (0);
- TALER_MERCHANT_parse_error_details_ (json,
- MHD_HTTP_CONFLICT,
- &pr->hr);
- return GNUNET_OK;
- case TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_ALREADY_PAID:
- /* We can only be happy and accept the result;
- FIXME: parse the refunds... */
- TALER_MERCHANT_parse_error_details_ (json,
- MHD_HTTP_CONFLICT,
- &pr->hr);
- return GNUNET_OK;
- case TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS:
- /* main case, handled below */
- break;
- default:
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_JSON_parse (ereply,
- hspec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- GNUNET_JSON_parse_free (spec);
-
- for (unsigned int i = 0; i<oph->num_coins; i++)
- {
- if (0 ==
- GNUNET_memcmp (&oph->coins[i].coin_pub,
- &coin_pub))
- {
- oph->error_pc = &oph->coins[i];
- oph->full_reply = json_incref ((json_t *) json);
- oph->exchange = TALER_EXCHANGE_connect (oph->ctx,
- oph->error_pc->exchange_url,
- &cert_cb,
- oph,
- TALER_EXCHANGE_OPTION_END);
- return GNUNET_NO;
- }
- }
- GNUNET_break_op (0); /* complaint is not about any of the coins
- that we actually paid with... */
- GNUNET_JSON_parse_free (hspec);
- return GNUNET_SYSERR;
-}
-
-
-/**
* Function called when we're done processing the
* HTTP /pay request.
*
@@ -406,7 +158,12 @@ handle_pay_finished (void *cls,
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto (
"sig",
- &pr.details.success.merchant_sig),
+ &pr.details.ok.merchant_sig),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string (
+ "pos_confirmation",
+ &pr.details.ok.pos_confirmation),
+ NULL),
GNUNET_JSON_spec_end ()
};
@@ -425,7 +182,7 @@ handle_pay_finished (void *cls,
if (GNUNET_OK !=
TALER_merchant_pay_verify (&oph->h_contract_terms,
&oph->merchant_pub,
- &pr.details.success.merchant_sig))
+ &pr.details.ok.merchant_sig))
{
GNUNET_break_op (0);
pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
@@ -475,27 +232,10 @@ handle_pay_finished (void *cls,
Pass on to application. */
break;
case MHD_HTTP_CONFLICT:
- {
- enum GNUNET_GenericReturnValue ret;
-
- ret = parse_conflict (oph,
- &pr,
- json);
- switch (ret)
- {
- case GNUNET_OK:
- /* continued below, 'pr' was modified */
- break;
- case GNUNET_NO:
- /* handled asynchronously! */
- return; /* ! */
- case GNUNET_SYSERR:
- GNUNET_break_op (0);
- response_code = 0;
- break;
- }
- break;
- }
+ TALER_MERCHANT_parse_error_details_ (json,
+ MHD_HTTP_CONFLICT,
+ &pr.hr);
+ break;
case MHD_HTTP_GONE:
TALER_MERCHANT_parse_error_details_ (json,
response_code,
@@ -565,8 +305,9 @@ TALER_MERCHANT_order_pay_frontend (
const char *merchant_url,
const char *order_id,
const char *session_id,
+ const json_t *wallet_data,
unsigned int num_coins,
- const struct TALER_MERCHANT_PaidCoin coins[],
+ const struct TALER_MERCHANT_PaidCoin coins[static num_coins],
TALER_MERCHANT_OrderPayCallback pay_cb,
void *pay_cb_cls)
{
@@ -655,6 +396,9 @@ TALER_MERCHANT_order_pay_frontend (
GNUNET_JSON_pack_array_steal ("coins",
j_coins),
GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("wallet_data",
+ (json_t *) wallet_data)),
+ GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("session_id",
session_id)));
@@ -684,9 +428,9 @@ TALER_MERCHANT_order_pay_frontend (
oph->num_coins = num_coins;
oph->coins = GNUNET_new_array (num_coins,
struct TALER_MERCHANT_PaidCoin);
- memcpy (oph->coins,
- coins,
- num_coins * sizeof (struct TALER_MERCHANT_PaidCoin));
+ GNUNET_memcpy (oph->coins,
+ coins,
+ num_coins * sizeof (struct TALER_MERCHANT_PaidCoin));
eh = TALER_MERCHANT_curl_easy_get_ (oph->url);
if (GNUNET_OK !=
@@ -717,6 +461,7 @@ TALER_MERCHANT_order_pay (
const char *merchant_url,
const char *session_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
+ const json_t *wallet_data,
const struct TALER_Amount *amount,
const struct TALER_Amount *max_fee,
const struct TALER_MerchantPublicKeyP *merchant_pub,
@@ -727,10 +472,12 @@ TALER_MERCHANT_order_pay (
const struct TALER_MerchantWireHashP *h_wire,
const char *order_id,
unsigned int num_coins,
- const struct TALER_MERCHANT_PayCoin coins[],
+ const struct TALER_MERCHANT_PayCoin coins[static num_coins],
TALER_MERCHANT_OrderPayCallback pay_cb,
void *pay_cb_cls)
{
+ struct GNUNET_HashCode wallet_data_hash;
+
if (GNUNET_YES !=
TALER_amount_cmp_currency (amount,
max_fee))
@@ -738,7 +485,9 @@ TALER_MERCHANT_order_pay (
GNUNET_break (0);
return NULL;
}
-
+ if (NULL != wallet_data)
+ TALER_json_hash (wallet_data,
+ &wallet_data_hash);
{
struct TALER_MERCHANT_PaidCoin pc[num_coins];
@@ -765,6 +514,9 @@ TALER_MERCHANT_order_pay (
&fee,
h_wire,
h_contract_terms,
+ (NULL != wallet_data)
+ ? &wallet_data_hash
+ : NULL,
coin->h_age_commitment,
NULL /* h_extensions! */,
&h_denom_pub,
@@ -789,6 +541,7 @@ TALER_MERCHANT_order_pay (
merchant_url,
order_id,
session_id,
+ wallet_data,
num_coins,
pc,
pay_cb,
@@ -812,11 +565,6 @@ TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *oph)
GNUNET_CURL_job_cancel (oph->job);
oph->job = NULL;
}
- if (NULL != oph->exchange)
- {
- TALER_EXCHANGE_disconnect (oph->exchange);
- oph->exchange = NULL;
- }
TALER_curl_easy_post_finished (&oph->post_ctx);
json_decref (oph->error_history);
json_decref (oph->full_reply);
diff --git a/src/lib/merchant_api_post_order_refund.c b/src/lib/merchant_api_post_order_refund.c
index be996dc2..4414bd86 100644
--- a/src/lib/merchant_api_post_order_refund.c
+++ b/src/lib/merchant_api_post_order_refund.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -85,30 +85,26 @@ handle_refund_finished (void *cls,
{
struct TALER_MERCHANT_OrderRefundHandle *orh = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_RefundResponse rr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
orh->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- orh->cb (orh->cb_cls,
- &hr,
- NULL,
- NULL);
+ rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
{
- const char *taler_refund_uri;
- struct TALER_PrivateContractHashP h_contract;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("taler_refund_uri",
- &taler_refund_uri),
- GNUNET_JSON_spec_fixed_auto ("h_contract",
- &h_contract),
+ GNUNET_JSON_spec_string (
+ "taler_refund_uri",
+ &rr.details.ok.taler_refund_uri),
+ GNUNET_JSON_spec_fixed_auto (
+ "h_contract",
+ &rr.details.ok.h_contract),
GNUNET_JSON_spec_end ()
};
@@ -118,51 +114,36 @@ handle_refund_finished (void *cls,
NULL, NULL))
{
GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- orh->cb (orh->cb_cls,
- &hr,
- NULL,
- NULL);
+ rr.hr.http_status = 0;
+ rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
- orh->cb (orh->cb_cls,
- &hr,
- taler_refund_uri,
- &h_contract);
- GNUNET_JSON_parse_free (spec);
+ break;
}
- break;
case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ rr.hr.ec = TALER_JSON_get_error_code (json);
+ rr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, merchant says we need to authenticate. */
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ rr.hr.ec = TALER_JSON_get_error_code (json);
+ rr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, merchant says we need to authenticate. */
break;
case MHD_HTTP_NOT_FOUND:
case MHD_HTTP_CONFLICT:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- orh->cb (orh->cb_cls,
- &hr,
- NULL,
- NULL);
+ rr.hr.ec = TALER_JSON_get_error_code (json);
+ rr.hr.hint = TALER_JSON_get_error_hint (json);
break;
default:
GNUNET_break_op (0); /* unexpected status code */
TALER_MERCHANT_parse_error_details_ (json,
response_code,
- &hr);
- orh->cb (orh->cb_cls,
- &hr,
- NULL,
- NULL);
+ &rr.hr);
break;
}
+ orh->cb (orh->cb_cls,
+ &rr);
TALER_MERCHANT_post_order_refund_cancel (orh);
}
diff --git a/src/lib/merchant_api_post_orders.c b/src/lib/merchant_api_post_orders.c
index c0fd5fcb..56881133 100644
--- a/src/lib/merchant_api_post_orders.c
+++ b/src/lib/merchant_api_post_orders.c
@@ -92,9 +92,9 @@ handle_post_order_finished (void *cls,
po->job = NULL;
TALER_MERCHANT_handle_order_creation_response_ (po->cb,
- po->cb_cls,
- response_code,
- json);
+ po->cb_cls,
+ response_code,
+ json);
TALER_MERCHANT_orders_post_cancel (po);
}
@@ -107,6 +107,8 @@ TALER_MERCHANT_orders_post (struct GNUNET_CURL_Context *ctx,
TALER_MERCHANT_PostOrdersCallback cb,
void *cb_cls)
{
+ static const char *no_uuids[GNUNET_NZL (0)];
+
return TALER_MERCHANT_orders_post2 (ctx,
backend_url,
order,
@@ -115,7 +117,7 @@ TALER_MERCHANT_orders_post (struct GNUNET_CURL_Context *ctx,
0,
NULL,
0,
- NULL,
+ no_uuids,
true,
cb,
cb_cls);
@@ -132,7 +134,40 @@ TALER_MERCHANT_orders_post2 (
unsigned int inventory_products_length,
const struct TALER_MERCHANT_InventoryProduct inventory_products[],
unsigned int uuids_length,
- const char *uuids[],
+ const char *uuids[static uuids_length],
+ bool create_token,
+ TALER_MERCHANT_PostOrdersCallback cb,
+ void *cb_cls)
+{
+ return TALER_MERCHANT_orders_post3 (
+ ctx,
+ backend_url,
+ order,
+ NULL, /* session ID */
+ refund_delay,
+ payment_target,
+ inventory_products_length,
+ inventory_products,
+ uuids_length,
+ uuids,
+ create_token,
+ cb,
+ cb_cls);
+}
+
+
+struct TALER_MERCHANT_PostOrdersHandle *
+TALER_MERCHANT_orders_post3 (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const json_t *order,
+ const char *session_id,
+ struct GNUNET_TIME_Relative refund_delay,
+ const char *payment_target,
+ unsigned int inventory_products_length,
+ const struct TALER_MERCHANT_InventoryProduct inventory_products[],
+ unsigned int uuids_length,
+ const char *uuids[static uuids_length],
bool create_token,
TALER_MERCHANT_PostOrdersCallback cb,
void *cb_cls)
@@ -152,6 +187,9 @@ TALER_MERCHANT_orders_post2 (
GNUNET_JSON_pack_object_incref ("order",
(json_t *) order),
GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("session_id",
+ session_id)),
+ GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("payment_target",
payment_target)));
if (0 != refund_delay.rel_value_us)
diff --git a/src/lib/merchant_api_post_otp_devices.c b/src/lib/merchant_api_post_otp_devices.c
new file mode 100644
index 00000000..456abd09
--- /dev/null
+++ b/src/lib/merchant_api_post_otp_devices.c
@@ -0,0 +1,237 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with TALER; see the file COPYING.LGPL.
+ If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant_api_post_otp_devices.c
+ * @brief Implementation of the POST /otp-devices request
+ * of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_curl_defaults.h"
+#include "merchant_api_common.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_curl_lib.h>
+
+
+/**
+ * Handle for a POST /otp-devices/$ID operation.
+ */
+struct TALER_MERCHANT_OtpDevicesPostHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_OtpDevicesPostCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /otp-devices request.
+ *
+ * @param cls the `struct TALER_MERCHANT_OtpDevicesPostHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_post_otp_devices_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_OtpDevicesPostHandle *tph = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_HttpResponse hr = {
+ .http_status = (unsigned int) response_code,
+ .reply = json
+ };
+
+ tph->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "POST /otp-devices completed with response code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case 0:
+ hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* This should never happen, either us
+ * or the merchant is buggy (or API version conflict);
+ * just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we need to authenticate. */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we tried to abort the payment
+ * after it was successful. We should pass the JSON reply to the
+ * application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the
+ application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* Server had an internal issue; we should retry,
+ but this API leaves this to the application */
+ break;
+ default:
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &hr);
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ tph->cb (tph->cb_cls,
+ &hr);
+ TALER_MERCHANT_otp_devices_post_cancel (tph);
+}
+
+
+struct TALER_MERCHANT_OtpDevicesPostHandle *
+TALER_MERCHANT_otp_devices_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *otp_device_id,
+ const char *otp_device_description,
+ const char *otp_key,
+ enum TALER_MerchantConfirmationAlgorithm otp_algorithm,
+ uint64_t otp_ctr,
+ TALER_MERCHANT_OtpDevicesPostCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_OtpDevicesPostHandle *tph;
+ json_t *req_obj;
+
+ req_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("otp_device_id",
+ otp_device_id),
+ GNUNET_JSON_pack_string ("otp_device_description",
+ otp_device_description),
+ GNUNET_JSON_pack_uint64 ("otp_algorithm",
+ (uint32_t) otp_algorithm),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("otp_key",
+ otp_key)),
+ GNUNET_JSON_pack_uint64 ("otp_ctr",
+ otp_ctr));
+ tph = GNUNET_new (struct TALER_MERCHANT_OtpDevicesPostHandle);
+ tph->ctx = ctx;
+ tph->cb = cb;
+ tph->cb_cls = cb_cls;
+ tph->url = TALER_url_join (backend_url,
+ "private/otp-devices",
+ NULL);
+ if (NULL == tph->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ json_decref (req_obj);
+ GNUNET_free (tph);
+ return NULL;
+ }
+ {
+ CURL *eh;
+
+ eh = TALER_MERCHANT_curl_easy_get_ (tph->url);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_curl_easy_post (&tph->post_ctx,
+ eh,
+ req_obj));
+ json_decref (req_obj);
+ tph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ tph->post_ctx.headers,
+ &handle_post_otp_devices_finished,
+ tph);
+ GNUNET_assert (NULL != tph->job);
+ }
+ return tph;
+}
+
+
+void
+TALER_MERCHANT_otp_devices_post_cancel (
+ struct TALER_MERCHANT_OtpDevicesPostHandle *tph)
+{
+ if (NULL != tph->job)
+ {
+ GNUNET_CURL_job_cancel (tph->job);
+ tph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&tph->post_ctx);
+ GNUNET_free (tph->url);
+ GNUNET_free (tph);
+}
+
+
+/* end of merchant_api_post_otp_devices.c */
diff --git a/src/lib/merchant_api_post_products.c b/src/lib/merchant_api_post_products.c
index 2a5b9a9b..0f09f397 100644
--- a/src/lib/merchant_api_post_products.c
+++ b/src/lib/merchant_api_post_products.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020-2021 Taler Systems SA
+ Copyright (C) 2020-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -159,7 +159,7 @@ handle_post_products_finished (void *cls,
struct TALER_MERCHANT_ProductsPostHandle *
-TALER_MERCHANT_products_post (
+TALER_MERCHANT_products_post2 (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
const char *product_id,
@@ -172,6 +172,7 @@ TALER_MERCHANT_products_post (
int64_t total_stock,
const json_t *address,
struct GNUNET_TIME_Timestamp next_restock,
+ uint32_t minimum_age,
TALER_MERCHANT_ProductsPostCallback cb,
void *cb_cls)
{
@@ -183,20 +184,26 @@ TALER_MERCHANT_products_post (
product_id),
GNUNET_JSON_pack_string ("description",
description),
- GNUNET_JSON_pack_object_incref ("description_i18n",
- (json_t *) description_i18n),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("description_i18n",
+ (json_t *) description_i18n)),
GNUNET_JSON_pack_string ("unit",
unit),
TALER_JSON_pack_amount ("price",
price),
GNUNET_JSON_pack_string ("image",
image),
- GNUNET_JSON_pack_array_incref ("taxes",
- (json_t *) taxes),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_incref ("taxes",
+ (json_t *) taxes)),
GNUNET_JSON_pack_uint64 ("total_stock",
total_stock),
- GNUNET_JSON_pack_object_incref ("address",
- (json_t *) address),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_uint64 ("minimum_age",
+ minimum_age)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("address",
+ (json_t *) address)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_timestamp ("next_restock",
next_restock)));
@@ -235,6 +242,41 @@ TALER_MERCHANT_products_post (
}
+struct TALER_MERCHANT_ProductsPostHandle *
+TALER_MERCHANT_products_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *product_id,
+ const char *description,
+ const json_t *description_i18n,
+ const char *unit,
+ const struct TALER_Amount *price,
+ const char *image,
+ const json_t *taxes,
+ int64_t total_stock,
+ const json_t *address,
+ struct GNUNET_TIME_Timestamp next_restock,
+ TALER_MERCHANT_ProductsPostCallback cb,
+ void *cb_cls)
+{
+ return TALER_MERCHANT_products_post2 (ctx,
+ backend_url,
+ product_id,
+ description,
+ description_i18n,
+ unit,
+ price,
+ image,
+ taxes,
+ total_stock,
+ address,
+ next_restock,
+ 0,
+ cb,
+ cb_cls);
+}
+
+
void
TALER_MERCHANT_products_post_cancel (
struct TALER_MERCHANT_ProductsPostHandle *pph)
diff --git a/src/lib/merchant_api_post_reserves.c b/src/lib/merchant_api_post_reserves.c
deleted file mode 100644
index 65239dfb..00000000
--- a/src/lib/merchant_api_post_reserves.c
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014, 2015, 2016, 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
- Foundation; either version 2.1, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along with
- TALER; see the file COPYING.LGPL. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file merchant_api_post_reserves.c
- * @brief Implementation of the POST /reserves request of the merchant's HTTP API
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <curl/curl.h>
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include "taler_merchant_service.h"
-#include "merchant_api_curl_defaults.h"
-#include "merchant_api_common.h"
-#include <taler/taler_curl_lib.h>
-#include <taler/taler_json_lib.h>
-
-
-/**
- * @brief A handle for POSTing reserve data.
- */
-struct TALER_MERCHANT_PostReservesHandle
-{
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_MERCHANT_PostReservesCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Reference to the execution context.
- */
- struct GNUNET_CURL_Context *ctx;
-
- /**
- * Minor context that holds body and headers.
- */
- struct TALER_CURL_PostContext post_ctx;
-
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP POST /reserves request.
- *
- * @param cls the `struct TALER_MERCHANT_PostReservesHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response response body, NULL if not in JSON
- */
-static void
-handle_post_reserves_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_MERCHANT_PostReservesHandle *prh = cls;
- const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
- };
-
- prh->job = NULL;
- switch (response_code)
- {
- case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- const char *payto_uri;
- struct TALER_ReservePublicKeyP reserve_pub;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("reserve_pub",
- &reserve_pub),
- GNUNET_JSON_spec_string ("payto_uri",
- &payto_uri),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- else
- {
- prh->cb (prh->cb_cls,
- &hr,
- &reserve_pub,
- payto_uri);
- GNUNET_JSON_parse_free (spec);
- TALER_MERCHANT_reserves_post_cancel (prh);
- return;
- }
- }
- case MHD_HTTP_ACCEPTED:
- break;
- case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- /* Nothing really to verify, merchant says we need to authenticate. */
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Did not find any data\n");
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &hr);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- (int) hr.ec);
- break;
- }
- prh->cb (prh->cb_cls,
- &hr,
- NULL,
- NULL);
- TALER_MERCHANT_reserves_post_cancel (prh);
-}
-
-
-struct TALER_MERCHANT_PostReservesHandle *
-TALER_MERCHANT_reserves_post (
- struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_Amount *initial_balance,
- const char *exchange_url,
- const char *wire_method,
- TALER_MERCHANT_PostReservesCallback cb,
- void *cb_cls)
-{
- struct TALER_MERCHANT_PostReservesHandle *prh;
- CURL *eh;
- json_t *req;
-
- prh = GNUNET_new (struct TALER_MERCHANT_PostReservesHandle);
- prh->ctx = ctx;
- prh->cb = cb;
- prh->cb_cls = cb_cls;
- prh->url = TALER_url_join (backend_url,
- "private/reserves",
- NULL);
- if (NULL == prh->url)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not construct request URL.\n");
- GNUNET_free (prh);
- return NULL;
- }
- req = GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("initial_balance",
- initial_balance),
- GNUNET_JSON_pack_string ("wire_method",
- wire_method),
- GNUNET_JSON_pack_string ("exchange_url",
- exchange_url));
- eh = TALER_MERCHANT_curl_easy_get_ (prh->url);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&prh->post_ctx,
- eh,
- req))
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- json_decref (req);
- GNUNET_free (prh->url);
- GNUNET_free (prh);
- return NULL;
- }
- json_decref (req);
- prh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- prh->post_ctx.headers,
- &handle_post_reserves_finished,
- prh);
- return prh;
-}
-
-
-void
-TALER_MERCHANT_reserves_post_cancel (
- struct TALER_MERCHANT_PostReservesHandle *prh)
-{
- if (NULL != prh->job)
- {
- GNUNET_CURL_job_cancel (prh->job);
- prh->job = NULL;
- }
- GNUNET_free (prh->url);
- TALER_curl_easy_post_finished (&prh->post_ctx);
- GNUNET_free (prh);
-}
-
-
-/* end of merchant_api_post_reserves.c */
diff --git a/src/lib/merchant_api_post_templates.c b/src/lib/merchant_api_post_templates.c
index 9592a5e3..3ab4320c 100644
--- a/src/lib/merchant_api_post_templates.c
+++ b/src/lib/merchant_api_post_templates.c
@@ -129,6 +129,10 @@ handle_post_templates_finished (void *cls,
happen, we should pass the JSON reply to the
application */
break;
+ case MHD_HTTP_CONFLICT:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
hr.ec = TALER_JSON_get_error_code (json);
hr.hint = TALER_JSON_get_error_hint (json);
@@ -199,7 +203,7 @@ TALER_MERCHANT_templates_post (
const char *backend_url,
const char *template_id,
const char *template_description,
- const char *image,
+ const char *otp_id,
const json_t *template_contract,
TALER_MERCHANT_TemplatesPostCallback cb,
void *cb_cls)
@@ -218,8 +222,8 @@ TALER_MERCHANT_templates_post (
GNUNET_JSON_pack_string ("template_description",
template_description),
GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("image",
- image)),
+ GNUNET_JSON_pack_string ("otp_id",
+ otp_id)),
GNUNET_JSON_pack_object_incref ("template_contract",
(json_t *) template_contract));
tph = GNUNET_new (struct TALER_MERCHANT_TemplatesPostHandle);
diff --git a/src/lib/merchant_api_post_transfers.c b/src/lib/merchant_api_post_transfers.c
index 450b46d9..615453fa 100644
--- a/src/lib/merchant_api_post_transfers.c
+++ b/src/lib/merchant_api_post_transfers.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -85,116 +85,22 @@ handle_post_transfers_finished (void *cls,
const void *response)
{
struct TALER_MERCHANT_PostTransfersHandle *pth = cls;
- const json_t *json = response;
- json_t *deposit_sum = NULL;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_PostTransfersResponse ptr = {
+ .hr.reply = response,
+ .hr.http_status = (unsigned int) response_code
};
pth->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ ptr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
- case MHD_HTTP_OK:
- {
- struct TALER_Amount total;
- struct TALER_Amount wire_fee;
- struct GNUNET_TIME_Timestamp execution_time;
- json_t *deposit_sums;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("total",
- &total),
- TALER_JSON_spec_amount_any ("wire_fee",
- &wire_fee),
- GNUNET_JSON_spec_timestamp ("execution_time",
- &execution_time),
- GNUNET_JSON_spec_json ("deposit_sums",
- &deposit_sums),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- else
- {
- size_t deposit_sums_length;
- struct TALER_MERCHANT_TrackTransferDetail *details;
- unsigned int i;
- bool ok;
-
- if (! json_is_array (deposit_sums))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- deposit_sums_length = json_array_size (deposit_sums);
- details = GNUNET_new_array (deposit_sums_length,
- struct TALER_MERCHANT_TrackTransferDetail);
- ok = true;
- json_array_foreach (deposit_sums, i, deposit_sum) {
- struct TALER_MERCHANT_TrackTransferDetail *d = &details[i];
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_string ("order_id",
- &d->order_id),
- TALER_JSON_spec_amount_any ("deposit_value",
- &d->deposit_value),
- TALER_JSON_spec_amount_any ("deposit_fee",
- &d->deposit_fee),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (deposit_sum,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- ok = false;
- break;
- }
- }
-
- if (! ok)
- {
- GNUNET_break_op (0);
- GNUNET_free (details);
- GNUNET_JSON_parse_free (spec);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- pth->cb (pth->cb_cls,
- &hr,
- execution_time,
- &total,
- &wire_fee,
- deposit_sums_length,
- details);
- GNUNET_free (details);
- GNUNET_JSON_parse_free (spec);
- TALER_MERCHANT_transfers_post_cancel (pth);
- return;
- }
- }
- case MHD_HTTP_ACCEPTED:
+ case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ ptr.hr.ec = TALER_JSON_get_error_code (ptr.hr.reply);
+ ptr.hr.hint = TALER_JSON_get_error_hint (ptr.hr.reply);
/* Nothing really to verify, merchant says we need to authenticate. */
break;
case MHD_HTTP_NOT_FOUND:
@@ -202,44 +108,47 @@ handle_post_transfers_finished (void *cls,
happen, we should pass the JSON reply to the application */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Did not find any data\n");
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ ptr.hr.ec = TALER_JSON_get_error_code (ptr.hr.reply);
+ ptr.hr.hint = TALER_JSON_get_error_hint (ptr.hr.reply);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ ptr.hr.ec = TALER_JSON_get_error_code (ptr.hr.reply);
+ ptr.hr.hint = TALER_JSON_get_error_hint (ptr.hr.reply);
break;
case MHD_HTTP_BAD_GATEWAY:
/* Exchange had an issue; we should retry, but this API
leaves this to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ ptr.hr.ec = TALER_JSON_get_error_code (ptr.hr.reply);
+ ptr.hr.hint = TALER_JSON_get_error_hint (ptr.hr.reply);
{
- uint32_t eec;
uint32_t ehc;
struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_uint32 ("exchange_code",
- &eec),
+ TALER_JSON_spec_ec ("exchange_code",
+ &ptr.details.bad_gateway.exchange_ec),
GNUNET_JSON_spec_uint32 ("exchange_http_status",
&ehc),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
- GNUNET_JSON_parse (deposit_sum,
+ GNUNET_JSON_parse (ptr.hr.reply,
ispec,
NULL, NULL))
{
GNUNET_break_op (0);
+ ptr.details.bad_gateway.exchange_http_status = 0;
+ ptr.details.bad_gateway.exchange_ec = TALER_EC_NONE;
break;
}
else
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ ptr.details.bad_gateway.exchange_http_status
+ = (unsigned int) ehc;
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Exchange returned %u/%u\n",
- (unsigned int) eec,
+ (unsigned int) ptr.details.bad_gateway.exchange_ec,
(unsigned int) ehc);
}
}
@@ -247,28 +156,23 @@ handle_post_transfers_finished (void *cls,
case MHD_HTTP_GATEWAY_TIMEOUT:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ ptr.hr.ec = TALER_JSON_get_error_code (ptr.hr.reply);
+ ptr.hr.hint = TALER_JSON_get_error_hint (ptr.hr.reply);
break;
default:
/* unexpected response code */
GNUNET_break_op (0);
- TALER_MERCHANT_parse_error_details_ (json,
+ TALER_MERCHANT_parse_error_details_ (ptr.hr.reply,
response_code,
- &hr);
+ &ptr.hr);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- (int) hr.ec);
+ (unsigned int) ptr.hr.http_status,
+ (int) ptr.hr.ec);
break;
}
pth->cb (pth->cb_cls,
- &hr,
- GNUNET_TIME_UNIT_FOREVER_TS,
- NULL,
- NULL,
- 0,
- NULL);
+ &ptr);
TALER_MERCHANT_transfers_post_cancel (pth);
}
diff --git a/src/lib/merchant_api_post_using_templates.c b/src/lib/merchant_api_post_using_templates.c
index d72768f3..f09c34cb 100644
--- a/src/lib/merchant_api_post_using_templates.c
+++ b/src/lib/merchant_api_post_using_templates.c
@@ -89,9 +89,9 @@ handle_post_using_templates_finished (void *cls,
utph->job = NULL;
TALER_MERCHANT_handle_order_creation_response_ (utph->cb,
- utph->cb_cls,
- response_code,
- json);
+ utph->cb_cls,
+ response_code,
+ json);
TALER_MERCHANT_using_templates_post_cancel (utph);
}
diff --git a/src/lib/merchant_api_tip_authorize.c b/src/lib/merchant_api_tip_authorize.c
deleted file mode 100644
index 036d40af..00000000
--- a/src/lib/merchant_api_tip_authorize.c
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2017, 2020, 2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
- Foundation; either version 2.1, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along with
- TALER; see the file COPYING.LGPL. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file merchant_api_tip_authorize.c
- * @brief Implementation of the /tip-authorize request of the merchant's HTTP API
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <curl/curl.h>
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_merchant_service.h"
-#include "merchant_api_curl_defaults.h"
-#include "merchant_api_common.h"
-#include <taler/taler_json_lib.h>
-#include <taler/taler_signatures.h>
-#include <taler/taler_curl_lib.h>
-
-
-/**
- * @brief A handle for tip authorizations.
- */
-struct TALER_MERCHANT_TipAuthorizeHandle
-{
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_MERCHANT_TipAuthorizeCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Reference to the execution context.
- */
- struct GNUNET_CURL_Context *ctx;
-
- /**
- * Minor context that holds body and headers.
- */
- struct TALER_CURL_PostContext post_ctx;
-};
-
-
-/**
- * We got a 200 response back from the exchange (or the merchant).
- * Now we need to parse the response and if it is well-formed,
- * call the callback (and set it to NULL afterwards).
- *
- * @param tao handle of the original authorization operation
- * @param json cryptographic proof returned by the exchange/merchant
- * @return #GNUNET_OK if response is valid
- */
-static int
-check_ok (struct TALER_MERCHANT_TipAuthorizeHandle *tao,
- const json_t *json)
-{
- const char *tip_status_url;
- const char *taler_tip_uri;
- struct TALER_TipIdentifierP tip_id;
- struct GNUNET_TIME_Timestamp expiration_time;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("tip_status_url", &tip_status_url),
- GNUNET_JSON_spec_string ("taler_tip_uri", &taler_tip_uri),
- GNUNET_JSON_spec_timestamp ("tip_expiration", &expiration_time),
- GNUNET_JSON_spec_fixed_auto ("tip_id", &tip_id),
- GNUNET_JSON_spec_end ()
- };
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = MHD_HTTP_OK,
- .reply = json
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- char *log;
-
- GNUNET_break_op (0);
- log = json_dumps (json,
- 0);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "JSON %s\n",
- log);
- free (log);
- return GNUNET_SYSERR;
- }
- tao->cb (tao->cb_cls,
- &hr,
- &tip_id,
- taler_tip_uri,
- expiration_time);
- tao->cb = NULL; /* do not call twice */
- GNUNET_JSON_parse_free (spec);
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reservers/$TIP_ID/tip-authorize request.
- *
- * @param cls the `struct TALER_MERCHANT_TipAuthorizeHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response response body, NULL if not in JSON
- */
-static void
-handle_tip_authorize_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_MERCHANT_TipAuthorizeHandle *tao = cls;
- const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
- };
-
- tao->job = NULL;
- switch (response_code)
- {
- case MHD_HTTP_OK:
- if (GNUNET_OK ==
- check_ok (tao,
- json))
- {
- TALER_MERCHANT_tip_authorize_cancel (tao);
- return;
- }
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_UNAUTHORIZED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- /* Nothing really to verify, merchant says we need to authenticate. */
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Well-defined status code, pass on to application! */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_PRECONDITION_FAILED:
- /* Well-defined status code, pass on to application! */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_SERVICE_UNAVAILABLE:
- /* Server had an unclear (internal or external) issue; we should retry,
- but this API leaves this to the application */
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &hr);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &hr);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- (int) hr.ec);
- break;
- }
- if (NULL != tao->cb)
- tao->cb (tao->cb_cls,
- &hr,
- NULL,
- NULL,
- GNUNET_TIME_UNIT_ZERO_TS);
- TALER_MERCHANT_tip_authorize_cancel (tao);
-}
-
-
-struct TALER_MERCHANT_TipAuthorizeHandle *
-TALER_MERCHANT_tip_authorize2 (
- struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const char *next_url,
- const struct TALER_Amount *amount,
- const char *justification,
- TALER_MERCHANT_TipAuthorizeCallback authorize_cb,
- void *authorize_cb_cls)
-{
- struct TALER_MERCHANT_TipAuthorizeHandle *tao;
- CURL *eh;
- json_t *te_obj;
-
- tao = GNUNET_new (struct TALER_MERCHANT_TipAuthorizeHandle);
- tao->ctx = ctx;
- tao->cb = authorize_cb;
- tao->cb_cls = authorize_cb_cls;
-
- {
- char res_str[sizeof (*reserve_pub) * 2];
- char arg_str[sizeof (res_str) + 48];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (reserve_pub,
- sizeof (*reserve_pub),
- res_str,
- sizeof (res_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "private/reserves/%s/authorize-tip",
- res_str);
- tao->url = TALER_url_join (backend_url,
- arg_str,
- NULL);
- }
- if (NULL == tao->url)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not construct request URL.\n");
- GNUNET_free (tao);
- return NULL;
- }
- te_obj = GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("amount",
- amount),
- GNUNET_JSON_pack_string ("justification",
- justification),
- GNUNET_JSON_pack_string ("next_url",
- next_url));
- eh = curl_easy_init ();
- GNUNET_assert (NULL != eh);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&tao->post_ctx,
- eh,
- te_obj))
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- json_decref (te_obj);
- GNUNET_free (tao->url);
- GNUNET_free (tao);
- return NULL;
- }
-
- json_decref (te_obj);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Requesting URL '%s'\n",
- tao->url);
- GNUNET_assert (CURLE_OK == curl_easy_setopt (eh,
- CURLOPT_URL,
- tao->url));
-
- tao->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- tao->post_ctx.headers,
- &handle_tip_authorize_finished,
- tao);
- return tao;
-}
-
-
-struct TALER_MERCHANT_TipAuthorizeHandle *
-TALER_MERCHANT_tip_authorize (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const char *next_url,
- const struct TALER_Amount *amount,
- const char *justification,
- TALER_MERCHANT_TipAuthorizeCallback authorize_cb,
- void *authorize_cb_cls)
-{
- struct TALER_MERCHANT_TipAuthorizeHandle *tao;
- CURL *eh;
- json_t *te_obj;
-
- tao = GNUNET_new (struct TALER_MERCHANT_TipAuthorizeHandle);
- tao->ctx = ctx;
- tao->cb = authorize_cb;
- tao->cb_cls = authorize_cb_cls;
-
- tao->url = TALER_url_join (backend_url,
- "private/tips",
- NULL);
- if (NULL == tao->url)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not construct request URL.\n");
- GNUNET_free (tao);
- return NULL;
- }
- te_obj = GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("amount",
- amount),
- GNUNET_JSON_pack_string ("justification",
- justification),
- GNUNET_JSON_pack_string ("next_url",
- next_url));
- eh = TALER_MERCHANT_curl_easy_get_ (tao->url);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&tao->post_ctx,
- eh,
- te_obj))
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- json_decref (te_obj);
- GNUNET_free (tao->url);
- GNUNET_free (tao);
- return NULL;
- }
- json_decref (te_obj);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Requesting URL '%s'\n",
- tao->url);
- tao->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- tao->post_ctx.headers,
- &handle_tip_authorize_finished,
- tao);
- return tao;
-}
-
-
-void
-TALER_MERCHANT_tip_authorize_cancel (
- struct TALER_MERCHANT_TipAuthorizeHandle *tao)
-{
- if (NULL != tao->job)
- {
- GNUNET_CURL_job_cancel (tao->job);
- tao->job = NULL;
- }
- TALER_curl_easy_post_finished (&tao->post_ctx);
- GNUNET_free (tao->url);
- GNUNET_free (tao);
-}
-
-
-/* end of merchant_api_tip_authorize.c */
diff --git a/src/lib/merchant_api_tip_pickup.c b/src/lib/merchant_api_tip_pickup.c
deleted file mode 100644
index 593efa43..00000000
--- a/src/lib/merchant_api_tip_pickup.c
+++ /dev/null
@@ -1,446 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
- Foundation; either version 2.1, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along with
- TALER; see the file COPYING.LGPL. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file merchant_api_tip_pickup.c
- * @brief Implementation of the /tip-pickup request of the merchant's HTTP API
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <curl/curl.h>
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_merchant_service.h"
-#include <taler/taler_json_lib.h>
-#include <taler/taler_signatures.h>
-#include <taler/taler_curl_lib.h>
-
-
-/**
- * Data we keep per planchet.
- */
-struct PlanchetData
-{
- /**
- * Secrets of the planchet.
- */
- struct TALER_PlanchetMasterSecretP ps;
-
- /**
- * Denomination key we are withdrawing.
- */
- struct TALER_EXCHANGE_DenomPublicKey pk;
-
- /**
- * Hash of the public key of the coin we are signing.
- */
- struct TALER_CoinPubHashP c_hash;
-
- /**
- * Nonce used for @e csr request, if any.
- */
- struct TALER_CsNonce nonce;
-
- /**
- * Handle for a /csr request we may optionally need
- * to trigger.
- */
- struct TALER_EXCHANGE_CsRWithdrawHandle *csr;
-
- /**
- * Handle for the /tip-pickup operation we are part of.
- */
- struct TALER_MERCHANT_TipPickupHandle *tp;
-
- /**
- * Offset of this entry in the array.
- */
- unsigned int off;
-};
-
-
-/**
- * Handle for a /tip-pickup operation.
- */
-struct TALER_MERCHANT_TipPickupHandle
-{
-
- /**
- * Function to call with the result.
- */
- TALER_MERCHANT_TipPickupCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Handle for the actual (internal) withdraw operation.
- */
- struct TALER_MERCHANT_TipPickup2Handle *tpo2;
-
- /**
- * Array of length @e num_planchets.
- */
- struct PlanchetData *planchets;
-
- /**
- * Array of length @e num_planchets.
- */
- struct TALER_EXCHANGE_PrivateCoinDetails *pcds;
-
- /**
- * Context for making HTTP requests.
- */
- struct GNUNET_CURL_Context *ctx;
-
- /**
- * URL of the merchant backend.
- */
- char *backend_url;
-
- /**
- * ID of the tip we are picking up.
- */
- struct TALER_TipIdentifierP tip_id;
-
- /**
- * Number of planchets/coins used for this operation.
- */
- unsigned int num_planchets;
-
- /**
- * Number of remaining active /csr-withdraw requests.
- */
- unsigned int csr_active;
-};
-
-
-/**
- * Fail the pickup operation @a tp, returning @a ec.
- * Also cancels @a tp.
- *
- * @param[in] tp operation to fail
- * @param ec reason for the failure
- */
-static void
-fail_pickup (struct TALER_MERCHANT_TipPickupHandle *tp,
- enum TALER_ErrorCode ec)
-{
- struct TALER_MERCHANT_PickupDetails pd = {
- .hr.ec = ec
- };
-
- tp->cb (tp->cb_cls,
- &pd);
- TALER_MERCHANT_tip_pickup_cancel (tp);
-}
-
-
-/**
- * Callback for a /tip-pickup request. Returns the result of the operation.
- * Note that the client MUST still do the unblinding of the @a blind_sigs.
- *
- * @param cls closure, a `struct TALER_MERCHANT_TipPickupHandle *`
- * @param hr HTTP response details
- * @param num_blind_sigs length of the @a reserve_sigs array, 0 on error
- * @param blind_sigs array of blind signatures over the planchets, NULL on error
- */
-static void
-pickup_done_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int num_blind_sigs,
- const struct TALER_BlindedDenominationSignature *blind_sigs)
-{
- struct TALER_MERCHANT_TipPickupHandle *tp = cls;
- struct TALER_MERCHANT_PickupDetails pd = {
- .hr = *hr
- };
-
- tp->tpo2 = NULL;
- if (NULL == blind_sigs)
- {
- tp->cb (tp->cb_cls,
- &pd);
- TALER_MERCHANT_tip_pickup_cancel (tp);
- return;
- }
- {
- enum GNUNET_GenericReturnValue ok = GNUNET_OK;
-
- for (unsigned int i = 0; i<num_blind_sigs; i++)
- {
- struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[i];
- struct TALER_FreshCoin fc;
-
- if (GNUNET_OK !=
- TALER_planchet_to_coin (&tp->planchets[i].pk.key,
- &blind_sigs[i],
- &pcd->bks,
- &pcd->coin_priv,
- NULL,
- &tp->planchets[i].c_hash,
- &pcd->exchange_vals,
- &fc))
- {
- ok = GNUNET_SYSERR;
- break;
- }
- pcd->sig = fc.sig;
- }
- if (GNUNET_OK != ok)
- {
- struct TALER_MERCHANT_HttpResponse hrx = {
- .reply = hr->reply,
- .ec = TALER_EC_MERCHANT_TIP_PICKUP_UNBLIND_FAILURE
- };
-
- pd.hr = hrx;
- tp->cb (tp->cb_cls,
- &pd);
- }
- else
- {
- pd.details.success.num_sigs = num_blind_sigs;
- pd.details.success.pcds = tp->pcds;
- tp->cb (tp->cb_cls,
- &pd);
- }
- }
- TALER_MERCHANT_tip_pickup_cancel (tp);
-}
-
-
-/**
- * We have obtained all of the exchange inputs. Continue the pickup.
- *
- * @param[in,out] tp operation to continue
- */
-static void
-pickup_post_csr (struct TALER_MERCHANT_TipPickupHandle *tp)
-{
- struct TALER_PlanchetDetail details[tp->num_planchets];
-
- for (unsigned int i = 0; i<tp->num_planchets; i++)
- {
- const struct PlanchetData *pd = &tp->planchets[i];
- struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[i];
-
- TALER_planchet_setup_coin_priv (&pd->ps,
- &pcd->exchange_vals,
- &pcd->coin_priv);
- TALER_planchet_blinding_secret_create (&pd->ps,
- &pcd->exchange_vals,
- &pcd->bks);
- if (TALER_DENOMINATION_CS == pcd->exchange_vals.cipher)
- {
- details[i].blinded_planchet.details.cs_blinded_planchet.nonce
- = pd->nonce;
- }
- if (GNUNET_OK !=
- TALER_planchet_prepare (&pd->pk.key,
- &pcd->exchange_vals,
- &pcd->bks,
- &pcd->coin_priv,
- NULL,
- &tp->planchets[i].c_hash,
- &details[i]))
- {
- GNUNET_break (0);
- for (unsigned int j = 0; j<i; j++)
- TALER_planchet_detail_free (&details[j]);
- fail_pickup (tp,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE);
- return;
- }
- }
- tp->tpo2 = TALER_MERCHANT_tip_pickup2 (tp->ctx,
- tp->backend_url,
- &tp->tip_id,
- tp->num_planchets,
- details,
- &pickup_done_cb,
- tp);
- for (unsigned int j = 0; j<tp->num_planchets; j++)
- TALER_planchet_detail_free (&details[j]);
- if (NULL == tp->tpo2)
- {
- GNUNET_break (0);
- fail_pickup (tp,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE);
- return;
- }
-}
-
-
-/**
- * Callbacks of this type are used to serve the result of submitting a
- * CS R request to a exchange.
- *
- * @param cls a `struct TALER_MERCHANT_TipPickupHandle`
- * @param csrr response details
- */
-static void
-csr_cb (void *cls,
- const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr)
-{
- struct PlanchetData *pd = cls;
- struct TALER_MERCHANT_TipPickupHandle *tp = pd->tp;
-
- pd->csr = NULL;
- tp->csr_active--;
- switch (csrr->hr.http_status)
- {
- case MHD_HTTP_OK:
- {
- struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[pd->off];
-
- pcd->exchange_vals = csrr->details.success.alg_values;
- }
- if (0 != tp->csr_active)
- return;
- pickup_post_csr (tp);
- return;
- default:
- {
- struct TALER_MERCHANT_PickupDetails pd = {
- .hr.hint = "/csr-withdraw failed",
- .hr.exchange_http_status = csrr->hr.http_status
- };
-
- tp->cb (tp->cb_cls,
- &pd);
- TALER_MERCHANT_tip_pickup_cancel (tp);
- return;
- }
- }
-}
-
-
-struct TALER_MERCHANT_TipPickupHandle *
-TALER_MERCHANT_tip_pickup (struct GNUNET_CURL_Context *ctx,
- struct TALER_EXCHANGE_Handle *exchange,
- const char *backend_url,
- const struct TALER_TipIdentifierP *tip_id,
- unsigned int num_planchets,
- const struct TALER_MERCHANT_PlanchetData *pds,
- TALER_MERCHANT_TipPickupCallback pickup_cb,
- void *pickup_cb_cls)
-{
- struct TALER_MERCHANT_TipPickupHandle *tp;
-
- if (0 == num_planchets)
- {
- GNUNET_break (0);
- return NULL;
- }
- tp = GNUNET_new (struct TALER_MERCHANT_TipPickupHandle);
- tp->cb = pickup_cb;
- tp->cb_cls = pickup_cb_cls;
- tp->ctx = ctx;
- tp->backend_url = GNUNET_strdup (backend_url);
- tp->tip_id = *tip_id;
- tp->num_planchets = num_planchets;
- tp->planchets = GNUNET_new_array (num_planchets,
- struct PlanchetData);
- tp->pcds = GNUNET_new_array (num_planchets,
- struct TALER_EXCHANGE_PrivateCoinDetails);
- for (unsigned int i = 0; i<num_planchets; i++)
- {
- const struct TALER_MERCHANT_PlanchetData *mpd = &pds[i];
- const struct TALER_EXCHANGE_DenomPublicKey *pk = mpd->pk;
- struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[i];
- struct PlanchetData *pd = &tp->planchets[i];
-
- pd->off = i;
- pd->tp = tp;
- tp->planchets[i].ps = mpd->ps;
- tp->planchets[i].pk = *pds[i].pk;
- TALER_denom_pub_deep_copy (&tp->planchets[i].pk.key,
- &pds[i].pk->key);
- switch (pk->key.cipher)
- {
- case TALER_DENOMINATION_RSA:
- pcd->exchange_vals.cipher = TALER_DENOMINATION_RSA;
- break;
- case TALER_DENOMINATION_CS:
- {
- TALER_cs_withdraw_nonce_derive (&pd->ps,
- &pd->nonce);
- pd->csr = TALER_EXCHANGE_csr_withdraw (exchange,
- &pd->pk,
- &pd->nonce,
- &csr_cb,
- pd);
- if (NULL == pd->csr)
- {
- GNUNET_break (0);
- TALER_MERCHANT_tip_pickup_cancel (tp);
- return NULL;
- }
- tp->csr_active++;
- break;
- }
- default:
- GNUNET_break (0);
- TALER_MERCHANT_tip_pickup_cancel (tp);
- return NULL;
- }
- }
- if (0 == tp->csr_active)
- {
- pickup_post_csr (tp);
- return tp;
- }
- return tp;
-}
-
-
-void
-TALER_MERCHANT_tip_pickup_cancel (struct TALER_MERCHANT_TipPickupHandle *tp)
-{
- for (unsigned int i = 0; i<tp->num_planchets; i++)
- {
- struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[i];
- struct PlanchetData *pd = &tp->planchets[i];
-
- TALER_denom_sig_free (&pcd->sig);
- TALER_denom_pub_free (&tp->planchets[i].pk.key);
- if (NULL != pd->csr)
- {
- TALER_EXCHANGE_csr_withdraw_cancel (pd->csr);
- pd->csr = NULL;
- }
- }
- GNUNET_array_grow (tp->planchets,
- tp->num_planchets,
- 0);
- if (NULL != tp->tpo2)
- {
- TALER_MERCHANT_tip_pickup2_cancel (tp->tpo2);
- tp->tpo2 = NULL;
- }
- GNUNET_free (tp->backend_url);
- GNUNET_free (tp->pcds);
- GNUNET_free (tp);
-}
-
-
-/* end of merchant_api_tip_pickup.c */
diff --git a/src/lib/merchant_api_tip_pickup2.c b/src/lib/merchant_api_tip_pickup2.c
deleted file mode 100644
index 33457024..00000000
--- a/src/lib/merchant_api_tip_pickup2.c
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
- Foundation; either version 2.1, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along with
- TALER; see the file COPYING.LGPL. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file merchant_api_tip_pickup2.c
- * @brief Implementation of the /tip-pickup request of the merchant's HTTP API
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <curl/curl.h>
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_merchant_service.h"
-#include "merchant_api_curl_defaults.h"
-#include "merchant_api_common.h"
-#include <taler/taler_json_lib.h>
-#include <taler/taler_signatures.h>
-#include <taler/taler_curl_lib.h>
-
-
-/**
- * @brief A handle for tracking transactions.
- */
-struct TALER_MERCHANT_TipPickup2Handle
-{
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Minor context that holds body and headers.
- */
- struct TALER_CURL_PostContext post_ctx;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_MERCHANT_TipPickup2Callback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Reference to the execution context.
- */
- struct GNUNET_CURL_Context *ctx;
-
- /**
- * Expected number of planchets.
- */
- unsigned int num_planchets;
-};
-
-
-/**
- * We got a 200 response back from the exchange (or the merchant).
- * Now we need to parse the response and if it is well-formed,
- * call the callback (and set it to NULL afterwards).
- *
- * @param tpo handle of the original authorization operation
- * @param json cryptographic proof returned by the exchange/merchant
- * @return #GNUNET_OK if response is valid
- */
-static int
-check_ok (struct TALER_MERCHANT_TipPickup2Handle *tpo,
- const json_t *json)
-{
- json_t *ja;
- unsigned int ja_len;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("blind_sigs", &ja),
- GNUNET_JSON_spec_end ()
- };
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = MHD_HTTP_OK,
- .reply = json
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- ja_len = json_array_size (ja);
- if (ja_len != tpo->num_planchets)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- {
- struct TALER_BlindedDenominationSignature mblind_sigs[ja_len];
-
- for (unsigned int i = 0; i<ja_len; i++)
- {
- json_t *pj = json_array_get (ja, i);
- struct GNUNET_JSON_Specification ispec[] = {
- TALER_JSON_spec_blinded_denom_sig ("blind_sig",
- &mblind_sigs[i]),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (pj,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- }
- tpo->cb (tpo->cb_cls,
- &hr,
- ja_len,
- mblind_sigs);
- for (unsigned int i = 0; i<ja_len; i++)
- TALER_blinded_denom_sig_free (&mblind_sigs[i]);
- tpo->cb = NULL; /* do not call twice */
- }
- GNUNET_JSON_parse_free (spec);
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /track/transaction request.
- *
- * @param cls the `struct TALER_MERCHANT_TipPickupHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response response body, NULL if not in JSON
- */
-static void
-handle_tip_pickup_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_MERCHANT_TipPickup2Handle *tpo = cls;
- const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
- };
-
- tpo->job = NULL;
- switch (response_code)
- {
- case MHD_HTTP_OK:
- if (GNUNET_OK != check_ok (tpo,
- json))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- }
- break;
- case MHD_HTTP_BAD_REQUEST:
- /* Can happen if we pickup an amount that exceeds the tip... */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- GNUNET_break (TALER_EC_MERCHANT_TIP_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING ==
- hr.ec);
- break;
- case MHD_HTTP_CONFLICT:
- /* legal, can happen if we pickup a tip twice... */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_NOT_FOUND:
- /* legal, can happen if tip ID is unknown */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &hr);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- (int) hr.ec);
- break;
- }
- if (NULL != tpo->cb)
- {
- tpo->cb (tpo->cb_cls,
- &hr,
- 0,
- NULL);
- tpo->cb = NULL;
- }
- TALER_MERCHANT_tip_pickup2_cancel (tpo);
-}
-
-
-struct TALER_MERCHANT_TipPickup2Handle *
-TALER_MERCHANT_tip_pickup2 (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_TipIdentifierP *tip_id,
- unsigned int num_planchets,
- const struct TALER_PlanchetDetail planchets[],
- TALER_MERCHANT_TipPickup2Callback pickup_cb,
- void *pickup_cb_cls)
-{
- struct TALER_MERCHANT_TipPickup2Handle *tpo;
- CURL *eh;
- json_t *pa;
- json_t *tp_obj;
-
- if (0 == num_planchets)
- {
- GNUNET_break (0);
- return NULL;
- }
- pa = json_array ();
- for (unsigned int i = 0; i<num_planchets; i++)
- {
- const struct TALER_PlanchetDetail *planchet = &planchets[i];
- json_t *p;
-
- p = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("denom_pub_hash",
- &planchet->denom_pub_hash),
- TALER_JSON_pack_blinded_planchet ("coin_ev",
- &planchet->blinded_planchet));
- if (0 !=
- json_array_append_new (pa,
- p))
- {
- GNUNET_break (0);
- json_decref (pa);
- return NULL;
- }
- }
- tp_obj = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_array_steal ("planchets",
- pa));
- tpo = GNUNET_new (struct TALER_MERCHANT_TipPickup2Handle);
- tpo->num_planchets = num_planchets;
- tpo->ctx = ctx;
- tpo->cb = pickup_cb;
- tpo->cb_cls = pickup_cb_cls;
-
- {
- char tip_str[sizeof (*tip_id) * 2];
- char arg_str[sizeof (tip_str) + 32];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (tip_id,
- sizeof (*tip_id),
- tip_str,
- sizeof (tip_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "tips/%s/pickup",
- tip_str);
- tpo->url = TALER_url_join (backend_url,
- arg_str,
- NULL);
- }
- if (NULL == tpo->url)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not construct request URL.\n");
- json_decref (tp_obj);
- GNUNET_free (tpo);
- return NULL;
- }
- eh = TALER_MERCHANT_curl_easy_get_ (tpo->url);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&tpo->post_ctx,
- eh,
- tp_obj))
- {
- GNUNET_break (0);
- json_decref (tp_obj);
- curl_easy_cleanup (eh);
- GNUNET_free (tpo->url);
- GNUNET_free (tpo);
- return NULL;
- }
- json_decref (tp_obj);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Requesting URL '%s'\n",
- tpo->url);
- tpo->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- tpo->post_ctx.headers,
- &handle_tip_pickup_finished,
- tpo);
- if (NULL == tpo->job)
- {
- TALER_MERCHANT_tip_pickup2_cancel (tpo);
- return NULL;
- }
- return tpo;
-}
-
-
-void
-TALER_MERCHANT_tip_pickup2_cancel (
- struct TALER_MERCHANT_TipPickup2Handle *tpo)
-{
- if (NULL != tpo->job)
- {
- GNUNET_CURL_job_cancel (tpo->job);
- tpo->job = NULL;
- }
- TALER_curl_easy_post_finished (&tpo->post_ctx);
- GNUNET_free (tpo->url);
- GNUNET_free (tpo);
-}
-
-
-/* end of merchant_api_tip_pickup2.c */
diff --git a/src/lib/merchant_api_wallet_get_order.c b/src/lib/merchant_api_wallet_get_order.c
index 61bed534..763b2c83 100644
--- a/src/lib/merchant_api_wallet_get_order.c
+++ b/src/lib/merchant_api_wallet_get_order.c
@@ -116,11 +116,11 @@ handle_wallet_get_order_finished (void *cls,
};
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_bool ("refunded",
- &owgr.details.success.refunded),
+ &owgr.details.ok.refunded),
GNUNET_JSON_spec_bool ("refund_pending",
- &owgr.details.success.refund_pending),
+ &owgr.details.ok.refund_pending),
TALER_JSON_spec_amount_any ("refund_amount",
- &owgr.details.success.refund_amount),
+ &owgr.details.ok.refund_amount),
GNUNET_JSON_spec_end ()
};
@@ -205,8 +205,7 @@ TALER_MERCHANT_wallet_order_get (
void *cb_cls)
{
struct TALER_MERCHANT_OrderWalletGetHandle *owgh;
- unsigned long long tms;
- long tlong;
+ unsigned int tms;
GNUNET_assert (NULL != backend_url);
GNUNET_assert (NULL != order_id);
@@ -214,14 +213,8 @@ TALER_MERCHANT_wallet_order_get (
owgh->ctx = ctx;
owgh->cb = cb;
owgh->cb_cls = cb_cls;
- tms = (unsigned long long) (timeout.rel_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
- /* set curl timeout to *our* long poll timeout plus one minute
- (for network latency and processing delays) */
- tlong = (long) (GNUNET_TIME_relative_add (timeout,
- GNUNET_TIME_UNIT_MINUTES).
- rel_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
+ tms = (unsigned int) (timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
{
char timeout_ms[32];
struct GNUNET_CRYPTO_HashAsciiEncoded h_contract_s;
@@ -231,7 +224,7 @@ TALER_MERCHANT_wallet_order_get (
&h_contract_s);
GNUNET_snprintf (timeout_ms,
sizeof (timeout_ms),
- "%llu",
+ "%u",
tms);
GNUNET_asprintf (&path,
"orders/%s",
@@ -271,28 +264,10 @@ TALER_MERCHANT_wallet_order_get (
eh = TALER_MERCHANT_curl_easy_get_ (owgh->url);
if (0 != tms)
{
- if (CURLE_OK !=
- curl_easy_setopt (eh,
- CURLOPT_TIMEOUT_MS,
- (long) tms))
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- GNUNET_free (owgh->url);
- GNUNET_free (owgh);
- return NULL;
- }
- }
- if (CURLE_OK !=
- curl_easy_setopt (eh,
- CURLOPT_TIMEOUT_MS,
- tlong))
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- GNUNET_free (owgh->url);
- GNUNET_free (owgh);
- return NULL;
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 100L)));
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
diff --git a/src/lib/merchant_api_wallet_get_template.c b/src/lib/merchant_api_wallet_get_template.c
new file mode 100644
index 00000000..d9ca95bc
--- /dev/null
+++ b/src/lib/merchant_api_wallet_get_template.c
@@ -0,0 +1,195 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 2.1, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING.LGPL. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant_api_wallet_get_template.c
+ * @brief Implementation of the GET /templates/$ID request of the merchant's HTTP API
+ * @author Priscilla HUANG
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_curl_defaults.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Handle for a GET /templates/$ID operation.
+ */
+struct TALER_MERCHANT_WalletTemplateGetHandle
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_WalletTemplateGetCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /templates/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_TemplateGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_get_template_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_WalletTemplateGetHandle *tgh = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_WalletTemplateGetResponse tgr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ tgh->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got /templates/$ID response with status code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ const json_t *contract;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_object_const ("template_contract",
+ &contract),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK ==
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ tgr.details.ok.template_contract = contract;
+ tgh->cb (tgh->cb_cls,
+ &tgr);
+ TALER_MERCHANT_wallet_template_get_cancel (tgh);
+ return;
+ }
+ tgr.hr.http_status = 0;
+ tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ case MHD_HTTP_UNAUTHORIZED:
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we need to authenticate. */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ tgr.hr.ec = TALER_JSON_get_error_code (json);
+ tgr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) tgr.hr.ec);
+ break;
+ }
+ tgh->cb (tgh->cb_cls,
+ &tgr);
+ TALER_MERCHANT_wallet_template_get_cancel (tgh);
+}
+
+
+struct TALER_MERCHANT_WalletTemplateGetHandle *
+TALER_MERCHANT_wallet_template_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *template_id,
+ TALER_MERCHANT_WalletTemplateGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_WalletTemplateGetHandle *tgh;
+ CURL *eh;
+
+ tgh = GNUNET_new (struct TALER_MERCHANT_WalletTemplateGetHandle);
+ tgh->ctx = ctx;
+ tgh->cb = cb;
+ tgh->cb_cls = cb_cls;
+ {
+ char *path;
+
+ GNUNET_asprintf (&path,
+ "templates/%s",
+ template_id);
+ tgh->url = TALER_url_join (backend_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == tgh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (tgh);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ tgh->url);
+ eh = TALER_MERCHANT_curl_easy_get_ (tgh->url);
+ tgh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_get_template_finished,
+ tgh);
+ return tgh;
+}
+
+
+void
+TALER_MERCHANT_wallet_template_get_cancel (
+ struct TALER_MERCHANT_WalletTemplateGetHandle *tgh)
+{
+ if (NULL != tgh->job)
+ GNUNET_CURL_job_cancel (tgh->job);
+ GNUNET_free (tgh->url);
+ GNUNET_free (tgh);
+}
diff --git a/src/lib/merchant_api_wallet_get_tip.c b/src/lib/merchant_api_wallet_get_tip.c
deleted file mode 100644
index 49e507d0..00000000
--- a/src/lib/merchant_api_wallet_get_tip.c
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2018, 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
- Foundation; either version 2.1, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along with
- TALER; see the file COPYING.LGPL. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file merchant_api_wallet_get_tip.c
- * @brief Implementation of the GET /tips/$TIP_ID request of the merchant's HTTP API
- * @author Florian Dold
- */
-#include "platform.h"
-#include <curl/curl.h>
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_merchant_service.h"
-#include "merchant_api_curl_defaults.h"
-#include "merchant_api_common.h"
-#include <taler/taler_json_lib.h>
-#include <taler/taler_signatures.h>
-
-
-/**
- * @brief A handle for tracking /tip-get operations
- */
-struct TALER_MERCHANT_TipWalletGetHandle
-{
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_MERCHANT_TipWalletGetCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Reference to the execution context.
- */
- struct GNUNET_CURL_Context *ctx;
-
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP /track/transaction request.
- *
- * @param cls the `struct TALER_MERCHANT_TipGetHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response response body, NULL if not in JSON
- */
-static void
-handle_wallet_tip_get_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_MERCHANT_TipWalletGetHandle *tgh = cls;
- const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Got /tip/$TIP_ID response with status code %u\n",
- (unsigned int) response_code);
-
- tgh->job = NULL;
- switch (response_code)
- {
- case MHD_HTTP_OK:
- {
- const char *exchange_url;
- struct TALER_Amount amount_remaining;
- struct GNUNET_TIME_Timestamp expiration;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_timestamp ("expiration",
- &expiration),
- GNUNET_JSON_spec_string ("exchange_url",
- &exchange_url),
- TALER_JSON_spec_amount_any ("tip_amount",
- &amount_remaining),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- tgh->cb (tgh->cb_cls,
- &hr,
- expiration,
- exchange_url,
- &amount_remaining);
- TALER_MERCHANT_wallet_tip_get_cancel (tgh);
- return;
- }
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_NOT_FOUND:
- /* legal, can happen if instance or tip reserve is unknown */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &hr);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- (int) hr.ec);
- break;
- }
- tgh->cb (tgh->cb_cls,
- &hr,
- GNUNET_TIME_UNIT_ZERO_TS,
- NULL,
- NULL);
- TALER_MERCHANT_wallet_tip_get_cancel (tgh);
-}
-
-
-struct TALER_MERCHANT_TipWalletGetHandle *
-TALER_MERCHANT_wallet_tip_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_TipIdentifierP *tip_id,
- TALER_MERCHANT_TipWalletGetCallback cb,
- void *cb_cls)
-{
- struct TALER_MERCHANT_TipWalletGetHandle *tgh;
- CURL *eh;
-
- tgh = GNUNET_new (struct TALER_MERCHANT_TipWalletGetHandle);
- tgh->ctx = ctx;
- tgh->cb = cb;
- tgh->cb_cls = cb_cls;
- {
- char res_str[sizeof (*tip_id) * 2];
- char arg_str[sizeof (res_str) + 48];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (tip_id,
- sizeof (*tip_id),
- res_str,
- sizeof (res_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "tips/%s",
- res_str);
- tgh->url = TALER_url_join (backend_url,
- arg_str,
- NULL);
- }
-
- if (NULL == tgh->url)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not construct request URL.\n");
- GNUNET_free (tgh);
- return NULL;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Requesting URL '%s'\n",
- tgh->url);
- eh = TALER_MERCHANT_curl_easy_get_ (tgh->url);
- tgh->job = GNUNET_CURL_job_add (ctx,
- eh,
- &handle_wallet_tip_get_finished,
- tgh);
- return tgh;
-}
-
-
-void
-TALER_MERCHANT_wallet_tip_get_cancel (
- struct TALER_MERCHANT_TipWalletGetHandle *tgh)
-{
- if (NULL != tgh->job)
- {
- GNUNET_CURL_job_cancel (tgh->job);
- tgh->job = NULL;
- }
- GNUNET_free (tgh->url);
- GNUNET_free (tgh);
-}
-
-
-/* end of merchant_api_wallet_get_tip.c */
diff --git a/src/lib/merchant_api_wallet_post_order_refund.c b/src/lib/merchant_api_wallet_post_order_refund.c
index fc4b0abd..e72982f3 100644
--- a/src/lib/merchant_api_wallet_post_order_refund.c
+++ b/src/lib/merchant_api_wallet_post_order_refund.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -32,6 +32,10 @@
#include <taler/taler_signatures.h>
#include <taler/taler_curl_lib.h>
+/**
+ * Maximum number of refunds we return.
+ */
+#define MAX_REFUNDS 1024
/**
* Handle for a (public) POST /orders/ID/refund operation.
@@ -71,33 +75,6 @@ struct TALER_MERCHANT_WalletOrderRefundHandle
/**
- * Convenience function to call the callback in @a owgh with an error code of
- * @a ec and the exchange body being set to @a reply.
- *
- * @param orh handle providing callback
- * @param ec error code to return to application
- * @param reply JSON reply we got from the exchange, can be NULL
- */
-static void
-cb_failure (struct TALER_MERCHANT_WalletOrderRefundHandle *orh,
- enum TALER_ErrorCode ec,
- const json_t *reply)
-{
- struct TALER_MERCHANT_HttpResponse hr = {
- .ec = ec,
- .reply = reply
- };
-
- orh->cb (orh->cb_cls,
- &hr,
- NULL,
- NULL,
- NULL,
- 0);
-}
-
-
-/**
* Callback to process (public) POST /orders/ID/refund response
*
* @param cls the `struct TALER_MERCHANT_OrderRefundHandle`
@@ -111,37 +88,31 @@ handle_refund_finished (void *cls,
{
struct TALER_MERCHANT_WalletOrderRefundHandle *orh = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_WalletRefundResponse wrr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
orh->job = NULL;
-
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- orh->cb (orh->cb_cls,
- &hr,
- NULL,
- NULL,
- NULL,
- 0);
+ wrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
{
- struct TALER_Amount refund_amount;
- json_t *refunds;
- struct TALER_MerchantPublicKeyP merchant_pub;
+ const json_t *refunds;
unsigned int refund_len;
struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("refund_amount",
- &refund_amount),
- GNUNET_JSON_spec_json ("refunds",
- &refunds),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &merchant_pub),
+ TALER_JSON_spec_amount_any (
+ "refund_amount",
+ &wrr.details.ok.refund_amount),
+ GNUNET_JSON_spec_array_const (
+ "refunds",
+ &refunds),
+ GNUNET_JSON_spec_fixed_auto (
+ "merchant_pub",
+ &wrr.details.ok.merchant_pub),
GNUNET_JSON_spec_end ()
};
@@ -151,27 +122,21 @@ handle_refund_finished (void *cls,
NULL, NULL))
{
GNUNET_break_op (0);
- cb_failure (orh,
- TALER_EC_GENERIC_REPLY_MALFORMED,
- json);
- TALER_MERCHANT_wallet_post_order_refund_cancel (orh);
- return;
+ wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ wrr.hr.http_status = 0;
+ break;
}
-
- if (! json_is_array (refunds))
+ refund_len = json_array_size (refunds);
+ if ( (json_array_size (refunds) != (size_t) refund_len) ||
+ (refund_len > MAX_REFUNDS) )
{
- GNUNET_break_op (0);
- cb_failure (orh,
- TALER_EC_GENERIC_REPLY_MALFORMED,
- json);
- GNUNET_JSON_parse_free (spec);
- TALER_MERCHANT_wallet_post_order_refund_cancel (orh);
- return;
+ GNUNET_break (0);
+ wrr.hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
+ wrr.hr.http_status = 0;
+ break;
}
-
- refund_len = json_array_size (refunds);
{
- struct TALER_MERCHANT_RefundDetail rds[refund_len];
+ struct TALER_MERCHANT_RefundDetail rds[GNUNET_NZL (refund_len)];
memset (rds,
0,
@@ -183,10 +148,26 @@ handle_refund_finished (void *cls,
i);
const char *refund_status_type;
uint32_t exchange_status;
- int ret;
+ uint32_t eec = 0;
struct GNUNET_JSON_Specification espec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &refund_status_type),
GNUNET_JSON_spec_uint32 ("exchange_status",
&exchange_status),
+ GNUNET_JSON_spec_uint64 ("rtransaction_id",
+ &rd->rtransaction_id),
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &rd->coin_pub),
+ TALER_JSON_spec_amount_any ("refund_amount",
+ &rd->refund_amount),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("exchange_reply",
+ &rd->hr.reply),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("exchange_code",
+ &eec),
+ NULL),
GNUNET_JSON_spec_end ()
};
@@ -196,151 +177,85 @@ handle_refund_finished (void *cls,
NULL, NULL))
{
GNUNET_break_op (0);
- cb_failure (orh,
- TALER_EC_GENERIC_REPLY_MALFORMED,
- json);
- TALER_MERCHANT_wallet_post_order_refund_cancel (orh);
- return;
+ wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ wrr.hr.http_status = 0;
+ goto finish;
}
- if (MHD_HTTP_OK == exchange_status)
+ rd->hr.http_status = exchange_status;
+ rd->hr.ec = (enum TALER_ErrorCode) eec;
+ switch (exchange_status)
{
- struct GNUNET_JSON_Specification rspec[] = {
- GNUNET_JSON_spec_string ("type",
- &refund_status_type),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &rd->exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &rd->exchange_pub),
- GNUNET_JSON_spec_uint64 ("rtransaction_id",
- &rd->rtransaction_id),
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &rd->coin_pub),
- TALER_JSON_spec_amount_any ("refund_amount",
- &rd->refund_amount),
- GNUNET_JSON_spec_end ()
- };
-
- ret = GNUNET_JSON_parse (jrefund,
- rspec,
- NULL, NULL);
- if (GNUNET_OK == ret)
+ case MHD_HTTP_OK:
{
- /* check that type field is correct */
- if (0 != strcmp ("success", refund_status_type))
- {
- GNUNET_break_op (0);
- ret = GNUNET_SYSERR;
- }
- }
- }
- else
- {
- struct GNUNET_JSON_Specification rspec[] = {
- GNUNET_JSON_spec_string ("type",
- &refund_status_type),
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &rd->coin_pub),
- GNUNET_JSON_spec_uint64 ("rtransaction_id",
- &rd->rtransaction_id),
- TALER_JSON_spec_amount_any ("refund_amount",
- &rd->refund_amount),
- GNUNET_JSON_spec_end ()
- };
-
- ret = GNUNET_JSON_parse (jrefund,
+ struct GNUNET_JSON_Specification rspec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rd->details.ok.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rd->details.ok.exchange_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jrefund,
rspec,
- NULL, NULL);
- if (GNUNET_OK == ret)
- {
- /* parse optional arguments */
- json_t *jec;
-
- jec = json_object_get (jrefund,
- "exchange_code");
- if (NULL != jec)
+ NULL,
+ NULL))
{
- if (! json_is_integer (jec))
- {
- GNUNET_break_op (0);
- ret = GNUNET_SYSERR;
- }
- else
- {
- rd->hr.ec = (enum TALER_ErrorCode) json_integer_value (jec);
- }
+ GNUNET_break_op (0);
+ wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ wrr.hr.http_status = 0;
+ goto finish;
}
- rd->hr.reply = json_object_get (jrefund,
- "exchange_reply");
/* check that type field is correct */
- if (0 != strcmp ("failure", refund_status_type))
+ if (0 != strcmp ("success",
+ refund_status_type))
{
GNUNET_break_op (0);
- ret = GNUNET_SYSERR;
+ wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ wrr.hr.http_status = 0;
+ goto finish;
}
}
- }
- if (GNUNET_OK != ret)
- {
- GNUNET_break_op (0);
- cb_failure (orh,
- TALER_EC_GENERIC_REPLY_MALFORMED,
- json);
- TALER_MERCHANT_wallet_post_order_refund_cancel (orh);
- return;
- }
- rd->hr.http_status = exchange_status;
- }
-
- {
- struct TALER_MERCHANT_HttpResponse hr = {
- .reply = json,
- .http_status = MHD_HTTP_OK
- };
+ break; /* end MHD_HTTP_OK */
+ default:
+ if (0 != strcmp ("failure",
+ refund_status_type))
+ {
+ GNUNET_break_op (0);
+ wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ wrr.hr.http_status = 0;
+ goto finish;
+ }
+ } /* switch on exchange status code */
+ } /* for all refunds */
- orh->cb (orh->cb_cls,
- &hr,
- &refund_amount,
- &merchant_pub,
- rds,
- refund_len);
- }
- }
- GNUNET_JSON_parse_free (spec);
- }
+ wrr.details.ok.refunds = rds;
+ wrr.details.ok.refunds_length = refund_len;
+ orh->cb (orh->cb_cls,
+ &wrr);
+ TALER_MERCHANT_wallet_post_order_refund_cancel (orh);
+ return;
+ } /* end 'rds' scope */
+ } /* case MHD_HTTP_OK */
break;
case MHD_HTTP_NO_CONTENT:
- orh->cb (orh->cb_cls,
- &hr,
- NULL,
- NULL,
- NULL,
- 0);
break;
case MHD_HTTP_CONFLICT:
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- orh->cb (orh->cb_cls,
- &hr,
- NULL,
- NULL,
- NULL,
- 0);
+ wrr.hr.ec = TALER_JSON_get_error_code (json);
+ wrr.hr.hint = TALER_JSON_get_error_hint (json);
break;
default:
GNUNET_break_op (0); /* unexpected status code */
TALER_MERCHANT_parse_error_details_ (json,
response_code,
- &hr);
- orh->cb (orh->cb_cls,
- &hr,
- NULL,
- NULL,
- NULL,
- 0);
+ &wrr.hr);
break;
}
+finish:
+ orh->cb (orh->cb_cls,
+ &wrr);
TALER_MERCHANT_wallet_post_order_refund_cancel (orh);
}
diff --git a/src/merchant-tools/.gitignore b/src/merchant-tools/.gitignore
new file mode 100644
index 00000000..93285154
--- /dev/null
+++ b/src/merchant-tools/.gitignore
@@ -0,0 +1,5 @@
+*.edited
+exchange_benchmark_home/taler/exchange-offline/
+exchange_benchmark_home/taler/exchange-secmod-cs/
+exchange_benchmark_home/taler/exchange-secmod-eddsa/
+exchange_benchmark_home/taler/exchange-secmod-rsa/
diff --git a/src/merchant-tools/Makefile.am b/src/merchant-tools/Makefile.am
index 987a0897..21ddb89a 100644
--- a/src/merchant-tools/Makefile.am
+++ b/src/merchant-tools/Makefile.am
@@ -7,9 +7,16 @@ if USE_COVERAGE
endif
bin_PROGRAMS = \
- taler-merchant-benchmark \
taler-merchant-dbinit \
- taler-merchant-setup-reserve
+ taler-merchant-passwd \
+ taler-merchant-benchmark
+
+EXTRA_DIST = \
+ benchmark-common.conf \
+ benchmark-cs.conf \
+ benchmark-rsa.conf \
+ coins-cs.conf \
+ coins-rsa.conf
taler_merchant_benchmark_SOURCES = \
taler-merchant-benchmark.c
@@ -40,12 +47,12 @@ taler_merchant_dbinit_LDADD = \
-lgnunetutil \
$(XLIB)
-taler_merchant_setup_reserve_SOURCES = \
- taler-merchant-setup-reserve.c
-taler_merchant_setup_reserve_LDADD = \
+taler_merchant_passwd_SOURCES = \
+ taler-merchant-passwd.c
+taler_merchant_passwd_LDADD = \
$(LIBGCRYPT_LIBS) \
- $(top_builddir)/src/lib/libtalermerchant.la \
+ $(top_builddir)/src/backenddb/libtalermerchantdb.la \
-ltalerutil \
- -lgnunetcurl \
+ -ltalerpq \
-lgnunetutil \
$(XLIB)
diff --git a/src/merchant-tools/benchmark-common.conf b/src/merchant-tools/benchmark-common.conf
new file mode 100644
index 00000000..1f54127f
--- /dev/null
+++ b/src/merchant-tools/benchmark-common.conf
@@ -0,0 +1,88 @@
+# This file is in the public domain.
+[paths]
+TALER_TEST_HOME=exchange_benchmark_home/
+
+[taler]
+CURRENCY=EUR
+CURRENCY_ROUND_UNIT=EUR:0.01
+
+[merchant-benchmark]
+MERCHANT_URL = "http://localhost:9966/"
+
+[exchange]
+AML_THRESHOLD=EUR:99999999
+SIGNKEY_LEGAL_DURATION=2 years
+PORT=8081
+MASTER_PUBLIC_KEY=MN7KME8DKVVXFSX7H2VTG7YGRFWFJV37KHJG7FEBFKMEDP73V3VG
+DB=postgres
+BASE_URL="http://localhost:8081/"
+# AGGREGATOR_SHARD_SIZE=67108864
+WIREWATCH_IDLE_SLEEP_INTERVAL=5 ms
+
+[exchangedb-postgres]
+CONFIG="postgres:///talercheck"
+
+[exchange-offline]
+MASTER_PRIV_FILE=${TALER_DATA_HOME}/exchange/offline-keys/master.priv
+
+[taler-exchange-secmod-rsa]
+LOOKAHEAD_SIGN="1 d"
+
+[taler-exchange-secmod-cs]
+LOOKAHEAD_SIGN="1 d"
+
+[taler-exchange-secmod-eddsa]
+DURATION="2 d"
+LOOKAHEAD_SIGN="1 d"
+
+# account-2 is suitable for fakebank
+[exchange-account-1]
+PAYTO_URI = "payto://x-taler-bank/localhost/42?receiver-name=42"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-1]
+WIRE_GATEWAY_AUTH_METHOD = none
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/42/taler-wire-gateway/"
+
+# account-2 is suitable for libeufin
+[exchange-account-2]
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+PAYTO_URI = payto://iban/SANDBOXX/DE033310?receiver-name=Exchange+Company
+
+[exchange-accountcredentials-2]
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = exchange
+PASSWORD = x
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/exchange/taler-wire-gateway/"
+
+
+# Trust local exchange for "EUR" currency
+[merchant-exchange-benchmark]
+EXCHANGE_BASE_URL = http://localhost:8081/
+MASTER_KEY=MN7KME8DKVVXFSX7H2VTG7YGRFWFJV37KHJG7FEBFKMEDP73V3VG
+CURRENCY = EUR
+
+
+[merchantdb-postgres]
+CONFIG="postgres:///talercheck"
+
+[auditordb-postgres]
+CONFIG="postgres:///talercheck"
+
+[syncdb-postgres]
+CONFIG="postgres:///talercheck"
+
+[bank]
+HTTP_PORT=8082
+SERVE=http
+
+[libeufin-nexus]
+DB_CONNECTION="postgresql:///talercheck"
+
+[libeufin-sandbox]
+DB_CONNECTION="postgresql:///talercheck"
+
+[auditor]
+BASE_URL="http://localhost:8083/"
diff --git a/src/merchant-tools/benchmark-cs.conf b/src/merchant-tools/benchmark-cs.conf
new file mode 100644
index 00000000..7f660ad3
--- /dev/null
+++ b/src/merchant-tools/benchmark-cs.conf
@@ -0,0 +1,16 @@
+# This file is in the public domain.
+@INLINE@ benchmark-common.conf
+@INLINE@ coins-cs.conf
+
+[exchange-account-test]
+# What is the bank account (with the "Taler Bank" demo system)? Must end with "/".
+PAYTO_URI = "payto://x-taler-bank/localhost/Exchange"
+# Authentication information for basic authentication
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-test]
+WIRE_GATEWAY_URL = http://localhost:8082/accounts/Exchange/taler-wire-gateway/
+WIRE_GATEWAY_AUTH_METHOD = "basic"
+USERNAME = Exchange
+PASSWORD = x
diff --git a/src/merchant-tools/benchmark-rsa.conf b/src/merchant-tools/benchmark-rsa.conf
new file mode 100644
index 00000000..a6c1512e
--- /dev/null
+++ b/src/merchant-tools/benchmark-rsa.conf
@@ -0,0 +1,16 @@
+# This file is in the public domain.
+@INLINE@ benchmark-common.conf
+@INLINE@ coins-rsa.conf
+
+[exchange-account-test]
+# What is the bank account (with the "Taler Bank" demo system)? Must end with "/".
+PAYTO_URI = "payto://x-taler-bank/localhost/Exchange"
+# Authentication information for basic authentication
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-test]
+WIRE_GATEWAY_URL = http://localhost:8082/accounts/Exchange/taler-wire-gateway/
+WIRE_GATEWAY_AUTH_METHOD = "basic"
+USERNAME = Exchange
+PASSWORD = x
diff --git a/src/merchant-tools/coins-cs.conf b/src/merchant-tools/coins-cs.conf
new file mode 100644
index 00000000..c4b5a45c
--- /dev/null
+++ b/src/merchant-tools/coins-cs.conf
@@ -0,0 +1,58 @@
+# This file is in the public domain.
+#
+# Sections starting with "coin_" specify which denominations
+# the exchange should support (and their respective fee structure)
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_1]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_5]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_10]
+value = EUR:10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
diff --git a/src/merchant-tools/coins-rsa.conf b/src/merchant-tools/coins-rsa.conf
new file mode 100644
index 00000000..42eb8acf
--- /dev/null
+++ b/src/merchant-tools/coins-rsa.conf
@@ -0,0 +1,63 @@
+# This file is in the public domain.
+#
+# Sections starting with "coin_" specify which denominations
+# the exchange should support (and their respective fee structure)
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 2048
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 2048
+
+[coin_eur_1]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 2048
+
+[coin_eur_5]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 2048
+
+[coin_eur_10]
+value = EUR:10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 2048
diff --git a/src/merchant-tools/exchange_benchmark_home/taler/exchange/offline-keys/master.priv b/src/merchant-tools/exchange_benchmark_home/taler/exchange/offline-keys/master.priv
new file mode 100644
index 00000000..b10ea6f6
--- /dev/null
+++ b/src/merchant-tools/exchange_benchmark_home/taler/exchange/offline-keys/master.priv
@@ -0,0 +1 @@
+-¸ÚŸºè|±FlÉ#L ©ruMoë|ž,p] \ No newline at end of file
diff --git a/src/merchant-tools/taler-merchant-benchmark.c b/src/merchant-tools/taler-merchant-benchmark.c
index d60cfeed..238b9f03 100644
--- a/src/merchant-tools/taler-merchant-benchmark.c
+++ b/src/merchant-tools/taler-merchant-benchmark.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014--2020 Taler Systems SA
+ (C) 2014--2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as
@@ -16,7 +16,6 @@
along with TALER; see the file COPYING. If not,
see <http://www.gnu.org/licenses/>
*/
-
/**
* @file taler-merchant-benchmark.c
* @brief benchmark the backend to evaluate performance
@@ -25,18 +24,9 @@
*/
#include "platform.h"
#include <taler/taler_util.h>
-#include <taler/taler_signatures.h>
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_json_lib.h>
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include <taler/taler_bank_service.h>
-#include <taler/taler_fakebank_lib.h>
#include <taler/taler_testing_lib.h>
-#include <taler/taler_error_codes.h>
#include "taler_merchant_testing_lib.h"
-#define PAYTO_I1 "payto://x-taler-bank/localhost/42"
/**
* Maximum length of an amount (value plus currency string) needed by the test.
@@ -51,22 +41,6 @@
#define MAX_ORDER_LEN (MAX_AMOUNT_LEN * 4 + 2048)
-/* Error codes. */
-enum PaymentGeneratorError
-{
- PG_SUCCESS = 0,
- PG_NO_SUBCOMMAND,
- PG_BAD_OPTIONS,
- PG_BAD_CONFIG_FILE,
- PG_FAILED_CFG_CURRENCY,
- PG_FAILED_TO_PREPARE_MERCHANT,
- PG_FAILED_TO_PREPARE_BANK,
- PG_FAILED_TO_LAUNCH_MERCHANT,
- PG_FAILED_TO_LAUNCH_BANK,
- PG_RUNTIME_FAILURE
-};
-
-
/**
* ID to use for the 'alternative' instance.
*/
@@ -108,21 +82,11 @@ static unsigned int twocoins_number = 1;
static unsigned int payments_number = 1;
/**
- * How many /tracks operation we want to perform.
- */
-static unsigned int tracks_number = 1;
-
-/**
* Config filename to give to commands (like wirewatch).
*/
static char *cfg_filename;
/**
- * Bank configuration.
- */
-static struct TALER_TESTING_BankConfiguration bc;
-
-/**
* Merchant base URL.
*/
static char *merchant_url;
@@ -132,6 +96,22 @@ static char *merchant_url;
*/
static char *currency;
+/**
+ * Set to 1 if `-f` command line option given.
+ */
+static int use_fakebank;
+
+/**
+ * Configuration section with details about the exchange
+ * bank account to use.
+ */
+static char *exchange_bank_section;
+
+/**
+ * Credentials to use for the benchmark.
+ */
+static struct TALER_TESTING_Credentials cred;
+
/**
* Actual commands collection.
@@ -186,106 +166,79 @@ run (void *cls,
sizeof (CURRENCY_0_01),
"%s:0.01",
currency);
-
- if (NULL != apikey)
- {
- char *hdr;
-
- GNUNET_asprintf (&hdr,
- "%s: %s",
- MHD_HTTP_HEADER_AUTHORIZATION,
- apikey);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CURL_append_header (is->ctx,
- hdr));
- GNUNET_free (hdr);
- }
-
if (ordinary)
{
struct TALER_TESTING_Command ordinary_commands[] = {
- TALER_TESTING_cmd_merchant_post_instances ("instance-create-default",
- merchant_url,
- "default",
- PAYTO_I1,
- currency,
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_admin_add_incoming ("create-reserve-1",
- CURRENCY_10_02,
- &bc.exchange_auth,
- bc.user43_payto),
- TALER_TESTING_cmd_exec_wirewatch ("wirewatch-1",
- cfg_filename),
- TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
- "create-reserve-1",
- CURRENCY_5,
- 0,
- MHD_HTTP_OK),
- TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
- "create-reserve-1",
- CURRENCY_5,
- 0,
- MHD_HTTP_OK),
- TALER_TESTING_cmd_merchant_post_orders ("create-proposal-1",
- merchant_url,
- MHD_HTTP_OK,
- NULL, /* random order ID please */
- GNUNET_TIME_UNIT_ZERO_TS,
- GNUNET_TIME_UNIT_FOREVER_TS,
- CURRENCY_5),
- TALER_TESTING_cmd_merchant_pay_order ("deposit-simple",
- merchant_url,
- MHD_HTTP_OK,
- "create-proposal-1",
- "withdraw-coin-1",
- CURRENCY_5,
- CURRENCY_4_99,
- NULL),
- TALER_TESTING_cmd_rewind_ip ("rewind-payments",
- "create-reserve-1",
- payments_number),
- /* Next proposal-pay cycle will be used by /track CMDs
- * and so it will not have to be looped over, only /track
- * CMDs will have to. */
- TALER_TESTING_cmd_merchant_post_orders ("create-proposal-2",
- merchant_url,
- MHD_HTTP_OK,
- NULL, /* random order ID */
- GNUNET_TIME_UNIT_ZERO_TS,
- GNUNET_TIME_UNIT_FOREVER_TS,
- CURRENCY_5),
- TALER_TESTING_cmd_merchant_pay_order ("deposit-simple-2",
- merchant_url,
- MHD_HTTP_OK,
- "create-proposal-2",
- "withdraw-coin-2",
- CURRENCY_5,
- CURRENCY_4_99,
- NULL),
- /* /track/transaction over deposit-simple-2 */
-
- TALER_TESTING_cmd_exec_aggregator ("aggregate-1",
- cfg_filename),
- TALER_TESTING_cmd_exec_transfer ("transfer-1",
- cfg_filename),
- TALER_TESTING_cmd_merchant_post_transfer (
- "post-transfer-1",
- &bc.exchange_auth,
- bc.exchange_auth.wire_gateway_url,
+ TALER_TESTING_cmd_get_exchange (
+ "get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_set_authorization (
+ "set-auth-valid",
+ apikey),
+ TALER_TESTING_cmd_merchant_post_instances (
+ "instance-create-default",
+ merchant_url,
+ "default",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_account (
+ "instance-create-default-account",
+ merchant_url,
+ cred.user42_payto,
+ NULL, NULL,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_admin_add_incoming (
+ "create-reserve-1",
+ CURRENCY_10_02,
+ &cred.ba,
+ cred.user43_payto),
+ TALER_TESTING_cmd_exec_wirewatch2 (
+ "wirewatch-1",
+ cfg_filename,
+ exchange_bank_section),
+ TALER_TESTING_cmd_withdraw_amount (
+ "withdraw-coin-1",
+ "create-reserve-1",
+ CURRENCY_5,
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount (
+ "withdraw-coin-2",
+ "create-reserve-1",
+ CURRENCY_5,
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_orders (
+ "create-proposal-1",
+ cred.cfg,
+ merchant_url,
+ MHD_HTTP_OK,
+ NULL, /* random order ID please */
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
+ CURRENCY_5),
+ TALER_TESTING_cmd_merchant_pay_order (
+ "deposit-simple",
merchant_url,
- CURRENCY_4_98,
MHD_HTTP_OK,
- "deposit-simple-2",
+ "create-proposal-1",
+ "withdraw-coin-1",
+ CURRENCY_5,
+ CURRENCY_4_99,
NULL),
- TALER_TESTING_cmd_merchant_get_transfers ("track-transfer-1",
- merchant_url,
- bc.user42_payto,
- MHD_HTTP_OK,
- "post-transfer-1",
- NULL),
- TALER_TESTING_cmd_rewind_ip ("rewind-tracks",
- "track-transfer-1",
- tracks_number),
+ TALER_TESTING_cmd_rewind_ip (
+ "rewind-payments",
+ "create-reserve-1",
+ payments_number),
+ TALER_TESTING_cmd_exec_aggregator (
+ "aggregate-1x",
+ cfg_filename),
+ TALER_TESTING_cmd_exec_transfer (
+ "transfer-1",
+ cfg_filename),
+
TALER_TESTING_cmd_end ()
};
@@ -297,85 +250,123 @@ run (void *cls,
if (corner) /* should never be 'false' here */
{
struct TALER_TESTING_Command corner_commands[] = {
- TALER_TESTING_cmd_merchant_post_instances ("instance-create-default",
- merchant_url,
- "default",
- PAYTO_I1,
- currency,
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_post_instances ("instance-create-alt",
- merchant_url,
- alt_instance_id,
- PAYTO_I1,
- currency,
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_admin_add_incoming ("create-reserve-1",
- CURRENCY_5_01,
- &bc.exchange_auth,
- bc.user43_payto),
- TALER_TESTING_cmd_exec_wirewatch ("wirewatch-1",
- cfg_filename),
- TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
- "create-reserve-1",
- CURRENCY_5,
- 0,
- MHD_HTTP_OK),
- TALER_TESTING_cmd_merchant_post_orders ("create-unaggregated-proposal",
- alt_instance_url,
- MHD_HTTP_OK,
- NULL, /* use random order ID */
- GNUNET_TIME_UNIT_ZERO_TS,
- GNUNET_TIME_UNIT_FOREVER_TS,
- CURRENCY_5),
- TALER_TESTING_cmd_merchant_pay_order ("deposit-unaggregated",
- alt_instance_url,
- MHD_HTTP_OK,
- "create-unaggregated-proposal",
- "withdraw-coin-1",
- CURRENCY_5,
- CURRENCY_4_99,
- NULL),
- TALER_TESTING_cmd_rewind_ip ("rewind-unaggregated",
- "create-reserve-1",
- unaggregated_number),
- TALER_TESTING_cmd_admin_add_incoming ("create-reserve-2",
- CURRENCY_10_02,
- &bc.exchange_auth,
- bc.user43_payto),
- TALER_TESTING_cmd_exec_wirewatch ("wirewatch-2",
- cfg_filename),
- TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
- "create-reserve-2",
- CURRENCY_5,
- 0,
- MHD_HTTP_OK),
- TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-3",
- "create-reserve-2",
- CURRENCY_5,
- 0,
- MHD_HTTP_OK),
- TALER_TESTING_cmd_merchant_post_orders ("create-twocoins-proposal",
- merchant_url,
- MHD_HTTP_OK,
- NULL, /* use random order ID */
- GNUNET_TIME_UNIT_ZERO_TS,
- GNUNET_TIME_UNIT_FOREVER_TS,
- CURRENCY_10),
- TALER_TESTING_cmd_merchant_pay_order ("deposit-twocoins",
- merchant_url,
- MHD_HTTP_OK,
- "create-twocoins-proposal",
- "withdraw-coin-2;withdraw-coin-3",
- CURRENCY_10,
- CURRENCY_9_98,
- NULL),
- TALER_TESTING_cmd_exec_aggregator ("aggregate-twocoins",
- cfg_filename),
- TALER_TESTING_cmd_exec_transfer ("transfer-twocoins",
- cfg_filename),
- TALER_TESTING_cmd_rewind_ip ("rewind-twocoins",
- "create-reserve-2",
- twocoins_number),
+ TALER_TESTING_cmd_get_exchange (
+ "get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_set_authorization (
+ "set-auth-valid",
+ apikey),
+ TALER_TESTING_cmd_merchant_post_instances (
+ "instance-create-default",
+ merchant_url,
+ "default",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_account (
+ "instance-create-default-account",
+ merchant_url,
+ cred.user42_payto,
+ NULL, NULL,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_instances (
+ "instance-create-alt",
+ merchant_url,
+ alt_instance_id,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_account (
+ "instance-create-alt-account",
+ alt_instance_url,
+ cred.user42_payto,
+ NULL, NULL,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_admin_add_incoming (
+ "create-reserve-1",
+ CURRENCY_5_01,
+ &cred.ba,
+ cred.user43_payto),
+ TALER_TESTING_cmd_exec_wirewatch2 (
+ "wirewatch-1",
+ cfg_filename,
+ exchange_bank_section),
+ TALER_TESTING_cmd_withdraw_amount (
+ "withdraw-coin-1",
+ "create-reserve-1",
+ CURRENCY_5,
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_orders (
+ "create-unaggregated-proposal",
+ cred.cfg,
+ alt_instance_url,
+ MHD_HTTP_OK,
+ NULL, /* use random order ID */
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
+ CURRENCY_5),
+ TALER_TESTING_cmd_merchant_pay_order (
+ "deposit-unaggregated",
+ alt_instance_url,
+ MHD_HTTP_OK,
+ "create-unaggregated-proposal",
+ "withdraw-coin-1",
+ CURRENCY_5,
+ CURRENCY_4_99,
+ NULL),
+ TALER_TESTING_cmd_rewind_ip (
+ "rewind-unaggregated",
+ "create-reserve-1",
+ unaggregated_number),
+ TALER_TESTING_cmd_admin_add_incoming (
+ "create-reserve-2",
+ CURRENCY_10_02,
+ &cred.ba,
+ cred.user43_payto),
+ TALER_TESTING_cmd_exec_wirewatch2 (
+ "wirewatch-2",
+ cfg_filename,
+ exchange_bank_section),
+ TALER_TESTING_cmd_withdraw_amount (
+ "withdraw-coin-2",
+ "create-reserve-2",
+ CURRENCY_5,
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount (
+ "withdraw-coin-3",
+ "create-reserve-2",
+ CURRENCY_5,
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_orders (
+ "create-twocoins-proposal",
+ cred.cfg,
+ merchant_url,
+ MHD_HTTP_OK,
+ NULL, /* use random order ID */
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
+ CURRENCY_10),
+ TALER_TESTING_cmd_merchant_pay_order (
+ "deposit-twocoins",
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-twocoins-proposal",
+ "withdraw-coin-2;withdraw-coin-3",
+ CURRENCY_10,
+ CURRENCY_9_98,
+ NULL),
+ TALER_TESTING_cmd_exec_aggregator (
+ "aggregate-twocoins",
+ cfg_filename),
+ TALER_TESTING_cmd_exec_transfer (
+ "transfer-twocoins",
+ cfg_filename),
+ TALER_TESTING_cmd_rewind_ip (
+ "rewind-twocoins",
+ "create-reserve-2",
+ twocoins_number),
TALER_TESTING_cmd_end ()
};
@@ -387,21 +378,6 @@ run (void *cls,
/**
- * Send SIGTERM and wait for process termination.
- *
- * @param process process to terminate.
- */
-static void
-terminate_process (struct GNUNET_OS_Process *process)
-{
- GNUNET_OS_process_kill (process,
- SIGTERM);
- GNUNET_OS_process_wait (process);
- GNUNET_OS_process_destroy (process);
-}
-
-
-/**
* The main function of the serve tool
*
* @param argc number of arguments from the command line
@@ -415,86 +391,110 @@ main (int argc,
char *loglev = NULL;
char *logfile = NULL;
char *exchange_account = NULL;
- struct GNUNET_OS_Process *bankd;
- struct GNUNET_OS_Process *merchantd;
struct GNUNET_GETOPT_CommandLineOption *options;
struct GNUNET_GETOPT_CommandLineOption root_options[] = {
GNUNET_GETOPT_option_cfgfile (&cfg_filename),
+ GNUNET_GETOPT_option_string (
+ 'u',
+ "exchange-account-section",
+ "SECTION",
+ "use exchange bank account configuration from the given SECTION",
+ &exchange_bank_section),
+ GNUNET_GETOPT_option_flag (
+ 'f',
+ "fakebank",
+ "use fakebank for the banking system",
+ &use_fakebank),
GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION),
- GNUNET_GETOPT_option_help ("Runs benchmark logic against merchant backend. "
- "Must be used with either 'ordinary' or 'corner' sub-commands."),
- GNUNET_GETOPT_option_string ('l',
- "logfile",
- "LF",
- "will log to file LF",
- &logfile),
+ GNUNET_GETOPT_option_help (
+ "Runs benchmark logic against merchant backend. "
+ "Must be used with either 'ordinary' or 'corner' sub-commands."),
+ GNUNET_GETOPT_option_string (
+ 'l',
+ "logfile",
+ "LF",
+ "will log to file LF",
+ &logfile),
GNUNET_GETOPT_option_loglevel (&loglev),
GNUNET_GETOPT_OPTION_END
};
struct GNUNET_GETOPT_CommandLineOption corner_options[] = {
- GNUNET_GETOPT_option_string ('l',
- "logfile",
- "LF",
- "will log to file LF",
- &logfile),
- GNUNET_GETOPT_option_loglevel (&loglev),
+ GNUNET_GETOPT_option_string (
+ 'a',
+ "apikey",
+ "APIKEY",
+ "HTTP 'Authorization' header to send to the merchant",
+ &apikey),
GNUNET_GETOPT_option_cfgfile (&cfg_filename),
+ GNUNET_GETOPT_option_flag (
+ 'f',
+ "fakebank",
+ "use fakebank for the banking system",
+ &use_fakebank),
GNUNET_GETOPT_option_help ("Populate databases with corner case payments"),
- GNUNET_GETOPT_option_uint ('u',
- "unaggregated-number",
- "UN",
- "will generate UN unaggregated payments, defaults to 1",
- &unaggregated_number),
- GNUNET_GETOPT_option_uint ('t',
- "two-coins",
- "TC",
- "will perform TC 2-coins payments, defaults to 1",
- &twocoins_number),
- GNUNET_GETOPT_option_mandatory (
- GNUNET_GETOPT_option_string ('e',
- "exchange-account",
- "SECTION",
- "configuration section specifying the exchange account to use, mandatory",
- &exchange_account)),
- GNUNET_GETOPT_option_string ('a',
- "apikey",
- "APIKEY",
- "HTTP 'Authorization' header to send to the merchant",
- &apikey),
+ GNUNET_GETOPT_option_string (
+ 'l',
+ "logfile",
+ "LF",
+ "will log to file LF",
+ &logfile),
+ GNUNET_GETOPT_option_loglevel (&loglev),
+ GNUNET_GETOPT_option_uint (
+ 't',
+ "two-coins",
+ "TC",
+ "will perform TC 2-coins payments, defaults to 1",
+ &twocoins_number),
+ GNUNET_GETOPT_option_uint (
+ 'U',
+ "unaggregated-number",
+ "UN",
+ "will generate UN unaggregated payments, defaults to 1",
+ &unaggregated_number),
+ GNUNET_GETOPT_option_string (
+ 'u',
+ "exchange-account-section",
+ "SECTION",
+ "use exchange bank account configuration from the given SECTION",
+ &exchange_bank_section),
GNUNET_GETOPT_OPTION_END
};
struct GNUNET_GETOPT_CommandLineOption ordinary_options[] = {
- GNUNET_GETOPT_option_string ('l',
- "logfile",
- "LF",
- "will log to file LF",
- &logfile),
- GNUNET_GETOPT_option_loglevel (&loglev),
+ GNUNET_GETOPT_option_string (
+ 'a',
+ "apikey",
+ "APIKEY",
+ "HTTP 'Authorization' header to send to the merchant",
+ &apikey),
GNUNET_GETOPT_option_cfgfile (&cfg_filename),
- GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION),
- GNUNET_GETOPT_option_help ("Generate Taler ordinary payments"
- " to populate the databases"),
GNUNET_GETOPT_option_mandatory (
- GNUNET_GETOPT_option_string ('e',
- "exchange-account",
- "SECTION",
- "configuration section specifying the exchange account to use, mandatory",
- &exchange_account)),
- GNUNET_GETOPT_option_uint ('p',
- "payments-number",
- "PN",
- "will generate PN payments, defaults to 1",
- &payments_number),
- GNUNET_GETOPT_option_string ('a',
- "apikey",
- "APIKEY",
- "HTTP 'Authorization' header to send to the merchant",
- &apikey),
- GNUNET_GETOPT_option_uint ('t',
- "tracks-number",
- "TN",
- "will perform TN /track operations, defaults to 1",
- &tracks_number),
+ GNUNET_GETOPT_option_string (
+ 'e',
+ "exchange-account",
+ "SECTION",
+ "configuration section specifying the exchange account to use, mandatory",
+ &exchange_account)),
+ GNUNET_GETOPT_option_flag (
+ 'f',
+ "fakebank",
+ "use fakebank for the banking system",
+ &use_fakebank),
+ GNUNET_GETOPT_option_help (
+ "Generate Taler ordinary payments"
+ " to populate the databases"),
+ GNUNET_GETOPT_option_string (
+ 'l',
+ "logfile",
+ "LF",
+ "will log to file LF",
+ &logfile),
+ GNUNET_GETOPT_option_loglevel (&loglev),
+ GNUNET_GETOPT_option_uint (
+ 'p',
+ "payments-number",
+ "PN",
+ "will generate PN payments, defaults to 1",
+ &payments_number),
GNUNET_GETOPT_option_version (PACKAGE_VERSION "-" VCS_VERSION),
GNUNET_GETOPT_OPTION_END
};
@@ -517,19 +517,26 @@ main (int argc,
}
{
- int result;
+ enum GNUNET_GenericReturnValue result;
result = GNUNET_GETOPT_run ("taler-merchant-benchmark",
options,
argc,
argv);
- if (GNUNET_SYSERR == result)
+ switch (result)
{
- return PG_BAD_OPTIONS;
+ case GNUNET_SYSERR:
+ return EXIT_INVALIDARGUMENT;
+ case GNUNET_NO:
+ return EXIT_SUCCESS;
+ case GNUNET_OK:
+ break;
}
- if (0 == result)
- return PG_SUCCESS;
}
+ if (NULL == exchange_bank_section)
+ exchange_bank_section = "exchange-account-1";
+ if (NULL == loglev)
+ loglev = "INFO";
GNUNET_log_setup ("taler-merchant-benchmark",
loglev,
logfile);
@@ -537,7 +544,7 @@ main (int argc,
(! corner) )
{
TALER_LOG_ERROR ("Please use 'ordinary' or 'corner' subcommands.\n");
- return PG_NO_SUBCOMMAND;
+ return EXIT_INVALIDARGUMENT;
}
if (NULL == cfg_filename)
cfg_filename = (char *) default_config_file;
@@ -551,7 +558,7 @@ main (int argc,
cfg_filename))
{
TALER_LOG_ERROR ("Could not parse configuration\n");
- return PG_BAD_CONFIG_FILE;
+ return EXIT_NOTCONFIGURED;
}
if (GNUNET_OK !=
TALER_config_get_currency (cfg,
@@ -559,56 +566,59 @@ main (int argc,
{
TALER_LOG_ERROR ("Failed to read currency from configuration\n");
GNUNET_CONFIGURATION_destroy (cfg);
- return PG_FAILED_CFG_CURRENCY;
+ return EXIT_NOTCONFIGURED;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "merchant-benchmark",
+ "MERCHANT_URL",
+ &merchant_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "merchant-benchmark",
+ "MERCHANT_URL");
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return EXIT_NOTCONFIGURED;
+ }
+ if ( (0 == strlen (merchant_url)) ||
+ (merchant_url[strlen (merchant_url) - 1] != '/') )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "merchant-benchmark",
+ "MERCHANT_URL",
+ "Not a valid URL");
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return EXIT_NOTCONFIGURED;
+ }
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_credentials (
+ cfg_filename,
+ exchange_bank_section,
+ use_fakebank
+ ? TALER_TESTING_BS_FAKEBANK
+ : TALER_TESTING_BS_IBAN,
+ &cred))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Required bank credentials not given in configuration\n");
+ GNUNET_free (cfg_filename);
+ return EXIT_NOTCONFIGURED;
}
+
GNUNET_CONFIGURATION_destroy (cfg);
}
- /* prepare merchant and bank */
- merchant_url = TALER_TESTING_prepare_merchant (cfg_filename);
- if (NULL == merchant_url)
- {
- TALER_LOG_ERROR ("Failed to prepare for the merchant\n");
- return PG_FAILED_TO_PREPARE_MERCHANT;
- }
- GNUNET_assert (0 < strlen (merchant_url));
- GNUNET_assert (merchant_url[strlen (merchant_url) - 1] == '/');
- GNUNET_assert (0 < GNUNET_asprintf (&alt_instance_url,
- "%sinstances/%s/",
- merchant_url,
- alt_instance_id));
- if (GNUNET_OK !=
- TALER_TESTING_prepare_bank (cfg_filename,
- GNUNET_NO,
- exchange_account,
- &bc))
- {
- TALER_LOG_ERROR ("Failed to prepare for the bank\n");
- return PG_FAILED_TO_PREPARE_BANK;
- }
- /* launch merchant and bank */
- if (NULL == (merchantd = TALER_TESTING_run_merchant (cfg_filename,
- merchant_url)))
+ GNUNET_asprintf (&alt_instance_url,
+ "%sinstances/%s/",
+ merchant_url,
+ alt_instance_id);
{
- TALER_LOG_ERROR ("Failed to launch the merchant\n");
- return PG_FAILED_TO_LAUNCH_MERCHANT;
- }
- if (NULL == (bankd = TALER_TESTING_run_bank (cfg_filename,
- bc.exchange_auth.wire_gateway_url)))
- {
- TALER_LOG_ERROR ("Failed to run the bank\n");
- terminate_process (merchantd);
- return PG_FAILED_TO_LAUNCH_BANK;
- }
+ enum GNUNET_GenericReturnValue result;
- /* launch exchange and run benchmark */
- {
- int result;
-
- result = TALER_TESTING_setup_with_exchange (&run,
- NULL,
- cfg_filename);
- terminate_process (merchantd);
- terminate_process (bankd);
- return (GNUNET_OK == result) ? 0 : PG_RUNTIME_FAILURE;
+ result = TALER_TESTING_loop (&run,
+ NULL);
+ return (GNUNET_OK == result)
+ ? 0
+ : EXIT_FAILURE;
}
}
diff --git a/src/merchant-tools/taler-merchant-passwd.c b/src/merchant-tools/taler-merchant-passwd.c
new file mode 100644
index 00000000..bfd6534d
--- /dev/null
+++ b/src/merchant-tools/taler-merchant-passwd.c
@@ -0,0 +1,187 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant-tools/taler-merchant-passwd.c
+ * @brief Reset access tokens for instances.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_util.h>
+#include <taler/taler_dbevents.h>
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchantdb_lib.h"
+#include "taler_merchantdb_lib.h"
+
+/**
+ * Instance to set password for.
+ */
+static char *instance;
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * Main function that will be run.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param config configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *config)
+{
+ struct TALER_MERCHANTDB_Plugin *plugin;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+ const char *pw = args[0];
+ struct TALER_MERCHANTDB_InstanceAuthSettings ias;
+ enum GNUNET_DB_QueryStatus qs;
+
+ if (NULL == pw)
+ pw = getenv ("TALER_MERCHANT_PASSWORD");
+ if (NULL == pw)
+ {
+ fprintf (stderr,
+ "New password not specified (pass on command-line or via TALER_MERCHANT_PASSWORD)\n");
+ global_ret = -1;
+ return;
+ }
+ if (NULL == instance)
+ instance = GNUNET_strdup ("default");
+ cfg = GNUNET_CONFIGURATION_dup (config);
+ if (NULL ==
+ (plugin = TALER_MERCHANTDB_plugin_load (cfg)))
+ {
+ fprintf (stderr,
+ "Failed to initialize database plugin.\n");
+ global_ret = 1;
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return;
+ }
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &ias.auth_salt,
+ sizeof (ias.auth_salt));
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CRYPTO_kdf (&ias.auth_hash,
+ sizeof (ias.auth_hash),
+ &ias.auth_salt,
+ sizeof (ias.auth_salt),
+ pw,
+ strlen (pw),
+ "merchant-instance-auth",
+ strlen ("merchant-instance-auth"),
+ NULL,
+ 0));
+ if (GNUNET_OK !=
+ plugin->connect (plugin->cls))
+ {
+ fprintf (stderr,
+ "Failed to connect to database\n");
+ global_ret = 1;
+ TALER_MERCHANTDB_plugin_unload (plugin);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return;
+ }
+ qs = plugin->update_instance_auth (plugin->cls,
+ instance,
+ &ias);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = ntohs (sizeof (es)),
+ .type = ntohs (TALER_DBEVENT_MERCHANT_INSTANCE_SETTINGS)
+ };
+
+ plugin->event_notify (plugin->cls,
+ &es,
+ instance,
+ strlen (instance) + 1);
+ }
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ fprintf (stderr,
+ "Instance `%s' unknown, cannot reset token\n",
+ instance);
+ global_ret = 2;
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ fprintf (stderr,
+ "Internal database error.\n");
+ global_ret = 3;
+ break;
+ }
+ TALER_MERCHANTDB_plugin_unload (plugin);
+ GNUNET_CONFIGURATION_destroy (cfg);
+}
+
+
+/**
+ * The main function of the database initialization tool.
+ * Used to initialize the Taler Exchange's database.
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_string ('i',
+ "instance",
+ "ID",
+ "which instance to reset the password of",
+ &instance),
+
+ GNUNET_GETOPT_option_version (PACKAGE_VERSION "-" VCS_VERSION),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ /* force linker to link against libtalerutil; if we do
+ not do this, the linker may "optimize" libtalerutil
+ away and skip #TALER_OS_init(), which we do need */
+ (void) TALER_project_data_default ();
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return 4;
+ ret = GNUNET_PROGRAM_run (
+ argc, argv,
+ "taler-merchant-passwd",
+ gettext_noop ("Reset instance password"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return 3;
+ if (GNUNET_NO == ret)
+ return 0;
+ return global_ret;
+}
+
+
+/* end of taler-merchant-passwd.c */
diff --git a/src/merchant-tools/taler-merchant-setup-reserve.c b/src/merchant-tools/taler-merchant-setup-reserve.c
index 1ed50530..46888171 100644
--- a/src/merchant-tools/taler-merchant-setup-reserve.c
+++ b/src/merchant-tools/taler-merchant-setup-reserve.c
@@ -156,46 +156,81 @@ do_request (void *cls);
* POST /reserves request to a merchant
*
* @param cls closure
- * @param hr HTTP response details
- * @param reserve_pub public key of the created reserve, NULL on error
- * @param payto_uri where to make the payment to for filling the reserve, NULL on error
+ * @param prr response details
*/
static void
result_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const char *payto_uri)
+ const struct TALER_MERCHANT_PostReservesResponse *prr)
{
(void) cls;
prh = NULL;
- switch (hr->http_status)
+ switch (prr->hr.http_status)
{
case MHD_HTTP_OK:
{
- char res_str[sizeof (*reserve_pub) * 2 + 1];
+ char res_str[sizeof (prr->details.ok.reserve_pub) * 2 + 1];
- GNUNET_STRINGS_data_to_string (reserve_pub,
- sizeof (*reserve_pub),
+ GNUNET_STRINGS_data_to_string (&prr->details.ok.reserve_pub,
+ sizeof (prr->details.ok.reserve_pub),
res_str,
sizeof (res_str));
- if (NULL != strchr (payto_uri, '?'))
- fprintf (stdout,
- "%s&message=%s\n",
- payto_uri,
- res_str);
- else
- fprintf (stdout,
- "%s?message=%s\n",
- payto_uri,
- res_str);
+ for (unsigned int i = 0; i<prr->details.ok.accounts_len; i++)
+ {
+ const struct TALER_EXCHANGE_WireAccount *wa
+ = &prr->details.ok.accounts[i];
+ const char *payto_uri = wa->payto_uri;
+ bool skip = false;
+
+ for (unsigned int j = 0; j<wa->credit_restrictions_length; j++)
+ if (TALER_EXCHANGE_AR_DENY ==
+ wa->credit_restrictions[j].type)
+ skip = true;
+ if (skip)
+ continue;
+ if (NULL != strchr (payto_uri, '?'))
+ fprintf (stdout,
+ "%s&message=%s\n",
+ payto_uri,
+ res_str);
+ else
+ fprintf (stdout,
+ "%s?message=%s\n",
+ payto_uri,
+ res_str);
+ if (NULL != wa->conversion_url)
+ fprintf (stdout,
+ "\tConversion needed: %s\n",
+ wa->conversion_url);
+ for (unsigned int j = 0; j<wa->credit_restrictions_length; j++)
+ {
+ const struct TALER_EXCHANGE_AccountRestriction *cr
+ = &wa->credit_restrictions[j];
+
+ switch (cr->type)
+ {
+ case TALER_EXCHANGE_AR_INVALID:
+ GNUNET_assert (0);
+ break;
+ case TALER_EXCHANGE_AR_DENY:
+ GNUNET_assert (0);
+ break;
+ case TALER_EXCHANGE_AR_REGEX:
+ fprintf (stdout,
+ "\tCredit restriction: %s (%s)\n",
+ cr->details.regex.human_hint,
+ cr->details.regex.posix_egrep);
+ break;
+ }
+ }
+ }
}
break;
case MHD_HTTP_CONFLICT:
fprintf (stderr,
"Conflict trying to setup reserve: %u/%d\nHint: %s\n",
- hr->http_status,
- (int) hr->ec,
- hr->hint);
+ prr->hr.http_status,
+ (int) prr->hr.ec,
+ prr->hr.hint);
global_ret = 1;
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
@@ -212,16 +247,16 @@ result_cb (void *cls,
}
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Merchant failed too often (%u/%d), giving up\n",
- hr->http_status,
- hr->ec);
+ prr->hr.http_status,
+ prr->hr.ec);
global_ret = 1;
break;
default:
fprintf (stderr,
"Unexpected backend failure: %u/%d\nHint: %s\n",
- hr->http_status,
- (int) hr->ec,
- hr->hint);
+ prr->hr.http_status,
+ (int) prr->hr.ec,
+ prr->hr.hint);
global_ret = 1;
break;
}
diff --git a/src/testing/.gitignore b/src/testing/.gitignore
index 9898208e..07fd2be3 100644
--- a/src/testing/.gitignore
+++ b/src/testing/.gitignore
@@ -13,3 +13,6 @@ test_reducer_home/.local/share/taler/exchange-secmod-eddsa/
test_reducer_home/.local/share/taler/exchange-secmod-rsa/
test_reducer_home/
test_kyc_api
+test_merchant_api_home/taler/exchange-secmod-*
+*.edited
+test_merchant_api_home/taler/exchange-offline/secm_tofus.pub
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
index 159b922e..49eb8cc8 100644
--- a/src/testing/Makefile.am
+++ b/src/testing/Makefile.am
@@ -8,74 +8,78 @@ endif
check_SCRIPTS = \
- test-merchant-walletharness.sh \
test_merchant_instance_auth.sh \
+ test_merchant_instance_creation.sh \
test_merchant_instance_response.sh \
test_merchant_instance_purge.sh \
- test_merchant_reserve_creation.sh \
test_merchant_product_creation.sh \
test_merchant_order_creation.sh \
- test_merchant_transfer_tracking.sh
-
+ test_merchant_transfer_tracking.sh \
+ test_merchant_kyc.sh \
+ test_merchant_order_autocleanup.sh \
+ test_merchant_wirewatch.sh \
+ test-merchant-walletharness.sh
lib_LTLIBRARIES = \
libtalermerchanttesting.la
libtalermerchanttesting_la_LDFLAGS = \
- -version-info 2:0:0 \
+ -version-info 4:0:1 \
-no-undefined
libtalermerchanttesting_la_SOURCES = \
testing_api_cmd_config.c \
testing_api_cmd_abort_order.c \
testing_api_cmd_claim_order.c \
+ testing_api_cmd_depositcheck.c \
testing_api_cmd_get_instance.c \
testing_api_cmd_get_instances.c \
testing_api_cmd_get_orders.c \
+ testing_api_cmd_get_otp_device.c \
+ testing_api_cmd_get_otp_devices.c \
testing_api_cmd_get_product.c \
testing_api_cmd_get_products.c \
- testing_api_cmd_get_reserve.c \
- testing_api_cmd_get_reserves.c \
- testing_api_cmd_get_tips.c \
testing_api_cmd_get_transfers.c \
testing_api_cmd_get_templates.c \
testing_api_cmd_get_template.c \
testing_api_cmd_get_webhooks.c \
testing_api_cmd_get_webhook.c \
+ testing_api_cmd_delete_account.c \
testing_api_cmd_delete_instance.c \
testing_api_cmd_delete_order.c \
+ testing_api_cmd_delete_otp_device.c \
testing_api_cmd_delete_product.c \
testing_api_cmd_delete_template.c \
testing_api_cmd_delete_webhook.c \
- testing_api_cmd_delete_reserve.c \
testing_api_cmd_delete_transfer.c \
testing_api_cmd_forget_order.c \
testing_api_cmd_kyc_get.c \
testing_api_cmd_lock_product.c \
testing_api_cmd_instance_auth.c \
testing_api_cmd_merchant_get_order.c \
- testing_api_cmd_merchant_get_tip.c \
+ testing_api_cmd_patch_instance.c \
+ testing_api_cmd_patch_otp_device.c \
+ testing_api_cmd_patch_product.c \
+ testing_api_cmd_patch_template.c \
+ testing_api_cmd_patch_webhook.c \
testing_api_cmd_pay_order.c \
+ testing_api_cmd_post_account.c \
testing_api_cmd_post_instances.c \
testing_api_cmd_post_orders_paid.c \
testing_api_cmd_post_orders.c \
+ testing_api_cmd_post_otp_devices.c \
testing_api_cmd_post_products.c \
- testing_api_cmd_post_reserves.c \
testing_api_cmd_post_transfers.c \
testing_api_cmd_post_templates.c \
testing_api_cmd_post_using_templates.c \
testing_api_cmd_post_webhooks.c \
- testing_api_cmd_patch_instance.c \
- testing_api_cmd_patch_product.c \
- testing_api_cmd_patch_template.c \
- testing_api_cmd_patch_webhook.c \
testing_api_cmd_refund_order.c \
- \
- testing_api_cmd_tip_authorize.c \
- testing_api_cmd_tip_pickup.c \
+ testing_api_cmd_tme.c \
testing_api_cmd_wallet_get_order.c \
- testing_api_cmd_wallet_get_tip.c \
testing_api_cmd_wallet_post_orders_refund.c \
+ testing_api_cmd_webhook.c \
+ testing_api_cmd_testserver.c \
+ testing_api_cmd_checkserver.c \
testing_api_helpers.c \
testing_api_traits.c
@@ -84,11 +88,13 @@ libtalermerchanttesting_la_LIBADD = \
-ltalerbank \
-ltalerexchange \
-ltalerjson \
+ -ltalermhd \
-ltalerutil \
-lgnunetcurl \
-lgnunetjson \
-lgnunetutil \
-ljansson \
+ -lmicrohttpd \
-ltalertesting \
$(XLIB)
@@ -125,7 +131,6 @@ test_merchant_api_twisted_cs_LDADD = \
-ltalerexchange \
-ltalerjson \
-ltalerutil \
- -lgnunettesting \
-lgnunetjson \
-lgnunetcurl \
-lgnunetutil \
@@ -147,7 +152,6 @@ test_merchant_api_twisted_rsa_LDADD = \
-ltalerexchange \
-ltalerjson \
-ltalerutil \
- -lgnunettesting \
-lgnunetjson \
-lgnunetcurl \
-lgnunetutil \
@@ -168,7 +172,6 @@ test_merchant_api_cs_LDADD = \
-ltalerexchange \
-ltalerjson \
-ltalerutil \
- -lgnunettesting \
-lgnunetjson \
-lgnunetcurl \
-lgnunetutil \
@@ -188,7 +191,6 @@ test_merchant_api_rsa_LDADD = \
-ltalerexchange \
-ltalerjson \
-ltalerutil \
- -lgnunettesting \
-lgnunetjson \
-lgnunetcurl \
-lgnunetutil \
@@ -215,10 +217,10 @@ test_kyc_api_LDADD = \
$(XLIB)
EXTRA_DIST = \
- initialize_taler_system.sh \
- test_key_rotation.sh \
+ setup.sh \
test_key_rotation.conf \
test_kyc_api.conf \
+ test_merchant_api.conf \
test_merchant_api-cs.conf \
test_merchant_api-rsa.conf \
test_merchant_api_twisted-cs.conf \
@@ -226,8 +228,6 @@ EXTRA_DIST = \
test_merchant_api_proxy_merchant.conf \
test_merchant_api_proxy_exchange.conf \
test_merchant_api_home/.local/share/taler/exchange-offline/master.priv \
- test_merchant_api_home/.local/share/taler/exchange/offline-keys/master.priv \
- test_merchant_api_home/.config/taler/exchange/account-2.json \
test_merchant.priv \
test_template.conf \
$(check_SCRIPTS)
diff --git a/src/testing/initialize_taler_system.sh b/src/testing/initialize_taler_system.sh
deleted file mode 100755
index a74d7e7a..00000000
--- a/src/testing/initialize_taler_system.sh
+++ /dev/null
@@ -1,283 +0,0 @@
-# This file is part of TALER
-# Copyright (C) 2014-2021 Taler Systems SA
-#
-# TALER is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 3, or
-# (at your option) any later version.
-#
-# TALER is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public
-# License along with TALER; see the file COPYING. If not, see
-# <http://www.gnu.org/licenses/>
-#
-## Coloring style Text shell script
-COLOR='\033[0;35m'
-NOCOLOR='\033[0m'
-
-set -eu
-
-# Exit, with status code "skip" (no 'real' failure)
-function exit_skip() {
- echo " SKIP: $1"
- exit 77
-}
-
-# Exit, with error message (hard failure)
-function exit_fail() {
- echo " FAIL: $1"
- exit 1
-}
-
-# Cleanup to run whenever we exit
-function cleanup()
-{
- # kill main HTTP servers first
- kill ${MERCHANT_HTTPD_PID:-X} &> /dev/null || true
- kill ${EXCHANGE_HTTPD_PID:-X} &> /dev/null || true
- for n in `jobs -p`
- do
- kill $n 2> /dev/null || true
- done
- wait
- rm -rf $CONF $WALLET_DB $TMP_DIR $LAST_RESPONSE
- # kill euFin
- kill `cat libeufin-sandbox.pid 2> /dev/null` &> /dev/null || true
- kill `cat libeufin-nexus.pid 2> /dev/null` &> /dev/null || true
-}
-
-NEXUS_PORT=8082
-SANDBOX_URL=http://localhost:1$NEXUS_PORT
-export BANK_URL=$SANDBOX_URL/demobanks/default
-function get_payto_uri() {
- export LIBEUFIN_SANDBOX_USERNAME=$1
- export LIBEUFIN_SANDBOX_PASSWORD=$2
- export LIBEUFIN_SANDBOX_URL=$SANDBOX_URL
- libeufin-cli sandbox demobank info --bank-account $1 | jq --raw-output '.paytoUri'
-}
-export get_payto_uri
-
-function get_bankaccount_balance() {
- export LIBEUFIN_SANDBOX_USERNAME=$1
- export LIBEUFIN_SANDBOX_PASSWORD=$2
- export LIBEUFIN_SANDBOX_URL=$SANDBOX_URL
- libeufin-cli sandbox demobank info --bank-account $1 | jq --raw-output '.balance.amount'
-}
-export get_bankaccount_balance
-
-function get_bankaccount_transactions() {
- export LIBEUFIN_SANDBOX_USERNAME=$1
- export LIBEUFIN_SANDBOX_PASSWORD=$2
- export LIBEUFIN_SANDBOX_URL=$SANDBOX_URL
- libeufin-cli sandbox demobank list-transactions --bank-account $1
-}
-export get_bankaccount_transactions
-
-# Exchange configuration file will be edited, so we create one
-# from the template.
-CONF=`mktemp test_template.conf-XXXXXX`
-cp test_template.conf $CONF
-
-TMP_DIR=`mktemp -d keys-tmp-XXXXXX`
-WALLET_DB=`mktemp test_wallet.json-XXXXXX`
-LAST_RESPONSE=`mktemp test_response.conf-XXXXXX`
-
-# Install cleanup handler (except for kill -9)
-trap cleanup EXIT
-
-# Check we can actually run
-echo -n "Testing for jq"
-jq -h > /dev/null || exit_skip "jq required"
-echo " FOUND"
-
-echo -n "Testing for taler"
-taler-exchange-httpd -h > /dev/null || exit_skip " taler-exchange required"
-taler-merchant-httpd -h > /dev/null || exit_skip " taler-merchant required"
-echo " FOUND"
-
-echo -n "Testing for libeufin-cli"
-libeufin-cli --help >/dev/null </dev/null || exit_skip " MISSING"
-echo " FOUND"
-echo -n "Testing for taler-wallet-cli"
-taler-wallet-cli -v >/dev/null </dev/null 2> /dev/null || exit_skip " MISSING"
-echo " FOUND"
-
-echo -n "Generating Taler auditor, exchange and merchant configurations ..."
-
-DATA_DIR=`taler-config -f -c $CONF -s PATHS -o TALER_HOME`
-rm -rf $DATA_DIR
-
-# obtain key configuration data
-MASTER_PRIV_FILE=`taler-config -f -c ${CONF} -s "EXCHANGE-OFFLINE" -o "MASTER_PRIV_FILE"`
-MASTER_PRIV_DIR=`dirname $MASTER_PRIV_FILE`
-mkdir -p $MASTER_PRIV_DIR
-gnunet-ecc -g1 $MASTER_PRIV_FILE > /dev/null 2> /dev/null
-MASTER_PUB=`gnunet-ecc -p ${MASTER_PRIV_FILE}`
-EXCHANGE_URL=`taler-config -c $CONF -s EXCHANGE -o BASE_URL`
-MERCHANT_PORT=`taler-config -c $CONF -s MERCHANT -o PORT`
-MERCHANT_URL=http://localhost:${MERCHANT_PORT}/
-AUDITOR_URL=http://localhost:8083/
-AUDITOR_PRIV_FILE=`taler-config -f -c $CONF -s AUDITOR -o AUDITOR_PRIV_FILE`
-AUDITOR_PRIV_DIR=`dirname $AUDITOR_PRIV_FILE`
-mkdir -p $AUDITOR_PRIV_DIR
-gnunet-ecc -g1 $AUDITOR_PRIV_FILE > /dev/null 2> /dev/null
-AUDITOR_PUB=`gnunet-ecc -p $AUDITOR_PRIV_FILE`
-
-# patch configuration
-TALER_DB=talercheck
-taler-config -c $CONF -s exchange -o MASTER_PUBLIC_KEY -V $MASTER_PUB
-taler-config -c $CONF -s merchant-exchange-default -o MASTER_KEY -V $MASTER_PUB
-taler-config -c $CONF -s exchangedb-postgres -o CONFIG -V postgres:///$TALER_DB
-taler-config -c $CONF -s auditordb-postgres -o CONFIG -V postgres:///$TALER_DB
-taler-config -c $CONF -s merchantdb-postgres -o CONFIG -V postgres:///$TALER_DB
-BANK_DB="jdbc:sqlite:$TMP_DIR/$TALER_DB"
-taler-config -c $CONF -s exchange -o KEYDIR -V "${TMP_DIR}/keydir/"
-taler-config -c $CONF -s exchange -o REVOCATION_DIR -V "${TMP_DIR}/revdir/"
-
-echo " OK"
-
-echo "Printing software versions..."
-
-echo -n "taler-wallet-cli "
-taler-wallet-cli --version
-taler-exchange-httpd --version
-taler-merchant-httpd --version
-libeufin-cli --version
-
-# Setup libeuFin before the exchange, because we must set
-# the PAYTO_URI config option (with unguessable IBAN)
-# before the exchange starts.
-echo "Setting up libeufin ..."
-# reset libeufin database
-rm -f $TALER_DB
-taler-bank-manage-testing $NEXUS_PORT $TALER_DB $EXCHANGE_URL $CONF
-echo -n "Setting up exchange ..."
-# reset database
-dropdb $TALER_DB >/dev/null 2>/dev/null || true
-createdb $TALER_DB || exit_skip "Could not create database $TALER_DB"
-taler-exchange-dbinit -c $CONF
-taler-merchant-dbinit -c $CONF
-taler-auditor-dbinit -c $CONF
-taler-auditor-exchange -c $CONF -m $MASTER_PUB -u $EXCHANGE_URL
-taler-exchange-secmod-eddsa -c $CONF -L DEBUG 2> taler-exchange-secmod-eddsa.log &
-taler-exchange-secmod-rsa -c $CONF -L DEBUG 2> taler-exchange-secmod-rsa.log &
-taler-exchange-secmod-cs -c $CONF -L DEBUG 2> taler-exchange-secmod-cs.log &
-taler-exchange-httpd -c $CONF -L DEBUG 2> taler-exchange-httpd.log &
-EXCHANGE_HTTPD_PID=$!
-taler-merchant-httpd -c $CONF -L DEBUG 2> taler-merchant-httpd.log &
-MERCHANT_HTTPD_PID=$!
-taler-exchange-wirewatch -c $CONF 2> taler-exchange-wirewatch.log &
-WIREWATCH_PID=$!
-taler-auditor-httpd -L INFO -c $CONF 2> taler-auditor-httpd.log &
-echo " OK"
-# Launch services
-
-echo -n "Waiting for the bank"
-# Wait for bank to be available (usually the slowest)
-for n in `seq 1 300`
-do
- echo -n "."
- sleep 0.1
- OK=0
- # bank
- wget --waitretry=0 --timeout=1 \
- --user admin --password secret \
- http://localhost:8082/ \
- -o /dev/null \
- -O /dev/null >/dev/null || continue
- OK=1
- break
-done
-
-if [ 1 != $OK ]
-then
- exit_skip "Failed to launch services (bank)"
-fi
-echo " OK"
-
-echo -n "Waiting for taler services "
-# Wait for all other taler services to be available
-for n in `seq 1 20`
-do
- echo -n "."
- sleep 0.1
- OK=0
- # exchange
- wget --tries=1 --timeout=1 http://localhost:8081/seed -o /dev/null -O /dev/null >/dev/null || continue
- # merchant
- wget --tries=1 --timeout=1 http://localhost:9966/ -o /dev/null -O /dev/null >/dev/null || continue
- # auditor
- wget --tries=1 --timeout=1 http://localhost:8083/ -o /dev/null -O /dev/null >/dev/null || continue
- OK=1
- break
-done
-
-if [ 1 != $OK ]
-then
- exit_skip "Failed to launch taler services"
-fi
-
-echo "OK"
-
-set +e
-echo -n 'Wait the exchange for gather its keys '
-for n in `seq 1 50`
-do
- echo -n "."
- sleep 0.1
- OK=0
- # exchange
- wget --tries=3 --waitretry=0 --timeout=1 http://localhost:8081/management/keys -o /dev/null -O $LAST_RESPONSE >/dev/null
- DENOMS_COUNT=`jq '.future_denoms|length' < $LAST_RESPONSE`
- SIGNKEYS_COUNT=`jq '.future_signkeys|length' < $LAST_RESPONSE`
- [[ -z "$SIGNKEYS_COUNT" || "$SIGNKEYS_COUNT" == "0" || -z "$DENOMS_COUNT" || "$DENOMS_COUNT" == "0" ]] && continue
- OK=1
- break;
-done
-set -e
-
-if [ 1 != $OK ]
-then
- exit_skip "Failed to setup exchange keys, check secmod logs"
-fi
-
-echo " OK"
-
-echo -n "Setting up keys ..."
-taler-exchange-offline -c $CONF \
- download \
- sign \
- enable-account `taler-config -c $CONF -s exchange-account-1 -o PAYTO_URI` \
- enable-auditor $AUDITOR_PUB $AUDITOR_URL "TESTKUDOS Auditor" \
- wire-fee now iban TESTKUDOS:0.01 TESTKUDOS:0.01 \
- global-fee now TESTKUDOS:0.01 TESTKUDOS:0.01 TESTKUDOS:0.01 1h 1year 5 \
- upload &> taler-exchange-offline.log
-
-echo -n "."
-
-for n in `seq 1 3`
-do
- echo -n "."
- OK=0
- wget --tries=1 --timeout=1 \
- http://localhost:8081/keys \
- -o /dev/null -O /dev/null >/dev/null || continue
- OK=1
- break
-done
-
-if [ 1 != $OK ]
-then
- exit_skip "Failed to setup keys"
-fi
-
-echo " OK"
-
-echo -n "Setting up auditor signatures ..."
-taler-auditor-offline -c $CONF \
- download sign upload &> taler-auditor-offline.log
-echo " OK"
diff --git a/src/testing/setup.sh b/src/testing/setup.sh
new file mode 100755
index 00000000..80933149
--- /dev/null
+++ b/src/testing/setup.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+# This file is in the public domain
+
+# Script to be inlined into the main test scripts. Defines function 'setup()'
+# which wraps around 'taler-unified-setup.sh' to launch GNU Taler services.
+# Call setup() with the arguments to pass to 'taler-unified-setup'. setup()
+# will then launch GNU Taler, wait for the process to be complete before
+# returning. The script will also install an exit handler to ensure the GNU
+# Taler processes are stopped when the shell exits.
+
+set -eu
+
+unset XDG_DATA_HOME
+unset XDG_CONFIG_HOME
+
+
+# Cleanup to run whenever we exit
+function exit_cleanup()
+{
+ if [ ! -z ${SETUP_PID+x} ]
+ then
+ echo "Killing taler-unified-setup ($SETUP_PID)" >&2
+ kill -TERM "$SETUP_PID" 2> /dev/null || true
+ wait "$SETUP_PID" 2> /dev/null || true
+ fi
+}
+
+# Install cleanup handler (except for kill -9)
+trap exit_cleanup EXIT
+
+function setup()
+{
+ echo "Starting test system ..." >&2
+ # Create a named pipe in a temp directory we own.
+ FIFO_DIR=$(mktemp -p "${TMPDIR:-/tmp}" -d fifo-XXXXXX)
+ FIFO_OUT=$(echo "$FIFO_DIR/out")
+ mkfifo "$FIFO_OUT"
+ # Open pipe as FD 3 (RW) and FD 4 (RO)
+ exec 3<> "$FIFO_OUT" 4< "$FIFO_OUT"
+ rm -rf "$FIFO_DIR"
+ # We require '-W' for our termination logic to work.
+ taler-unified-setup.sh -W "$@" >&3 &
+ SETUP_PID=$!
+ # Close FD3
+ exec 3>&-
+ sed -u '/<<READY>>/ q' <&4
+ # Close FD4
+ exec 4>&-
+ echo "Test system ready" >&2
+}
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_fail() {
+ echo "$@" >&2
+ exit 1
+}
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo "SKIPPING: $1"
+ exit 77
+}
+
+function get_payto_uri() {
+ libeufin-bank create-account -u "$1" -p "$2" --name "$1" 2> /dev/null
+}
+
diff --git a/src/testing/test-merchant-walletharness.sh b/src/testing/test-merchant-walletharness.sh
index 91cb542a..c3c048d1 100755
--- a/src/testing/test-merchant-walletharness.sh
+++ b/src/testing/test-merchant-walletharness.sh
@@ -22,21 +22,20 @@
set -eu
-# Exit, with status code "skip" (no 'real' failure)
-function exit_skip() {
- echo $1
- exit 77
-}
-
-# If CLI is installed, assume all the suite is.
-echo -n "Testing for libeufin(-cli)"
-libeufin-cli --help >/dev/null </dev/null || exit_skip " MISSING"
+unset XDG_DATA_HOME
+unset XDG_CONFIG_HOME
+
+. setup.sh
+
+echo -n "Testing for libeufin-bank"
+libeufin-bank --help >/dev/null </dev/null || exit_skip " MISSING"
echo " FOUND"
echo -n "Testing for taler-harness"
taler-harness --help >/dev/null </dev/null || exit_skip " MISSING"
echo " FOUND"
+
export WALLET_HARNESS_WITH_EUFIN=1
res=0
taler-harness run-integrationtests --dry --suites merchant 2&>/dev/null || res=$?
diff --git a/src/testing/test.conf b/src/testing/test.conf
new file mode 100644
index 00000000..2cfc0418
--- /dev/null
+++ b/src/testing/test.conf
@@ -0,0 +1,181 @@
+# This file is in the public domain.
+#
+[PATHS]
+# Persistent data storage for the testcase
+TALER_TEST_HOME = test_merchant_api_home/
+TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
+
+# Persistent data storage
+TALER_DATA_HOME = $TALER_HOME/.local/share/taler/
+
+# Configuration files
+TALER_CONFIG_HOME = $TALER_HOME/.config/taler/
+
+# Cached data, no big deal if lost
+TALER_CACHE_HOME = $TALER_HOME/.cache/taler/
+
+[taler]
+# What currency do we use?
+CURRENCY = EUR
+CURRENCY_ROUND_UNIT = EUR:0.01
+
+[taler-helper-crypto-rsa]
+# Reduce from 1 year to speed up test
+LOOKAHEAD_SIGN = 24 days
+
+[taler-helper-crypto-eddsa]
+# Reduce from 1 year to speed up test
+LOOKAHEAD_SIGN = 24 days
+# Reduce from 12 weeks to ensure we have multiple
+DURATION = 14 days
+
+[bank]
+HTTP_PORT = 8082
+
+##########################################
+# Configuration for the merchant backend #
+##########################################
+
+[merchant]
+
+# Which port do we run the backend on? (HTTP server)
+PORT = 8080
+SERVE = tcp
+
+# Which plugin (backend) do we use for the DB.
+DB = postgres
+
+# This specifies which database the postgres backend uses.
+[merchantdb-postgres]
+CONFIG = postgres:///talercheck
+SQL_DIR = $DATADIR/sql/merchant/
+
+# Sections starting with "merchant-exchange-" specify trusted exchanges
+# (by the merchant)
+[merchant-exchange-test]
+MASTER_KEY = NKX42KSCQHDQK7CF1PC6X9DMQPXW6KHXKGD3DPQJMP32FKXSWYK0
+EXCHANGE_BASE_URL = http://localhost:8081/
+CURRENCY = EUR
+
+
+#######################################################
+# Configuration for the auditor for the testcase
+#######################################################
+[auditor]
+BASE_URL = http://the.auditor/
+
+
+#######################################################
+# Configuration for ??? Is this used?
+#######################################################
+
+# Auditors must be in sections "auditor-", the rest of the section
+# name could be anything.
+[auditor-ezb]
+# Informal name of the auditor. Just for the user.
+NAME = European Central Bank
+
+# URL of the auditor (especially for in the future, when the
+# auditor offers an automated issue reporting system).
+# Not really used today.
+URL = http://taler.ezb.eu/
+
+# This is the important bit: the signing key of the auditor.
+PUBLIC_KEY = 9QXF7XY7E9VPV47B5Z806NDFSX2VJ79SVHHD29QEQ3BG31ANHZ60
+
+# Which currency is this auditor trusted for?
+CURRENCY = EUR
+
+
+###################################################
+# Configuration for the exchange for the testcase #
+###################################################
+
+[exchange]
+AML_THRESHOLD = EUR:1000000
+
+
+# How to access our database
+DB = postgres
+
+# HTTP port the exchange listens to
+PORT = 8081
+
+# Our public key
+MASTER_PUBLIC_KEY = NKX42KSCQHDQK7CF1PC6X9DMQPXW6KHXKGD3DPQJMP32FKXSWYK0
+
+# Base URL of the exchange.
+BASE_URL = "http://localhost:8081/"
+
+
+[exchangedb-postgres]
+CONFIG = "postgres:///talercheck"
+
+
+[auditordb-postgres]
+CONFIG = postgres:///talercheck
+
+
+# Account of the EXCHANGE
+[exchange-account-exchange]
+# What is the exchange's bank account (with the "Taler Bank" demo system)?
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-exchange]
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+WIRE_GATEWAY_AUTH_METHOD = NONE
+
+[admin-accountcredentials-exchange]
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+WIRE_GATEWAY_AUTH_METHOD = NONE
+
+
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+CIPHER = CS
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+CIPHER = CS
+
+[coin_eur_1]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+CIPHER = CS
+
+[coin_eur_5]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+CIPHER = CS
diff --git a/src/testing/test_key_rotation.conf b/src/testing/test_key_rotation.conf
index 988a5219..cc7035b0 100644
--- a/src/testing/test_key_rotation.conf
+++ b/src/testing/test_key_rotation.conf
@@ -12,7 +12,6 @@ CURRENCY_ROUND_UNIT = TESTKUDOS:0.01
[exchange]
MAX_KEYS_CACHING = forever
DB = postgres
-MASTER_PRIV_FILE = ${TALER_DATA_HOME}/exchange/offline-keys/master.priv
SERVE = tcp
UNIXPATH = ${TALER_RUNTIME_DIR}/exchange.http
UNIXPATH_MODE = 660
@@ -69,7 +68,6 @@ UNIXPATH_MODE = 660
PORT = 8083
AUDITOR_URL = http://localhost:8083/
TINY_AMOUNT = TESTKUDOS:0.01
-AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}/auditor/offline-keys/auditor.priv
BASE_URL = "http://localhost:8083/"
[bank]
@@ -80,7 +78,7 @@ HTTP_PORT = 8082
SUGGESTED_EXCHANGE = http://localhost:8081/
SUGGESTED_EXCHANGE_PAYTO = payto://x-taler-bank/localhost/2
ALLOW_REGISTRATIONS = YES
-SERVE = http
+SERVE = tcp
[exchangedb]
IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
@@ -92,7 +90,13 @@ enable_debit = yes
enable_credit = yes
[exchange-accountcredentials-1]
-WIRE_GATEWAY_URL = "http://localhost:8082/taler-wire-gateway/Exchange/"
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/Exchange/taler-wire-gateway/"
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
+
+[admin-accountcredentials-1]
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/Exchange/taler-wire-gateway/"
WIRE_GATEWAY_AUTH_METHOD = basic
USERNAME = Exchange
PASSWORD = x
diff --git a/src/testing/test_key_rotation.sh b/src/testing/test_key_rotation.sh
deleted file mode 100755
index b3609b1d..00000000
--- a/src/testing/test_key_rotation.sh
+++ /dev/null
@@ -1,411 +0,0 @@
-#!/bin/bash
-# This file is part of TALER
-# Copyright (C) 2014-2021 Taler Systems SA
-#
-# TALER is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 3, or
-# (at your option) any later version.
-#
-# TALER is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public
-# License along with TALER; see the file COPYING. If not, see
-# <http://www.gnu.org/licenses/>
-#
-#
-# Note that this test is intentionally NOT run as part of the standard test
-# suite, because it is awfully slow (due to necessary 'wait' operations) and
-# may even hang on slower computers (with the wallet trying to withdraw and
-# failing because all keys have expired) due to the relatively short timeouts
-# involved.
-#
-## Coloring style Text shell script
-COLOR='\033[0;35m'
-NOCOLOR='\033[0m'
-
-
-set -eu
-
-# We use VERY short withdraw periods, set flag to
-# tell wallet explicitly that this is OK...
-export TALER_WALLET_DEBUG_DENOMSEL_ALLOW_LATE=1
-
-# Exit, with status code "skip" (no 'real' failure)
-function exit_skip() {
- echo " SKIP: $1"
- exit 77
-}
-
-# Exit, with error message (hard failure)
-function exit_fail() {
- echo " FAIL: $1"
- exit 1
-}
-
-# Cleanup to run whenever we exit
-function cleanup()
-{
- for n in `jobs -p`
- do
- kill $n 2> /dev/null || true
- done
- rm -rf $CONF $WALLET_DB $TMP_DIR
- wait
-}
-
-# Exchange configuration file will be edited, so we create one
-# from the template.
-CONF=`mktemp test_template.conf-XXXXXX`
-cp test_key_rotation.conf $CONF
-
-TMP_DIR=`mktemp -d keys-tmp-XXXXXX`
-WALLET_DB=`mktemp test_wallet.json-XXXXXX`
-
-# Install cleanup handler (except for kill -9)
-trap cleanup EXIT
-
-# Check we can actually run
-echo -n "Testing for jq"
-jq -h > /dev/null || exit_skip "jq required"
-echo " FOUND"
-
-echo -n "Testing for taler"
-taler-exchange-httpd -h > /dev/null || exit_skip " taler-exchange required"
-taler-merchant-httpd -h > /dev/null || exit_skip " taler-merchant required"
-echo " FOUND"
-
-echo -n "Testing for taler-bank-manage"
-taler-bank-manage --help >/dev/null </dev/null || exit_skip " MISSING"
-echo " FOUND"
-echo -n "Testing for taler-wallet-cli"
-taler-wallet-cli -v >/dev/null </dev/null || exit_skip " MISSING"
-echo " FOUND"
-
-echo -n "Generating Taler auditor, exchange and merchant configurations ..."
-
-DATA_DIR=`taler-config -f -c $CONF -s PATHS -o TALER_HOME`
-rm -rf $DATA_DIR
-
-# obtain key configuration data
-MASTER_PRIV_FILE=`taler-config -f -c $CONF -s EXCHANGE -o MASTER_PRIV_FILE`
-MASTER_PRIV_DIR=`dirname $MASTER_PRIV_FILE`
-mkdir -p $MASTER_PRIV_DIR
-gnunet-ecc -g1 $MASTER_PRIV_FILE > /dev/null 2> /dev/null
-MASTER_PUB=`gnunet-ecc -p $MASTER_PRIV_FILE`
-EXCHANGE_URL=`taler-config -c $CONF -s EXCHANGE -o BASE_URL`
-MERCHANT_PORT=`taler-config -c $CONF -s MERCHANT -o PORT`
-MERCHANT_URL=http://localhost:${MERCHANT_PORT}/
-BANK_PORT=`taler-config -c $CONF -s BANK -o HTTP_PORT`
-BANK_URL=http://localhost:${BANK_PORT}/
-AUDITOR_URL=http://localhost:8083/
-AUDITOR_PRIV_FILE=`taler-config -f -c $CONF -s AUDITOR -o AUDITOR_PRIV_FILE`
-AUDITOR_PRIV_DIR=`dirname $AUDITOR_PRIV_FILE`
-mkdir -p $AUDITOR_PRIV_DIR
-gnunet-ecc -g1 $AUDITOR_PRIV_FILE > /dev/null 2> /dev/null
-AUDITOR_PUB=`gnunet-ecc -p $AUDITOR_PRIV_FILE`
-
-# patch configuration
-TALER_DB=talercheck
-taler-config -c $CONF -s exchange -o MASTER_PUBLIC_KEY -V $MASTER_PUB
-taler-config -c $CONF -s merchant-exchange-default -o MASTER_KEY -V $MASTER_PUB
-taler-config -c $CONF -s exchangedb-postgres -o CONFIG -V postgres:///$TALER_DB
-taler-config -c $CONF -s auditordb-postgres -o CONFIG -V postgres:///$TALER_DB
-taler-config -c $CONF -s merchantdb-postgres -o CONFIG -V postgres:///$TALER_DB
-taler-config -c $CONF -s bank -o database -V postgres:///$TALER_DB
-taler-config -c $CONF -s exchange -o KEYDIR -V "${TMP_DIR}/keydir/"
-taler-config -c $CONF -s exchange -o REVOCATION_DIR -V "${TMP_DIR}/revdir/"
-
-echo " OK"
-
-echo -n "Setting up exchange ..."
-
-# reset database
-dropdb $TALER_DB >/dev/null 2>/dev/null || true
-createdb $TALER_DB || exit_skip "Could not create database $TALER_DB"
-taler-exchange-dbinit -c $CONF
-taler-merchant-dbinit -c $CONF
-taler-auditor-dbinit -c $CONF
-taler-auditor-exchange -c $CONF -m $MASTER_PUB -u $EXCHANGE_URL
-
-echo " OK"
-
-# Launch services
-echo -n "Launching taler services ..."
-taler-bank-manage-testing $CONF postgres:///$TALER_DB serve > taler-bank.log 2> taler-bank.err &
-taler-exchange-secmod-eddsa -c $CONF 2> taler-exchange-secmod-eddsa.log &
-taler-exchange-secmod-rsa -c $CONF 2> taler-exchange-secmod-rsa.log &
-taler-exchange-secmod-cs -c $CONF 2> taler-exchange-secmod-cs.log &
-taler-exchange-httpd -c $CONF 2> taler-exchange-httpd.log &
-taler-merchant-httpd -c $CONF -L INFO 2> taler-merchant-httpd.log &
-taler-exchange-wirewatch -c $CONF 2> taler-exchange-wirewatch.log &
-taler-auditor-httpd -L INFO -c $CONF 2> taler-auditor-httpd.log &
-
-echo " OK"
-
-# Wait for bank to be available (usually the slowest)
-for n in `seq 1 50`
-do
- echo -n "."
- sleep 0.2
- OK=0
- # bank
- wget --tries=1 --timeout=1 http://localhost:8082/ -o /dev/null -O /dev/null >/dev/null || continue
- OK=1
- break
-done
-
-if [ 1 != $OK ]
-then
- exit_skip "Failed to launch services (bank)"
-fi
-
-# Wait for all other taler services to be available
-for n in `seq 1 50`
-do
- echo -n "."
- sleep 0.1
- OK=0
- # exchange
- wget --tries=1 --timeout=1 http://localhost:8081/seed -o /dev/null -O /dev/null >/dev/null || continue
- # merchant
- wget --tries=1 --timeout=1 http://localhost:9966/ -o /dev/null -O /dev/null >/dev/null || continue
- # auditor
- wget --tries=1 --timeout=1 http://localhost:8083/ -o /dev/null -O /dev/null >/dev/null || continue
- OK=1
- break
-done
-
-if [ 1 != $OK ]
-then
- exit_skip "Failed to launch taler services"
-fi
-
-echo "OK"
-
-
-echo -n "Setting up merchant instance"
-STATUS=$(curl -H "Content-Type: application/json" -X POST \
- http://localhost:9966/management/instances \
- -d '{"auth":{"method":"external"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
- -w "%{http_code}" -s -o /dev/null)
-
-if [ "$STATUS" != "204" ]
-then
- echo 'should respond ok, instance created. got:' $STATUS
- exit 1
-fi
-echo " OK"
-
-echo -n "Setting up orders ..."
-
-
-ORDER_1=`curl -s -H "Content-Type: application/json" -X POST \
- http://localhost:9966/private/orders \
- -d '{"create_token":false, "order":{"amount":"TESTKUDOS:0.01","summary":"Minimal test order #1"}}' \
- | jq -er '.order_id'`
-PAY1=taler+http://pay/localhost:9966/${ORDER_1}/
-
-ORDER_2=`curl -s -H "Content-Type: application/json" -X POST \
- http://localhost:9966/private/orders \
- -d '{"create_token":false, "order":{"amount":"TESTKUDOS:0.01","summary":"Minimal test order #2"}}' \
- | jq -er '.order_id'`
-PAY2=taler+http://pay/localhost:9966/${ORDER_2}/
-
-ORDER_3=`curl -s -H "Content-Type: application/json" -X POST \
- http://localhost:9966/private/orders \
- -d '{"create_token":false, "order":{"amount":"TESTKUDOS:0.01","summary":"Minimal test order #3"}}' \
- | jq -er '.order_id'`
-PAY3=taler+http://pay/localhost:9966/${ORDER_3}/
-
-ORDER_4=`curl -s -H "Content-Type: application/json" -X POST \
- http://localhost:9966/private/orders \
- -d '{"create_token":false, "order":{"amount":"TESTKUDOS:0.01","summary":"Minimal test order #4"}}' \
- | jq -er '.order_id'`
-PAY4=taler+http://pay/localhost:9966/${ORDER_4}/
-
-
-if [ "$STATUS" != "204" ]
-then
- echo 'should respond ok, order created. got:' $STATUS
- exit 1
-fi
-
-
-echo "OK"
-
-export CONF
-export AUDITOR_PUB
-export AUDITOR_URL
-export EXCHANGE_URL
-export WALLET_DB
-
-echo -n "Setting up keys ..."
-taler-exchange-offline -L INFO -c $CONF \
- download \
- sign \
- enable-account payto://x-taler-bank/localhost/Exchange \
- enable-auditor $AUDITOR_PUB $AUDITOR_URL "TESTKUDOS Auditor" \
- wire-fee now x-taler-bank TESTKUDOS:0.01 TESTKUDOS:0.01 \
- upload &> taler-exchange-offline.log
-
-echo -n "."
-
-for n in `seq 1 30`
-do
- echo -n "."
- OK=0
- wget --tries=1 http://localhost:8081/keys -o /dev/null -O /dev/null >/dev/null || continue
- OK=1
- sleep 0.1
- break
-done
-
-if [ 1 != $OK ]
-then
- exit_skip "Failed to setup keys"
-fi
-
-echo " OK"
-
-echo -n "Setting up auditor signatures ..."
-taler-auditor-offline -L INFO -c $CONF \
- download sign upload &> taler-auditor-offline.log
-echo " OK"
-
-
-echo -n "First withdraw wallet"
-rm $WALLET_DB
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api --expect-success 'withdrawTestBalance' \
- "$(jq -n '
- {
- amount: "TESTKUDOS:1",
- bankBaseUrl: $BANK_URL,
- exchangeBaseUrl: $EXCHANGE_URL
- }' \
- --arg BANK_URL "$BANK_URL" \
- --arg EXCHANGE_URL "$EXCHANGE_URL"
- )" 2>wallet-withdraw-1.err >wallet-withdraw-1.out
-taler-wallet-cli --wallet-db=$WALLET_DB run-until-done 2>wallet-withdraw-finish-1.err >wallet-withdraw-finish-1.out
-echo " OK"
-
-
-echo -n "Pay first order ..."
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB handle-uri ${PAY1} -y 2> wallet-pay1.err > wallet-pay1.log
-echo " OK"
-
-echo -n "Wait for keys to rotate, but not ALL to expire..."
-sleep 20
-echo " OK"
-
-
-echo -n "Updating keys ..."
-taler-exchange-offline -L INFO -c $CONF \
- download \
- sign \
- upload &> taler-exchange-offline-2.log
-taler-auditor-offline -L INFO -c $CONF \
- download sign upload &> taler-auditor-offline-2.log
-echo " OK"
-
-echo -n "Second withdraw wallet"
-rm $WALLET_DB
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api --expect-success 'withdrawTestBalance' \
- "$(jq -n '
- {
- amount: "TESTKUDOS:1",
- bankBaseUrl: $BANK_URL,
- exchangeBaseUrl: $EXCHANGE_URL
- }' \
- --arg BANK_URL "$BANK_URL" \
- --arg EXCHANGE_URL "$EXCHANGE_URL"
- )" 2>wallet-withdraw-2.err >wallet-withdraw-2.out
-taler-wallet-cli --wallet-db=$WALLET_DB run-until-done 2>wallet-withdraw-finish-2.err >wallet-withdraw-finish-2.out
-echo " OK"
-
-echo -n "Pay second order ..."
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB handle-uri ${PAY2} -y 2> wallet-pay2.err > wallet-pay2.log
-echo " OK"
-
-
-echo -n "Wait for keys to rotate, and original ones to expire..."
-sleep 60
-echo " OK"
-
-date
-echo -n "Updating keys ..."
-taler-exchange-offline -c $CONF \
- download > taler-exchange-offline-download-3.log
-taler-exchange-offline -c $CONF \
- download sign > taler-exchange-offline-sign-3.log
-taler-exchange-offline -L INFO -c $CONF \
- download \
- sign \
- upload &> taler-exchange-offline-3.log
-taler-auditor-offline -L INFO -c $CONF \
- download sign upload &> taler-auditor-offline-3.log
-echo " OK"
-
-echo -n "Third withdraw wallet"
-rm $WALLET_DB
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api --expect-success 'withdrawTestBalance' \
- "$(jq -n '
- {
- amount: "TESTKUDOS:1",
- bankBaseUrl: $BANK_URL,
- exchangeBaseUrl: $EXCHANGE_URL
- }' \
- --arg BANK_URL "$BANK_URL" \
- --arg EXCHANGE_URL "$EXCHANGE_URL"
- )" 2>wallet-withdraw-3.err >wallet-withdraw-3.out
-
-echo " OK"
-date
-echo -n "Waiting for wallet to finish ..."
-taler-wallet-cli --wallet-db=$WALLET_DB run-until-done 2>wallet-withdraw-finish-3.err >wallet-withdraw-finish-3.out
-echo " OK"
-
-echo -n "Pay third order ..."
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB handle-uri ${PAY3} -y 2> wallet-pay3.err > wallet-pay3.log
-echo " OK"
-
-
-echo -n "Wait for everything to expire..."
-sleep 120
-echo " OK"
-
-echo -n "Updating keys ..."
-taler-exchange-offline -L INFO -c $CONF \
- download \
- sign \
- upload &> taler-exchange-offline-4.log
-taler-auditor-offline -L INFO -c $CONF \
- download sign upload &> taler-auditor-offline-4.log
-echo " OK"
-
-echo -n "Fourth withdraw wallet"
-rm $WALLET_DB
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api 'withdrawTestBalance' \
- "$(jq -n '
- {
- amount: "TESTKUDOS:1",
- bankBaseUrl: $BANK_URL,
- exchangeBaseUrl: $EXCHANGE_URL
- }' \
- --arg BANK_URL "$BANK_URL" \
- --arg EXCHANGE_URL "$EXCHANGE_URL"
- )" 2>wallet-withdraw-4.err >wallet-withdraw-4.out
-taler-wallet-cli --wallet-db=$WALLET_DB run-until-done 2>wallet-withdraw-finish-4.err >wallet-withdraw-finish-4.out
-echo " OK"
-
-echo -n "Pay fourth order ..."
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB handle-uri ${PAY4} -y 2> wallet-pay4.err > wallet-pay4.log
-echo " OK"
-
-
-
-
-exit 0
diff --git a/src/testing/test_kyc_api.c b/src/testing/test_kyc_api.c
index 507338e4..6ef40b45 100644
--- a/src/testing/test_kyc_api.c
+++ b/src/testing/test_kyc_api.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -43,8 +43,6 @@
*/
#define CONFIG_FILE "test_kyc_api.conf"
-#define PAYTO_I1 "payto://x-taler-bank/localhost/3?receiver-name=3"
-
/**
* Exchange base URL. Could also be taken from config.
*/
@@ -53,32 +51,27 @@
/**
* Payto URI of the customer (payer).
*/
-static char *payer_payto;
+static const char *payer_payto;
/**
* Payto URI of the exchange (escrow account).
*/
-static char *exchange_payto;
+static const char *exchange_payto;
/**
* Payto URI of the merchant (receiver).
*/
-static char *merchant_payto;
-
-/**
- * Configuration of the bank.
- */
-static struct TALER_TESTING_BankConfiguration bc;
+static const char *merchant_payto;
/**
- * Configuration of the exchange.
+ * Credentials for the test.
*/
-static struct TALER_TESTING_ExchangeConfiguration ec;
+static struct TALER_TESTING_Credentials cred;
/**
* Merchant base URL.
*/
-static char *merchant_url;
+static const char *merchant_url;
/**
* Merchant instance "i1a" base URL.
@@ -86,11 +79,6 @@ static char *merchant_url;
static char *merchant_url_i1a;
/**
- * Merchant process.
- */
-static struct GNUNET_OS_Process *merchantd;
-
-/**
* Account number of the exchange at the bank.
*/
#define EXCHANGE_ACCOUNT_NAME "2"
@@ -101,18 +89,13 @@ static struct GNUNET_OS_Process *merchantd;
#define USER_ACCOUNT_NAME "62"
/**
- * Account number of some other user.
- */
-#define USER_ACCOUNT_NAME2 "63"
-
-/**
* Account number used by the merchant
*/
#define MERCHANT_ACCOUNT_NAME "3"
/**
- * Execute the taler-exchange-aggregator, closer and transfer commands with
+ * Execute the taler-exchange-aggregator and transfer commands with
* our configuration file.
*
* @param label label to use for the command.
@@ -136,7 +119,7 @@ cmd_transfer_to_exchange (const char *label,
{
return TALER_TESTING_cmd_admin_add_incoming (label,
amount,
- &bc.exchange_auth,
+ &cred.ba,
payer_payto);
}
@@ -155,157 +138,321 @@ run (void *cls,
/**
* Move money to the exchange's bank account.
*/
- cmd_transfer_to_exchange ("create-reserve-1",
- "EUR:10.02"),
+ cmd_transfer_to_exchange (
+ "create-reserve-1",
+ "EUR:10.02"),
/**
* Make a reserve exist, according to the previous transfer.
*/
- TALER_TESTING_cmd_exec_wirewatch ("wirewatch-1",
- CONFIG_FILE),
- TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2",
- "EUR:10.02",
- payer_payto,
- exchange_payto,
- "create-reserve-1"),
- TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
- "create-reserve-1",
- "EUR:5",
- 0,
- MHD_HTTP_OK),
- TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
- "create-reserve-1",
- "EUR:5",
- 0,
- MHD_HTTP_OK),
- TALER_TESTING_cmd_merchant_get_orders ("get-orders-empty",
- merchant_url,
- MHD_HTTP_OK,
- NULL),
+ TALER_TESTING_cmd_exec_wirewatch (
+ "wirewatch-1",
+ CONFIG_FILE),
+ TALER_TESTING_cmd_check_bank_admin_transfer (
+ "check_bank_transfer-2",
+ "EUR:10.02",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-1"),
+ TALER_TESTING_cmd_withdraw_amount (
+ "withdraw-coin-1",
+ "create-reserve-1",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount (
+ "withdraw-coin-2",
+ "create-reserve-1",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_get_orders (
+ "get-orders-empty",
+ merchant_url,
+ MHD_HTTP_OK,
+ NULL),
/**
* Check the reserve is depleted.
*/
- TALER_TESTING_cmd_status ("withdraw-status-1",
- "create-reserve-1",
- "EUR:0",
- MHD_HTTP_OK),
- TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-1",
- merchant_url,
- MHD_HTTP_OK,
- "1", /* order ID */
- GNUNET_TIME_UNIT_ZERO_TS,
- GNUNET_TIME_UNIT_FOREVER_TS,
- true,
- "EUR:5.0",
- "x-taler-bank",
- "",
- "",
- NULL),
- TALER_TESTING_cmd_merchant_claim_order ("reclaim-1",
- merchant_url,
- MHD_HTTP_OK,
- "create-proposal-1",
- NULL),
- TALER_TESTING_cmd_merchant_pay_order ("deposit-simple",
- merchant_url,
- MHD_HTTP_OK,
- "create-proposal-1",
- "withdraw-coin-1",
- "EUR:5",
- "EUR:4.99",
- "session-0"),
- TALER_TESTING_cmd_merchant_post_orders_paid ("verify-order-1-paid",
- merchant_url,
- "deposit-simple",
- "session-1",
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-1"),
- CMD_EXEC_AGGREGATOR ("run-aggregator"),
+ TALER_TESTING_cmd_status (
+ "withdraw-status-1",
+ "create-reserve-1",
+ "EUR:0",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_orders2 (
+ "create-proposal-1",
+ cred.cfg,
+ merchant_url,
+ MHD_HTTP_OK,
+ "1", /* order ID */
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
+ true,
+ "EUR:5.0",
+ "x-taler-bank",
+ "",
+ "",
+ NULL),
+ TALER_TESTING_cmd_merchant_claim_order (
+ "reclaim-1",
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-1",
+ NULL),
+ TALER_TESTING_cmd_merchant_pay_order (
+ "deposit-simple",
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-1",
+ "withdraw-coin-1",
+ "EUR:5",
+ "EUR:4.99",
+ "session-0"),
+ TALER_TESTING_cmd_merchant_post_orders_paid (
+ "verify-order-1-paid",
+ merchant_url,
+ "deposit-simple",
+ "session-1",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_check_bank_empty (
+ "check_bank_empty-1"),
+ CMD_EXEC_AGGREGATOR (
+ "run-aggregator"),
/* KYC: hence nothing happened at the bank yet: */
- TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-2"),
+ TALER_TESTING_cmd_check_bank_empty (
+ "check_bank_empty-2"),
/* KYC: we don't even know the legitimization UUID yet */
- TALER_TESTING_cmd_merchant_kyc_get ("kyc-pending",
- merchant_url,
- NULL,
- NULL,
- EXCHANGE_URL,
- MHD_HTTP_NO_CONTENT),
- /* now we get the legi UUID */
- TALER_TESTING_cmd_merchant_get_order ("get-order-kyc",
- merchant_url,
- "create-proposal-1",
- TALER_MERCHANT_OSC_PAID,
- false,
- MHD_HTTP_OK,
- NULL),
+ TALER_TESTING_cmd_merchant_kyc_get (
+ "kyc-pending-early",
+ merchant_url,
+ NULL,
+ NULL,
+ EXCHANGE_URL,
+ MHD_HTTP_NO_CONTENT,
+ TALER_AML_NORMAL),
+ /* now we get the legi UUID by running taler-merchant-depositcheck */
+ TALER_TESTING_cmd_depositcheck (
+ "deposit-check",
+ CONFIG_FILE),
/* Now we should get a status of pending */
- TALER_TESTING_cmd_merchant_kyc_get ("kyc-pending",
- merchant_url,
- NULL,
- NULL,
- EXCHANGE_URL,
- MHD_HTTP_ACCEPTED),
- TALER_TESTING_cmd_proof_kyc_oauth2 ("kyc-do",
- "kyc-pending",
- "kyc-provider-test-oauth2",
- "pass",
- MHD_HTTP_SEE_OTHER),
+ TALER_TESTING_cmd_merchant_kyc_get (
+ "kyc-pending",
+ merchant_url,
+ NULL,
+ NULL,
+ EXCHANGE_URL,
+ MHD_HTTP_ACCEPTED,
+ TALER_AML_NORMAL),
+ TALER_TESTING_cmd_proof_kyc_oauth2 (
+ "kyc-do",
+ "kyc-pending",
+ "kyc-provider-test-oauth2",
+ "pass",
+ MHD_HTTP_SEE_OTHER),
CMD_EXEC_AGGREGATOR ("run-aggregator"),
- TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-498c",
- EXCHANGE_URL,
- "EUR:4.98",
- exchange_payto,
- merchant_payto),
- TALER_TESTING_cmd_merchant_post_transfer ("post-transfer-1",
- &bc.exchange_auth,
- PAYTO_I1,
- merchant_url,
- "EUR:4.98",
- MHD_HTTP_OK,
- "deposit-simple",
- NULL),
- TALER_TESTING_cmd_merchant_get_transfers ("get-transfers-1",
- merchant_url,
- PAYTO_I1,
- MHD_HTTP_OK,
- "post-transfer-1",
- NULL),
- TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-3"),
+ TALER_TESTING_cmd_check_bank_transfer (
+ "check_bank_transfer-498c",
+ EXCHANGE_URL,
+ "EUR:4.98",
+ exchange_payto,
+ merchant_payto),
+ TALER_TESTING_cmd_merchant_post_transfer (
+ "post-transfer-1",
+ &cred.ba,
+ merchant_payto,
+ merchant_url,
+ "EUR:4.98",
+ MHD_HTTP_NO_CONTENT,
+ "deposit-simple",
+ NULL),
+ TALER_TESTING_cmd_run_tme (
+ "run taler-merchant-exchange-1",
+ CONFIG_FILE),
+ TALER_TESTING_cmd_merchant_get_transfers (
+ "get-transfers-1",
+ merchant_url,
+ merchant_payto,
+ MHD_HTTP_OK,
+ "post-transfer-1",
+ NULL),
+ TALER_TESTING_cmd_check_bank_empty (
+ "check_bank_empty-3"),
TALER_TESTING_cmd_end ()
};
+ struct TALER_TESTING_Command aml[] = {
+ TALER_TESTING_cmd_set_officer (
+ "aml-officer",
+ NULL,
+ "Ernest&Young",
+ true,
+ false),
+ cmd_transfer_to_exchange (
+ "create-reserve-big",
+ "EUR:100.02"),
+ TALER_TESTING_cmd_exec_wirewatch (
+ "wirewatch-big",
+ CONFIG_FILE),
+ TALER_TESTING_cmd_take_aml_decision (
+ "freeze",
+ "aml-officer",
+ "post-transfer-1",
+ "EUR:1",
+ "suspicious",
+ TALER_AML_FROZEN,
+ NULL,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_check_bank_admin_transfer (
+ "check_bank_transfer-big",
+ "EUR:100.02",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-big"),
+ TALER_TESTING_cmd_withdraw_amount (
+ "withdraw-coin-aml",
+ "create-reserve-big",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_orders2 (
+ "create-proposal-aml",
+ cred.cfg,
+ merchant_url,
+ MHD_HTTP_OK,
+ "10-aml", /* order ID */
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
+ true,
+ "EUR:5.0",
+ "x-taler-bank",
+ "",
+ "",
+ NULL),
+ TALER_TESTING_cmd_merchant_claim_order (
+ "reclaim-aml",
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-aml",
+ NULL),
+ TALER_TESTING_cmd_merchant_pay_order (
+ "deposit-simple",
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-aml",
+ "withdraw-coin-aml",
+ "EUR:5",
+ "EUR:4.99",
+ "session-aml"),
+ TALER_TESTING_cmd_merchant_post_orders_paid (
+ "verify-order-aml-paid",
+ merchant_url,
+ "deposit-simple",
+ "session-aml",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_check_bank_empty (
+ "check_bank_empty-aml-1"),
+ CMD_EXEC_AGGREGATOR ("run-aggregator-aml-frozen"),
+ /* AML-frozen: hence nothing happened at the bank yet: */
+ TALER_TESTING_cmd_check_bank_empty (
+ "check_bank_empty-aml-2"),
+ /* Now we should get a status of frozen */
+ TALER_TESTING_cmd_merchant_kyc_get (
+ "aml-frozen",
+ merchant_url,
+ NULL, /* no instance ID */
+ NULL, /* no wire ref */
+ EXCHANGE_URL,
+ MHD_HTTP_ACCEPTED,
+ TALER_AML_FROZEN),
+ TALER_TESTING_cmd_sleep (
+ "sleep to de-collide AML timestamps",
+ 1),
+ TALER_TESTING_cmd_take_aml_decision (
+ "unfreeze",
+ "aml-officer",
+ "post-transfer-1",
+ "EUR:100",
+ "fine",
+ TALER_AML_NORMAL,
+ NULL,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_kyc_get (
+ "aml-unfrozen",
+ merchant_url,
+ NULL, /* no instance ID */
+ NULL, /* no wire ref */
+ EXCHANGE_URL,
+ MHD_HTTP_NO_CONTENT,
+ TALER_AML_NORMAL),
+ CMD_EXEC_AGGREGATOR ("run-aggregator-aml-normal"),
+ TALER_TESTING_cmd_check_bank_transfer (
+ "check_bank_transfer-498c-post-unfreeze",
+ EXCHANGE_URL,
+ "EUR:4.98",
+ exchange_payto,
+ merchant_payto),
+ TALER_TESTING_cmd_merchant_post_transfer (
+ "post-transfer-aml",
+ &cred.ba,
+ merchant_payto,
+ merchant_url,
+ "EUR:4.98",
+ MHD_HTTP_NO_CONTENT,
+ "deposit-simple",
+ NULL),
+ TALER_TESTING_cmd_run_tme (
+ "run taler-merchant-exchange-2-aml",
+ CONFIG_FILE),
+ TALER_TESTING_cmd_merchant_get_transfers (
+ "get-transfers-aml",
+ merchant_url,
+ merchant_payto,
+ MHD_HTTP_OK,
+ "post-transfer-1",
+ "post-transfer-aml",
+ NULL),
+ TALER_TESTING_cmd_end ()
+ }; /* end of aml batch */
struct TALER_TESTING_Command commands[] = {
/* general setup */
- TALER_TESTING_cmd_oauth ("start-oauth-service",
- 6666),
- TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_wire_add ("add-wire-account",
- "payto://x-taler-bank/localhost/2?receiver-name=2",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
- CONFIG_FILE),
- TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
- CONFIG_FILE,
- "EUR:0.01",
- "EUR:0.01"),
- TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
- 1),
- TALER_TESTING_cmd_merchant_post_instances ("instance-create-default-setup",
- merchant_url,
- "default",
- PAYTO_I1,
- "EUR",
- MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_run_fakebank (
+ "run-fakebank",
+ cred.cfg,
+ "exchange-account-exchange"),
+ TALER_TESTING_cmd_system_start (
+ "start-taler",
+ CONFIG_FILE,
+ "-ema",
+ "-u", "exchange-account-exchange",
+ NULL),
+ TALER_TESTING_cmd_get_exchange (
+ "get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_oauth (
+ "start-oauth-service",
+ 6666),
+ TALER_TESTING_cmd_merchant_post_instances (
+ "instance-create-default-setup",
+ merchant_url,
+ "default",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_account (
+ "instance-create-default-account",
+ merchant_url,
+ merchant_payto,
+ NULL, NULL,
+ MHD_HTTP_OK),
TALER_TESTING_cmd_batch ("pay",
pay),
+ TALER_TESTING_cmd_batch ("aml",
+ aml),
TALER_TESTING_cmd_end ()
};
- TALER_TESTING_run_with_fakebank (is,
- commands,
- bc.exchange_auth.wire_gateway_url);
+ TALER_TESTING_run (is,
+ commands);
}
@@ -313,68 +460,28 @@ int
main (int argc,
char *const *argv)
{
- enum GNUNET_GenericReturnValue ret;
-
- /* These environment variables get in the way... */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup ("test-kyc-api",
- "INFO",
- NULL);
- if (GNUNET_OK !=
- TALER_TESTING_prepare_fakebank (CONFIG_FILE,
- "exchange-account-exchange",
- &bc))
- return 77;
-
payer_payto =
- ("payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME "?receiver-name="
- USER_ACCOUNT_NAME);
+ "payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME "?receiver-name="
+ USER_ACCOUNT_NAME;
exchange_payto =
- ("payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME "?receiver-name="
- EXCHANGE_ACCOUNT_NAME);
+ "payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME "?receiver-name="
+ EXCHANGE_ACCOUNT_NAME;
merchant_payto =
- ("payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME "?receiver-name="
- MERCHANT_ACCOUNT_NAME);
-
- if (NULL ==
- (merchant_url = TALER_TESTING_prepare_merchant (CONFIG_FILE)))
- return 77;
+ "payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME "?receiver-name="
+ MERCHANT_ACCOUNT_NAME;
+ merchant_url = "http://localhost:8080/";
GNUNET_asprintf (&merchant_url_i1a,
"%sinstances/i1a/",
merchant_url);
- TALER_TESTING_cleanup_files (CONFIG_FILE);
-
- switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
- GNUNET_YES,
- &ec))
- {
- case GNUNET_SYSERR:
- GNUNET_break (0);
- return 1;
- case GNUNET_NO:
- return 77;
- case GNUNET_OK:
- if (NULL == (merchantd =
- TALER_TESTING_run_merchant (CONFIG_FILE,
- merchant_url)))
- return 1;
- ret = TALER_TESTING_setup_with_exchange (&run,
- NULL,
- CONFIG_FILE);
- GNUNET_OS_process_kill (merchantd, SIGTERM);
- GNUNET_OS_process_wait (merchantd);
- GNUNET_OS_process_destroy (merchantd);
- GNUNET_free (merchant_url);
- if (GNUNET_OK != ret)
- return 1;
- break;
- default:
- GNUNET_break (0);
- return 1;
- }
- return 0;
+ return TALER_TESTING_main (argv,
+ "INFO",
+ CONFIG_FILE,
+ "exchange-account-exchange",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
}
-/* end of test_merchant_api.c */
+/* end of test_kyc_api.c */
diff --git a/src/testing/test_kyc_api.conf b/src/testing/test_kyc_api.conf
index c7615cf7..bbbfc17c 100644
--- a/src/testing/test_kyc_api.conf
+++ b/src/testing/test_kyc_api.conf
@@ -1,109 +1,46 @@
# This file is in the public domain.
#
[PATHS]
-# Persistent data storage for the testcase
TALER_TEST_HOME = test_merchant_api_home/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
-
-# Persistent data storage
-TALER_DATA_HOME = $TALER_HOME/.local/share/taler/
-
-# Configuration files
-TALER_CONFIG_HOME = $TALER_HOME/.config/taler/
-
-# Cached data, no big deal if lost
-TALER_CACHE_HOME = $TALER_HOME/.cache/taler/
[taler]
-# What currency do we use?
CURRENCY = EUR
CURRENCY_ROUND_UNIT = EUR:0.01
[taler-helper-crypto-rsa]
-# Reduce from 1 year to speed up test
LOOKAHEAD_SIGN = 24 days
[taler-helper-crypto-eddsa]
-# Reduce from 1 year to speed up test
LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
DURATION = 14 days
[bank]
HTTP_PORT = 8082
-##########################################
-# Configuration for the merchant backend #
-##########################################
-
[merchant]
-
-# Which port do we run the backend on? (HTTP server)
PORT = 8080
-
-# Which plugin (backend) do we use for the DB.
+SERVE = tcp
DB = postgres
# This specifies which database the postgres backend uses.
[merchantdb-postgres]
CONFIG = postgres:///talercheck
+SQL_DIR = $DATADIR/sql/merchant/
-# Sections starting with "merchant-exchange-" specify trusted exchanges
-# (by the merchant)
[merchant-exchange-test]
-MASTER_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
+MASTER_KEY = NKX42KSCQHDQK7CF1PC6X9DMQPXW6KHXKGD3DPQJMP32FKXSWYK0
EXCHANGE_BASE_URL = http://localhost:8081/
CURRENCY = EUR
-
-#######################################################
-# Configuration for the auditor for the testcase
-#######################################################
-[auditor]
-BASE_URL = http://the.auditor/
-
[auditordb-postgres]
CONFIG = postgres:///talercheck
-
-#######################################################
-# Configuration for ??? Is this used?
-#######################################################
-
-# Auditors must be in sections "auditor-", the rest of the section
-# name could be anything.
-[auditor-ezb]
-# Informal name of the auditor. Just for the user.
-NAME = European Central Bank
-
-# URL of the auditor (especially for in the future, when the
-# auditor offers an automated issue reporting system).
-# Not really used today.
-URL = http://taler.ezb.eu/
-
-# This is the important bit: the signing key of the auditor.
-PUBLIC_KEY = 9QXF7XY7E9VPV47B5Z806NDFSX2VJ79SVHHD29QEQ3BG31ANHZ60
-
-# Which currency is this auditor trusted for?
-CURRENCY = EUR
-
-
-###################################################
-# Configuration for the exchange for the testcase #
-###################################################
-
[exchange]
-# How to access our database
-DB = postgres
-
-# HTTP port the exchange listens to
+AML_THRESHOLD = EUR:1000000
PORT = 8081
-
-# Our public key
-MASTER_PUBLIC_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
-
-# Base URL of the exchange.
+MASTER_PUBLIC_KEY = NKX42KSCQHDQK7CF1PC6X9DMQPXW6KHXKGD3DPQJMP32FKXSWYK0
BASE_URL = "http://localhost:8081/"
+STEFAN_ABS = "EUR:5"
[kyc-provider-test-oauth2]
COST = 0
@@ -111,12 +48,13 @@ LOGIC = oauth2
USER_TYPE = BUSINESS
PROVIDED_CHECKS = DUMMY
KYC_OAUTH2_VALIDITY = forever
-KYC_OAUTH2_AUTH_URL = http://localhost:6666/oauth/v2/token
-KYC_OAUTH2_LOGIN_URL = http://localhost:6666/oauth/v2/login
+KYC_OAUTH2_TOKEN_URL = http://localhost:6666/oauth/v2/token
+KYC_OAUTH2_AUTHORIZE_URL = http://localhost:6666/oauth/v2/login
KYC_OAUTH2_INFO_URL = http://localhost:6666/api/user/me
KYC_OAUTH2_CLIENT_ID = taler-exchange
KYC_OAUTH2_CLIENT_SECRET = exchange-secret
KYC_OAUTH2_POST_URL = http://example.com/
+KYC_OAUTH2_CONVERTER_HELPER = taler-exchange-kyc-oauth2-test-converter.sh
[kyc-legitimization-deposit-any]
OPERATION_TYPE = DEPOSIT
@@ -140,13 +78,16 @@ CONFIG = "postgres:///talercheck"
# Account of the EXCHANGE
[exchange-account-exchange]
-# What is the exchange's bank account (with the "Taler Bank" demo system)?
PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
ENABLE_DEBIT = YES
ENABLE_CREDIT = YES
[exchange-accountcredentials-exchange]
-WIRE_GATEWAY_URL = "http://localhost:8082/2/"
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+WIRE_GATEWAY_AUTH_METHOD = NONE
+
+[admin-accountcredentials-exchange]
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
WIRE_GATEWAY_AUTH_METHOD = NONE
diff --git a/src/testing/test_merchant_api-cs.conf b/src/testing/test_merchant_api-cs.conf
index 502d807a..4d1c14b6 100644
--- a/src/testing/test_merchant_api-cs.conf
+++ b/src/testing/test_merchant_api-cs.conf
@@ -1,127 +1,5 @@
# This file is in the public domain.
-#
-[PATHS]
-# Persistent data storage for the testcase
-TALER_TEST_HOME = test_merchant_api_home/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
-
-# Persistent data storage
-TALER_DATA_HOME = $TALER_HOME/.local/share/taler/
-
-# Configuration files
-TALER_CONFIG_HOME = $TALER_HOME/.config/taler/
-
-# Cached data, no big deal if lost
-TALER_CACHE_HOME = $TALER_HOME/.cache/taler/
-
-[taler]
-# What currency do we use?
-CURRENCY = EUR
-CURRENCY_ROUND_UNIT = EUR:0.01
-
-[taler-helper-crypto-rsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-
-[taler-helper-crypto-eddsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
-DURATION = 14 days
-
-[bank]
-HTTP_PORT = 8082
-
-##########################################
-# Configuration for the merchant backend #
-##########################################
-
-[merchant]
-
-# Which port do we run the backend on? (HTTP server)
-PORT = 8080
-
-# Which plugin (backend) do we use for the DB.
-DB = postgres
-
-# This specifies which database the postgres backend uses.
-[merchantdb-postgres]
-CONFIG = postgres:///talercheck
-
-# Sections starting with "merchant-exchange-" specify trusted exchanges
-# (by the merchant)
-[merchant-exchange-test]
-MASTER_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
-EXCHANGE_BASE_URL = http://localhost:8081/
-CURRENCY = EUR
-
-
-#######################################################
-# Configuration for the auditor for the testcase
-#######################################################
-[auditor]
-BASE_URL = http://the.auditor/
-
-
-#######################################################
-# Configuration for ??? Is this used?
-#######################################################
-
-# Auditors must be in sections "auditor-", the rest of the section
-# name could be anything.
-[auditor-ezb]
-# Informal name of the auditor. Just for the user.
-NAME = European Central Bank
-
-# URL of the auditor (especially for in the future, when the
-# auditor offers an automated issue reporting system).
-# Not really used today.
-URL = http://taler.ezb.eu/
-
-# This is the important bit: the signing key of the auditor.
-PUBLIC_KEY = 9QXF7XY7E9VPV47B5Z806NDFSX2VJ79SVHHD29QEQ3BG31ANHZ60
-
-# Which currency is this auditor trusted for?
-CURRENCY = EUR
-
-
-###################################################
-# Configuration for the exchange for the testcase #
-###################################################
-
-[exchange]
-# How to access our database
-DB = postgres
-
-# HTTP port the exchange listens to
-PORT = 8081
-
-# Our public key
-MASTER_PUBLIC_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
-
-# Base URL of the exchange.
-BASE_URL = "http://localhost:8081/"
-
-
-[exchangedb-postgres]
-CONFIG = "postgres:///talercheck"
-
-
-[auditordb-postgres]
-CONFIG = postgres:///talercheck
-
-
-# Account of the EXCHANGE
-[exchange-account-exchange]
-# What is the exchange's bank account (with the "Taler Bank" demo system)?
-PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
-ENABLE_DEBIT = YES
-ENABLE_CREDIT = YES
-
-[exchange-accountcredentials-exchange]
-WIRE_GATEWAY_URL = "http://localhost:8082/2/"
-WIRE_GATEWAY_AUTH_METHOD = NONE
-
+@INLINE@ test_merchant_api.conf
[coin_eur_ct_1]
value = EUR:0.01
diff --git a/src/testing/test_merchant_api-rsa.conf b/src/testing/test_merchant_api-rsa.conf
index 5246fc40..44d8e978 100644
--- a/src/testing/test_merchant_api-rsa.conf
+++ b/src/testing/test_merchant_api-rsa.conf
@@ -1,127 +1,5 @@
# This file is in the public domain.
-#
-[PATHS]
-# Persistent data storage for the testcase
-TALER_TEST_HOME = test_merchant_api_home/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
-
-# Persistent data storage
-TALER_DATA_HOME = $TALER_HOME/.local/share/taler/
-
-# Configuration files
-TALER_CONFIG_HOME = $TALER_HOME/.config/taler/
-
-# Cached data, no big deal if lost
-TALER_CACHE_HOME = $TALER_HOME/.cache/taler/
-
-[taler]
-# What currency do we use?
-CURRENCY = EUR
-CURRENCY_ROUND_UNIT = EUR:0.01
-
-[taler-helper-crypto-rsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-
-[taler-helper-crypto-eddsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
-DURATION = 14 days
-
-[bank]
-HTTP_PORT = 8082
-
-##########################################
-# Configuration for the merchant backend #
-##########################################
-
-[merchant]
-
-# Which port do we run the backend on? (HTTP server)
-PORT = 8080
-
-# Which plugin (backend) do we use for the DB.
-DB = postgres
-
-# This specifies which database the postgres backend uses.
-[merchantdb-postgres]
-CONFIG = postgres:///talercheck
-
-# Sections starting with "merchant-exchange-" specify trusted exchanges
-# (by the merchant)
-[merchant-exchange-test]
-MASTER_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
-EXCHANGE_BASE_URL = http://localhost:8081/
-CURRENCY = EUR
-
-
-#######################################################
-# Configuration for the auditor for the testcase
-#######################################################
-[auditor]
-BASE_URL = http://the.auditor/
-
-
-#######################################################
-# Configuration for ??? Is this used?
-#######################################################
-
-# Auditors must be in sections "auditor-", the rest of the section
-# name could be anything.
-[auditor-ezb]
-# Informal name of the auditor. Just for the user.
-NAME = European Central Bank
-
-# URL of the auditor (especially for in the future, when the
-# auditor offers an automated issue reporting system).
-# Not really used today.
-URL = http://taler.ezb.eu/
-
-# This is the important bit: the signing key of the auditor.
-PUBLIC_KEY = 9QXF7XY7E9VPV47B5Z806NDFSX2VJ79SVHHD29QEQ3BG31ANHZ60
-
-# Which currency is this auditor trusted for?
-CURRENCY = EUR
-
-
-###################################################
-# Configuration for the exchange for the testcase #
-###################################################
-
-[exchange]
-# How to access our database
-DB = postgres
-
-# HTTP port the exchange listens to
-PORT = 8081
-
-# Our public key
-MASTER_PUBLIC_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
-
-# Base URL of the exchange.
-BASE_URL = "http://localhost:8081/"
-
-
-[exchangedb-postgres]
-CONFIG = "postgres:///talercheck"
-
-
-[auditordb-postgres]
-CONFIG = postgres:///talercheck
-
-
-# Account of the EXCHANGE
-[exchange-account-exchange]
-# What is the exchange's bank account (with the "Taler Bank" demo system)?
-PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
-ENABLE_DEBIT = YES
-ENABLE_CREDIT = YES
-
-[exchange-accountcredentials-exchange]
-WIRE_GATEWAY_URL = "http://localhost:8082/2/"
-WIRE_GATEWAY_AUTH_METHOD = NONE
-
+@INLINE@ test_merchant_api.conf
[coin_eur_ct_1]
value = EUR:0.01
diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c
index a04bbe57..ed07bce6 100644
--- a/src/testing/test_merchant_api.c
+++ b/src/testing/test_merchant_api.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -66,39 +66,30 @@ static char *config_file;
*/
#define EXCHANGE_URL "http://localhost:8081/"
-static const char *pickup_amounts_1[] = {"EUR:5", NULL};
-
-static const char *pickup_amounts_2[] = {"EUR:0.01", NULL};
-
/**
* Payto URI of the customer (payer).
*/
-static char *payer_payto;
+static const char *payer_payto;
/**
* Payto URI of the exchange (escrow account).
*/
-static char *exchange_payto;
+static const char *exchange_payto;
/**
* Payto URI of the merchant (receiver).
*/
-static char *merchant_payto;
-
-/**
- * Configuration of the bank.
- */
-static struct TALER_TESTING_BankConfiguration bc;
+static const char *merchant_payto;
/**
- * Configuration of the exchange.
+ * Credentials for the test.
*/
-static struct TALER_TESTING_ExchangeConfiguration ec;
+static struct TALER_TESTING_Credentials cred;
/**
* Merchant base URL.
*/
-static char *merchant_url;
+static const char *merchant_url;
/**
* Merchant instance "i1a" base URL.
@@ -106,11 +97,6 @@ static char *merchant_url;
static char *merchant_url_i1a;
/**
- * Merchant process.
- */
-static struct GNUNET_OS_Process *merchantd;
-
-/**
* Account number of the exchange at the bank.
*/
#define EXCHANGE_ACCOUNT_NAME "2"
@@ -130,38 +116,41 @@ static struct GNUNET_OS_Process *merchantd;
*/
#define MERCHANT_ACCOUNT_NAME "3"
-/**
- * Payto URIs to use for testing accounts on the merchant.
- */
-const char *payto_uris[] = {
- PAYTO_I1,
- "payto://iban/CH9300762011623852957?receiver-name=Test"
- /* Just for testing account inactivation. */
-};
-
-const char *order_1_transfers[] = {
+static const char *order_1_transfers[] = {
"post-transfer-1",
NULL
};
-const char *order_1_forgets_1[] = {
+static const char *order_1_forgets_1[] = {
"forget-1",
NULL
};
-const char *order_1_forgets_2[] = {
+static const char *order_1_forgets_2[] = {
"forget-1",
"forget-order-array-elem",
NULL
};
-const char *order_1_forgets_3[] = {
+static const char *order_1_forgets_3[] = {
"forget-1",
"forget-order-array-elem",
"forget-order-array-wc",
NULL
};
+/**
+ * Execute the taler-merchant-webhook command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+static struct TALER_TESTING_Command
+cmd_webhook (const char *label)
+{
+ return TALER_TESTING_cmd_webhook (label, config_file);
+}
+
/**
* Execute the taler-exchange-wirewatch command with
@@ -202,7 +191,7 @@ cmd_transfer_to_exchange (const char *label,
{
return TALER_TESTING_cmd_admin_add_incoming (label,
amount,
- &bc.exchange_auth,
+ &cred.ba,
payer_payto);
}
@@ -221,15 +210,28 @@ run (void *cls,
TALER_TESTING_cmd_merchant_post_instances ("instance-create-default",
merchant_url,
"default",
- PAYTO_I1,
- "EUR",
MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_account (
+ "instance-create-default-account",
+ merchant_url,
+ PAYTO_I1,
+ NULL, NULL,
+ MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_kyc_get ("instance-create-kyc-0",
merchant_url,
NULL,
NULL,
EXCHANGE_URL,
- MHD_HTTP_NO_CONTENT),
+ MHD_HTTP_NO_CONTENT,
+ TALER_AML_NORMAL),
+ TALER_TESTING_cmd_merchant_post_orders_no_claim (
+ "create-proposal-bad-currency",
+ merchant_url,
+ MHD_HTTP_BAD_REQUEST,
+ "4",
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
+ "CHF:5.0"),
TALER_TESTING_cmd_merchant_post_orders_no_claim ("create-proposal-4",
merchant_url,
MHD_HTTP_OK,
@@ -304,9 +306,10 @@ run (void *cls,
NULL,
"1"),
TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-1",
+ cred.cfg,
merchant_url,
MHD_HTTP_OK,
- "1", /* order ID */
+ "1",
GNUNET_TIME_UNIT_ZERO_TS,
GNUNET_TIME_UNIT_FOREVER_TS,
true,
@@ -315,10 +318,18 @@ run (void *cls,
"",
"",
NULL),
+ TALER_TESTING_cmd_merchant_post_webhooks ("post-webhooks-pay-w1",
+ merchant_url,
+ "webhook-pay-1",
+ "pay",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_testserver ("launch-http-server-for-webhooks",
+ 12345),
TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-1-idem",
+ cred.cfg,
merchant_url,
MHD_HTTP_OK,
- "1", /* order ID */
+ "1",
GNUNET_TIME_UNIT_ZERO_TS,
GNUNET_TIME_UNIT_FOREVER_TS,
true,
@@ -328,9 +339,10 @@ run (void *cls,
"",
"create-proposal-1"),
TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-1x",
+ cred.cfg,
merchant_url,
MHD_HTTP_OK,
- "1x", /* order ID */
+ "1x",
GNUNET_TIME_UNIT_ZERO_TS,
GNUNET_TIME_UNIT_FOREVER_TS,
true,
@@ -355,6 +367,7 @@ run (void *cls,
"create-proposal-1x",
NULL),
TALER_TESTING_cmd_merchant_post_orders ("create-proposal-1-pre-exists",
+ cred.cfg,
merchant_url,
MHD_HTTP_CONFLICT,
"1",
@@ -414,6 +427,13 @@ run (void *cls,
MHD_HTTP_OK,
NULL,
"poll-order-wallet-start-1"),
+ /* Check for webhook */
+ cmd_webhook ("pending-webhooks-pay-w1"),
+ /* Check webhook did anything: have a command that inspects traits of the testserver
+ and check if the traits have the right values set! */
+ TALER_TESTING_cmd_checkserver ("check-http-server-for-webhooks",
+ "launch-http-server-for-webhooks",
+ 0),
/* Here we expect to run into a timeout, as we do not pay this one */
TALER_TESTING_cmd_wallet_poll_order_conclude2 ("poll-order-1x-conclude",
MHD_HTTP_PAYMENT_REQUIRED,
@@ -424,7 +444,7 @@ run (void *cls,
merchant_url,
"deposit-simple",
"session-1",
- MHD_HTTP_NO_CONTENT),
+ MHD_HTTP_OK),
TALER_TESTING_cmd_wallet_get_order ("get-order-wallet-1-2",
merchant_url,
"create-proposal-1",
@@ -432,7 +452,7 @@ run (void *cls,
false,
false,
MHD_HTTP_OK),
- TALER_TESTING_cmd_merchant_get_order ("get-order-merchant-1-2",
+ TALER_TESTING_cmd_merchant_get_order ("get-order-merchant-1-2a",
merchant_url,
"create-proposal-1",
TALER_MERCHANT_OSC_PAID,
@@ -461,23 +481,25 @@ run (void *cls,
exchange_payto,
merchant_payto),
TALER_TESTING_cmd_merchant_post_transfer ("post-transfer-1",
- &bc.exchange_auth,
+ &cred.ba,
PAYTO_I1,
merchant_url,
"EUR:4.98",
- MHD_HTTP_OK,
+ MHD_HTTP_NO_CONTENT,
"deposit-simple",
NULL),
+ TALER_TESTING_cmd_run_tme ("run taler-merchant-exchange-1",
+ config_file),
TALER_TESTING_cmd_merchant_post_transfer2 ("post-transfer-bad",
merchant_url,
PAYTO_I1,
"EUR:4.98",
- NULL /* random WTID */,
- /* non-routable IP address,
- so we are sure to not get
- any reply */
+ NULL,
+ /*non-routable IP address
+ so we are sure to not get
+ any reply*/
"http://192.0.2.1/404/",
- MHD_HTTP_GATEWAY_TIMEOUT),
+ MHD_HTTP_NO_CONTENT),
TALER_TESTING_cmd_merchant_get_transfers ("get-transfers-1",
merchant_url,
PAYTO_I1,
@@ -489,19 +511,19 @@ run (void *cls,
merchant_url,
"post-transfer-bad",
MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_get_order2 ("get-order-merchant-1-2",
+ TALER_TESTING_cmd_merchant_get_order2 ("get-order-merchant-1-2b",
merchant_url,
"create-proposal-1",
TALER_MERCHANT_OSC_PAID,
true,
- order_1_transfers,
+ order_1_transfers, /* "post-transfer-1" */
false,
NULL,
NULL,
MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_forget_order ("forget-1",
merchant_url,
- MHD_HTTP_OK,
+ MHD_HTTP_NO_CONTENT,
"create-proposal-1",
NULL,
"$.dummy_obj",
@@ -532,7 +554,7 @@ run (void *cls,
NULL),
TALER_TESTING_cmd_merchant_forget_order ("forget-order-array-elem",
merchant_url,
- MHD_HTTP_OK,
+ MHD_HTTP_NO_CONTENT,
"create-proposal-1",
NULL,
"$.dummy_array[0].item",
@@ -549,7 +571,7 @@ run (void *cls,
MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_forget_order ("forget-order-array-wc",
merchant_url,
- MHD_HTTP_OK,
+ MHD_HTTP_NO_CONTENT,
"create-proposal-1",
NULL,
"$.dummy_array[*].item",
@@ -577,6 +599,20 @@ run (void *cls,
"a product",
"EUR:1",
MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_products2 ("post-products-p4",
+ merchant_url,
+ "product-4age",
+ "an age-restricted product",
+ NULL,
+ "unit",
+ "EUR:1",
+ "", /* image */
+ NULL,
+ 4,
+ 16 /* minimum age */,
+ NULL,
+ GNUNET_TIME_UNIT_FOREVER_TS,
+ MHD_HTTP_NO_CONTENT),
TALER_TESTING_cmd_merchant_patch_product ("patch-products-p3",
merchant_url,
"product-3",
@@ -599,18 +635,20 @@ run (void *cls,
2,
MHD_HTTP_NO_CONTENT),
TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-p3-wm-nx",
+ cred.cfg,
merchant_url,
MHD_HTTP_NOT_FOUND,
"order-p3",
GNUNET_TIME_UNIT_ZERO_TS,
GNUNET_TIME_UNIT_FOREVER_TS,
- true, /* claim token */
+ true,
"EUR:5.0",
"unsupported-wire-method",
"product-3/2",
"", /* locks */
- NULL /* duplicate_of */),
+ NULL),
TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-p3-pd-nx",
+ cred.cfg,
merchant_url,
MHD_HTTP_NOT_FOUND,
"order-p3",
@@ -624,6 +662,7 @@ run (void *cls,
NULL),
TALER_TESTING_cmd_merchant_post_orders2 (
"create-proposal-p3-not-enough-stock",
+ cred.cfg,
merchant_url,
MHD_HTTP_GONE,
"order-p3",
@@ -636,6 +675,7 @@ run (void *cls,
"",
NULL),
TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-p3",
+ cred.cfg,
merchant_url,
MHD_HTTP_OK,
"order-p3",
@@ -647,23 +687,47 @@ run (void *cls,
"product-3/3",
"lock-product-p3",
NULL),
+ TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-p4-age",
+ cred.cfg,
+ merchant_url,
+ MHD_HTTP_OK,
+ "order-p4-age",
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
+ false,
+ "EUR:5.0",
+ "x-taler-bank",
+ "product-4age",
+ "", /* locks */
+ NULL),
+ TALER_TESTING_cmd_merchant_get_order4 ("get-order-merchant-p4-age",
+ merchant_url,
+ "create-proposal-p4-age",
+ TALER_MERCHANT_OSC_CLAIMED,
+ 16,
+ MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_delete_order ("delete-order-paid",
merchant_url,
"1",
MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_merchant_post_orders ("create-proposal-no-id",
+ cred.cfg,
merchant_url,
MHD_HTTP_OK,
NULL,
GNUNET_TIME_UNIT_ZERO_TS,
GNUNET_TIME_UNIT_FOREVER_TS,
"EUR:5.0"),
+ TALER_TESTING_cmd_merchant_delete_webhook ("post-webhooks-pay-w1",
+ merchant_url,
+ "webhook-pay-1",
+ MHD_HTTP_NO_CONTENT),
TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-2"),
TALER_TESTING_cmd_end ()
};
-
struct TALER_TESTING_Command double_spending[] = {
TALER_TESTING_cmd_merchant_post_orders ("create-proposal-2",
+ cred.cfg,
merchant_url,
MHD_HTTP_OK,
"2",
@@ -721,6 +785,7 @@ run (void *cls,
"EUR:0",
MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_post_orders ("create-proposal-1r",
+ cred.cfg,
merchant_url,
MHD_HTTP_OK,
"1r",
@@ -823,6 +888,7 @@ run (void *cls,
/* Test /refund on a contract that was never paid. */
TALER_TESTING_cmd_merchant_post_orders ("create-proposal-not-to-be-paid",
+ cred.cfg,
merchant_url,
MHD_HTTP_OK,
"1-unpaid",
@@ -867,6 +933,7 @@ run (void *cls,
MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_post_orders (
"create-proposal-unincreased-refund",
+ cred.cfg,
merchant_url,
MHD_HTTP_OK,
"unincreased-proposal",
@@ -897,9 +964,13 @@ run (void *cls,
TALER_TESTING_cmd_merchant_post_instances ("instance-create-i1a",
merchant_url,
"i1a",
- PAYTO_I1,
- "EUR",
MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_account (
+ "instance-create-i1a-account",
+ merchant_url_i1a,
+ PAYTO_I1,
+ NULL, NULL,
+ MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_get_product ("get-nx-product-i1a-1",
merchant_url_i1a,
"nx-product",
@@ -987,183 +1058,6 @@ run (void *cls,
TALER_TESTING_cmd_end ()
};
- struct TALER_TESTING_Command tip[] = {
- TALER_TESTING_cmd_merchant_post_reserves ("create-reserve-tip-1",
- merchant_url,
- "EUR:20.04",
- EXCHANGE_URL,
- "x-taler-bank",
- MHD_HTTP_OK),
- TALER_TESTING_cmd_admin_add_incoming_with_ref ("create-reserve-tip-1-exch",
- "EUR:20.04",
- &bc.exchange_auth,
- payer_payto,
- "create-reserve-tip-1",
- MHD_HTTP_OK),
- /* We need to wait until the merchant re-tries fetching the
- reserve from the exchange. */
- cmd_exec_wirewatch ("wirewatch-3"),
- TALER_TESTING_cmd_sleep ("tip-sleep", 3),
- TALER_TESTING_cmd_tip_authorize ("authorize-tip-1",
- merchant_url,
- EXCHANGE_URL,
- MHD_HTTP_OK,
- "tip 1",
- "EUR:5.01"),
- TALER_TESTING_cmd_tip_authorize_from_reserve ("authorize-tip-2",
- merchant_url,
- EXCHANGE_URL,
- "create-reserve-tip-1-exch",
- MHD_HTTP_OK,
- "tip 2",
- "EUR:5.01"),
- TALER_TESTING_cmd_wallet_get_tip ("get-tip-1",
- merchant_url,
- "authorize-tip-1",
- MHD_HTTP_OK),
- TALER_TESTING_cmd_merchant_get_tip ("merchant-get-tip-1",
- merchant_url,
- "authorize-tip-1",
- MHD_HTTP_OK),
- TALER_TESTING_cmd_get_tips ("get-tips-1",
- merchant_url,
- MHD_HTTP_OK,
- "authorize-tip-2",
- "authorize-tip-1",
- NULL),
- TALER_TESTING_cmd_get_tips2 ("get-tips-1-asc",
- merchant_url,
- 0,
- 20,
- MHD_HTTP_OK,
- "authorize-tip-1",
- "authorize-tip-2",
- NULL),
- TALER_TESTING_cmd_get_tips2 ("get-tips-1-asc-offset",
- merchant_url,
- 1,
- 20,
- MHD_HTTP_OK,
- "authorize-tip-2",
- NULL),
- TALER_TESTING_cmd_merchant_get_reserves ("get-reserves-1",
- merchant_url,
- MHD_HTTP_OK,
- "create-reserve-tip-1-exch",
- NULL),
- TALER_TESTING_cmd_merchant_get_reserve ("get-reserve-1",
- merchant_url,
- MHD_HTTP_OK,
- "create-reserve-tip-1-exch"),
- TALER_TESTING_cmd_merchant_get_reserve_with_tips ("get-reserve-2",
- merchant_url,
- MHD_HTTP_OK,
- "create-reserve-tip-1-exch",
- "authorize-tip-1",
- "authorize-tip-2",
- NULL),
- TALER_TESTING_cmd_tip_pickup ("pickup-tip-1",
- merchant_url,
- MHD_HTTP_OK,
- "authorize-tip-1",
- pickup_amounts_1),
- TALER_TESTING_cmd_wallet_get_tip2 ("query-tip-2",
- merchant_url,
- "authorize-tip-1",
- "EUR:0.01",
- MHD_HTTP_OK),
- TALER_TESTING_cmd_tip_pickup ("pickup-tip-2",
- merchant_url,
- MHD_HTTP_OK,
- "authorize-tip-2",
- pickup_amounts_1),
-
- TALER_TESTING_cmd_tip_pickup_with_ec ("pickup-tip-3-too-much",
- merchant_url,
- MHD_HTTP_BAD_REQUEST,
- "authorize-tip-1",
- pickup_amounts_1,
- TALER_EC_MERCHANT_TIP_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING),
-
- TALER_TESTING_cmd_tip_pickup ("pickup-tip-4",
- merchant_url,
- MHD_HTTP_OK,
- "authorize-tip-1",
- pickup_amounts_2),
- TALER_TESTING_cmd_merchant_get_tip_with_pickups ("merchant-get-tip-2",
- merchant_url,
- "authorize-tip-1",
- MHD_HTTP_OK,
- "pickup-tip-1",
- "pickup-tip-4",
- NULL),
-
- /* This command tests the authorization of tip
- * against a reserve that does not exist. This is
- * implemented by passing a "tip instance" that
- * specifies a reserve key that was never used to
- * actually create a reserve. *///
- TALER_TESTING_cmd_merchant_post_reserves_fake ("create-reserve-tip-2-fake"),
- TALER_TESTING_cmd_tip_authorize_from_reserve_with_ec ("authorize-tip-null",
- merchant_url,
- EXCHANGE_URL,
- "create-reserve-tip-2-fake",
- MHD_HTTP_NOT_FOUND,
- "tip 3",
- "EUR:5.01",
- TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_NOT_FOUND),
-
- /* Test reserve with insufficient funds */
- TALER_TESTING_cmd_merchant_post_reserves ("create-reserve-tip-2",
- merchant_url,
- "EUR:1.04",
- EXCHANGE_URL,
- "x-taler-bank",
- MHD_HTTP_OK),
- TALER_TESTING_cmd_admin_add_incoming_with_ref ("create-reserve-tip-2-exch",
- "EUR:1.04",
- &bc.exchange_auth,
- payer_payto,
- "create-reserve-tip-2",
- MHD_HTTP_OK),
- cmd_exec_wirewatch ("wirewatch-4"),
- TALER_TESTING_cmd_tip_authorize_from_reserve_with_ec (
- "authorize-tip-insufficient-funds",
- merchant_url,
- EXCHANGE_URL,
- "create-reserve-tip-2",
- MHD_HTTP_PRECONDITION_FAILED,
- "tip 4",
- "EUR:5.01",
- TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_INSUFFICIENT_FUNDS),
- TALER_TESTING_cmd_tip_authorize_fake ("fake-tip-authorization"),
- TALER_TESTING_cmd_tip_pickup_with_ec ("pickup-non-existent-id",
- merchant_url,
- MHD_HTTP_NOT_FOUND,
- "fake-tip-authorization",
- pickup_amounts_1,
- TALER_EC_MERCHANT_GENERIC_TIP_ID_UNKNOWN),
- TALER_TESTING_cmd_merchant_get_reserves ("get-reserves-2",
- merchant_url,
- MHD_HTTP_OK,
- "create-reserve-tip-1",
- "create-reserve-tip-2",
- NULL),
- TALER_TESTING_cmd_merchant_delete_reserve ("delete-reserve-tip-1",
- merchant_url,
- "create-reserve-tip-1",
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_purge_reserve ("delete-reserve-tip-2",
- merchant_url,
- "create-reserve-tip-1",
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_purge_reserve ("delete-reserve-tip-3",
- merchant_url,
- "create-reserve-tip-1",
- MHD_HTTP_NOT_FOUND),
- TALER_TESTING_cmd_end ()
- };
-
struct TALER_TESTING_Command pay_again[] = {
cmd_transfer_to_exchange ("create-reserve-20",
"EUR:20.04"),
@@ -1198,6 +1092,7 @@ run (void *cls,
"EUR:0",
MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_post_orders ("create-proposal-10",
+ cred.cfg,
merchant_url,
MHD_HTTP_OK,
"10",
@@ -1262,6 +1157,7 @@ run (void *cls,
"EUR:0",
MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_post_orders ("create-proposal-11",
+ cred.cfg,
merchant_url,
MHD_HTTP_OK,
"11",
@@ -1270,7 +1166,7 @@ run (void *cls,
"EUR:10.0"),
TALER_TESTING_cmd_merchant_pay_order ("pay-fail-partial-double-11-good",
merchant_url,
- MHD_HTTP_NOT_ACCEPTABLE,
+ MHD_HTTP_BAD_REQUEST,
"create-proposal-11",
"withdraw-coin-11a",
"EUR:5",
@@ -1294,6 +1190,38 @@ run (void *cls,
};
struct TALER_TESTING_Command templates[] = {
+ cmd_transfer_to_exchange ("create-reserve-20x",
+ "EUR:20.04"),
+ cmd_exec_wirewatch ("wirewatch-20x"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-20x",
+ "EUR:20.04",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-20x"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-xa",
+ "create-reserve-20x",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-xb",
+ "create-reserve-20x",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-xc",
+ "create-reserve-20x",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-xd",
+ "create-reserve-20x",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_status ("withdraw-status-20x",
+ "create-reserve-20x",
+ "EUR:0",
+ MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_get_templates ("get-templates-empty",
merchant_url,
MHD_HTTP_OK,
@@ -1333,11 +1261,11 @@ run (void *cls,
merchant_url,
"template-2",
"another template",
- "data:image/jpeg;base64,RAWDATA",
+ NULL,
GNUNET_JSON_PACK (
- GNUNET_JSON_pack_uint64 ("minimum_age", 0),
- GNUNET_JSON_pack_time_rel ("pay_duration",
- GNUNET_TIME_UNIT_MINUTES)),
+ GNUNET_JSON_pack_uint64 ("minimum_age", 0),
+ GNUNET_JSON_pack_time_rel ("pay_duration",
+ GNUNET_TIME_UNIT_MINUTES)),
MHD_HTTP_NO_CONTENT),
TALER_TESTING_cmd_merchant_get_template ("get-template-t2",
merchant_url,
@@ -1349,16 +1277,25 @@ run (void *cls,
"template-nx",
MHD_HTTP_NOT_FOUND,
NULL),
+ TALER_TESTING_cmd_merchant_post_otp_devices (
+ "post-otp-device",
+ merchant_url,
+ "otp-dev",
+ "my OTP device",
+ "FEE4P2J",
+ TALER_MCA_WITH_PRICE,
+ 0,
+ MHD_HTTP_NO_CONTENT),
TALER_TESTING_cmd_merchant_patch_template (
"patch-templates-t3-nx",
merchant_url,
"template-3",
"updated template",
- "data:image/jpeg;base64,RAWDATA",
+ "otp-dev",
GNUNET_JSON_PACK (
- GNUNET_JSON_pack_uint64 ("minimum_age", 0),
- GNUNET_JSON_pack_time_rel ("pay_duration",
- GNUNET_TIME_UNIT_MINUTES)),
+ GNUNET_JSON_pack_uint64 ("minimum_age", 0),
+ GNUNET_JSON_pack_time_rel ("pay_duration",
+ GNUNET_TIME_UNIT_MINUTES)),
MHD_HTTP_NOT_FOUND),
TALER_TESTING_cmd_merchant_post_templates2 (
"post-templates-t3-amount",
@@ -1367,47 +1304,75 @@ run (void *cls,
"a different template with an amount",
NULL,
GNUNET_JSON_PACK (
- GNUNET_JSON_pack_uint64 ("minimum_age", 0),
- GNUNET_JSON_pack_time_rel ("pay_duration",
- GNUNET_TIME_UNIT_MINUTES),
- GNUNET_JSON_pack_string ("amount",
- "EUR:4")),
+ GNUNET_JSON_pack_uint64 ("minimum_age", 0),
+ GNUNET_JSON_pack_time_rel ("pay_duration",
+ GNUNET_TIME_UNIT_MINUTES),
+ GNUNET_JSON_pack_string ("amount",
+ "EUR:4")),
MHD_HTTP_NO_CONTENT),
TALER_TESTING_cmd_merchant_post_using_templates (
"using-templates-t1",
"post-templates-t1",
+ NULL,
merchant_url,
+ "1",
"summary-1",
- "EUR:10",
+ "EUR:9.98",
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_post_using_templates (
"using-templates-t1-amount-missing",
"post-templates-t1",
+ NULL,
merchant_url,
+ "2",
"summary-1",
NULL,
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_merchant_post_using_templates (
"using-templates-t1-summary-missing",
"post-templates-t1",
+ NULL,
merchant_url,
+ "3",
NULL,
"EUR:10",
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_merchant_post_using_templates (
"using-templates-t1-amount-conflict",
"post-templates-t3-amount",
+ NULL,
merchant_url,
+ "4",
"summary-1",
"EUR:10",
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_merchant_post_using_templates (
"using-templates-t1-amount-duplicate",
"post-templates-t3-amount",
+ NULL,
merchant_url,
+ "4",
"summary-1",
"EUR:4",
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_merchant_pay_order ("pay-100",
+ merchant_url,
+ MHD_HTTP_OK,
+ "using-templates-t1",
+ "withdraw-coin-xa;withdraw-coin-xb",
+ "EUR:4.99",
+ "EUR:4.99",
+ NULL),
TALER_TESTING_cmd_merchant_delete_template ("get-templates-empty",
merchant_url,
"t1",
@@ -1419,10 +1384,56 @@ run (void *cls,
TALER_TESTING_cmd_merchant_post_using_templates (
"post-templates-t1-deleted",
"post-templates-t1",
+ NULL,
merchant_url,
+ "0",
"summary-1",
"EUR:5",
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
MHD_HTTP_NOT_FOUND),
+ TALER_TESTING_cmd_merchant_post_otp_devices (
+ "post-otp-device",
+ merchant_url,
+ "otp-dev-2",
+ "my OTP device",
+ "secret",
+ TALER_MCA_WITH_PRICE,
+ 0,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_templates2 (
+ "post-templates-with-pos-key",
+ merchant_url,
+ "template-key",
+ "a different template with POS KEY",
+ "otp-dev-2",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("minimum_age", 0),
+ GNUNET_JSON_pack_time_rel ("pay_duration",
+ GNUNET_TIME_UNIT_MINUTES)),
+ MHD_HTTP_NO_CONTENT),
+
+ TALER_TESTING_cmd_merchant_post_using_templates (
+ "using-templates-pos-key",
+ "post-templates-with-pos-key",
+ "post-otp-device",
+ merchant_url,
+ "1",
+ "summary-1-pos",
+ "EUR:9.98",
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_pay_order ("pay-with-pos",
+ merchant_url,
+ MHD_HTTP_OK,
+ "using-templates-pos-key",
+ "withdraw-coin-xc;withdraw-coin-xd",
+ "EUR:4.99",
+ "EUR:4.99",
+ NULL),
+
+
TALER_TESTING_cmd_end ()
};
@@ -1465,7 +1476,7 @@ run (void *cls,
merchant_url,
"webhook-2",
"Refund2",
- "https://example.com",
+ "http://localhost:38188/",
"POST",
"Authorization:WHWOXZXPLL",
"Amount",
@@ -1499,108 +1510,215 @@ run (void *cls,
MHD_HTTP_NO_CONTENT),
TALER_TESTING_cmd_end ()
};
+ struct TALER_TESTING_Command repurchase[] = {
+ cmd_transfer_to_exchange (
+ "create-reserve-30x",
+ "EUR:30.06"),
+ cmd_exec_wirewatch (
+ "wirewatch-30x"),
+ TALER_TESTING_cmd_check_bank_admin_transfer (
+ "check_bank_transfer-30x",
+ "EUR:30.06",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-30x"),
+ TALER_TESTING_cmd_withdraw_amount (
+ "withdraw-coin-rep",
+ "create-reserve-30x",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_orders3 (
+ "post-order-repurchase-original",
+ cred.cfg,
+ merchant_url,
+ MHD_HTTP_OK,
+ "repurchase-original",
+ GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MINUTES),
+ GNUNET_TIME_UNIT_FOREVER_TS,
+ "https://fulfillment.example.com/",
+ "EUR:1.0"),
+ TALER_TESTING_cmd_merchant_pay_order (
+ "repurchase-pay-first",
+ merchant_url,
+ MHD_HTTP_OK,
+ "post-order-repurchase-original",
+ "withdraw-coin-rep",
+ "EUR:1.00",
+ "EUR:0.99",
+ "repurchase-session"),
+ TALER_TESTING_cmd_wallet_get_order (
+ "repurchase-wallet-check-primary-order",
+ merchant_url,
+ "post-order-repurchase-original",
+ true,
+ false,
+ false,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_get_order3 (
+ "repurchase-check-primary-payment",
+ merchant_url,
+ "post-order-repurchase-original",
+ TALER_MERCHANT_OSC_PAID,
+ "repurchase-session",
+ NULL,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_get_order3 (
+ "repurchase-check-primary-payment-bad-binding",
+ merchant_url,
+ "post-order-repurchase-original",
+ TALER_MERCHANT_OSC_CLAIMED, /* someone else has it! */
+ "wrong-session",
+ NULL,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_orders3 (
+ "post-order-repurchase-secondary",
+ cred.cfg,
+ merchant_url,
+ MHD_HTTP_OK,
+ "repurchase-secondary",
+ GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MINUTES),
+ GNUNET_TIME_UNIT_FOREVER_TS,
+ "https://fulfillment.example.com/",
+ "EUR:1.0"),
+ TALER_TESTING_cmd_merchant_get_order3 (
+ "repurchase-check-secondary-payment",
+ merchant_url,
+ "post-order-repurchase-secondary",
+ TALER_MERCHANT_OSC_UNPAID,
+ "repurchase-session",
+ NULL,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_get_order3 (
+ "repurchase-check-secondary-payment",
+ merchant_url,
+ "post-order-repurchase-secondary",
+ TALER_MERCHANT_OSC_UNPAID,
+ "repurchase-session",
+ "post-order-repurchase-original",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_wallet_get_order2 (
+ "repurchase-wallet-check-order-secondary",
+ merchant_url,
+ "post-order-repurchase-secondary",
+ "repurchase-session",
+ false,
+ false,
+ false,
+ "post-order-repurchase-original",
+ MHD_HTTP_PAYMENT_REQUIRED),
+ TALER_TESTING_cmd_wallet_get_order2 (
+ "repurchase-wallet-check-order-secondary-bad-session",
+ merchant_url,
+ "post-order-repurchase-secondary",
+ "wrong-session",
+ false,
+ false,
+ false,
+ NULL,
+ MHD_HTTP_PAYMENT_REQUIRED),
+ TALER_TESTING_cmd_merchant_order_refund (
+ "refund-repurchased",
+ merchant_url,
+ "refund repurchase",
+ "repurchase-original",
+ "EUR:1.0",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_wallet_get_order2 (
+ "repurchase-wallet-check-primary-order-refunded-no-session",
+ merchant_url,
+ "post-order-repurchase-original",
+ NULL,
+ true,
+ true,
+ true,
+ "post-order-repurchase-original",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_wallet_get_order2 (
+ "repurchase-wallet-check-primary-order-refunded",
+ merchant_url,
+ "post-order-repurchase-original",
+ "repurchase-session",
+ true,
+ true,
+ true,
+ "post-order-repurchase-original",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_get_order3 (
+ "repurchase-check-refunded",
+ merchant_url,
+ "post-order-repurchase-secondary",
+ TALER_MERCHANT_OSC_CLAIMED,
+ "repurchase-session",
+ NULL,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
struct TALER_TESTING_Command commands[] = {
/* general setup */
- TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_wire_add ("add-wire-account",
- "payto://x-taler-bank/localhost/2?receiver-name=2",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
- config_file),
- TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
- config_file,
- "EUR:0.01",
- "EUR:0.01"),
- TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
- 1),
- TALER_TESTING_cmd_batch ("orders-id",
- get_private_order_id),
- TALER_TESTING_cmd_config ("config",
- merchant_url,
- MHD_HTTP_OK),
- TALER_TESTING_cmd_merchant_get_instances ("instances-empty",
- merchant_url,
- MHD_HTTP_OK,
- NULL),
- TALER_TESTING_cmd_merchant_post_instances ("instance-create-default-setup",
- merchant_url,
- "default",
- PAYTO_I1,
- "EUR",
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_post_instances ("instance-create-i1",
- merchant_url,
- "i1",
- PAYTO_I1,
- "EUR",
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_get_instances ("instances-get-i1",
- merchant_url,
- MHD_HTTP_OK,
- "instance-create-i1",
- "instance-create-default-setup",
- NULL),
- TALER_TESTING_cmd_merchant_get_instance ("instances-get-i1",
- merchant_url,
- "i1",
- MHD_HTTP_OK,
- "instance-create-i1"),
- TALER_TESTING_cmd_merchant_patch_instance ("instance-patch-i1-bad-currency",
- merchant_url,
- "i1",
- 2,
- payto_uris,
- "bob-the-merchant",
- json_pack ("{s:s}",
- "street",
- "bobstreet"),
- json_pack ("{s:s}",
- "street",
- "bobjuryst"),
- "USD:0.1",
- 4,
- "USD:0.5",
- GNUNET_TIME_UNIT_MINUTES,
- GNUNET_TIME_UNIT_MINUTES,
- MHD_HTTP_BAD_REQUEST),
- TALER_TESTING_cmd_merchant_patch_instance ("instance-patch-i1",
- merchant_url,
- "i1",
- 2,
- payto_uris,
- "bob-the-merchant",
- json_pack ("{s:s}",
- "street",
- "bobstreet"),
- json_pack ("{s:s}",
- "street",
- "bobjuryst"),
- "EUR:0.1",
- 4,
- "EUR:0.5",
- GNUNET_TIME_UNIT_MINUTES,
- GNUNET_TIME_UNIT_MINUTES,
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_get_instance2 ("instances-get-i1-2",
- merchant_url,
- "i1",
- MHD_HTTP_OK,
- "instance-patch-i1",
- payto_uris,
- 2,
- NULL,
- 0),
+ TALER_TESTING_cmd_run_fakebank (
+ "run-fakebank",
+ cred.cfg,
+ "exchange-account-exchange"),
+ TALER_TESTING_cmd_system_start (
+ "start-taler",
+ config_file,
+ "-ema",
+ "-u", "exchange-account-exchange",
+ NULL),
+ TALER_TESTING_cmd_get_exchange (
+ "get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_batch (
+ "orders-id",
+ get_private_order_id),
+ TALER_TESTING_cmd_config (
+ "config",
+ merchant_url,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_get_instances (
+ "instances-empty",
+ merchant_url,
+ MHD_HTTP_OK,
+ NULL),
+ TALER_TESTING_cmd_merchant_post_instances (
+ "instance-create-default-setup",
+ merchant_url,
+ "default",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_account (
+ "instance-create-default-account",
+ merchant_url,
+ PAYTO_I1,
+ NULL, NULL,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_instances (
+ "instance-create-i1",
+ merchant_url,
+ "i1",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_get_instances (
+ "instances-get-i1",
+ merchant_url,
+ MHD_HTTP_OK,
+ "instance-create-i1",
+ "instance-create-default-setup",
+ NULL),
+ TALER_TESTING_cmd_merchant_get_instance (
+ "instances-get-i1",
+ merchant_url,
+ "i1",
+ MHD_HTTP_OK,
+ "instance-create-i1"),
TALER_TESTING_cmd_merchant_patch_instance (
- "instance-patch-i1-inactivate-account",
+ "instance-patch-i1",
merchant_url,
"i1",
- 1,
- payto_uris,
"bob-the-merchant",
json_pack ("{s:s}",
"street",
@@ -1608,235 +1726,240 @@ run (void *cls,
json_pack ("{s:s}",
"street",
"bobjuryst"),
- "EUR:0.1",
- 4,
- "EUR:0.5",
+ true,
GNUNET_TIME_UNIT_MINUTES,
GNUNET_TIME_UNIT_MINUTES,
MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_get_instance2 ("instances-get-i1-3",
- merchant_url,
- "i1",
- MHD_HTTP_OK,
- "instance-patch-i1-inactivate-account",
- payto_uris,
- 1,
- &payto_uris[1],
- 1),
- TALER_TESTING_cmd_merchant_get_instance ("instances-get-i2-nx",
- merchant_url,
- "i2",
- MHD_HTTP_NOT_FOUND,
- NULL),
- TALER_TESTING_cmd_merchant_post_instances ("instance-create-bad-currency",
- merchant_url,
- "i2",
- PAYTO_I1,
- "USD",
- MHD_HTTP_BAD_REQUEST),
- TALER_TESTING_cmd_merchant_post_instances2 ("instance-create-ACL",
- merchant_url,
- "i-acl",
- 0, NULL,
- "controlled instance",
- json_pack ("{s:s}", "city",
- "shopcity"),
- json_pack ("{s:s}", "city",
- "lawyercity"),
- "EUR:0.1",
- 42,
- "EUR:0.2",
- GNUNET_TIME_UNIT_MINUTES,
- GNUNET_TIME_UNIT_MINUTES,
- // FIXME: change this back once
- // we have a update auth test CMD
- // RFC_8959_PREFIX "EXAMPLE",
- NULL,
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_patch_instance ("instance-patch-ACL",
- merchant_url,
- "i-acl",
- 1,
- payto_uris,
- "controlled instance",
- json_pack ("{s:s}",
- "street",
- "bobstreet"),
- json_pack ("{s:s}",
- "street",
- "bobjuryst"),
- "EUR:0.1",
- 4,
- "EUR:0.5",
- GNUNET_TIME_UNIT_MINUTES,
- GNUNET_TIME_UNIT_MINUTES,
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_post_instances ("instance-create-i2",
- merchant_url,
- "i2",
- PAYTO_I1,
- "EUR",
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_post_instances ("instance-create-i2-idem",
- merchant_url,
- "i2",
- PAYTO_I1,
- "EUR",
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_post_instances ("instance-create-i2-non-idem",
- merchant_url,
- "i2",
- "payto://other-method/?receiver-name=X",
- "EUR",
- MHD_HTTP_CONFLICT),
- TALER_TESTING_cmd_merchant_delete_instance ("instance-delete-i2",
- merchant_url,
- "i2",
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_get_instance ("instances-get-i2-post-deletion",
- merchant_url,
- "i2",
- MHD_HTTP_NOT_FOUND,
- NULL),
- TALER_TESTING_cmd_merchant_purge_instance ("instance-delete-then-purge-i2",
- merchant_url,
- "i2",
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_purge_instance ("instance-purge-i1",
- merchant_url,
- "i1",
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_delete_instance ("instance-purge-then-delete-i1",
- merchant_url,
- "i1",
- MHD_HTTP_NOT_FOUND),
- TALER_TESTING_cmd_merchant_purge_instance ("instance-purge-i-acl-middle",
- merchant_url,
- "i-acl",
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_purge_instance ("instance-purge-default-middle",
- merchant_url,
- "default",
- MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_get_instance (
+ "instances-get-i1-2",
+ merchant_url,
+ "i1",
+ MHD_HTTP_OK,
+ "instance-patch-i1"),
+ TALER_TESTING_cmd_merchant_get_instance (
+ "instances-get-i2-nx",
+ merchant_url,
+ "i2",
+ MHD_HTTP_NOT_FOUND,
+ NULL),
+ TALER_TESTING_cmd_merchant_post_instances2 (
+ "instance-create-ACL",
+ merchant_url,
+ "i-acl",
+ "controlled instance",
+ json_pack ("{s:s}", "city",
+ "shopcity"),
+ json_pack ("{s:s}", "city",
+ "lawyercity"),
+ true,
+ GNUNET_TIME_UNIT_MINUTES,
+ GNUNET_TIME_UNIT_MINUTES,
+ // FIXME: change this back once
+ // we have a update auth test CMD
+ // RFC_8959_PREFIX "EXAMPLE",
+ NULL,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_patch_instance (
+ "instance-patch-ACL",
+ merchant_url,
+ "i-acl",
+ "controlled instance",
+ json_pack ("{s:s}",
+ "street",
+ "bobstreet"),
+ json_pack ("{s:s}",
+ "street",
+ "bobjuryst"),
+ true,
+ GNUNET_TIME_UNIT_MINUTES,
+ GNUNET_TIME_UNIT_MINUTES,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_instances (
+ "instance-create-i2",
+ merchant_url,
+ "i2",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_instances (
+ "instance-create-i2-idem",
+ merchant_url,
+ "i2",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_delete_instance (
+ "instance-delete-i2",
+ merchant_url,
+ "i2",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_get_instance (
+ "instances-get-i2-post-deletion",
+ merchant_url,
+ "i2",
+ MHD_HTTP_NOT_FOUND,
+ NULL),
+ TALER_TESTING_cmd_merchant_purge_instance (
+ "instance-delete-then-purge-i2",
+ merchant_url,
+ "i2",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_purge_instance (
+ "instance-purge-i1",
+ merchant_url,
+ "i1",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_delete_instance (
+ "instance-purge-then-delete-i1",
+ merchant_url,
+ "i1",
+ MHD_HTTP_NOT_FOUND),
+ TALER_TESTING_cmd_merchant_purge_instance (
+ "instance-purge-i-acl-middle",
+ merchant_url,
+ "i-acl",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_purge_instance (
+ "instance-purge-default-middle",
+ merchant_url,
+ "default",
+ MHD_HTTP_NO_CONTENT),
TALER_TESTING_cmd_merchant_post_instances (
"instance-create-default-after-purge",
merchant_url,
"default",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_account (
+ "instance-create-default-account-after-purge",
+ merchant_url,
PAYTO_I1,
- "EUR",
+ NULL, NULL,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_get_products (
+ "get-products-empty",
+ merchant_url,
+ MHD_HTTP_OK,
+ NULL),
+ TALER_TESTING_cmd_merchant_post_products (
+ "post-products-p1",
+ merchant_url,
+ "product-1",
+ "a product",
+ "EUR:1",
MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_get_products ("get-products-empty",
- merchant_url,
- MHD_HTTP_OK,
- NULL),
- TALER_TESTING_cmd_merchant_post_products ("post-products-p1",
- merchant_url,
- "product-1",
- "a product",
- "EUR:1",
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_post_products ("post-products-p1-idem",
- merchant_url,
- "product-1",
- "a product",
- "EUR:1",
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_post_products ("post-products-p1-non-idem",
- merchant_url,
- "product-1",
- "a different product",
- "EUR:1",
- MHD_HTTP_CONFLICT),
- TALER_TESTING_cmd_merchant_get_products ("get-products-p1",
- merchant_url,
- MHD_HTTP_OK,
- "post-products-p1",
- NULL),
- TALER_TESTING_cmd_merchant_get_product ("get-product-p1",
- merchant_url,
- "product-1",
- MHD_HTTP_OK,
- "post-products-p1"),
- TALER_TESTING_cmd_merchant_post_products ("post-products-p2",
- merchant_url,
- "product-2",
- "a product",
- "EUR:1",
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_patch_product ("patch-products-p2",
- merchant_url,
- "product-2",
- "another product",
- json_pack ("{s:s}", "en", "text"),
- "kg",
- "EUR:1",
- "data:image/jpeg;base64,RAWDATA",
- json_array (),
- 40,
- 0,
- json_pack ("{s:s}",
- "street",
- "pstreet"),
- GNUNET_TIME_relative_to_timestamp (
- GNUNET_TIME_UNIT_MINUTES),
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_get_product ("get-product-p2",
- merchant_url,
- "product-2",
- MHD_HTTP_OK,
- "patch-products-p2"),
- TALER_TESTING_cmd_merchant_get_product ("get-product-nx",
- merchant_url,
- "product-nx",
- MHD_HTTP_NOT_FOUND,
- NULL),
- TALER_TESTING_cmd_merchant_patch_product ("patch-products-p3-nx",
- merchant_url,
- "product-3",
- "nx updated product",
- json_pack ("{s:s}", "en", "text"),
- "kg",
- "EUR:1",
- "data:image/jpeg;base64,RAWDATA",
- json_array (),
- 40,
- 0,
- json_pack ("{s:s}",
- "street",
- "pstreet"),
- GNUNET_TIME_relative_to_timestamp (
- GNUNET_TIME_UNIT_MINUTES),
- MHD_HTTP_NOT_FOUND),
- TALER_TESTING_cmd_merchant_delete_product ("get-products-empty",
- merchant_url,
- "p1",
- MHD_HTTP_NOT_FOUND),
- TALER_TESTING_cmd_merchant_delete_product ("get-products-empty",
- merchant_url,
- "product-1",
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_lock_product ("lock-product-p2",
- merchant_url,
- "product-2",
- GNUNET_TIME_UNIT_MINUTES,
- 2,
- MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_lock_product ("lock-product-nx",
- merchant_url,
- "product-nx",
- GNUNET_TIME_UNIT_MINUTES,
- 2,
- MHD_HTTP_NOT_FOUND),
- TALER_TESTING_cmd_merchant_lock_product ("lock-product-too-much",
- merchant_url,
- "product-2",
- GNUNET_TIME_UNIT_MINUTES,
- 39,
- MHD_HTTP_GONE),
- TALER_TESTING_cmd_merchant_delete_product ("delete-product-locked",
- merchant_url,
- "product-2",
- MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_merchant_post_products (
+ "post-products-p1-idem",
+ merchant_url,
+ "product-1",
+ "a product",
+ "EUR:1",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_products (
+ "post-products-p1-non-idem",
+ merchant_url,
+ "product-1",
+ "a different product",
+ "EUR:1",
+ MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_merchant_get_products (
+ "get-products-p1",
+ merchant_url,
+ MHD_HTTP_OK,
+ "post-products-p1",
+ NULL),
+ TALER_TESTING_cmd_merchant_get_product (
+ "get-product-p1",
+ merchant_url,
+ "product-1",
+ MHD_HTTP_OK,
+ "post-products-p1"),
+ TALER_TESTING_cmd_merchant_post_products (
+ "post-products-p2",
+ merchant_url,
+ "product-2",
+ "a product",
+ "EUR:1",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_patch_product (
+ "patch-products-p2",
+ merchant_url,
+ "product-2",
+ "another product",
+ json_pack ("{s:s}", "en", "text"),
+ "kg",
+ "EUR:1",
+ "data:image/jpeg;base64,RAWDATA",
+ json_array (),
+ 40,
+ 0,
+ json_pack ("{s:s}",
+ "street",
+ "pstreet"),
+ GNUNET_TIME_relative_to_timestamp (
+ GNUNET_TIME_UNIT_MINUTES),
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_get_product (
+ "get-product-p2",
+ merchant_url,
+ "product-2",
+ MHD_HTTP_OK,
+ "patch-products-p2"),
+ TALER_TESTING_cmd_merchant_get_product (
+ "get-product-nx",
+ merchant_url,
+ "product-nx",
+ MHD_HTTP_NOT_FOUND,
+ NULL),
+ TALER_TESTING_cmd_merchant_patch_product (
+ "patch-products-p3-nx",
+ merchant_url,
+ "product-3",
+ "nx updated product",
+ json_pack ("{s:s}", "en", "text"),
+ "kg",
+ "EUR:1",
+ "data:image/jpeg;base64,RAWDATA",
+ json_array (),
+ 40,
+ 0,
+ json_pack ("{s:s}",
+ "street",
+ "pstreet"),
+ GNUNET_TIME_relative_to_timestamp (
+ GNUNET_TIME_UNIT_MINUTES),
+ MHD_HTTP_NOT_FOUND),
+ TALER_TESTING_cmd_merchant_delete_product (
+ "get-products-empty",
+ merchant_url,
+ "p1",
+ MHD_HTTP_NOT_FOUND),
+ TALER_TESTING_cmd_merchant_delete_product (
+ "get-products-empty",
+ merchant_url,
+ "product-1",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_lock_product (
+ "lock-product-p2",
+ merchant_url,
+ "product-2",
+ GNUNET_TIME_UNIT_MINUTES,
+ 2,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_lock_product (
+ "lock-product-nx",
+ merchant_url,
+ "product-nx",
+ GNUNET_TIME_UNIT_MINUTES,
+ 2,
+ MHD_HTTP_NOT_FOUND),
+ TALER_TESTING_cmd_merchant_lock_product (
+ "lock-product-too-much",
+ merchant_url,
+ "product-2",
+ GNUNET_TIME_UNIT_MINUTES,
+ 39,
+ MHD_HTTP_GONE),
+ TALER_TESTING_cmd_merchant_delete_product (
+ "delete-product-locked",
+ merchant_url,
+ "product-2",
+ MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_batch ("pay",
pay),
TALER_TESTING_cmd_batch ("double-spending",
@@ -1847,23 +1970,22 @@ run (void *cls,
pay_abort),
TALER_TESTING_cmd_batch ("refund",
refund),
- TALER_TESTING_cmd_batch ("tip",
- tip),
- TALER_TESTING_cmd_batch ("auth",
- auth),
TALER_TESTING_cmd_batch ("templates",
templates),
TALER_TESTING_cmd_batch ("webhooks",
webhooks),
+ TALER_TESTING_cmd_batch ("auth",
+ auth),
+ TALER_TESTING_cmd_batch ("repurchase",
+ repurchase),
/**
* End the suite.
*/
TALER_TESTING_cmd_end ()
};
- TALER_TESTING_run_with_fakebank (is,
- commands,
- bc.exchange_auth.wire_gateway_url);
+ TALER_TESTING_run (is,
+ commands);
}
@@ -1871,75 +1993,37 @@ int
main (int argc,
char *const *argv)
{
- char *cipher;
- enum GNUNET_GenericReturnValue ret;
-
- /* These environment variables get in the way... */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup (argv[0],
- "INFO",
- NULL);
- cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
- GNUNET_assert (NULL != cipher);
- GNUNET_asprintf (&config_file,
- "test_merchant_api-%s.conf",
- cipher);
- GNUNET_free (cipher);
- if (GNUNET_OK !=
- TALER_TESTING_prepare_fakebank (config_file,
- "exchange-account-exchange",
- &bc))
- return 77;
+ {
+ char *cipher;
+ cipher = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
+ GNUNET_assert (NULL != cipher);
+ GNUNET_asprintf (&config_file,
+ "test_merchant_api-%s.conf",
+ cipher);
+ GNUNET_free (cipher);
+ }
payer_payto =
- ("payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME "?receiver-name="
- USER_ACCOUNT_NAME);
+ "payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME "?receiver-name="
+ USER_ACCOUNT_NAME;
exchange_payto =
- ("payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME "?receiver-name="
- EXCHANGE_ACCOUNT_NAME);
+ "payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME "?receiver-name="
+ EXCHANGE_ACCOUNT_NAME;
merchant_payto =
- ("payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME "?receiver-name="
- MERCHANT_ACCOUNT_NAME);
-
- if (NULL ==
- (merchant_url = TALER_TESTING_prepare_merchant (config_file)))
- return 77;
+ "payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME "?receiver-name="
+ MERCHANT_ACCOUNT_NAME;
+ merchant_url = "http://localhost:8080/";
GNUNET_asprintf (&merchant_url_i1a,
"%sinstances/i1a/",
merchant_url);
- TALER_TESTING_cleanup_files (config_file);
- switch (TALER_TESTING_prepare_exchange (config_file,
- GNUNET_YES,
- &ec))
- {
- case GNUNET_SYSERR:
- GNUNET_break (0);
- return 1;
- case GNUNET_NO:
- return 77;
- case GNUNET_OK:
- if (NULL == (merchantd =
- TALER_TESTING_run_merchant (config_file,
- merchant_url)))
- return 1;
-
- ret = TALER_TESTING_setup_with_exchange (&run,
- NULL,
- config_file);
-
- GNUNET_OS_process_kill (merchantd, SIGTERM);
- GNUNET_OS_process_wait (merchantd);
- GNUNET_OS_process_destroy (merchantd);
- GNUNET_free (merchant_url);
- if (GNUNET_OK != ret)
- return 1;
- break;
- default:
- GNUNET_break (0);
- return 1;
- }
- return 0;
+ return TALER_TESTING_main (argv,
+ "INFO",
+ config_file,
+ "exchange-account-exchange",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
}
diff --git a/src/testing/test_merchant_api.conf b/src/testing/test_merchant_api.conf
new file mode 100644
index 00000000..59da281f
--- /dev/null
+++ b/src/testing/test_merchant_api.conf
@@ -0,0 +1,73 @@
+# This file is in the public domain.
+#
+[PATHS]
+TALER_TEST_HOME = test_merchant_api_home/
+
+[taler]
+CURRENCY = EUR
+CURRENCY_ROUND_UNIT = EUR:0.01
+
+[merchant-exchange-kudos]
+DISABLED = YES
+
+[taler-helper-crypto-rsa]
+LOOKAHEAD_SIGN = 10 days
+
+[taler-helper-crypto-eddsa]
+LOOKAHEAD_SIGN = 24 days
+DURATION = 14 days
+
+[bank]
+HTTP_PORT = 8082
+
+[libeufin-bank]
+CURRENCY = EUR
+WIRE_TYPE = iban
+IBAN_PAYTO_BIC = SANDBOXX
+DEFAULT_CUSTOMER_DEBT_LIMIT = EUR:200
+DEFAULT_ADMIN_DEBT_LIMIT = EUR:2000
+REGISTRATION_BONUS_ENABLED = yes
+REGISTRATION_BONUS = EUR:100
+SUGGESTED_WITHDRAWAL_EXCHANGE = http://localhost:8081/
+SERVE = tcp
+PORT = 8082
+
+[merchant]
+PORT = 8080
+SERVE = tcp
+DB = postgres
+
+[merchantdb-postgres]
+CONFIG = postgres:///talercheck
+SQL_DIR = $DATADIR/sql/merchant/
+
+[merchant-exchange-test]
+MASTER_KEY = NKX42KSCQHDQK7CF1PC6X9DMQPXW6KHXKGD3DPQJMP32FKXSWYK0
+EXCHANGE_BASE_URL = http://localhost:8081/
+CURRENCY = EUR
+
+[exchange]
+AML_THRESHOLD = EUR:1000000
+PORT = 8081
+MASTER_PUBLIC_KEY = NKX42KSCQHDQK7CF1PC6X9DMQPXW6KHXKGD3DPQJMP32FKXSWYK0
+BASE_URL = "http://localhost:8081/"
+STEFAN_ABS = "EUR:5"
+
+[exchangedb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[auditordb-postgres]
+CONFIG = postgres:///talercheck
+
+[exchange-account-exchange]
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-exchange]
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+WIRE_GATEWAY_AUTH_METHOD = NONE
+
+[admin-accountcredentials-exchange]
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+WIRE_GATEWAY_AUTH_METHOD = NONE
diff --git a/src/testing/test_merchant_api_home/.config/taler/exchange/account-2.json b/src/testing/test_merchant_api_home/.config/taler/exchange/account-2.json
deleted file mode 100644
index b84273e8..00000000
--- a/src/testing/test_merchant_api_home/.config/taler/exchange/account-2.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "payto_uri": "payto://x-taler-bank/localhost/2",
- "master_sig": "A296BYJDP182XS8GHB362ENK4JVPQYHJ34AMNC52YKSK78NH85NA3MWCPM7XAYXWSHK0X9SH46JN84T2AYGP0YAV0E9HQXAYZGABP0R"
-} \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/.local/share/taler/exchange-offline/master.priv b/src/testing/test_merchant_api_home/.local/share/taler/exchange-offline/master.priv
index c20942d6..efee2ec3 100644
--- a/src/testing/test_merchant_api_home/.local/share/taler/exchange-offline/master.priv
+++ b/src/testing/test_merchant_api_home/.local/share/taler/exchange-offline/master.priv
@@ -1 +1 @@
-åÊk;d³_Uû}£A.wÔ"!Gûçv_m "_ò \ No newline at end of file
+L ›ŠkCOµÒ ¦×±Š¡©ñ÷‘èèa–¿MÌ)G@_û \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/auditor/offline-keys/auditor.priv b/src/testing/test_merchant_api_home/taler/auditor/offline-keys/auditor.priv
new file mode 100644
index 00000000..b1d1e3e1
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/auditor/offline-keys/auditor.priv
@@ -0,0 +1 @@
+±Y½74N¤ÉÿŠÃßOíôÀV|1ƒ\uY0 G¿rïû \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1696437704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1696437704
new file mode 100644
index 00000000..ab4ab213
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1696437704
@@ -0,0 +1 @@
+æ’àÙž—ÒØÿÊ>e@cD¡ÁÑÓ´_!õŸ.ç" \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1697042204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1697042204
new file mode 100644
index 00000000..895251e7
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1697042204
@@ -0,0 +1 @@
+­ý57æ´´çTU^«Þ¹QØ&kœ '¾GRTóóJø
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1697646704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1697646704
new file mode 100644
index 00000000..bf62233c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1697646704
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1698251204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1698251204
new file mode 100644
index 00000000..0ad216c4
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1698251204
@@ -0,0 +1 @@
+Šß^úœt…9Æ2L¼j7ÂiS7ûD^¬Öt(¦ü|” \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1698855704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1698855704
new file mode 100644
index 00000000..31e290ff
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1698855704
@@ -0,0 +1 @@
+ßFl6;åZ›Ì¥hd>cÈ©+œq8ÞÏi÷²]¦Ú \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1699460204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1699460204
new file mode 100644
index 00000000..60a6a9d8
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1699460204
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1700064704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1700064704
new file mode 100644
index 00000000..bf23c192
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1700064704
@@ -0,0 +1 @@
+ç¸v¤kÅ=²h½L‚2ÑPoK~¡ôáÒˆi¯k—8X \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1700669204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1700669204
new file mode 100644
index 00000000..bb72cade
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1700669204
@@ -0,0 +1 @@
+ÔGáÈ¢Mw7#9¼\îr¢\#·)‹Çÿù†(ÃK \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1701273704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1701273704
new file mode 100644
index 00000000..fe214623
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1701273704
@@ -0,0 +1 @@
+AÙs¾¤úâ2‚I} yýÕÄeЪÙc0- K‰ÇŠ€ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1701878204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1701878204
new file mode 100644
index 00000000..dc26e8b6
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1701878204
@@ -0,0 +1 @@
+£~òeÇÂGöZÇ; l¾—~Â~K¶Lm%óæ¢ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1702482704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1702482704
new file mode 100644
index 00000000..45bf2bed
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1702482704
@@ -0,0 +1,2 @@
+Ž4|í7&<
+_¸Û}€ôúŸ)|5€ÜV·êY¿:ºôP \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1703087204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1703087204
new file mode 100644
index 00000000..da362cdf
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1703087204
@@ -0,0 +1 @@
+ÃA0‡>gc¸ÏÄ ÷ÞáFUÄÜ÷û¸hÛt¼.Q \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1703691704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1703691704
new file mode 100644
index 00000000..ba11ebec
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1703691704
@@ -0,0 +1,2 @@
+ʾ:C§%yÂA
+ˆ×íäJÛÜÕ5-»‘ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1704296204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1704296204
new file mode 100644
index 00000000..727eff6b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1704296204
@@ -0,0 +1 @@
+„þ¶{ƒâu­pÈS©­W/WÏ×ßÔúpžVØfKj \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1704900704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1704900704
new file mode 100644
index 00000000..25552b45
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1704900704
@@ -0,0 +1 @@
+žå–ìKxÆÊaA '¯D41¬²Bž4O[ß \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1705505204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1705505204
new file mode 100644
index 00000000..7242fa0d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1705505204
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1706109704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1706109704
new file mode 100644
index 00000000..34b500c6
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1706109704
@@ -0,0 +1 @@
+ë D-d…ÿ!Ò!†Ô[y’H$m7•ã‹ù€% \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1706714204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1706714204
new file mode 100644
index 00000000..3fe013b7
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1706714204
@@ -0,0 +1 @@
+©çbðá$àß—•ýºêq_ì~¶9¹o¶g`;½c÷é \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1707318704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1707318704
new file mode 100644
index 00000000..5053597b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1707318704
@@ -0,0 +1,2 @@
+³6Ù_̤êkÆ}©6"TiëCÍŽÁýf
+äø \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1707923204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1707923204
new file mode 100644
index 00000000..5cad2bc1
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1707923204
@@ -0,0 +1,2 @@
+
+* [ (•mabÄî¿M^+x®Í_¶ÇM×’Ò–ûD \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1708527704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1708527704
new file mode 100644
index 00000000..d1ad1680
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1708527704
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1709132204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1709132204
new file mode 100644
index 00000000..c79ca30e
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1709132204
@@ -0,0 +1 @@
+Yxþû/*™ÁÇ­õ†Md2ä-gžŠÀyÔØjÇÎÄN \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1709736704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1709736704
new file mode 100644
index 00000000..94a9482c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1709736704
@@ -0,0 +1 @@
+ê—Á×p±usFð¬¬mº mDrL’±Ü<áŒwð;?< \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1710341204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1710341204
new file mode 100644
index 00000000..0ec117aa
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1710341204
@@ -0,0 +1 @@
+3c&KÓ3ã„P<¯ãóöæYgüÉPeÒèR~ŽºuÃQ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1710945704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1710945704
new file mode 100644
index 00000000..0afb9a9b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1710945704
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1711550204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1711550204
new file mode 100644
index 00000000..2f382d5f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1711550204
@@ -0,0 +1,2 @@
+6€™8h²wb=u¹ˆ§/½ «
+ð@ò™P_·jDÞ6 \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1712154704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1712154704
new file mode 100644
index 00000000..bf6ed47c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1712154704
@@ -0,0 +1 @@
+ÁsôISnµ=k»šßF%*YÆTõ± k¶óia \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1712759204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1712759204
new file mode 100644
index 00000000..f0135fb3
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1712759204
@@ -0,0 +1 @@
+‚Ôž—øuBUß“(0\ÊÐÌG´®ÔgÖîž·š \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1713363704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1713363704
new file mode 100644
index 00000000..921f8b6f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1713363704
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1713968204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1713968204
new file mode 100644
index 00000000..ca4f82a0
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1713968204
@@ -0,0 +1 @@
+|¼XCFÕmž‘Ÿ’6Ç ÿc BúÇè*)?Wr \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1714572704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1714572704
new file mode 100644
index 00000000..a2a6d38e
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1714572704
@@ -0,0 +1 @@
+µµú´N¥TFpI„~WZÕ$n^–}×A–üзÞï² \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1715177204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1715177204
new file mode 100644
index 00000000..d6712222
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1715177204
@@ -0,0 +1 @@
+q„M/ӖΰÄQû¿Ð¹“èCë£ÔèÉhÿL \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1715781704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1715781704
new file mode 100644
index 00000000..70103e65
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1715781704
@@ -0,0 +1 @@
+×ã7Ö–ô ·^;P_ÝÀ’˜`)¨3: £þ¿QÝó \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1716386204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1716386204
new file mode 100644
index 00000000..9b43f31d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1716386204
@@ -0,0 +1 @@
+©0s23 Jßz¶Bp{„¿Q¼ %ç—Éœu ªÓ˜Ô \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1716990704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1716990704
new file mode 100644
index 00000000..27fae6fe
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1716990704
@@ -0,0 +1 @@
+ <Z~V8l(Ó3LJ7³®šøLàãËÚ"Ò \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1717595204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1717595204
new file mode 100644
index 00000000..72ae4efd
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_1/1717595204
@@ -0,0 +1 @@
+ ;~Îóö¯‘·”´ðû÷ÆD¡l*Ùû,»Ä³×Í \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1696437704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1696437704
new file mode 100644
index 00000000..256151dc
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1696437704
@@ -0,0 +1 @@
+9&gÄÙ‡PH<œJ®¶»Ža8DÎ#†ùN°BŠ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1697042204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1697042204
new file mode 100644
index 00000000..fdae36a5
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1697042204
@@ -0,0 +1 @@
+ò;sÚ›³ý[öœt¡›§µÚººÀfí'û‡kkC \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1697646704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1697646704
new file mode 100644
index 00000000..07c534ba
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1697646704
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1698251204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1698251204
new file mode 100644
index 00000000..d733cf16
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1698251204
@@ -0,0 +1 @@
+“Ê ;¦»Ð¤Tp2)Dý-hw§M¢:†Ž7ÅQ°M \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1698855704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1698855704
new file mode 100644
index 00000000..9c5bb02c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1698855704
@@ -0,0 +1 @@
+}묾ÕÕù@,"›ëÂãž LGÇtˆƒß²Ê¸Nê  \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1699460204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1699460204
new file mode 100644
index 00000000..98456040
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1699460204
@@ -0,0 +1 @@
+—z§ÜÌržɳ ÝúcFáP´R¦¶5k~_ö¶ø \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1700064704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1700064704
new file mode 100644
index 00000000..2fe3435c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1700064704
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1700669204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1700669204
new file mode 100644
index 00000000..6024c31a
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1700669204
@@ -0,0 +1 @@
+kS¿ƒMûä(ˆ!·Û÷²­ÚtΠ¢GÐL=œ: \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1701273704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1701273704
new file mode 100644
index 00000000..36dde967
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1701273704
@@ -0,0 +1 @@
+ããénÚÔq5¥—úyÆT°#F3þ‹ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1701878204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1701878204
new file mode 100644
index 00000000..c65618d6
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1701878204
@@ -0,0 +1 @@
+E|çÀ÷`5;‡ÖrœÃ³Îð냅]®`½¡ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1702482704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1702482704
new file mode 100644
index 00000000..4f50e1ec
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1702482704
@@ -0,0 +1,2 @@
+ê0jÉ‹ÃV­ÜZªüÑŠ´ëŽÛ«öø×
+×›ÑÛ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1703087204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1703087204
new file mode 100644
index 00000000..48a9b32f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1703087204
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1703691704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1703691704
new file mode 100644
index 00000000..95978f6c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1703691704
@@ -0,0 +1 @@
+©ó÷`+²zg‘¡0ÛÙbôkÔ?m ñˆi3uæ b¸Ÿ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1704296204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1704296204
new file mode 100644
index 00000000..f476a698
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1704296204
@@ -0,0 +1 @@
+Y>Å¡Æ}ο¥ìyKEv™02äüë2PZ©ÕͲ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1704900704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1704900704
new file mode 100644
index 00000000..01c429af
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1704900704
@@ -0,0 +1 @@
+§IERôÃ…pû!"5—ô”—™IÆ à¢Þp(C×À~ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1705505204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1705505204
new file mode 100644
index 00000000..b8c127a6
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1705505204
@@ -0,0 +1 @@
+H‹ìÏfÄY_Éé<Åò…iiýi]«%⩨Q ¯.«|ã \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1706109704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1706109704
new file mode 100644
index 00000000..e8d10e5a
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1706109704
@@ -0,0 +1,3 @@
+ÚÇ9B1ŸZ
+_ºrpn”‹ë2,ìh{Ú¤
+‹ÎðˆÙ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1706714204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1706714204
new file mode 100644
index 00000000..f1752f7b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1706714204
@@ -0,0 +1 @@
+Γ[Jù¼Q¡M º¶OTòN[ðy2Øå6I \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1707318704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1707318704
new file mode 100644
index 00000000..53710bff
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1707318704
@@ -0,0 +1 @@
+OC;.KÛúf0æb©°¾giÞV=cF}‚:Jðżñ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1707923204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1707923204
new file mode 100644
index 00000000..d8359467
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1707923204
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1708527704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1708527704
new file mode 100644
index 00000000..35f5f4c3
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1708527704
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1709132204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1709132204
new file mode 100644
index 00000000..f6f02e40
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1709132204
@@ -0,0 +1 @@
+sÚ-±*æÁð8üö†Ji<Û‘Äpª÷\ã®ñ¯ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1709736704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1709736704
new file mode 100644
index 00000000..291492bd
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1709736704
@@ -0,0 +1 @@
+µSI¿@Ÿ›fS’¤GÌS·‚VÀd⽄ç&Þ‘b ó \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1710341204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1710341204
new file mode 100644
index 00000000..c7e79229
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1710341204
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1710945704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1710945704
new file mode 100644
index 00000000..b65e0bda
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1710945704
@@ -0,0 +1 @@
+âK¤þe6£‡nØCAFž~ôÙ<‹Ð—ËÑØc{ öR \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1711550204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1711550204
new file mode 100644
index 00000000..4763e3bf
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1711550204
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1712154704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1712154704
new file mode 100644
index 00000000..3803be3c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1712154704
@@ -0,0 +1 @@
+”Èñ~vlÞZÄŒdcÑ×iŠäµz?§Ï×C \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1712759204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1712759204
new file mode 100644
index 00000000..efde898d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1712759204
@@ -0,0 +1 @@
+ú Áù2‰œÐÏšªUx«Ÿ^ŽPKýÙDxÞ6 \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1713363704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1713363704
new file mode 100644
index 00000000..8f34857c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1713363704
@@ -0,0 +1 @@
+vžQ°7a£®phƒöç l·Û;ðMÿ^U g- \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1713968204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1713968204
new file mode 100644
index 00000000..8827ac75
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1713968204
@@ -0,0 +1 @@
+FR(r;ö:ÑùŸÃ[N©<™M7æ}Ā͂²IT \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1714572704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1714572704
new file mode 100644
index 00000000..74c227bf
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1714572704
@@ -0,0 +1 @@
+æ«È?̵¢¾ ±¯†,Û@Nœr‘9£¨1ÙÛÜÔË¥¥ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1715177204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1715177204
new file mode 100644
index 00000000..5442c9b8
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1715177204
@@ -0,0 +1 @@
+N²^0ÄYmùtºçÒ¯*8Ñ”ÑMˆd¯òäÖˆ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1715781704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1715781704
new file mode 100644
index 00000000..b6ceae19
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1715781704
@@ -0,0 +1 @@
+M±*κdü&Lõ_žÖZœÍµÞI½šÕö \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1716386204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1716386204
new file mode 100644
index 00000000..30facaaa
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1716386204
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1716990704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1716990704
new file mode 100644
index 00000000..0e28c81d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1716990704
@@ -0,0 +1 @@
+ò>]ºéðª»ïyø%à Y}DxCmá$wGE \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1717595204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1717595204
new file mode 100644
index 00000000..1a5f9273
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_5/1717595204
@@ -0,0 +1,2 @@
++Ò_RméOÖ°AJ§©¨¨d®ì&íÃÄ}
+‹", \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1696437704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1696437704
new file mode 100644
index 00000000..2260d03f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1696437704
@@ -0,0 +1 @@
+uï4Gxnv]·,¥Ðîåuy H¦1rÓ¸ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1697042204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1697042204
new file mode 100644
index 00000000..193e653d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1697042204
@@ -0,0 +1,2 @@
+]¦H<„óõ¡É
+øþ".Øk43h‹°W³öxå \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1697646704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1697646704
new file mode 100644
index 00000000..47bc14d8
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1697646704
@@ -0,0 +1 @@
+äéÄð·ÌÕ°ÿ®óþ„ë@Ï[³*U £k¿ûår \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1698251204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1698251204
new file mode 100644
index 00000000..ba2059c4
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1698251204
@@ -0,0 +1 @@
+Ø…ðSüGeÎ݉G`\ÖœXYëþ¯P/ÍÂØ­P”N \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1698855704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1698855704
new file mode 100644
index 00000000..c41becef
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1698855704
@@ -0,0 +1 @@
+òZäê<îÙfÏ"¸‚!¢)Þ%ºiÝÜ ªÃ¢Ñùt \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1699460204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1699460204
new file mode 100644
index 00000000..ef4541c3
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1699460204
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1700064704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1700064704
new file mode 100644
index 00000000..4110af46
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1700064704
@@ -0,0 +1 @@
+faN éH‹â'¢ï…^ô>A•€ã¢ýtn+[#5Y \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1700669204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1700669204
new file mode 100644
index 00000000..b6bd01a3
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1700669204
@@ -0,0 +1 @@
+Ë›oO´¶ P6™ŠQD¨ŠWãq$kŒ0JŽ¢© \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1701273704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1701273704
new file mode 100644
index 00000000..9785b04d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1701273704
@@ -0,0 +1 @@
+×â²­ûíwæÄ/É•VŠvß%¶,iŠä 6#ø \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1701878204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1701878204
new file mode 100644
index 00000000..45dd2690
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1701878204
@@ -0,0 +1 @@
+µ=åpß;Æâã¼T3M[¯ßxÂÀ|œm¦~‰O \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1702482704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1702482704
new file mode 100644
index 00000000..286d043e
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1702482704
@@ -0,0 +1 @@
+t6ÌJo§÷“„²qþð`‡KKd:%œŽŽ(±¥S) \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1703087204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1703087204
new file mode 100644
index 00000000..9a49a0d7
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1703087204
@@ -0,0 +1 @@
+M°9Î; YÚi—Ã&!BàƒŽOź0,™Aì½Ë’ bæ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1703691704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1703691704
new file mode 100644
index 00000000..7b831419
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1703691704
@@ -0,0 +1 @@
+NƒîØŒÇD±«§Ö¾q>A-;™»ïÆŒþ¦irf \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1704296204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1704296204
new file mode 100644
index 00000000..e107b922
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1704296204
@@ -0,0 +1 @@
+maíH0ìíl%’ åy“âX?Y6_8¹¡X•G \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1704900704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1704900704
new file mode 100644
index 00000000..455ecdb7
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1704900704
@@ -0,0 +1 @@
+ë_¿2y|—²Ó ÚoÝ‹ªÊ[¶Ú£oV’RÕm \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1705505204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1705505204
new file mode 100644
index 00000000..b0afe0cd
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1705505204
@@ -0,0 +1 @@
+ÁˈôjèÉ¥<M*‹Ò°{ó²S•H4Ùóõj«³Q¤ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1706109704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1706109704
new file mode 100644
index 00000000..cf96d588
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1706109704
@@ -0,0 +1 @@
+”¶æíBÙyëØ—â~‚{çMŠNWô3KQå5‘» \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1706714204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1706714204
new file mode 100644
index 00000000..fdce10fe
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1706714204
@@ -0,0 +1 @@
+^ª[ð—ØÄ¿Êê†j>°áÑm0¹YgÚ³yภ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1707318704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1707318704
new file mode 100644
index 00000000..4ce6db5e
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1707318704
@@ -0,0 +1 @@
+mö¬Ô ÿˆòðƒà{íö¡LO|SߌGö€ðT3e \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1707923204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1707923204
new file mode 100644
index 00000000..d273e552
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1707923204
@@ -0,0 +1 @@
+T°Ühô2’{ø£5¬ØZ¡zÎD œ”S*¿)6a \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1709132204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1709132204
new file mode 100644
index 00000000..868af8ef
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1709132204
@@ -0,0 +1 @@
+Èn/3ÛìW'Þ ÍÛ—=š5mVÁïå[¤³ÿ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1709736704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1709736704
new file mode 100644
index 00000000..538ee496
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1709736704
@@ -0,0 +1 @@
+šëÈôHO Ë²%ðྡÕñcû]XØÏ>’‡… \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1710341204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1710341204
new file mode 100644
index 00000000..a24da62f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1710341204
@@ -0,0 +1 @@
+ûyz¦ÜÍ¥ ÁHW@-¼ª&y›Ö.¯®…/`Ð- \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1710945704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1710945704
new file mode 100644
index 00000000..b4ca3f1f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1710945704
@@ -0,0 +1 @@
+Ü€Þ‡#×­x0ÐáÆšTcn¤Ù¢Ð-©™¸rÏ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1711550204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1711550204
new file mode 100644
index 00000000..c57cde50
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1711550204
@@ -0,0 +1 @@
+–¥MßdèHn?Ö 3ÐãÜH²ÍÑÕ/ª"Q× \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1712154704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1712154704
new file mode 100644
index 00000000..c085d0a8
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1712154704
@@ -0,0 +1 @@
+7 9}…æ3ƒN§vÛVÖ4VMè*džVZä–é \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1712759204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1712759204
new file mode 100644
index 00000000..a29b261b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1712759204
@@ -0,0 +1 @@
+xâ‡H¬î0iKø‚FoO?PІͤdà…08  \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1713363704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1713363704
new file mode 100644
index 00000000..eb6972f8
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1713363704
@@ -0,0 +1 @@
+4Íû–,¶@WÁÛ!°X»‘§~<Txqf8A'Có \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1713968204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1713968204
new file mode 100644
index 00000000..e3e18a16
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1713968204
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1714572704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1714572704
new file mode 100644
index 00000000..7d288934
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1714572704
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1715177204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1715177204
new file mode 100644
index 00000000..c44343f9
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1715177204
@@ -0,0 +1 @@
+I’ñ<58N*'!ç³\AX±,S<~Ì<äEÓÁÞh \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1715781704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1715781704
new file mode 100644
index 00000000..2521c428
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1715781704
@@ -0,0 +1,2 @@
+rêj>u®ÆlƒHÛ©T!¾Œ·Ë¡?
+ºØIDàÇçö
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1716386204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1716386204
new file mode 100644
index 00000000..7a016552
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1716386204
@@ -0,0 +1 @@
+ùŒ)¹F!¾Æ @™b L`êO]FDü,¯(Ò>å’ñ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1716990704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1716990704
new file mode 100644
index 00000000..7cafdb67
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1716990704
@@ -0,0 +1 @@
+ ÊÜ»(¨FÌN)¶öò1û½cßn8‘TxU \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1717595204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1717595204
new file mode 100644
index 00000000..c7772341
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_1/1717595204
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1696437704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1696437704
new file mode 100644
index 00000000..3a48e83c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1696437704
@@ -0,0 +1 @@
+ ÈÎÆ´àö1pÁJ9A[àxªýž„³È7.kaþp \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1697042204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1697042204
new file mode 100644
index 00000000..02fcbef5
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1697042204
@@ -0,0 +1 @@
+ö(ò¸(Fƒ¿;3,ʇÈ›ªWI»­hžz!Óï؇[ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1697646704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1697646704
new file mode 100644
index 00000000..514a546f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1697646704
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1698251204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1698251204
new file mode 100644
index 00000000..b082e7da
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1698251204
@@ -0,0 +1 @@
+ÑCx˜,—K¿NÔƆkUY¸ýŽ…þz9„RÙo+nfë \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1698855704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1698855704
new file mode 100644
index 00000000..969cb8d8
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1698855704
@@ -0,0 +1 @@
+MqÚB–’¢ñ4鎥r¶PÙƇ›¸»›Hgi« \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1699460204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1699460204
new file mode 100644
index 00000000..fcfb97a2
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1699460204
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1700064704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1700064704
new file mode 100644
index 00000000..d7f0cffe
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1700064704
@@ -0,0 +1 @@
+‹µþ‡9RN•H³´›½Ä›ÕL+7NLäiL€Û \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1700669204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1700669204
new file mode 100644
index 00000000..912e633f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1700669204
@@ -0,0 +1 @@
+AÀ0‘¡$Ñ^†ë¢ˆ£²/¼u SE}†ù¯Èé¥÷ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1701273704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1701273704
new file mode 100644
index 00000000..c2d88da1
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1701273704
@@ -0,0 +1 @@
+(®ý* UÇB¹ã«¼´,ÁX'"÷°ò\JÅ|½k3¦ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1701878204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1701878204
new file mode 100644
index 00000000..14281a72
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1701878204
@@ -0,0 +1 @@
+sƒ›ÌRŠÍÝzü“4Œµ Å¸sd¶_ˆJߣu \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1702482704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1702482704
new file mode 100644
index 00000000..3f1f19d8
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1702482704
@@ -0,0 +1 @@
+Mne6ì{Õá’˜¸E1U™ VÑ…÷µúP"?€R \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1703087204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1703087204
new file mode 100644
index 00000000..4ce8f1ff
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1703087204
@@ -0,0 +1 @@
+‘€•Ê…$¾[¹R—ÅÛnîÝÒüí¬P7ÐG‚) \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1703691704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1703691704
new file mode 100644
index 00000000..1971537b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1703691704
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1704296204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1704296204
new file mode 100644
index 00000000..77c8c1d5
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1704296204
@@ -0,0 +1 @@
+%yÖk/lw5¼^¾æ_ و٭t`½2{íT¸Ïû8i \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1704900704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1704900704
new file mode 100644
index 00000000..c946503d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1704900704
@@ -0,0 +1 @@
+ÿMrqDq±rEtjXØâN{ÌZ„T¨÷Ì ö4
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1705505204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1705505204
new file mode 100644
index 00000000..86eea5e2
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1705505204
@@ -0,0 +1 @@
+òüîxCa^dO–’ô±¾üã¢?©[ÝŒx1VÚå \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1706109704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1706109704
new file mode 100644
index 00000000..1057eb4c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1706109704
@@ -0,0 +1 @@
+P¾ÐEçÆž³ð¼Oæ5rÉ0iH´Kb13 \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1706714204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1706714204
new file mode 100644
index 00000000..3cbb16ef
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1706714204
@@ -0,0 +1 @@
+ÝBÇ€E½À{œ¼“’œ+†’üoPŸá•²¿£Î" \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1707318704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1707318704
new file mode 100644
index 00000000..7f4ce85b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1707318704
@@ -0,0 +1,2 @@
+Í=íʾ°»©E§
+VSÍÙ9ðX„–{}ei\Êïñu \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1707923204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1707923204
new file mode 100644
index 00000000..d44fbfb7
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1707923204
@@ -0,0 +1 @@
+!2^ù.OÛÆIè$^ýÇ0XîÄ´qçæ²néó|4Æ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1708527704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1708527704
new file mode 100644
index 00000000..cba4467a
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1708527704
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1709132204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1709132204
new file mode 100644
index 00000000..528b7eee
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1709132204
@@ -0,0 +1 @@
+«ÂCCŽeº†A Mîì¾Bd³;/’ ä ÕÝnÅÑ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1709736704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1709736704
new file mode 100644
index 00000000..a5e1d42a
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1709736704
@@ -0,0 +1 @@
+oƒü>ÏIñ”D‰°Å…¨bvJæHNa7)²1‹òi \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1710341204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1710341204
new file mode 100644
index 00000000..47341063
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1710341204
@@ -0,0 +1 @@
+öRÛ¦‰%D1g€B‹YÄÿyípŠå´¢@㥎ix
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1710945704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1710945704
new file mode 100644
index 00000000..ebbad9d4
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1710945704
@@ -0,0 +1 @@
+ (~ƒcÝfÛåaMÛøþ M ——JjRª ýôä \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1711550204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1711550204
new file mode 100644
index 00000000..db6a523e
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1711550204
@@ -0,0 +1 @@
+!QV¤^p7"î/’¥vê³]ߤ¦dÅ«D°r=ù \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1712154704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1712154704
new file mode 100644
index 00000000..d281cbcc
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1712154704
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1712759204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1712759204
new file mode 100644
index 00000000..4cd2c6aa
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1712759204
@@ -0,0 +1 @@
+¶ŽÇ÷¥ÚŠ¤úÉÿn·ô<;q.­ñŽÚN³ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1713363704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1713363704
new file mode 100644
index 00000000..dba39243
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1713363704
@@ -0,0 +1 @@
+u°>/£{ÖÜ&AN|ÌsŽ3½ CÌZŸ¸·¿- ‡Í \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1713968204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1713968204
new file mode 100644
index 00000000..7cda9094
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1713968204
@@ -0,0 +1 @@
+Ž§?7¯ƒ!CSÈ%Aí ‹È¦‚Ä5CÄ4çá× \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1714572704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1714572704
new file mode 100644
index 00000000..6fe94e82
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1714572704
@@ -0,0 +1 @@
+ctí³BÒiû6,•Ð«uWöC2£nÖáY°]Ÿ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1715177204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1715177204
new file mode 100644
index 00000000..02d135b2
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1715177204
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1715781704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1715781704
new file mode 100644
index 00000000..b3015f6f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1715781704
@@ -0,0 +1 @@
+AvGâ!î áð<ñíÉo”§Sô6÷æ߈nÌ¡®"Ä
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1716386204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1716386204
new file mode 100644
index 00000000..2f426abf
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1716386204
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1716990704 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1716990704
new file mode 100644
index 00000000..61d52a98
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1716990704
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1717595204 b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1717595204
new file mode 100644
index 00000000..f839fc3d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/keys/coin_eur_ct_10/1717595204
@@ -0,0 +1 @@
+ÿA)ú€œÜÆ/V¿Õ.Ÿ­çqø¤•u/5PÃxÀó \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/secmod-private-key b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/secmod-private-key
new file mode 100644
index 00000000..8c53f4b1
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-cs/secmod-private-key
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-eddsa/secmod-private-key b/src/testing/test_merchant_api_home/taler/exchange-secmod-eddsa/secmod-private-key
new file mode 100644
index 00000000..a1ae416d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-eddsa/secmod-private-key
@@ -0,0 +1 @@
+„\š"þh(QH™,eóÒƒ=Ûœ2ÎNXÁÚšå8‚ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/exchange-secmod-rsa/secmod-private-key b/src/testing/test_merchant_api_home/taler/exchange-secmod-rsa/secmod-private-key
new file mode 100644
index 00000000..26eda485
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/exchange-secmod-rsa/secmod-private-key
@@ -0,0 +1 @@
+=E€<6œ$ÇÆ1©D· ßqzjþÚpÞJ ºÌOõn \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/taler/auditor/offline-keys/auditor.priv b/src/testing/test_merchant_api_home/taler/taler/auditor/offline-keys/auditor.priv
new file mode 100644
index 00000000..b1d1e3e1
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/auditor/offline-keys/auditor.priv
@@ -0,0 +1 @@
+±Y½74N¤ÉÿŠÃßOíôÀV|1ƒ\uY0 G¿rïû \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/1626561343 b/src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/1626561343
new file mode 100644
index 00000000..4ebda709
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/1626561343
@@ -0,0 +1 @@
+¶_Û¥ÿ2¦r+j@‡ƒõ°(ld¨TeºöåÉKJUtZ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/1633818643 b/src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/1633818643
new file mode 100644
index 00000000..f59e876d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/1633818643
@@ -0,0 +1 @@
+]tã¾÷R~9©ñ-0F¶NPùg¬zܤIB‹ñH>A \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/1641075943 b/src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/1641075943
new file mode 100644
index 00000000..6bff5766
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/1641075943
@@ -0,0 +1 @@
+¿7M¥"s`¿ºÈeöó¡¨ð|Ý“È”âN-kŽ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/1648333243 b/src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/1648333243
new file mode 100644
index 00000000..1421144a
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/1648333243
@@ -0,0 +1 @@
+8:o£` -Øã£ci‡Klçn 4<ŠÇbJz'I \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/1655590543 b/src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/1655590543
new file mode 100644
index 00000000..6cc325dc
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-eddsa/1655590543
@@ -0,0 +1 @@
+£˜ô¹·9Ü$k–‚ ·..ñÑôl–•_‚L”Êûúo \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1626554443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1626554443
new file mode 100644
index 00000000..c9dc2198
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1626554443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1627158943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1627158943
new file mode 100644
index 00000000..502eb080
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1627158943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1627763443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1627763443
new file mode 100644
index 00000000..38e4d600
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1627763443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1628367943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1628367943
new file mode 100644
index 00000000..94b19c17
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1628367943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1628972443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1628972443
new file mode 100644
index 00000000..2d806502
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1628972443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1629576943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1629576943
new file mode 100644
index 00000000..95e73f8d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1629576943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1630181443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1630181443
new file mode 100644
index 00000000..90ab6274
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1630181443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1630785943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1630785943
new file mode 100644
index 00000000..93a7c0f6
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1630785943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1631390443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1631390443
new file mode 100644
index 00000000..7f1a34b4
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1631390443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1631994943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1631994943
new file mode 100644
index 00000000..e7aead59
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1631994943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1632599443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1632599443
new file mode 100644
index 00000000..9a3ca629
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1632599443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1633203943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1633203943
new file mode 100644
index 00000000..eefe38a7
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1633203943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1633808443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1633808443
new file mode 100644
index 00000000..83e32064
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1633808443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1634412943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1634412943
new file mode 100644
index 00000000..34c4f7cc
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1634412943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1635017443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1635017443
new file mode 100644
index 00000000..7003d582
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1635017443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1635621943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1635621943
new file mode 100644
index 00000000..4ec323d5
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1635621943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1636226443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1636226443
new file mode 100644
index 00000000..ddf1340c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1636226443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1636830943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1636830943
new file mode 100644
index 00000000..e621edf4
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1636830943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1637435443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1637435443
new file mode 100644
index 00000000..8db32a7e
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1637435443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1638039943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1638039943
new file mode 100644
index 00000000..92ce9d37
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1638039943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1638644443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1638644443
new file mode 100644
index 00000000..68e1385f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1638644443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1639248943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1639248943
new file mode 100644
index 00000000..0a4a34f4
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1639248943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1639853443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1639853443
new file mode 100644
index 00000000..f41231b9
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1639853443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1640457943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1640457943
new file mode 100644
index 00000000..1ccd0bf8
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1640457943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1641062443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1641062443
new file mode 100644
index 00000000..1682390a
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1641062443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1641666943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1641666943
new file mode 100644
index 00000000..820be0de
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1641666943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1642271443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1642271443
new file mode 100644
index 00000000..775a21bf
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1642271443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1642875943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1642875943
new file mode 100644
index 00000000..bc45ac1b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1642875943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1643480443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1643480443
new file mode 100644
index 00000000..1594724d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1643480443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1644084943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1644084943
new file mode 100644
index 00000000..77191330
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1644084943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1644689443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1644689443
new file mode 100644
index 00000000..d881d7cb
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1644689443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1645293943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1645293943
new file mode 100644
index 00000000..c2b33607
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1645293943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1645898443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1645898443
new file mode 100644
index 00000000..bbebc198
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1645898443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1646502943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1646502943
new file mode 100644
index 00000000..ec849ae2
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1646502943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1647107443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1647107443
new file mode 100644
index 00000000..9fa67082
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1647107443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1647711943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1647711943
new file mode 100644
index 00000000..4bddf10c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1647711943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1648316443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1648316443
new file mode 100644
index 00000000..f20c785d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1648316443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1648920943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1648920943
new file mode 100644
index 00000000..89cfa2fa
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1648920943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1649525443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1649525443
new file mode 100644
index 00000000..a2cdc311
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1649525443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1650129943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1650129943
new file mode 100644
index 00000000..9d028df0
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1650129943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1650734443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1650734443
new file mode 100644
index 00000000..b704ed0f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1650734443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1651338943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1651338943
new file mode 100644
index 00000000..5cba6be3
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1651338943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1651943443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1651943443
new file mode 100644
index 00000000..c052e2da
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1651943443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1652547943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1652547943
new file mode 100644
index 00000000..5e9796d9
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1652547943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1653152443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1653152443
new file mode 100644
index 00000000..47132826
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1653152443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1653756943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1653756943
new file mode 100644
index 00000000..c850ac55
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1653756943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1654361443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1654361443
new file mode 100644
index 00000000..fcc92868
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1654361443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1654965943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1654965943
new file mode 100644
index 00000000..7828b2b5
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1654965943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1655570443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1655570443
new file mode 100644
index 00000000..e787aa88
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1655570443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1656174943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1656174943
new file mode 100644
index 00000000..fce4eb9b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1656174943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1656779443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1656779443
new file mode 100644
index 00000000..b411b18b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1656779443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1657383943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1657383943
new file mode 100644
index 00000000..60079b13
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1657383943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1657988443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1657988443
new file mode 100644
index 00000000..e52e35c1
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1657988443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1658592943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1658592943
new file mode 100644
index 00000000..59425e07
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_1/1658592943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1626554443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1626554443
new file mode 100644
index 00000000..2b0458b0
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1626554443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1627158943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1627158943
new file mode 100644
index 00000000..ff26fa40
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1627158943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1627763443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1627763443
new file mode 100644
index 00000000..c4c1fcdb
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1627763443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1628367943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1628367943
new file mode 100644
index 00000000..0c085113
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1628367943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1628972443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1628972443
new file mode 100644
index 00000000..ac7ada27
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1628972443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1629576943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1629576943
new file mode 100644
index 00000000..5b89db7d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1629576943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1630181443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1630181443
new file mode 100644
index 00000000..94693552
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1630181443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1630785943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1630785943
new file mode 100644
index 00000000..78c03b5a
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1630785943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1631390443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1631390443
new file mode 100644
index 00000000..bd93e1ec
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1631390443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1631994943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1631994943
new file mode 100644
index 00000000..5bf7bc2d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1631994943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1632599443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1632599443
new file mode 100644
index 00000000..4e0b5e0f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1632599443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1633203943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1633203943
new file mode 100644
index 00000000..9826bb6b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1633203943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1633808443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1633808443
new file mode 100644
index 00000000..cb7c3234
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1633808443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1634412943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1634412943
new file mode 100644
index 00000000..41de9949
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1634412943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1635017443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1635017443
new file mode 100644
index 00000000..1ed3e2e7
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1635017443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1635621943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1635621943
new file mode 100644
index 00000000..a5712db6
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1635621943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1636226443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1636226443
new file mode 100644
index 00000000..3c26311d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1636226443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1636830943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1636830943
new file mode 100644
index 00000000..ad2e4fe5
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1636830943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1637435443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1637435443
new file mode 100644
index 00000000..423be019
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1637435443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1638039943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1638039943
new file mode 100644
index 00000000..32b37b0f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1638039943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1638644443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1638644443
new file mode 100644
index 00000000..807680bf
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1638644443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1639248943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1639248943
new file mode 100644
index 00000000..a3b5a0e0
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1639248943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1639853443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1639853443
new file mode 100644
index 00000000..931a1b79
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1639853443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1640457943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1640457943
new file mode 100644
index 00000000..b6ca34b5
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1640457943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1641062443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1641062443
new file mode 100644
index 00000000..eb65de21
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1641062443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1641666943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1641666943
new file mode 100644
index 00000000..a1674e27
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1641666943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1642271443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1642271443
new file mode 100644
index 00000000..4186fed2
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1642271443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1642875943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1642875943
new file mode 100644
index 00000000..cc215d0f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1642875943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1643480443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1643480443
new file mode 100644
index 00000000..4b4f7f82
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1643480443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1644084943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1644084943
new file mode 100644
index 00000000..0b15bd15
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1644084943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1644689443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1644689443
new file mode 100644
index 00000000..ee6e55c0
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1644689443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1645293943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1645293943
new file mode 100644
index 00000000..3a30a716
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1645293943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1645898443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1645898443
new file mode 100644
index 00000000..15d356dc
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1645898443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1646502943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1646502943
new file mode 100644
index 00000000..47d0c3a4
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1646502943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1647107443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1647107443
new file mode 100644
index 00000000..6b111343
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1647107443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1647711943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1647711943
new file mode 100644
index 00000000..2b4cdaf6
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1647711943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1648316443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1648316443
new file mode 100644
index 00000000..0828bec8
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1648316443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1648920943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1648920943
new file mode 100644
index 00000000..23c07f08
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1648920943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1649525443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1649525443
new file mode 100644
index 00000000..2f45e330
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1649525443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1650129943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1650129943
new file mode 100644
index 00000000..1179c2b7
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1650129943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1650734443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1650734443
new file mode 100644
index 00000000..780c61d9
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1650734443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1651338943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1651338943
new file mode 100644
index 00000000..c550ea6a
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1651338943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1651943443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1651943443
new file mode 100644
index 00000000..601ae6cc
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1651943443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1652547943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1652547943
new file mode 100644
index 00000000..cffb0785
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1652547943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1653152443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1653152443
new file mode 100644
index 00000000..515f1183
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1653152443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1653756943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1653756943
new file mode 100644
index 00000000..533900fd
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1653756943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1654361443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1654361443
new file mode 100644
index 00000000..431bc19e
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1654361443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1654965943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1654965943
new file mode 100644
index 00000000..a21336fa
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1654965943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1655570443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1655570443
new file mode 100644
index 00000000..43229366
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1655570443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1656174943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1656174943
new file mode 100644
index 00000000..1925ea41
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1656174943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1656779443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1656779443
new file mode 100644
index 00000000..cd2e5e7b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1656779443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1657383943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1657383943
new file mode 100644
index 00000000..028daf37
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1657383943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1657988443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1657988443
new file mode 100644
index 00000000..58d9e8f2
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1657988443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1658592943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1658592943
new file mode 100644
index 00000000..abc9311b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_5/1658592943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1626554443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1626554443
new file mode 100644
index 00000000..654311d0
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1626554443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1627158943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1627158943
new file mode 100644
index 00000000..4e28d3cf
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1627158943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1627763443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1627763443
new file mode 100644
index 00000000..65de1c88
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1627763443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1628367943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1628367943
new file mode 100644
index 00000000..fbc5af8c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1628367943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1628972443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1628972443
new file mode 100644
index 00000000..bf78cee2
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1628972443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1629576943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1629576943
new file mode 100644
index 00000000..46e87d1d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1629576943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1630181443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1630181443
new file mode 100644
index 00000000..2de0a06d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1630181443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1630785943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1630785943
new file mode 100644
index 00000000..f3adfb6b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1630785943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1631390443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1631390443
new file mode 100644
index 00000000..09858250
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1631390443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1631994943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1631994943
new file mode 100644
index 00000000..8b5d1085
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1631994943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1632599443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1632599443
new file mode 100644
index 00000000..0f335ebd
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1632599443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1633203943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1633203943
new file mode 100644
index 00000000..1cc0dce1
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1633203943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1633808443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1633808443
new file mode 100644
index 00000000..4aed01bc
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1633808443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1634412943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1634412943
new file mode 100644
index 00000000..de1118b1
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1634412943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1635017443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1635017443
new file mode 100644
index 00000000..bf49a823
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1635017443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1635621943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1635621943
new file mode 100644
index 00000000..5143421a
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1635621943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1636226443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1636226443
new file mode 100644
index 00000000..b3689451
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1636226443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1636830943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1636830943
new file mode 100644
index 00000000..033ad011
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1636830943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1637435443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1637435443
new file mode 100644
index 00000000..2a1805d7
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1637435443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1638039943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1638039943
new file mode 100644
index 00000000..f517aa86
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1638039943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1638644443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1638644443
new file mode 100644
index 00000000..044e0d3e
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1638644443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1639248943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1639248943
new file mode 100644
index 00000000..6e3bdce3
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1639248943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1639853443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1639853443
new file mode 100644
index 00000000..57cc9645
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1639853443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1640457943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1640457943
new file mode 100644
index 00000000..ecb4a203
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1640457943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1641062443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1641062443
new file mode 100644
index 00000000..8a2f3a75
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1641062443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1641666943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1641666943
new file mode 100644
index 00000000..1b261a6d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1641666943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1642271443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1642271443
new file mode 100644
index 00000000..089b873e
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1642271443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1642875943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1642875943
new file mode 100644
index 00000000..4205bc9f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1642875943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1643480443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1643480443
new file mode 100644
index 00000000..c62e385f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1643480443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1644084943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1644084943
new file mode 100644
index 00000000..0d1aba5a
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1644084943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1644689443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1644689443
new file mode 100644
index 00000000..b29f0270
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1644689443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1645293943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1645293943
new file mode 100644
index 00000000..12941a40
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1645293943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1645898443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1645898443
new file mode 100644
index 00000000..5ecfa498
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1645898443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1646502943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1646502943
new file mode 100644
index 00000000..4ffff379
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1646502943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1647107443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1647107443
new file mode 100644
index 00000000..8aad7b5d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1647107443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1647711943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1647711943
new file mode 100644
index 00000000..72e89b66
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1647711943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1648316443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1648316443
new file mode 100644
index 00000000..1456d349
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1648316443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1648920943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1648920943
new file mode 100644
index 00000000..d405c337
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1648920943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1649525443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1649525443
new file mode 100644
index 00000000..f1f93785
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1649525443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1650129943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1650129943
new file mode 100644
index 00000000..bbfc50dc
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1650129943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1650734443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1650734443
new file mode 100644
index 00000000..eb55424d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1650734443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1651338943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1651338943
new file mode 100644
index 00000000..ffe4fdf0
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1651338943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1651943443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1651943443
new file mode 100644
index 00000000..d1208a2c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1651943443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1652547943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1652547943
new file mode 100644
index 00000000..63b05ec2
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1652547943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1653152443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1653152443
new file mode 100644
index 00000000..f81db926
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1653152443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1653756943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1653756943
new file mode 100644
index 00000000..b6eb861f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1653756943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1654361443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1654361443
new file mode 100644
index 00000000..23821fae
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1654361443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1654965943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1654965943
new file mode 100644
index 00000000..6d5198fa
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1654965943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1655570443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1655570443
new file mode 100644
index 00000000..311483d9
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1655570443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1656174943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1656174943
new file mode 100644
index 00000000..9a365d77
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1656174943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1656779443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1656779443
new file mode 100644
index 00000000..b4d8d28b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1656779443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1657383943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1657383943
new file mode 100644
index 00000000..781697b3
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1657383943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1657988443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1657988443
new file mode 100644
index 00000000..18420168
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1657988443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1658592943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1658592943
new file mode 100644
index 00000000..973eb566
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_1/1658592943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1626554443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1626554443
new file mode 100644
index 00000000..5c1e1a7d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1626554443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1627158943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1627158943
new file mode 100644
index 00000000..96a0efd8
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1627158943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1627763443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1627763443
new file mode 100644
index 00000000..eeea5d7b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1627763443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1628367943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1628367943
new file mode 100644
index 00000000..22ea6727
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1628367943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1628972443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1628972443
new file mode 100644
index 00000000..9a271c2f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1628972443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1629576943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1629576943
new file mode 100644
index 00000000..9b84ae3f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1629576943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1630181443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1630181443
new file mode 100644
index 00000000..d0ae6246
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1630181443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1630785943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1630785943
new file mode 100644
index 00000000..889130ad
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1630785943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1631390443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1631390443
new file mode 100644
index 00000000..f41c8d04
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1631390443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1631994943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1631994943
new file mode 100644
index 00000000..0632bddc
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1631994943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1632599443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1632599443
new file mode 100644
index 00000000..c13ae3d5
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1632599443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1633203943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1633203943
new file mode 100644
index 00000000..f3d65edd
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1633203943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1633808443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1633808443
new file mode 100644
index 00000000..01190d88
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1633808443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1634412943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1634412943
new file mode 100644
index 00000000..0f1bd20f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1634412943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1635017443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1635017443
new file mode 100644
index 00000000..109fa02c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1635017443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1635621943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1635621943
new file mode 100644
index 00000000..575f616a
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1635621943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1636226443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1636226443
new file mode 100644
index 00000000..a98b45f7
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1636226443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1636830943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1636830943
new file mode 100644
index 00000000..c038715a
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1636830943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1637435443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1637435443
new file mode 100644
index 00000000..592e22eb
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1637435443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1638039943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1638039943
new file mode 100644
index 00000000..4609184d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1638039943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1638644443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1638644443
new file mode 100644
index 00000000..32e65969
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1638644443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1639248943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1639248943
new file mode 100644
index 00000000..e84bc300
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1639248943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1639853443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1639853443
new file mode 100644
index 00000000..0f4a3a0a
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1639853443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1640457943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1640457943
new file mode 100644
index 00000000..9981a143
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1640457943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1641062443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1641062443
new file mode 100644
index 00000000..7b2bbc6f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1641062443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1641666943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1641666943
new file mode 100644
index 00000000..2ff58656
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1641666943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1642271443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1642271443
new file mode 100644
index 00000000..cc3c144e
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1642271443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1642875943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1642875943
new file mode 100644
index 00000000..24c32e68
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1642875943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1643480443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1643480443
new file mode 100644
index 00000000..9a3d2e91
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1643480443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1644084943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1644084943
new file mode 100644
index 00000000..bd002e8d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1644084943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1644689443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1644689443
new file mode 100644
index 00000000..820c2362
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1644689443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1645293943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1645293943
new file mode 100644
index 00000000..3e7f6b71
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1645293943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1645898443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1645898443
new file mode 100644
index 00000000..63bdb271
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1645898443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1646502943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1646502943
new file mode 100644
index 00000000..9fd36ee5
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1646502943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1647107443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1647107443
new file mode 100644
index 00000000..140e200b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1647107443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1647711943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1647711943
new file mode 100644
index 00000000..1118e221
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1647711943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1648316443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1648316443
new file mode 100644
index 00000000..5c1797ed
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1648316443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1648920943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1648920943
new file mode 100644
index 00000000..fcc85a41
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1648920943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1649525443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1649525443
new file mode 100644
index 00000000..a504ee57
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1649525443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1650129943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1650129943
new file mode 100644
index 00000000..bcff9616
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1650129943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1650734443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1650734443
new file mode 100644
index 00000000..ec6bbf71
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1650734443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1651338943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1651338943
new file mode 100644
index 00000000..d6b25100
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1651338943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1651943443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1651943443
new file mode 100644
index 00000000..6b2abf18
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1651943443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1652547943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1652547943
new file mode 100644
index 00000000..8e2df783
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1652547943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1653152443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1653152443
new file mode 100644
index 00000000..f3fdeec5
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1653152443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1653756943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1653756943
new file mode 100644
index 00000000..b7db07db
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1653756943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1654361443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1654361443
new file mode 100644
index 00000000..b354955d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1654361443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1654965943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1654965943
new file mode 100644
index 00000000..079028cc
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1654965943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1655570443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1655570443
new file mode 100644
index 00000000..5fdea4a8
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1655570443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1656174943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1656174943
new file mode 100644
index 00000000..8eb804c2
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1656174943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1656779443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1656779443
new file mode 100644
index 00000000..a42b189b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1656779443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1657383943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1657383943
new file mode 100644
index 00000000..e1327e45
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1657383943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1657988443 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1657988443
new file mode 100644
index 00000000..d66e5e7e
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1657988443
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1658592943 b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1658592943
new file mode 100644
index 00000000..71ab361d
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/crypto-rsa/coin_eur_ct_10/1658592943
Binary files differ
diff --git a/src/testing/test_merchant_api_home/.local/share/taler/exchange/offline-keys/master.priv b/src/testing/test_merchant_api_home/taler/taler/exchange-offline/master.priv
index c20942d6..c20942d6 100644
--- a/src/testing/test_merchant_api_home/.local/share/taler/exchange/offline-keys/master.priv
+++ b/src/testing/test_merchant_api_home/taler/taler/exchange-offline/master.priv
diff --git a/src/testing/test_merchant_api_home/taler/taler/exchange-offline/secm_tofus.pub b/src/testing/test_merchant_api_home/taler/taler/exchange-offline/secm_tofus.pub
new file mode 100644
index 00000000..56d1b939
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/exchange-offline/secm_tofus.pub
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/1686160442 b/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/1686160442
new file mode 100644
index 00000000..f708cb04
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/1686160442
@@ -0,0 +1 @@
+§È¾ƾ£…×H+O¨‡®H棚þ…¯ªÐ¾rk \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/1693417742 b/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/1693417742
new file mode 100644
index 00000000..a9f1e259
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/1693417742
@@ -0,0 +1 @@
+º#[ÕÛ–ôÛRülÚgŒèI× íä´AÓ¼ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/1700675042 b/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/1700675042
new file mode 100644
index 00000000..435cecc9
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/1700675042
@@ -0,0 +1 @@
+5þ6`ÿ5Äèêò}ªå¬D½»O€o…zþ¨!íÑr–{ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/1707932342 b/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/1707932342
new file mode 100644
index 00000000..a36b9da2
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/1707932342
@@ -0,0 +1 @@
+e•Á)`#¹Ýù RRïkõbd¦õX¹´Á¸výªjí \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/1715189642 b/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/1715189642
new file mode 100644
index 00000000..774f9a84
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/keys/1715189642
@@ -0,0 +1 @@
+Å=âºY÷”ÎÔ²ž\Þûdx©µw…ˆzõ^-!¾UË \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/secmod-private-key b/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/secmod-private-key
new file mode 100644
index 00000000..72e0c852
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/exchange-secmod-eddsa/secmod-private-key
@@ -0,0 +1,2 @@
+¿Ÿž¶¦]T‘L`
+Î)¾û3¡Ÿ‹Eû– ï’+Í#G* \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/taler/exchange/offline-keys/master.priv b/src/testing/test_merchant_api_home/taler/taler/exchange/offline-keys/master.priv
new file mode 100644
index 00000000..c20942d6
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/exchange/offline-keys/master.priv
@@ -0,0 +1 @@
+åÊk;d³_Uû}£A.wÔ"!Gûçv_m "_ò \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/taler/taler/exchange/wirefees/x-taler-bank.fee b/src/testing/test_merchant_api_home/taler/taler/exchange/wirefees/x-taler-bank.fee
new file mode 100644
index 00000000..771ac455
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/exchange/wirefees/x-taler-bank.fee
Binary files differ
diff --git a/src/testing/test_merchant_api_home/taler/taler/merchant/merchant.priv b/src/testing/test_merchant_api_home/taler/taler/merchant/merchant.priv
new file mode 100644
index 00000000..fd6e5f7f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/taler/taler/merchant/merchant.priv
@@ -0,0 +1 @@
+¶ù,åY%–FF<ßþR˜‰9ϳ5„¬v\þš×k4«6 \ No newline at end of file
diff --git a/src/testing/test_merchant_api_twisted-cs.conf b/src/testing/test_merchant_api_twisted-cs.conf
index 6c5416d5..8a6a21c0 100644
--- a/src/testing/test_merchant_api_twisted-cs.conf
+++ b/src/testing/test_merchant_api_twisted-cs.conf
@@ -8,9 +8,6 @@ EXCHANGE_BASE_URL = http://localhost:8888/
[exchange]
BASE_URL = http://localhost:8888/
-[auditor]
-BASE_URL = http://the.auditor/
-
# merchant: 8080
# exchange: 8081
# (Fake)bank: 8082
diff --git a/src/testing/test_merchant_api_twisted-rsa.conf b/src/testing/test_merchant_api_twisted-rsa.conf
index 5a61c855..cfa162b0 100644
--- a/src/testing/test_merchant_api_twisted-rsa.conf
+++ b/src/testing/test_merchant_api_twisted-rsa.conf
@@ -8,9 +8,6 @@ EXCHANGE_BASE_URL = http://localhost:8888/
[exchange]
BASE_URL = http://localhost:8888/
-[auditor]
-BASE_URL = http://the.auditor/
-
# merchant: 8080
# exchange: 8081
# (Fake)bank: 8082
diff --git a/src/testing/test_merchant_api_twisted.c b/src/testing/test_merchant_api_twisted.c
index 73b6905b..446e6eb3 100644
--- a/src/testing/test_merchant_api_twisted.c
+++ b/src/testing/test_merchant_api_twisted.c
@@ -1,6 +1,6 @@
/**
* This file is part of TALER
- * Copyright (C) 2014-2018 Taler Systems SA
+ * Copyright (C) 2014-2023 Taler Systems SA
*
* TALER is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
@@ -16,7 +16,6 @@
* License along with TALER; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>
*/
-
/**
* @file test_merchant_api_twisted.c
* @brief testcase to test exchange's HTTP API interface
@@ -63,7 +62,7 @@ static char *config_file;
#define USER_ACCOUNT_NAME "62"
-#define PAYTO_I1 "payto://x-taler-bank/localhost/3"
+#define PAYTO_I1 "payto://x-taler-bank/localhost/3?receiver-name=user3"
/**
@@ -108,12 +107,7 @@ static char *twister_merchant_url_instance_tor;
/**
* Merchant base URL.
*/
-static char *merchant_url;
-
-/**
- * Merchant process.
- */
-static struct GNUNET_OS_Process *merchantd;
+static const char *merchant_url;
/**
* Twister process that proxies the exchange.
@@ -126,11 +120,10 @@ static struct GNUNET_OS_Process *twisterexchanged;
static struct GNUNET_OS_Process *twistermerchantd;
-static char *payer_payto;
-static char *exchange_payto;
-static char *merchant_payto;
-static struct TALER_TESTING_BankConfiguration bc;
-static struct TALER_TESTING_ExchangeConfiguration ec;
+static const char *payer_payto;
+static const char *exchange_payto;
+static const char *merchant_payto;
+static struct TALER_TESTING_Credentials cred;
/**
* User name. Never checked by fakebank.
@@ -180,7 +173,7 @@ CMD_TRANSFER_TO_EXCHANGE (const char *label,
{
return TALER_TESTING_cmd_admin_add_incoming (label,
amount,
- &bc.exchange_auth,
+ &cred.ba,
payer_payto);
}
@@ -222,6 +215,7 @@ run (void *cls,
"EUR:0",
MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_post_orders ("create-proposal-abort-1",
+ cred.cfg,
twister_merchant_url,
MHD_HTTP_OK,
"abort-one",
@@ -232,7 +226,7 @@ run (void *cls,
* so we'll then have the right to abort. */
TALER_TESTING_cmd_merchant_pay_order ("deposit-simple-for-abort",
twister_merchant_url,
- MHD_HTTP_NOT_ACCEPTABLE,
+ MHD_HTTP_BAD_REQUEST,
"create-proposal-abort-1",
"withdraw-coin-abort-1",
"EUR:1.01",
@@ -279,6 +273,7 @@ run (void *cls,
"EUR:1.01"),
CMD_EXEC_WIREWATCH ("wirewatch-double-spend"),
TALER_TESTING_cmd_merchant_post_orders ("create-proposal-double-spend",
+ cred.cfg,
twister_merchant_url,
MHD_HTTP_OK,
"DS-1",
@@ -286,6 +281,7 @@ run (void *cls,
GNUNET_TIME_UNIT_FOREVER_TS,
"EUR:1.0"),
TALER_TESTING_cmd_merchant_post_orders ("create-proposal-double-spend-1",
+ cred.cfg,
twister_merchant_url,
MHD_HTTP_OK,
"DS-2",
@@ -325,27 +321,29 @@ run (void *cls,
struct TALER_TESTING_Command commands[] = {
/* general setup */
- TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_wire_add ("add-wire-account",
- "payto://x-taler-bank/localhost/2",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
- config_file),
- TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
- config_file,
- "EUR:0.01",
- "EUR:0.01"),
- TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
- 1),
+ TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+ cred.cfg,
+ "exchange-account-exchange"),
+ TALER_TESTING_cmd_system_start ("start-taler",
+ config_file,
+ "-ema",
+ "-u", "exchange-account-exchange",
+ NULL),
+ TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
TALER_TESTING_cmd_merchant_post_instances ("instance-create-default",
twister_merchant_url,
"default",
- PAYTO_I1,
- "EUR",
MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_account (
+ "instance-create-default-account",
+ twister_merchant_url,
+ PAYTO_I1,
+ NULL, NULL,
+ MHD_HTTP_OK),
TALER_TESTING_cmd_batch ("pay",
pay),
/* Malform the response from the exchange. */
@@ -358,7 +356,7 @@ run (void *cls,
* Make a reserve exist,
* according to the previous
* transfer.
- *///
+ */
CMD_EXEC_WIREWATCH ("wirewatch-1"),
TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2",
"EUR:10.02",
@@ -394,6 +392,7 @@ run (void *cls,
"create-proposal-1",
NULL),
TALER_TESTING_cmd_merchant_post_orders ("create-proposal-2",
+ cred.cfg,
merchant_url,
MHD_HTTP_OK,
"2",
@@ -402,7 +401,7 @@ run (void *cls,
"EUR:6.0"),
TALER_TESTING_cmd_merchant_pay_order ("deposit-2",
merchant_url,
- MHD_HTTP_NOT_ACCEPTABLE,
+ MHD_HTTP_BAD_REQUEST,
"create-proposal-2",
"withdraw-coin-1",
"EUR:5",
@@ -417,9 +416,8 @@ run (void *cls,
TALER_TESTING_cmd_end ()
};
- TALER_TESTING_run_with_fakebank (is,
- commands,
- bc.exchange_auth.wire_gateway_url);
+ TALER_TESTING_run (is,
+ commands);
}
@@ -431,7 +429,8 @@ run (void *cls,
static void
purge_process (struct GNUNET_OS_Process *process)
{
- GNUNET_OS_process_kill (process, SIGINT);
+ GNUNET_OS_process_kill (process,
+ SIGINT);
GNUNET_OS_process_wait (process);
GNUNET_OS_process_destroy (process);
}
@@ -441,93 +440,55 @@ int
main (int argc,
char *const *argv)
{
- char *cipher;
- unsigned int ret;
-
- /* These environment variables get in the way... */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup (argv[0],
- "INFO",
- NULL);
- cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
- GNUNET_assert (NULL != cipher);
- GNUNET_asprintf (&config_file,
- "test_merchant_api_twisted-%s.conf",
- cipher);
- GNUNET_free (cipher);
- if (GNUNET_OK !=
- TALER_TESTING_prepare_fakebank (config_file,
- "exchange-account-exchange",
- &bc))
- return 77;
-
+ int ret;
- payer_payto = ("payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME);
- exchange_payto = ("payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME);
- merchant_payto = ("payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME);
-
- if (NULL == (merchant_url = TALER_TESTING_prepare_merchant
- (config_file)))
- return 77;
-
- if (NULL == (twister_exchange_url = TALER_TWISTER_prepare_twister
- (PROXY_EXCHANGE_config_file)))
+ {
+ char *cipher;
+
+ cipher = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
+ GNUNET_assert (NULL != cipher);
+ GNUNET_asprintf (&config_file,
+ "test_merchant_api_twisted-%s.conf",
+ cipher);
+ GNUNET_free (cipher);
+ }
+ payer_payto =
+ "payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME "?receiver-name=user";
+ exchange_payto =
+ "payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME
+ "?receiver-name=exchange";
+ merchant_payto =
+ "payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME
+ "?receiver-name=merchant";
+ merchant_url = "http://localhost:8080/";
+ if (NULL == (twister_exchange_url = TALER_TWISTER_prepare_twister (
+ PROXY_EXCHANGE_config_file)))
return 77;
- if (NULL == (twister_merchant_url = TALER_TWISTER_prepare_twister
- (PROXY_MERCHANT_config_file)))
+ if (NULL == (twister_merchant_url = TALER_TWISTER_prepare_twister (
+ PROXY_MERCHANT_config_file)))
return 77;
-
twister_merchant_url_instance_nonexistent = TALER_url_join (
twister_merchant_url, "instances/foo/", NULL);
twister_merchant_url_instance_tor = TALER_url_join (
twister_merchant_url, "instances/tor/", NULL);
-
- TALER_TESTING_cleanup_files (config_file);
-
- switch (TALER_TESTING_prepare_exchange (config_file,
- GNUNET_YES,
- &ec))
- {
- case GNUNET_SYSERR:
- GNUNET_break (0);
- return 1;
- case GNUNET_NO:
+ if (NULL == (twisterexchanged = TALER_TWISTER_run_twister
+ (PROXY_EXCHANGE_config_file)))
return 77;
- case GNUNET_OK:
-
- if (NULL == (merchantd = TALER_TESTING_run_merchant
- (config_file, merchant_url)))
- // 1 is fine; after all this is merchant test cases.
- return 1;
-
- if (NULL == (twisterexchanged = TALER_TWISTER_run_twister
- (PROXY_EXCHANGE_config_file)))
- return 77;
-
- if (NULL == (twistermerchantd = TALER_TWISTER_run_twister
- (PROXY_MERCHANT_config_file)))
- return 77;
-
- /* Run the exchange and schedule 'run()' */
- ret = TALER_TESTING_setup_with_exchange (&run, NULL,
- config_file);
- purge_process (merchantd);
- purge_process (twisterexchanged);
- purge_process (twistermerchantd);
- GNUNET_free (merchant_url);
- GNUNET_free (twister_exchange_url);
- GNUNET_free (twister_merchant_url);
-
- if (GNUNET_OK != ret)
- return 1;
- break;
- default:
- GNUNET_break (0);
- return 1;
- }
- return 0;
+ if (NULL == (twistermerchantd = TALER_TWISTER_run_twister
+ (PROXY_MERCHANT_config_file)))
+ return 77;
+ ret = TALER_TESTING_main (argv,
+ "INFO",
+ config_file,
+ "exchange-account-exchange",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
+ purge_process (twisterexchanged);
+ purge_process (twistermerchantd);
+ return ret;
}
diff --git a/src/testing/test_merchant_instance_auth.sh b/src/testing/test_merchant_instance_auth.sh
index f4534b9a..85857b4f 100755
--- a/src/testing/test_merchant_instance_auth.sh
+++ b/src/testing/test_merchant_instance_auth.sh
@@ -1,6 +1,6 @@
#!/bin/bash
# This file is part of TALER
-# Copyright (C) 2014-2021 Taler Systems SA
+# Copyright (C) 2014-2023 Taler Systems SA
#
# TALER is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
@@ -17,45 +17,70 @@
# <http://www.gnu.org/licenses/>
#
-# Exit, with status code "skip" (no 'real' failure)
-function exit_skip() {
- echo $1
- exit 77
+# Cleanup to run whenever we exit
+function my_cleanup()
+{
+ for n in $(jobs -p)
+ do
+ kill "$n" 2> /dev/null || true
+ done
+ wait
+ if [ -n "${LAST_RESPONSE+x}" ]
+ then
+ rm -f "${LAST_RESPONSE}"
+ fi
}
-. initialize_taler_system.sh
+. setup.sh
+setup -c test_template.conf -m
+CONF="test_template.conf.edited"
+LAST_RESPONSE=$(mktemp -p "${TMPDIR:-/tmp}" test_response.conf-XXXXXX)
-echo -n "Configuring 'default' instance ..."
+echo -n "Configuring 'default' instance ..." >&2
STATUS=$(curl -H "Content-Type: application/json" -X POST \
http://localhost:9966/management/instances \
- -d '{"auth":{"method":"token","token":"secret-token:new_value"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
+ -d '{"auth":{"method":"token","token":"secret-token:new_value"},"id":"default","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance created. got:' $STATUS
- exit 1
+ exit_fail "Expected 204, instance created. got: $STATUS" >&2
fi
-echo " OK"
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ -H 'Authorization: Bearer secret-token:new_value' \
+ http://localhost:9966/private/accounts \
+ -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/43?receiver-name=user43"}' \
+ -w "%{http_code}" -s -o /dev/null)
+
+
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected 200 OK. Got: $STATUS"
+fi
+
+echo " OK" >&2
+# Kill merchant
+kill -TERM "$SETUP_PID"
+wait
+unset SETUP_PID
-kill $MERCHANT_HTTPD_PID
-wait $MERCHANT_HTTPD_PID
+setup -c test_template.conf -ef -u "exchange-account-2"
NEW_SECRET=secret-token:different_value
taler-merchant-httpd -a "${NEW_SECRET}" -c "${CONF}" -L DEBUG 2> taler-merchant-httpd.log &
-MERCHANT_HTTPD_PID=$!
-#taler-merchant-httpd -c $CONF -L DEBUG 2> taler-merchant-httpd.log &
+# Install cleanup handler (except for kill -9)
+trap my_cleanup EXIT
-echo -n "Waiting for the merchant..."
+echo -n "Waiting for the merchant..." >&2
# Wait for merchant to be available (usually the slowest)
-for n in `seq 1 50`
+for n in $(seq 1 50)
do
- echo -n "."
+ echo -n "." >&2
sleep 0.1
OK=0
# merchant
@@ -65,71 +90,149 @@ do
done
if [ "x$OK" != "x1" ]
-then
- exit_skip "Failed to start merchant backend"
+then
+ exit_fail "Failed to (re)start merchant backend"
fi
-echo -n "Creating order to test auth is ok..."
+echo -n "Creating order to test auth is ok..." >&2
STATUS=$(curl -H "Content-Type: application/json" -X POST \
- 'http://localhost:9966/instances/default/private/orders' \
- -H 'Authorization: Bearer '$NEW_SECRET \
+ 'http://localhost:9966/private/orders' \
+ -H 'Authorization: Bearer '"$NEW_SECRET" \
-d '{"order":{"amount":"TESTKUDOS:1","summary":"payme"}}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, order created. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE" >&2
+ 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`
+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}" \
- -H 'Authorization: Bearer '$NEW_SECRET \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -H 'Authorization: Bearer '"$NEW_SECRET" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, getting order info before claming it. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200, getting order info before claming it. got: $STATUS"
fi
-PAY_URL=`jq -e -r .taler_pay_uri < $LAST_RESPONSE`
+PAY_URL=$(jq -e -r .taler_pay_uri < "$LAST_RESPONSE")
-echo OK order $ORDER_ID with $TOKEN
+echo "OK order ${ORDER_ID} with ${TOKEN} and ${PAY_URL}" >&2
-echo -n "Configuring 'second' instance ..."
+echo -n "Configuring 'second' instance ..." >&2
STATUS=$(curl -H "Content-Type: application/json" -X POST \
- -H 'Authorization: Bearer '$NEW_SECRET \
+ -H 'Authorization: Bearer '"$NEW_SECRET" \
http://localhost:9966/management/instances \
- -d '{"auth":{"method":"token","token":"secret-token:second"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"second","name":"second","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
+ -d '{"auth":{"method":"token","token":"secret-token:second"},"id":"second","name":"second","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance created. got:' $STATUS
- exit 1
+ exit_fail "Expected 204, instance created. got: $STATUS"
fi
-echo "OK"
+echo "OK" >&2
-echo -n "Updating 'second' instance token using the 'default' auth token..."
+echo -n "Updating 'second' instance token using the 'default' auth token..." >&2
STATUS=$(curl -H "Content-Type: application/json" -X POST \
- -H 'Authorization: Bearer '$NEW_SECRET \
+ -H 'Authorization: Bearer '"$NEW_SECRET" \
http://localhost:9966/management/instances/second/auth \
-d '{"method":"token","token":"secret-token:new_one"}' \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance auth token changed. got:' $STATUS
- exit 1
+ exit_fail "Expected 204, instance auth token changed. got: $STATUS"
+fi
+NEW_SECRET="secret-token:new_one"
+echo " OK" >&2
+
+
+echo -n "Requesting login token..." >&2
+
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ -H 'Authorization: Bearer '"$NEW_SECRET" \
+ http://localhost:9966/instances/second/private/token \
+ -d '{"scope":"readonly","refreshable":true}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ jq < "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200, login token created. got: $STATUS"
+fi
+
+TOKEN=$(jq -e -r .token < "$LAST_RESPONSE")
+
+echo " OK" >&2
+
+echo -n "Using login token..." >&2
+
+STATUS=$(curl "http://localhost:9966/instances/second/private/orders" \
+ -H 'Authorization: Bearer '"$TOKEN" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ jq < "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200, getting orders. got: $STATUS"
+fi
+
+echo " OK" >&2
+
+echo -n "Refreshing login token..." >&2
+
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ -H 'Authorization: Bearer '"$TOKEN" \
+ http://localhost:9966/instances/second/private/token \
+ -d '{"scope":"write","refreshable":true}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "403" ]
+then
+ jq < "$LAST_RESPONSE" >&2
+ exit_fail "Expected 403, refused to upgrade login token. got: $STATUS"
+fi
+
+echo " OK" >&2
+
+
+echo -n "Deleting login token..." >&2
+
+STATUS=$(curl -H "Content-Type: application/json" -X DELETE \
+ -H 'Authorization: Bearer '"$TOKEN" \
+ http://localhost:9966/instances/second/private/token \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "204" ]
+then
+ jq < "$LAST_RESPONSE" >&2
+ exit_fail "Expected 204, login token deleted. got: $STATUS"
fi
+echo " OK" >&2
+
+echo -n "Using deleted login token..." >&2
+
+STATUS=$(curl "http://localhost:9966/instances/second/private/orders" \
+ -H 'Authorization: Bearer '"$TOKEN" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "401" ]
+then
+ jq < "$LAST_RESPONSE" >&2
+ exit_fail "Expected 401, token was deleted. got: $STATUS"
+fi
+
+echo " OK" >&2
-echo " OK"
+echo "Test PASSED"
exit 0
diff --git a/src/testing/test_merchant_instance_creation.sh b/src/testing/test_merchant_instance_creation.sh
index 9fee5773..c7eda54b 100755
--- a/src/testing/test_merchant_instance_creation.sh
+++ b/src/testing/test_merchant_instance_creation.sh
@@ -1,6 +1,6 @@
#!/bin/bash
# This file is part of TALER
-# Copyright (C) 2014-2021 Taler Systems SA
+# Copyright (C) 2014-2023 Taler Systems SA
#
# TALER is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
@@ -17,19 +17,22 @@
# <http://www.gnu.org/licenses/>
#
-. initialize_taler_system.sh
+. setup.sh
+
+# Launch only the merchant.
+setup -c test_template.conf -m
echo -n "Configuring a merchant instance before configuring the default instance ..."
STATUS=$(curl -H "Content-Type: application/json" -X POST \
http://localhost:9966/management/instances \
- -d '{"auth":{"method":"token","token":"secret-token:other_secret"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"first","name":"test","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
+ -d '{"auth":{"method":"token","token":"secret-token:other_secret"},"id":"first","name":"test","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance created. got:' $STATUS
+ echo "Expected 204, instance created. got: $STATUS"
exit 1
fi
@@ -40,12 +43,12 @@ echo -n "Configuring default instance ..."
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"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
+ -d '{"auth":{"method":"external"},"id":"default","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
-w "%{http_code}" -s -o /dev/null)
-if [ "$STATUS" != "204" ]
+if [ "$STATUS" != "401" ]
then
- echo 'should respond ok, instance created. got:' $STATUS
+ echo "Expected 401, permission denied. got: $STATUS"
exit 1
fi
@@ -55,16 +58,15 @@ echo -n "Configuring a second merchant instance ..."
STATUS=$(curl -H "Content-Type: application/json" -X POST \
http://localhost:9966/management/instances \
- -d '{"auth":{"method":"token","token":"secret-token:other_secret"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"second","name":"test","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
+ -d '{"auth":{"method":"token","token":"secret-token:other_secret"},"id":"second","name":"test","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
-w "%{http_code}" -s -o /dev/null)
-if [ "$STATUS" != "204" ]
+if [ "$STATUS" != "401" ]
then
- echo 'should respond ok, instance created. got:' $STATUS
+ echo "Expected 401, permission denied. got: $STATUS"
exit 1
fi
echo " OK"
-
exit 0
diff --git a/src/testing/test_merchant_instance_purge.sh b/src/testing/test_merchant_instance_purge.sh
index 60467385..f3992495 100755
--- a/src/testing/test_merchant_instance_purge.sh
+++ b/src/testing/test_merchant_instance_purge.sh
@@ -1,6 +1,6 @@
#!/bin/bash
# This file is part of TALER
-# Copyright (C) 2014-2021 Taler Systems SA
+# Copyright (C) 2014-2023 Taler Systems SA
#
# TALER is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
@@ -17,55 +17,51 @@
# <http://www.gnu.org/licenses/>
#
-. initialize_taler_system.sh
+. setup.sh
-echo -n "Configuring default instance ..."
+# Launch only the merchant.
+setup -c test_template.conf -m
+
+echo -n "Configuring default instance ..." >&2
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"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
+ -d '{"auth":{"method":"external"},"id":"default","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance created. got:' $STATUS
- exit 1
+ exit_fail "Expected 204, instance created. got: $STATUS"
fi
-echo " OK"
+echo " OK" >&2
-echo -n "Configuring merchant instance ..."
+echo -n "Configuring merchant instance ..." >&2
STATUS=$(curl -H "Content-Type: application/json" -X POST \
http://localhost:9966/management/instances \
- -d '{"auth":{"method":"token","token":"secret-token:other_secret"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"test","name":"test","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
+ -d '{"auth":{"method":"token","token":"secret-token:other_secret"},"id":"test","name":"test","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance created. got:' $STATUS
- exit 1
+ exit_fail "Expected 204, instance created. got: $STATUS"
fi
+echo " OK" >&2
-echo " OK"
-
-
-echo -n "Deleting instance ..."
-
+echo -n "Deleting instance ..." >&2
STATUS=$(curl -H "Content-Type: application/json" -X DELETE \
"http://localhost:9966/management/instances/test" \
-w "%{http_code}" -s -o /dev/null)
-
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance deleted. got:' $STATUS
- exit 1
+ exit_fail "Expected 204, instance deleted. got: $STATUS"
fi
-echo " OK"
-echo -n "Purging instance ..."
+echo " OK" >&2
+echo -n "Purging instance ..." >&2
STATUS=$(curl -H "Content-Type: application/json" -X DELETE \
"http://localhost:9966/management/instances/test?purge=yes" \
@@ -74,10 +70,10 @@ STATUS=$(curl -H "Content-Type: application/json" -X DELETE \
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance deleted. got:' $STATUS
- exit 1
+ exit_fail "Expected 204, instance deleted. got: $STATUS"
fi
-echo " OK"
+echo " OK" >&2
+echo "Test PASSED"
exit 0
diff --git a/src/testing/test_merchant_instance_response.sh b/src/testing/test_merchant_instance_response.sh
index 41ac983b..066efb67 100755
--- a/src/testing/test_merchant_instance_response.sh
+++ b/src/testing/test_merchant_instance_response.sh
@@ -1,6 +1,6 @@
#!/bin/bash
# This file is part of TALER
-# Copyright (C) 2014-2021 Taler Systems SA
+# Copyright (C) 2014-2023 Taler Systems SA
#
# TALER is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
@@ -17,121 +17,113 @@
# <http://www.gnu.org/licenses/>
#
-. initialize_taler_system.sh
+. setup.sh
+
+# Launch only the merchant.
+setup -c test_template.conf -m
-# echo -n "Configuring merchant instance ..."
STATUS=$(curl -H "Content-Type: application/json" -X OPTIONS \
- http://localhost:9966/instances/default/private/products \
+ http://localhost:9966/private/products \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'options should return 204 when default instance doest not exist yet. got:' $STATUS
- exit 1
+ exit_fail "Expected 204 when default instance does not exist yet. got: $STATUS"
fi
STATUS=$(curl -H "Content-Type: application/json" -X GET \
-H 'Authorization: Bearer secret-token:super_secret' \
- http://localhost:9966/instances/default/private/products \
+ http://localhost:9966/private/products \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "404" ]
then
- echo 'backend should respond 404 when the default instance is not yet created. got:' $STATUS
- exit 1
+ exit_fail "Expected 404 when the default instance is not yet created. got: $STATUS"
fi
STATUS=$(curl -H "Content-Type: application/json" -X POST \
-H 'Authorization: Bearer secret-token:super_secret' \
http://localhost:9966/management/instances \
- -d '{"auth":{"method":"token","token":"secret-token:other_secret"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
+ -d '{"auth":{"method":"token","token":"secret-token:other_secret"},"id":"default","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance created. got:' $STATUS
- exit 1
+ exit_fail "Expected 204, instance created. got: $STATUS"
fi
STATUS=$(curl -H "Content-Type: application/json" -X GET \
- http://localhost:9966/instances/default/private/products \
+ http://localhost:9966/private/products \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "401" ]
then
- echo 'should respond unauthorized without the token for the list of product when the default instance was created. got:' $STATUS
- exit 1
+ exit_fail "Expected 401 without the token for the list of product when the default instance was created. got: $STATUS"
fi
STATUS=$(curl -H "Content-Type: application/json" -X GET \
-H 'Authorization: Bearer secret-token:other_secret' \
- http://localhost:9966/instances/default/private/products \
+ http://localhost:9966/private/products \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "200" ]
then
- echo 'should respond ok for the list of product when the default instance was created. got:' $STATUS
- exit 1
+ exit_fail "Expected 200 for the list of product when the default instance was created. got: $STATUS"
fi
STATUS=$(curl -H "Content-Type: application/json" -X POST \
-H 'Authorization: Bearer secret-token:other_secret' \
- http://localhost:9966/instances/default/private/auth \
+ http://localhost:9966/private/auth \
-d '{"method":"token","token":"secret-token:zxc"}' \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance auth token changed. got:' $STATUS
- exit 1
+ exit_fail "Expected 204, instance auth token changed. got: $STATUS"
fi
STATUS=$(curl -H "Content-Type: application/json" -X DELETE \
- "http://localhost:9966/instances/default/private" \
+ "http://localhost:9966/private" \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "401" ]
then
- echo 'should respond unauthorized without the token, when purging the instance. got:' $STATUS
- exit 1
+ exit_fail "Expected 401 without the token, when purging the instance. got: $STATUS"
fi
STATUS=$(curl -H "Content-Type: application/json" -X DELETE \
-H 'Authorization: Bearer secret-token:other_secret' \
- "http://localhost:9966/instances/default/private" \
+ "http://localhost:9966/private" \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "401" ]
then
- echo 'should respond unauthorized using old token, when purging the instance. got:' $STATUS
- exit 1
+ exit_fail "Expected 401 using old token, when purging the instance. got: $STATUS"
fi
STATUS=$(curl -H "Content-Type: application/json" -X DELETE \
-H 'Authorization: Bearer secret-token:zxc' \
- "http://localhost:9966/instances/default/private" \
+ "http://localhost:9966/private" \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond unauthorized without the token, when purging the instance. got:' $STATUS
- exit 1
+ exit_fail "Expected 204 when purging the instance. got: $STATUS"
fi
STATUS=$(curl -H "Content-Type: application/json" -X GET \
-H 'Authorization: Bearer secret-token:zxc' \
- http://localhost:9966/instances/default/private/products \
+ http://localhost:9966/private/products \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "404" ]
then
- echo 'should respond not found when trying to list the product and the default instance was deleted. got:' $STATUS
- exit 1
+ exit_fail "Expected 404 when trying to list the product and the default instance was deleted. got: $STATUS"
fi
-echo "OK"
+echo "Test PASSED"
exit 0
diff --git a/src/testing/test_merchant_kyc.sh b/src/testing/test_merchant_kyc.sh
index e95e7586..1c818c31 100755
--- a/src/testing/test_merchant_kyc.sh
+++ b/src/testing/test_merchant_kyc.sh
@@ -1,6 +1,6 @@
#!/bin/bash
# This file is part of TALER
-# Copyright (C) 2014-2021 Taler Systems SA
+# Copyright (C) 2014-2023 Taler Systems SA
#
# TALER is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
@@ -16,22 +16,50 @@
# License along with TALER; see the file COPYING. If not, see
# <http://www.gnu.org/licenses/>
#
+set -eu
-. initialize_taler_system.sh
+. setup.sh
-echo -n "Configuring a merchant instance before configuring the default instance ..."
+# Launch system.
+setup -c "test_template.conf" -mef -u "exchange-account-2"
+LAST_RESPONSE=$(mktemp -p "${TMPDIR:-/tmp}" test_response.conf-XXXXXX)
+
+echo -n "Configuring a merchant default instance ..."
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"},"payto_uris":["payto://x-taler-bank/localhost:8082/43","payto://x-taler-bank/localhost:8082/44"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
+ -d '{"auth":{"method":"external"},"id":"default","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance created. got:' $STATUS
- exit 1
+ exit_fail "Expected 204 ok, instance created. got: $STATUS"
+fi
+
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ -H 'Authorization: Bearer secret-token:super_secret' \
+ http://localhost:9966/private/accounts \
+ -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/43?receiver-name=user43"}' \
+ -w "%{http_code}" -s -o /dev/null)
+
+
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected 200 OK. Got: $STATUS"
+fi
+
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ -H 'Authorization: Bearer secret-token:super_secret' \
+ http://localhost:9966/private/accounts \
+ -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/44?receiver-name=user44"}' \
+ -w "%{http_code}" -s -o /dev/null)
+
+
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected 200 OK. Got: $STATUS"
fi
echo " OK"
@@ -39,13 +67,12 @@ echo " OK"
echo -n "Check the instance exists ..."
STATUS=$(curl -H "Content-Type: application/json" -X GET \
- http://localhost:9966/instances/default/private/ \
+ http://localhost:9966/private/ \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "200" ]
then
- echo 'should respond ok, instance exists. got:' $STATUS
- exit 1
+ exit_fail "Expected 200 ok, instance exists. got: $STATUS"
fi
echo " OK"
@@ -57,52 +84,51 @@ RANDOM_IMG='data:image/png;base64,abcdefg'
#
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)
+STATUS=$(curl 'http://localhost:9966/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
- echo 'should response ok, order created. got:' $STATUS `cat $LAST_RESPONSE`
+ echo "Should respond 200 OK, order created. got: $STATUS"
+ jq < "$LAST_RESPONSE"
exit 1
fi
-ORDER_ID=`jq -r .order_id < $LAST_RESPONSE`
-TOKEN=`jq -r .token < $LAST_RESPONSE`
+ORDER_ID=$(jq -r .order_id < "$LAST_RESPONSE")
+TOKEN=$(jq -r .token < "$LAST_RESPONSE")
if [ "$TOKEN" != "null" ]
then
- echo 'token should be null, got:' $TOKEN
- exit 1
+ exit_fail "Token should be null, got: $TOKEN"
fi
-echo OK
+echo "OK"
echo -n "Checking created order without TOKEN..."
STATUS=$(curl http://localhost:9966/orders/$ORDER_ID \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
-PAY_URI=`jq -r .taler_pay_uri < $LAST_RESPONSE`
+PAY_URI=$(jq -r .taler_pay_uri < "$LAST_RESPONSE")
if [ "$PAY_URI" == "null" ]
then
- echo 'should have a payuri. got:' $PAY_URI `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected a taler_pay_uri. Got: $PAY_URI"
fi
-echo OK
+echo "OK"
-echo -n "Getting information about kyc ..."
+echo -n "Getting information about KYC ..."
STATUS=$(curl -H "Content-Type: application/json" -X GET \
- http://localhost:9966/instances/default/private/kyc \
+ http://localhost:9966/private/kyc \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance created. got:' $STATUS
- exit 1
+ exit_fail "Expected 204. got: $STATUS"
fi
echo " OK"
diff --git a/src/testing/test_merchant_order_autocleanup.sh b/src/testing/test_merchant_order_autocleanup.sh
index 4a53edca..3f792f12 100755
--- a/src/testing/test_merchant_order_autocleanup.sh
+++ b/src/testing/test_merchant_order_autocleanup.sh
@@ -1,21 +1,84 @@
#!/bin/bash
# This file is in the public domain.
-. initialize_taler_system.sh
+set -eu
+
+. setup.sh
+
+
+# Replace with 0 for nexus...
+USE_FAKEBANK=1
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ ACCOUNT="exchange-account-2"
+ WIRE_METHOD="x-taler-bank"
+ BANK_FLAGS="-f -d $WIRE_METHOD -u $ACCOUNT"
+ BANK_URL="http://localhost:8082/"
+else
+ ACCOUNT="exchange-account-1"
+ WIRE_METHOD="iban"
+ BANK_FLAGS="-ns -d $WIRE_METHOD -u $ACCOUNT"
+ BANK_URL="http://localhost:18082/"
+ echo -n "Testing for libeufin-bank"
+ libeufin-bank --help >/dev/null </dev/null || exit_skip " MISSING"
+ echo " FOUND"
+
+fi
+
+echo -n "Testing for taler-harness"
+taler-harness --help >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+
+# 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/"
+
echo -n "First, prepare wallet with coins..."
-rm $WALLET_DB
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api --expect-success 'withdrawTestBalance' \
+rm -f "$WALLET_DB"
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ api \
+ --expect-success 'withdrawTestBalance' \
"$(jq -n '
{
amount: "TESTKUDOS:99",
- bankBaseUrl: $BANK_URL,
+ corebankApiBaseUrl: $BANK_URL,
exchangeBaseUrl: $EXCHANGE_URL
}' \
- --arg BANK_URL "$BANK_URL" \
+ --arg BANK_URL "${BANK_URL}" \
--arg EXCHANGE_URL "$EXCHANGE_URL"
)" 2>wallet-withdraw-1.err >wallet-withdraw-1.out
-taler-wallet-cli --wallet-db=$WALLET_DB run-until-done 2>wallet-withdraw-finish-1.err >wallet-withdraw-finish-1.out
+echo -n "."
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ # Fakebank is instant...
+ sleep 0
+else
+ sleep 10
+ # 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
+fi
+echo -n "."
+taler-exchange-wirewatch \
+ -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"
#
@@ -23,236 +86,254 @@ echo " OK"
#
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 address
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"},"payto_uris":["payto://x-taler-bank/localhost:8082/43","payto://x-taler-bank/localhost:8082/44"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
+ "http://localhost:9966/management/instances" \
+ -d '{"auth":{"method":"external"},"id":"default","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance created. got:' $STATUS
- exit 1
+ exit_fail "Expected 204, instance created. got: $STATUS"
fi
-# remove one account address
-STATUS=$(curl -H "Content-Type: application/json" -X PATCH \
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
-H 'Authorization: Bearer secret-token:super_secret' \
- http://localhost:9966/instances/default/private/ \
- -d '{"auth":{"method":"external"},"payto_uris":["payto://x-taler-bank/localhost:8082/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
+ http://localhost:9966/private/accounts \
+ -d '{"payto_uri":"'"$FORTYTHREE"'"}' \
-w "%{http_code}" -s -o /dev/null)
-if [ "$STATUS" != "204" ]
+if [ "$STATUS" != "200" ]
then
- echo 'should respond ok, instance updated. got:' $STATUS
- exit 1
+ exit_fail "Expected '200 OK' response. Got instead $STATUS"
fi
-echo OK
-
-NOW=`date +%s`
-IN_TEN_SECS=`echo $(( $NOW + 5 ))`
+NOW=$(date +%s)
+IN_TEN_SECS=$(( NOW + 10 ))
echo -n "Creating order to be claimed..."
-STATUS=$(curl 'http://localhost:9966/instances/default/private/orders' \
- -d '{"order":{"amount":"TESTKUDOS:1","summary":"payme","pay_deadline": {"t_ms":'$NOW'000}}}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl 'http://localhost:9966/private/orders' \
+ -d '{"order":{"amount":"TESTKUDOS:1","summary":"payme","pay_deadline": {"t_s":'"$IN_TEN_SECS"'}}}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, order created. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, order created. got: $STATUS"
fi
-ORDER_ID=`jq -e -r .order_id < $LAST_RESPONSE`
-TOKEN=`jq -e -r .token < $LAST_RESPONSE`
+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)
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, getting order info before claming it. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, getting order info before claming it. got: $STATUS"
fi
-PAY_URL=`jq -e -r .taler_pay_uri < $LAST_RESPONSE`
+PAY_URL=$(jq -e -r .taler_pay_uri < "$LAST_RESPONSE")
echo "Ok (order $ORDER_ID)"
-NOW=`date +%s`
+NOW=$(date +%s)
echo -n "Claim the order but do not pay it ..."
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB advanced pay-prepare "${PAY_URL}" 2> wallet-pay1.err > wallet-pay1.log
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ advanced pay-prepare \
+ "${PAY_URL}" \
+ 2> wallet-claim1.err > wallet-claim1.log
-STATUS=$(curl "http://localhost:9966/instances/default/private/orders/${ORDER_ID}" \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, after pay. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, after pay. got: $STATUS"
fi
-ORDER_STATUS=`jq -r .order_status < $LAST_RESPONSE`
+ORDER_STATUS=$(jq -r .order_status < "$LAST_RESPONSE")
if [ "$ORDER_STATUS" != "claimed" ]
then
- echo 'order should be paid. got:' $ORDER_STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 'paid'. got: $ORDER_STATUS"
fi
-NOW2=`date +%s`
-echo " OK (took $( echo -n $(($NOW2 - $NOW)) ) secs )"
+NOW2=$(date +%s)
+echo " OK (took $(( NOW2 - NOW)) secs)"
-echo 'wait 8 secs '
+echo "Wait 8 secs ..."
sleep 8
echo -n "Trying to get the list of orders..."
-STATUS=$(curl "http://localhost:9966/instances/default/private/orders" \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl "http://localhost:9966/private/orders" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok. got: $STATUS"
fi
echo "ok"
echo -n "Creating a new order that will trigger the db cleanup..."
-STATUS=$(curl 'http://localhost:9966/instances/default/private/orders' \
+STATUS=$(curl 'http://localhost:9966/private/orders' \
-d '{"order":{"amount":"TESTKUDOS:1","summary":"payme"}}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, order created. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, order created. got: $STATUS"
fi
-ORDER_ID=`jq -e -r .order_id < $LAST_RESPONSE`
-TOKEN=`jq -e -r .token < $LAST_RESPONSE`
+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)
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, getting order info before claming it. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, getting order info before claming it. got: $STATUS"
fi
-PAY_URL=`jq -e -r .taler_pay_uri < $LAST_RESPONSE`
+PAY_URL=$(jq -e -r .taler_pay_uri < "$LAST_RESPONSE")
echo "Ok (order $ORDER_ID)"
echo -n "Trying to get the list of orders (issue #7025)..."
-STATUS=$(curl "http://localhost:9966/instances/default/private/orders" \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl "http://localhost:9966/private/orders" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok. got: $STATUS"
fi
-echo ok
+echo "OK"
# set -x
echo -n "Now paying this order..."
-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" \
+ handle-uri "${PAY_URL}"\
+ -y \
+ 2> wallet-pay1.err > wallet-pay1.log
-STATUS=$(curl "http://localhost:9966/instances/default/private/orders/${ORDER_ID}" \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, after pay. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, after pay. got: $STATUS"
fi
-ORDER_STATUS=`jq -r .order_status < $LAST_RESPONSE`
+ORDER_STATUS=$(jq -r .order_status < "$LAST_RESPONSE")
if [ "$ORDER_STATUS" != "paid" ]
then
- echo 'order should be paid. got:' $ORDER_STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 'paid'. got: $ORDER_STATUS"
fi
-NOW2=`date +%s`
-echo " OK (took $( echo -n $(($NOW2 - $NOW)) ) secs )"
+NOW2=$(date +%s)
+echo " OK (took $(( NOW2 - NOW)) secs )"
echo -n "Trying to get the list of orders..."
-STATUS=$(curl "http://localhost:9966/instances/default/private/orders" \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl "http://localhost:9966/private/orders" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok. got: $STATUS"
fi
echo ok
echo -n "Finally, create another order..."
-STATUS=$(curl 'http://localhost:9966/instances/default/private/orders' \
+STATUS=$(curl 'http://localhost:9966/private/orders' \
-d '{"order":{"amount":"TESTKUDOS:1","summary":"payme"}}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, order created. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, order created. got: $STATUS"
fi
-ORDER_ID=`jq -e -r .order_id < $LAST_RESPONSE`
-TOKEN=`jq -e -r .token < $LAST_RESPONSE`
+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)
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, getting order info before claming it. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, getting order info before claiming it. got: $STATUS"
fi
-PAY_URL=`jq -e -r .taler_pay_uri < $LAST_RESPONSE`
+PAY_URL=$(jq -e -r .taler_pay_uri < "$LAST_RESPONSE")
echo "Ok (order $ORDER_ID)"
echo -n "Now paying this order..."
-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" \
+ handle-uri \
+ "${PAY_URL}" \
+ -y \
+ 2> wallet-pay1.err > wallet-pay1.log
-STATUS=$(curl "http://localhost:9966/instances/default/private/orders/${ORDER_ID}" \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, after pay. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok. got: $STATUS"
fi
-ORDER_STATUS=`jq -r .order_status < $LAST_RESPONSE`
+ORDER_STATUS=$(jq -r .order_status < "$LAST_RESPONSE")
if [ "$ORDER_STATUS" != "paid" ]
then
- echo 'order should be paid. got:' $ORDER_STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 'paid'. got: $ORDER_STATUS"
fi
-NOW2=`date +%s`
+NOW2=$(date +%s)
echo " OK (took $( echo -n $(($NOW2 - $NOW)) ) secs )"
echo -n "Trying to get the list of orders..."
-STATUS=$(curl "http://localhost:9966/instances/default/private/orders" \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl "http://localhost:9966/private/orders" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok. got: $STATUS"
fi
-echo ok
+echo "ok"
+
+echo "Test PASSED"
exit 0
diff --git a/src/testing/test_merchant_order_creation.sh b/src/testing/test_merchant_order_creation.sh
index 684803b8..175667b9 100755
--- a/src/testing/test_merchant_order_creation.sh
+++ b/src/testing/test_merchant_order_creation.sh
@@ -3,36 +3,87 @@
set -eu
-. initialize_taler_system.sh
-
-# $1: sandbox username
-# $2: sandbox password
-# $3: payto with subject
-# $4: amount
-function wire_funds() {
- libeufin-cli sandbox demobank new-transaction
+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/"
+else
+ ACCOUNT="exchange-account-1"
+ BANK_FLAGS="-ns -d iban -u $ACCOUNT"
+ BANK_URL="http://localhost:18082/"
+ echo -n "Testing for libeufin-bank"
+ libeufin-bank --help >/dev/null </dev/null || exit_skip " MISSING"
+ echo " FOUND"
+
+fi
+
+. setup.sh
+
+echo -n "Testing for taler-harness"
+taler-harness --help >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+
+# 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 $WALLET_DB
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api --expect-success 'withdrawTestBalance' \
+rm -f "$WALLET_DB"
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ api \
+ --expect-success 'withdrawTestBalance' \
"$(jq -n '
{
amount: "TESTKUDOS:99",
- bankBaseUrl: $BANK_URL,
+ corebankApiBaseUrl: $BANK_URL,
exchangeBaseUrl: $EXCHANGE_URL
}' \
- --arg BANK_URL "$BANK_URL/access-api/" \
+ --arg BANK_URL "${BANK_URL}" \
--arg EXCHANGE_URL "$EXCHANGE_URL"
)" 2>wallet-withdraw-1.err >wallet-withdraw-1.out
-taler-wallet-cli --wallet-db=$WALLET_DB run-until-done 2>wallet-withdraw-finish-1.err >wallet-withdraw-finish-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')
+CURRENCY_COUNT=$(taler-wallet-cli --wallet-db="$WALLET_DB" balance | jq '.balances|length')
if [ "$CURRENCY_COUNT" = "0" ]
then
- echo 'should have at least one currency, withdrawal failed. check log.'
- exit 1
+ exit_fail "Expected least one currency, withdrawal failed. check log."
fi
#
@@ -41,35 +92,77 @@ fi
echo -n "Configuring merchant instance ..."
-# create with 2 address
-FORTYTHREE=`get_payto_uri fortythree x`
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"},"payto_uris":["'$FORTYTHREE'","payto://iban/SANDBOXX/DE270744?receiver-name=Forty+Four"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 50000000000},"default_pay_delay":{"d_us": 60000000000}}' \
+ -d '{"auth":{"method":"external"},"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
- echo 'should respond ok, instance created. got:' $STATUS
- exit 1
+ exit_fail "Expected '204 No content' response. Got instead $STATUS"
fi
+echo "Ok"
-# remove one account address
+echo -n "Configuring merchant account ..."
+
+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/private/accounts \
+ -d '{"payto_uri":"'"$FORTYTHREE"'"}' \
+ -w "%{http_code}" -s -o /dev/null)
+
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected '200 OK' response. Got instead $STATUS"
+fi
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ -H 'Authorization: Bearer secret-token:super_secret' \
+ http://localhost:9966/private/accounts \
+ -d '{"payto_uri":"payto://iban/SANDBOXX/DE270744?receiver-name=Forty+Four"}' \
+ -w "%{http_code}" -s -o /dev/null)
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected '200 OK' response. Got instead $STATUS"
+fi
+
+
+echo "Ok"
+
+echo -n "Get accounts..."
+STATUS=$(curl http://localhost:9966/private/accounts \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+PAY_URI=$(jq -r .accounts[1].payto_uri < "$LAST_RESPONSE")
+H_WIRE=$(jq -r .accounts[1].h_wire < "$LAST_RESPONSE")
+if [ "$PAY_URI" != "payto://iban/SANDBOXX/DE270744?receiver-name=Forty+Four" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected second payto URI. Got $PAY_URI"
+fi
+echo "OK"
+
+# remove one account address
+echo -n "Deleting one account ..."
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"},"payto_uris":["'$FORTYTHREE'"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 50000000000},"default_pay_delay":{"d_us": 60000000000}}' \
+ "http://localhost:9966/private/accounts/${H_WIRE}" \
+ -X DELETE \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance updated. got:' $STATUS
- exit 1
+ exit_fail "Expected '204 No content' for deletion of ${H_WIRE}. Got instead: $STATUS"
fi
+echo "OK"
-echo OK
RANDOM_IMG='data:image/png;base64,abcdefg'
#
@@ -77,38 +170,34 @@ RANDOM_IMG='data:image/png;base64,abcdefg'
#
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)
+STATUS=$(curl 'http://localhost:9966/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
- echo 'should response ok, order created. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ 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`
+ORDER_ID=$(jq -r .order_id < "$LAST_RESPONSE")
+TOKEN=$(jq -r .token < "$LAST_RESPONSE")
if [ "$TOKEN" != "null" ]
then
- echo 'token should be null, got:' $TOKEN
- exit 1
+ 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`
-
+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
- echo 'should have a payuri. got:' $PAY_URI `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected non-NULL payuri. got $PAY_URI"
fi
echo "OK"
@@ -117,41 +206,40 @@ echo "OK"
#
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)
+STATUS=$(curl 'http://localhost:9966/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
- echo 'should response ok, order created. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ 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`
+ORDER_ID=$(jq -r .order_id < "$LAST_RESPONSE")
+TOKEN=$(jq -r .token < "$LAST_RESPONSE")
if [ "$TOKEN" != "null" ]
then
- echo 'token should be null, got:' $TOKEN
- exit 1
+ exit_fail "Token should be null, got: $TOKEN"
fi
-STATUS=$(curl http://localhost:9966/orders/$ORDER_ID \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+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`
+PAY_URI=$(jq -r .taler_pay_uri < "$LAST_RESPONSE")
+FULLFILMENT_URL=$(jq -r .fulfillment_url < "$LAST_RESPONSE")
if [ "$FULLFILMENT_URL" != "go_here_please" ]
then
- echo 'should have a payuri. got:' $PAY_URI `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected a pay URI. got: $PAY_URI"
fi
if [ "$PAY_URI" == "null" ]
then
- echo 'should have a payuri. got:' $PAY_URI `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected non-NULL pay URI. Got: $PAY_URI"
fi
echo "OK"
@@ -160,43 +248,41 @@ echo "OK"
#
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)
+STATUS=$(curl 'http://localhost:9966/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
- echo 'should response ok, order created. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ 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`
+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)
+STATUS=$(curl http://localhost:9966/orders/"$ORDER_ID"/claim \
+ -d '{"nonce":"","token":"'"$TOKEN"'"}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, order claimed. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200, order claimed. got: $STATUS"
fi
-QUANTITY=`jq -r .contract_terms.products[0].quantity < $LAST_RESPONSE`
+QUANTITY=$(jq -r .contract_terms.products[0].quantity < "$LAST_RESPONSE")
if [ "$QUANTITY" != "1" ]
then
- echo 'should get quantity 1. got:' $QUANTITY # `jq .contract_terms.products[0] < $LAST_RESPONSE`
- exit 1
+ exit_fail "Expected quantity 1. got: $QUANTITY"
fi
-IMAGE=`jq -r .contract_terms.products[0].image < $LAST_RESPONSE`
+IMAGE=$(jq -r .contract_terms.products[0].image < "$LAST_RESPONSE")
if [ "$IMAGE" != "$RANDOM_IMG" ]
then
- echo 'should get image but got something else. got:' $IMAGE
- exit 1
+ exit_fail "Expected $RANDOM_IMG but got something else: $IMAGE"
fi
-echo OK
+echo "OK"
#
@@ -204,65 +290,62 @@ echo OK
#
echo -n "Creating product..."
-STATUS=$(curl 'http://localhost:9966/instances/default/private/products' \
+STATUS=$(curl 'http://localhost:9966/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
- echo 'should respond ok, product created. got:' $STATUS
- exit 1
+ exit_fail "Expected 204, product created. got: $STATUS"
fi
-echo OK
+echo "OK"
echo -n "Creating order with inventory products..."
-STATUS=$(curl 'http://localhost:9966/instances/default/private/orders' \
+STATUS=$(curl 'http://localhost:9966/private/orders' \
-d '{"order":{"amount":"TESTKUDOS:7","summary":"3"},"inventory_products":[{"product_id":"2","quantity":1}]}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, order created. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ 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`
+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)
+STATUS=$(curl http://localhost:9966/orders/"$ORDER_ID"/claim \
+ -d '{"nonce":"","token":"'"$TOKEN"'"}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, order claimed. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected 200, order claimed. got: $STATUS"
fi
-QUANTITY=`jq -r .contract_terms.products[0].quantity < $LAST_RESPONSE`
+QUANTITY=$(jq -r .contract_terms.products[0].quantity < "$LAST_RESPONSE")
if [ "$QUANTITY" != "1" ]
then
- echo 'should get quantity 1. got:' $QUANTITY #`jq .contract_terms.products[0] < $LAST_RESPONSE`
- exit 1
+ exit_fail "Expected quantity 1. got: $QUANTITY"
fi
echo "OK"
#
-# CREATE INVALID ORDER
+# Create product in another currency
#
-STATUS=$(curl 'http://localhost:9966/instances/default/private/products' \
+STATUS=$(curl 'http://localhost:9966/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" ]
+if [ "$STATUS" != "204" ]
then
- echo 'should respond bad request, product price is in another currency. got:' $STATUS
- exit 1
+ exit_fail "Expected 204 no content. got: $STATUS"
fi
#
@@ -270,144 +353,123 @@ fi
#
echo -n "Creating order to be paid..."
-STATUS=$(curl 'http://localhost:9966/instances/default/private/orders' \
+STATUS=$(curl 'http://localhost:9966/private/orders' \
-d '{"order":{"amount":"TESTKUDOS:1","summary":"payme"},"inventory_products":[{"product_id":"2","quantity":1}]}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, order created. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ 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`
+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)
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, getting order info before claming it. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ 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`
+PAY_URL=$(jq -e -r .taler_pay_uri < "$LAST_RESPONSE")
-echo OK
+echo "OK"
-NOW=`date +%s`
+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 $( echo -n $(($NOW2 - $NOW)) ) secs )"
+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)
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should response ok, after pay. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected 200, after pay. got: $STATUS"
fi
-ORDER_STATUS=`jq -r .order_status < $LAST_RESPONSE`
+ORDER_STATUS=$(jq -r .order_status < "$LAST_RESPONSE")
if [ "$ORDER_STATUS" != "paid" ]
then
- echo 'order should be paid. got:' $ORDER_STATUS `cat $LAST_RESPONSE`
- exit 1
+ 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`
+# 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`
+NOW=$(date +%s)
-TO_SLEEP=`echo $(( $WIRE_DEADLINE - $NOW ))`
-echo waiting $TO_SLEEP secs for wire transfer
+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
+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..."
-# 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"
+echo -n "Obtaining wire transfer details from bank ($USE_FAKEBANK)..."
+
+BANKDATA="$(curl 'http://localhost:8082/accounts/exchange/taler-wire-gateway/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)
if [ "$EXCHANGE_URL" != "$WURL" ]
then
- exit_fail "Wrong exchange URL in subject '$SUBJECT', expected '$EXCHANGE_URL'"
+ exit_fail "Wrong exchange URL in '$BANKDATA' response, expected '$EXCHANGE_URL'"
fi
echo " OK"
set +e
-export TARGET_PAYTO
-export WURL
-export WTID
-export CREDIT_AMOUNT
-export LAST_RESPONSE
-
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'"}' \
+STATUS=$(curl 'http://localhost:9966/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)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
-if [ "$STATUS" != "409" ]
+if [ "$STATUS" != "204" ]
then
- jq . < $LAST_RESPONSE
+ jq . < "$LAST_RESPONSE"
exit_fail "Expected to fail since the amount is not valid. got: $STATUS"
fi
echo "OK"
-echo -n "Notifying merchant of bogus wire transfer AGAIN ..."
-
-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" != "409" ]
-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'"}' \
+STATUS=$(curl 'http://localhost:9966/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)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "409" ]
then
- jq . < $LAST_RESPONSE
+ jq . < "$LAST_RESPONSE"
exit_fail "Expected response conflict, after providing conflicting transfer data. got: $STATUS"
fi
@@ -415,23 +477,23 @@ 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`
+TID=$(curl -s http://localhost:9966/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)
+ "http://localhost:9966/private/transfers/$TID" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "204" ]
then
- jq . < $LAST_RESPONSE
+ 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)
+ "http://localhost:9966/private/transfers/$TID" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "404" ]
then
- jq . < $LAST_RESPONSE
+ jq . < "$LAST_RESPONSE"
exit_fail "Expected response 404 Not found, after deleting TID again. got: $STATUS"
fi
@@ -439,148 +501,124 @@ 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'"}' \
+STATUS=$(curl 'http://localhost:9966/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)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
-if [ "$STATUS" != "200" ]
+if [ "$STATUS" != "204" ]
then
- jq . < $LAST_RESPONSE
- exit_fail "Expected response ok, after providing transfer data. got: $STATUS"
+ 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)
+STATUS=$(curl 'http://localhost:9966/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" != "200" ]
+if [ "$STATUS" != "204" ]
then
- jq . < $LAST_RESPONSE
- exit_fail "Expected response ok, after providing transfer data. got: $STATUS"
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected response No Content, after providing transfer data. got: $STATUS"
fi
echo " OK"
-echo -n "Sending bogus WTID ..."
-#
-# CHECK TRANSFER API
-#
-
-STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \
- -d '{"credit_amount":"'$CREDIT_AMOUNT'","wtid":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","payto_uri":"'$TARGET_PAYTO'","exchange_url":"'$WURL'"}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+echo -n "Testing taler-merchant-exchange ..."
+set -e
+taler-merchant-exchange -L INFO -c "$CONF" -t &> taler-merchant-exchange.log
+echo " OK"
-if [ "$STATUS" != "502" ]
-then
- jq . < $LAST_RESPONSE
- exit_fail "Expected response invalid since the WTID is fake. got: $STATUS"
-fi
-echo "OK"
echo -n "Fetching wire transfers ..."
-STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl 'http://localhost:9966/private/transfers' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- jq . < $LAST_RESPONSE
+ jq . < "$LAST_RESPONSE"
exit_fail "Expected response 200 Ok. got: $STATUS"
fi
-TRANSFERS_LIST_SIZE=`jq -r '.transfers | length' < $LAST_RESPONSE`
+TRANSFERS_LIST_SIZE=$(jq -r '.transfers | length' < "$LAST_RESPONSE")
-if [ "$TRANSFERS_LIST_SIZE" != "2" ]
+if [ "$TRANSFERS_LIST_SIZE" != "1" ]
then
- jq . < $LAST_RESPONSE
- exit_fail "Expected response ok. got: $STATUS"
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected 1 entry in transfer list. Got: $TRANSFERS_LIST_SIZE"
fi
echo "OK"
-echo -n "Fetching wire transfer details of bogus WTID ..."
-
-# Test for #6854: use a bogus WTID, causing the exchange to fail to
-# find the WTID.
-STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \
- -d '{"credit_amount":"'$CREDIT_AMOUNT'","wtid":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","payto_uri":"'$TARGET_PAYTO'","exchange_url":"'$WURL'"}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
-
-if [ "$STATUS" != "502" ]
-then
- jq . < $LAST_RESPONSE
- exit_fail "Expected response invalid since the WTID is fake. got: $STATUS"
-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)
-
-
-
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}?transfer=YES" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- jq . < $LAST_RESPONSE
- exit_fail 'should response ok, after order inquiry. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected 200, after order inquiry. got: $STATUS"
fi
-
-DEPOSIT_TOTAL=`jq -r .deposit_total < $LAST_RESPONSE`
-
+DEPOSIT_TOTAL=$(jq -r .deposit_total < "$LAST_RESPONSE")
if [ "$DEPOSIT_TOTAL" == "TESTKUDOS:0" ]
then
- echo 'deposit total is zero, expected greater than zero. got:' $DEPOSIT_TOTAL `cat $LAST_RESPONSE`
- exit 1
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected non-zero deposit total. got: $DEPOSIT_TOTAL"
fi
-
echo " OK"
-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
- echo "Expected response 200 Ok, getting account status. Got: $STATUS"
- exit 1
-fi
-
-BALANCE=`jq -r .balance.amount < $LAST_RESPONSE`
-
-if [ "$BALANCE" == "TESTKUDOS:0" ]
-then
- jq . < $LAST_RESPONSE
- echo "Wire transfer did not happen. Got: $BALANCE"
- exit 1
+echo -n "Checking bank account status ..."
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ STATUS=$(curl "http://localhost:8082/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"
+ STATUS=$(curl "http://$ACCOUNT_PASSWORD@$BANK_HOST/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 \
+ http://localhost:9966/private/kyc \
-w "%{http_code}" -s -o /dev/null)
-
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance created. got:' $STATUS
- exit 1
+ exit_fail "Expected 204. Got: $STATUS"
fi
-
echo " OK"
exit 0
diff --git a/src/testing/test_merchant_product_creation.sh b/src/testing/test_merchant_product_creation.sh
index 702b0e53..e745774c 100755
--- a/src/testing/test_merchant_product_creation.sh
+++ b/src/testing/test_merchant_product_creation.sh
@@ -1,6 +1,6 @@
#!/bin/bash
# This file is part of TALER
-# Copyright (C) 2014-2021 Taler Systems SA
+# Copyright (C) 2014-2023 Taler Systems SA
#
# TALER is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
@@ -17,216 +17,282 @@
# <http://www.gnu.org/licenses/>
#
-. initialize_taler_system.sh
+set -eu
+
+# Replace with 0 for nexus...
+USE_FAKEBANK=1
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ ACCOUNT="exchange-account-2"
+ WIRE_METHOD="x-taler-bank"
+ BANK_FLAGS="-f -d $WIRE_METHOD -u $ACCOUNT"
+ BANK_URL="http://localhost:8082/"
+else
+ echo -n "Testing for libeufin-bank"
+ libeufin-bank --help >/dev/null </dev/null || exit_skip " MISSING"
+ echo " FOUND"
+
+ ACCOUNT="exchange-account-1"
+ WIRE_METHOD="iban"
+ BANK_FLAGS="-ns -d $WIRE_METHOD -u $ACCOUNT"
+ BANK_URL="http://localhost:18082/"
+fi
+
+. setup.sh
+
+
+echo -n "Testing for taler-harness"
+taler-harness --help >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+
+
+# Launch system.
+setup -c "test_template.conf" \
+ -em \
+ $BANK_FLAGS
+LAST_RESPONSE=$(mktemp -p "${TMPDIR:-/tmp}" test_response.conf-XXXXXX)
+WALLET_DB=$(mktemp -p "${TMPDIR:-/tmp}" test_wallet.json-XXXXXX)
+CONF="test_template.conf.edited"
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ FORTYTHREE="payto://x-taler-bank/localhost/fortythree?receiver-name=fortythree"
+else
+ FORTYTHREE=$(get_payto_uri fortythree x)
+fi
+EXCHANGE_URL="http://localhost:8081/"
-FORTYTHREE=`get_payto_uri fortythree x`
echo -n "Configuring merchant instance ..."
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"},"payto_uris":["'$FORTYTHREE'"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
+ "http://localhost:9966/management/instances" \
+ -d '{"auth":{"method":"external"},"id":"default","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo "Should have responded 204 No content, instance created. Got $STATUS instead"
- exit 1
+ exit_fail "Expected 204 No content, instance created. Got $STATUS instead"
fi
-echo OK
-RANDOM_IMG='data:image/png;base64,abcdefg'
-INFINITE_PRODUCT_TEMPLATE='{"product_id":"2","description":"product with id 2 and price :15","price":"TESTKUDOS:15","total_stock":-1,"unit":"","image":"'$RANDOM_IMG'","taxes":[]}'
-MANAGED_PRODUCT_TEMPLATE='{"product_id":"3","description":"product with id 3 and price :10","price":"TESTKUDOS:150","total_stock":2,"unit":"","image":"'$RANDOM_IMG'","taxes":[]}'
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ -H 'Authorization: Bearer secret-token:super_secret' \
+ http://localhost:9966/private/accounts \
+ -d '{"payto_uri":"'"$FORTYTHREE"'"}' \
+ -w "%{http_code}" -s -o /dev/null)
+
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected 200 OK. Got: $STATUS"
+fi
+
+echo "OK"
+
+RANDOM_IMG='data:image/png;base64,abcdefg'
+INFINITE_PRODUCT_TEMPLATE='{"product_id":"2","description":"product with id 2 and price :15","price":"TESTKUDOS:15","total_stock":-1,"unit":"","image":"'"$RANDOM_IMG"'","taxes":[]}'
+MANAGED_PRODUCT_TEMPLATE='{"product_id":"3","description":"product with id 3 and price :10","price":"TESTKUDOS:150","total_stock":2,"unit":"","image":"'"$RANDOM_IMG"'","taxes":[]}'
echo -n "Creating products..."
-STATUS=$(curl 'http://localhost:9966/instances/default/private/products' \
+STATUS=$(curl 'http://localhost:9966/private/products' \
-d "$INFINITE_PRODUCT_TEMPLATE" \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, product created. got:' $STATUS
- exit 1
+ exit_fail "Expected 204, product created. got: $STATUS"
fi
-STATUS=$(curl 'http://localhost:9966/instances/default/private/products' \
+STATUS=$(curl 'http://localhost:9966/private/products' \
-d "$MANAGED_PRODUCT_TEMPLATE" \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, product created. got:' $STATUS
- exit 1
+ exit_fail "Expected 204, product created. got: $STATUS"
fi
-echo OK
+echo "OK"
-PRODUCT_DATA=$(echo $INFINITE_PRODUCT_TEMPLATE | jq 'del(.product_id) | . + {description: "other description"}')
+PRODUCT_DATA=$(echo "$INFINITE_PRODUCT_TEMPLATE" | jq 'del(.product_id) | . + {description: "other description"}')
echo -n "Updating infinite stock product..."
-STATUS=$(curl 'http://localhost:9966/instances/default/private/products/2' -X PATCH \
+STATUS=$(curl 'http://localhost:9966/private/products/2' -X PATCH \
-d "$PRODUCT_DATA" \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, updating product. got:' $STATUS
- cat $LAST_RESPONSE
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 204, updating product. got: $STATUS"
fi
-STATUS=$(curl 'http://localhost:9966/instances/default/private/products/2' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl 'http://localhost:9966/private/products/2' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
-DESCRIPTION=`jq -r .description < $LAST_RESPONSE`
+DESCRIPTION=$(jq -r .description < "$LAST_RESPONSE")
if [ "$DESCRIPTION" != "other description" ]
then
- echo 'should change description. got:' $DESCRIPTION
- cat $LAST_RESPONSE
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 'other description'. Got: $DESCRIPTION"
fi
-echo OK
+echo "OK"
-MANAGED_PRODUCT_ID=$(echo $MANAGED_PRODUCT_TEMPLATE | jq -r '.product_id')
+MANAGED_PRODUCT_ID=$(echo "$MANAGED_PRODUCT_TEMPLATE" | jq -r '.product_id')
echo -n "Locking inventory ..."
-STATUS=$(curl "http://localhost:9966/instances/default/private/products/${MANAGED_PRODUCT_ID}/lock" \
+STATUS=$(curl "http://localhost:9966/private/products/${MANAGED_PRODUCT_ID}/lock" \
-d '{"lock_uuid":"luck","duration":{"d_us": 100000000},"quantity":10}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "410" ]
then
- echo 'should respond gone, lock failed. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 410, lock failed. got: $STATUS"
fi
echo -n "."
-STATUS=$(curl "http://localhost:9966/instances/default/private/products/${MANAGED_PRODUCT_ID}/lock" \
+STATUS=$(curl "http://localhost:9966/private/products/${MANAGED_PRODUCT_ID}/lock" \
-d '{"lock_uuid":"luck","duration":{"d_us": 100000000},"quantity":1}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, lock created. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 204, lock created. got: $STATUS"
fi
-
echo " OK"
-
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":"'${MANAGED_PRODUCT_ID}'","quantity":1}]}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl 'http://localhost:9966/private/orders' \
+ -d '{"order":{"amount":"TESTKUDOS:1","summary":"payme"},"inventory_products":[{"product_id":"'"${MANAGED_PRODUCT_ID}"'","quantity":1}]}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should respond ok, order created. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$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`
+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)
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should respond ok, getting order info before claming it. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$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`
+PAY_URL=$(jq -e -r .taler_pay_uri < "$LAST_RESPONSE")
echo -n "."
-STATUS=$(curl 'http://localhost:9966/instances/default/private/orders' \
- -d '{"order":{"amount":"TESTKUDOS:1","summary":"payme"},"inventory_products":[{"product_id":"'${MANAGED_PRODUCT_ID}'","quantity":1}]}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl 'http://localhost:9966/private/orders' \
+ -d '{"order":{"amount":"TESTKUDOS:1","summary":"payme"},"inventory_products":[{"product_id":"'"${MANAGED_PRODUCT_ID}"'","quantity":1}]}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "410" ]
then
- echo 'should respond out of stock (what remains is locked). got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 410 out of stock (what remains is locked). got: $STATUS"
fi
echo -n "."
# Using the 'luck' inventory lock, order creation should work.
-STATUS=$(curl 'http://localhost:9966/instances/default/private/orders' \
- -d '{"order":{"amount":"TESTKUDOS:1","summary":"payme"},"lock_uuids":["luck"],"inventory_products":[{"product_id":"'$MANAGED_PRODUCT_ID'","quantity":1}]}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl 'http://localhost:9966/private/orders' \
+ -d '{"order":{"amount":"TESTKUDOS:1","summary":"payme"},"lock_uuids":["luck"],"inventory_products":[{"product_id":"'"$MANAGED_PRODUCT_ID"'","quantity":1}]}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'Should respond 200 OK, lock should apply. Got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 OK, lock should apply. Got: $STATUS"
fi
-
-
echo " OK"
echo -n "First withdraw wallet ..."
-rm $WALLET_DB
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api --expect-success 'withdrawTestBalance' \
+rm -f "$WALLET_DB"
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ api \
+ --expect-success 'withdrawTestBalance' \
"$(jq -n '
{
amount: "TESTKUDOS:5",
- bankBaseUrl: $BANK_URL,
+ corebankApiBaseUrl: $BANK_URL,
exchangeBaseUrl: $EXCHANGE_URL
}' \
- --arg BANK_URL "$BANK_URL/access-api/" \
+ --arg BANK_URL "$BANK_URL" \
--arg EXCHANGE_URL "$EXCHANGE_URL"
)" 2>wallet-withdraw-1.err >wallet-withdraw-1.out
echo -n "."
-taler-wallet-cli --wallet-db=$WALLET_DB run-until-done 2>wallet-withdraw-finish-1.err >wallet-withdraw-finish-1.out
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ # Fakebank is instant...
+ sleep 0
+else
+ sleep 10
+ # 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
+fi
+echo -n "."
+taler-exchange-wirewatch -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"
echo -n "Pay first order ..."
-NOW=`date +%s`
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB handle-uri "${PAY_URL}" -y 2> wallet-pay1.err > wallet-pay1.log
-NOW2=`date +%s`
-echo " OK (took $( echo -n $(($NOW2 - $NOW)) ) secs )"
+NOW=$(date +%s)
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ handle-uri "${PAY_URL}" \
+ -y \
+ 2> wallet-pay1.err > wallet-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)
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should respond ok, after pay. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, after pay. got: $STATUS"
fi
-ORDER_STATUS=`jq -r .order_status < $LAST_RESPONSE`
+ORDER_STATUS=$(jq -r .order_status < "$LAST_RESPONSE")
if [ "$ORDER_STATUS" != "paid" ]
then
- echo 'order should be paid. got:' $ORDER_STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 'paid'. got: $ORDER_STATUS"
fi
echo -n "Updating product..."
-PRODUCT_DATA=$(echo $MANAGED_PRODUCT_TEMPLATE | jq 'del(.product_id) | . + {"total_stock": (.total_stock + 2) }')
+PRODUCT_DATA=$(echo "$MANAGED_PRODUCT_TEMPLATE" | jq 'del(.product_id) | . + {"total_stock": (.total_stock + 2) }')
-STATUS=$(curl 'http://localhost:9966/instances/default/private/products/3' -X PATCH \
+STATUS=$(curl 'http://localhost:9966/private/products/3' -X PATCH \
-d "$PRODUCT_DATA" \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, updating product. got:' $STATUS
- cat $LAST_RESPONSE
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 204, updating product. got: $STATUS"
fi
-
echo " OK"
exit 0
diff --git a/src/testing/test_merchant_reserve_creation.sh b/src/testing/test_merchant_reserve_creation.sh
deleted file mode 100755
index f97986b3..00000000
--- a/src/testing/test_merchant_reserve_creation.sh
+++ /dev/null
@@ -1,179 +0,0 @@
-#!/bin/bash
-# This file is part of TALER
-# Copyright (C) 2014-2021 Taler Systems SA
-#
-# TALER is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 3, or
-# (at your option) any later version.
-#
-# TALER is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public
-# License along with TALER; see the file COPYING. If not, see
-# <http://www.gnu.org/licenses/>
-#
-
-set -eu
-
-. initialize_taler_system.sh
-
-echo -n "Configuring merchant instance ..."
-
-# create instance
-FORTYTHREE=`get_payto_uri fortythree x`
-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"},"payto_uris":["payto://x-taler-bank/localhost:1'$NEXUS_PORT'/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
- -w "%{http_code}" -s -o /dev/null)
-
-if [ "$STATUS" != "204" ]
-then
- echo 'Expected 204, instance created. Got instead: ' $STATUS
- exit 1
-fi
-echo "OK"
-
-echo -n "Creating reserve ..."
-STATUS=$(curl 'http://localhost:9966/instances/default/private/reserves' \
- -d '{"initial_balance":"TESTKUDOS:2","exchange_url":"'$EXCHANGE_URL'","wire_method":"iban"}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
-
-if [ "$STATUS" != "200" ]
-then
- echo 'Expected 200, reserve created. Got instead: ' $STATUS
- exit 1
-fi
-
-echo "OK"
-
-RESERVE_PUB=`jq -r .reserve_pub < $LAST_RESPONSE`
-
-STATUS=$(curl 'http://localhost:9966/instances/default/private/reserves/'$RESERVE_PUB \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
-
-FUNDED=`jq -r '.merchant_initial_amount == .exchange_initial_amount' < $LAST_RESPONSE`
-
-if [ "$FUNDED" != "false" ]
-then
- echo 'Should not yet be funded if we just created. Got:' $STATUS 'is founded: ' $FUNDED
- cat $LAST_RESPONSE
- exit 1
-fi
-
-
-echo -n "Wire transferring... "
-# Exchange wants TESTKUDOS:2 from account 43, under RESERVE_PUB.
-
-EXCHANGE_PAYTO=`get_payto_uri exchange x`
-export LIBEUFIN_SANDBOX_USERNAME=fortythree
-export LIBEUFIN_SANDBOX_PASSWORD=x
-export LIBEUFIN_SANDBOX_URL="http://localhost:18082/"
-libeufin-cli sandbox demobank new-transaction \
- --bank-account fortythree \
- --payto-with-subject "$EXCHANGE_PAYTO&message=$RESERVE_PUB" \
- --amount TESTKUDOS:2
-unset LIBEUFIN_SANDBOX_USERNAME
-unset LIBEUFIN_SANDBOX_PASSWORD
-unset LIBEUFIN_SANDBOX_URL
-echo "OK"
-echo -n "Give Nexus time to detect the payment... "
-sleep 10 # FIXME-MS: replace with call to Nexus to right now poll the sandbox ...
-echo "OK"
-
-# Stop existing background service, we need to run it here, now, and only once
-kill -TERM $WIREWATCH_PID
-wait $WIREWATCH_PID
-
-taler-exchange-wirewatch -c $CONF -t -L INFO &> taler-exchange-wirewatch.log
-
-STATUS=$(curl 'http://localhost:9966/instances/default/private/reserves/'$RESERVE_PUB \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
-
-FUNDED=`jq -r '.merchant_initial_amount == .exchange_initial_amount' < $LAST_RESPONSE`
-
-if [ "$FUNDED" != "true" ]
-then
- echo 'should be funded. got:' $STATUS 'is founded: ' $FUNDED
- cat $LAST_RESPONSE
- exit 1
-fi
-
-
-echo -n "authorizing tip ..."
-
-STATUS=$(curl 'http://localhost:9966/instances/default/private/reserves/'$RESERVE_PUB'/authorize-tip' \
- -d '{"amount":"TESTKUDOS:1","justification":"off course","next_url":"https://taler.net/"}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
-
-if [ "$STATUS" != "200" ]
-then
- echo 'should respond failed, we did not fund yet. got:' $STATUS
- exit 1
-fi
-
-echo OK
-
-echo -n Checking tip ....
-STATUS=$(curl 'http://localhost:9966/instances/default/private/reserves/'$RESERVE_PUB'?tips=yes' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
-
-TIPS_SIZE=`jq -r ".tips | length" < $LAST_RESPONSE`
-
-if [ "$TIPS_SIZE" != "1" ]
-then
- echo 'should respond 1, just 1 tip. got:' $TIPS_SIZE
- cat $LAST_RESPONSE
- exit 1
-fi
-
-TIP_ID=`jq -r .tips[0].tip_id < $LAST_RESPONSE`
-
-echo found
-
-echo -n Checking tip status ....
-
-STATUS=$(curl 'http://localhost:9966/instances/default/private/tips/'$TIP_ID \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
-
-if [ "$STATUS" != "200" ]
-then
- echo 'should respond ok, tip found. got:' $STATUS
- cat $LAST_RESPONSE
- exit 1
-fi
-
-echo -n " ... "
-
-STATUS=$(curl 'http://localhost:9966/instances/default/private/tips/'$TIP_ID'?pickups=yes' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
-
-if [ "$STATUS" != "200" ]
-then
- echo 'should respond ok, tip found. got:' $STATUS
- cat $LAST_RESPONSE
- exit 1
-fi
-
-echo OK
-
-echo -n "trying to create invalid reserve ..."
-
-STATUS=$(curl 'http://localhost:9966/instances/default/private/reserves' \
- -d '{"initial_balance":"INVALID:2","exchange_url":"'$EXCHANGE_URL'","wire_method":"iban"}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
-
-if [ "$STATUS" != "400" ]
-then
- echo 'should respond invalid, bad currency. got:' $STATUS
- exit 1
-fi
-
-echo "FAILED (which is ok)"
-
-
-exit 0
diff --git a/src/testing/test_merchant_transfer_tracking.sh b/src/testing/test_merchant_transfer_tracking.sh
index 8fb7be64..41a20c11 100755
--- a/src/testing/test_merchant_transfer_tracking.sh
+++ b/src/testing/test_merchant_transfer_tracking.sh
@@ -1,6 +1,6 @@
#!/bin/bash
# This file is part of TALER
-# Copyright (C) 2014-2021 Taler Systems SA
+# Copyright (C) 2014-2024 Taler Systems SA
#
# TALER is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
@@ -16,23 +16,82 @@
# License along with TALER; see the file COPYING. If not, see
# <http://www.gnu.org/licenses/>
#
-# Testcase for #6912 (failed to reproduce so far)
+# Testcase for #6912 and #8061
-. initialize_taler_system.sh
+set -eu
+
+. setup.sh
+
+# Replace with 0 for nexus...
+USE_FAKEBANK=1
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ ACCOUNT="exchange-account-2"
+ WIRE_METHOD="x-taler-bank"
+ BANK_FLAGS="-f -d $WIRE_METHOD -u $ACCOUNT"
+ BANK_URL="http://localhost:8082/"
+else
+ echo -n "Testing for libeufin-bank"
+ libeufin-bank --help >/dev/null </dev/null || exit_skip " MISSING"
+ echo " FOUND"
+ ACCOUNT="exchange-account-1"
+ WIRE_METHOD="iban"
+ BANK_FLAGS="-ns -d $WIRE_METHOD -u $ACCOUNT"
+ BANK_URL="http://localhost:18082/"
+fi
+
+
+echo -n "Testing for taler-harness"
+taler-harness --help >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+
+# Launch system.
+setup -c "test_template.conf" \
+ -em \
+ $BANK_FLAGS
+LAST_RESPONSE=$(mktemp -p "${TMPDIR:-/tmp}" test_response.conf-XXXXXX)
+WALLET_DB=$(mktemp -p "${TMPDIR:-/tmp}" test_wallet.json-XXXXXX)
+CONF="test_template.conf.edited"
+EXCHANGE_URL="http://localhost:8081/"
echo -n "First prepare wallet with coins..."
-rm $WALLET_DB
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api --expect-success 'withdrawTestBalance' \
+rm -f "$WALLET_DB"
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ api \
+ --expect-success 'withdrawTestBalance' \
"$(jq -n '
{
amount: "TESTKUDOS:99",
- bankBaseUrl: $BANK_URL,
+ corebankApiBaseUrl: $BANK_URL,
exchangeBaseUrl: $EXCHANGE_URL
}' \
- --arg BANK_URL "$BANK_URL/access-api/" \
+ --arg BANK_URL "${BANK_URL}" \
--arg EXCHANGE_URL "$EXCHANGE_URL"
)" 2>wallet-withdraw-1.err >wallet-withdraw-1.out
-taler-wallet-cli --wallet-db=$WALLET_DB run-until-done 2>wallet-withdraw-finish-1.err >wallet-withdraw-finish-1.out
+echo -n "."
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ # Fakebank is instant...
+ sleep 0
+else
+ sleep 10
+ # 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
+fi
+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 -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"
#
@@ -40,104 +99,143 @@ echo " OK"
#
echo -n "Configuring merchant default instance ..."
-TOR_PAYTO=`get_payto_uri tor x`
-GNUNET_PAYTO=`get_payto_uri gnunet x`
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ TOR_PAYTO="payto://x-taler-bank/localhost/tor?receiver-name=tor"
+ GNUNET_PAYTO="payto://x-taler-bank/localhost/gnunet?receiver-name=gnunet"
+ SURVEY_PAYTO="payto://x-taler-bank/localhost/survey?receiver-name=survey"
+ TUTORIAL_PAYTO="payto://x-taler-bank/localhost/tutorial?receiver-name=tutorial"
+else
+ TOR_PAYTO=$(get_payto_uri tor x)
+ GNUNET_PAYTO=$(get_payto_uri gnunet x)
+ SURVEY_PAYTO=$(get_payto_uri survey x)
+ TUTORIAL_PAYTO=$(get_payto_uri tutorial x)
+fi
# create with 2 address
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"},"payto_uris":["'$TOR_PAYTO'","'$GNUNET_PAYTO'"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
+ -d '{"auth":{"method":"external"},"id":"default","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance created. got:' $STATUS
- exit 1
+ exit_fail "Expected 204, instance created. got: $STATUS"
fi
-echo OK
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ -H 'Authorization: Bearer secret-token:super_secret' \
+ http://localhost:9966/private/accounts \
+ -d '{"payto_uri":"'"$TOR_PAYTO"'"}' \
+ -w "%{http_code}" -s -o /dev/null)
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected 200 OK. Got: $STATUS"
+fi
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ -H 'Authorization: Bearer secret-token:super_secret' \
+ http://localhost:9966/private/accounts \
+ -d '{"payto_uri":"'"$GNUNET_PAYTO"'"}' \
+ -w "%{http_code}" -s -o /dev/null)
+
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected 200 OK. Got: $STATUS"
+fi
+
+echo "OK"
echo -n "Configuring merchant test instance ..."
-SURVEY_PAYTO=`get_payto_uri survey x`
-TUTORIAL_PAYTO=`get_payto_uri tutorial x`
# create with 2 address
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"},"payto_uris":["'$SURVEY_PAYTO'","'$TUTORIAL_PAYTO'"],"id":"test","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
+ -d '{"auth":{"method":"external"},"id":"test","name":"test","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
-w "%{http_code}" -s -o /dev/null)
if [ "$STATUS" != "204" ]
then
- echo 'should respond ok, instance created. got:' $STATUS
- exit 1
+ exit_fail "Expected 204, instance created. got: $STATUS"
+fi
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ -H 'Authorization: Bearer secret-token:super_secret' \
+ http://localhost:9966/instances/test/private/accounts \
+ -d '{"payto_uri":"'"$SURVEY_PAYTO"'","credit_facade_url":"http://localhost:8082/accounts/survey/taler-revenue/","credit_facade_credentials":{"type":"basic","username":"survey","password":"x"}}' \
+ -w "%{http_code}" -s -o /dev/null)
+
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected 200 OK. Got: $STATUS"
fi
-echo OK
-RANDOM_IMG='data:image/png;base64,abcdefg'
# CREATE ORDER AND SELL IT
echo -n "Creating order to be paid..."
STATUS=$(curl 'http://localhost:9966/instances/test/private/orders' \
-d '{"order":{"amount":"TESTKUDOS:1","summary":"payme"}}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should respond ok, order created. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, order created. got: $STATUS"
fi
-ORDER_ID=`jq -e -r .order_id < $LAST_RESPONSE`
-TOKEN=`jq -e -r .token < $LAST_RESPONSE`
+ORDER_ID=$(jq -e -r .order_id < "$LAST_RESPONSE")
+#TOKEN=$(jq -e -r .token < "$LAST_RESPONSE")
STATUS=$(curl "http://localhost:9966/instances/test/private/orders/${ORDER_ID}" \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should respond ok, getting order info before claming it. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, getting order info before claming it. got: $STATUS"
fi
-PAY_URL=`jq -e -r .taler_pay_uri < $LAST_RESPONSE`
-echo OK
+PAY_URL=$(jq -e -r .taler_pay_uri < "$LAST_RESPONSE")
+echo "OK"
-NOW=`date +%s`
+NOW=$(date +%s)
echo -n "Pay first order ..."
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB handle-uri "${PAY_URL}" -y 2> wallet-pay1.err > wallet-pay1.log
-NOW2=`date +%s`
-echo " OK (took $( echo -n $(($NOW2 - $NOW))) secs)"
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ handle-uri "${PAY_URL}" \
+ -y \
+ 2> wallet-pay1.err > wallet-pay1.log
+NOW2=$(date +%s)
+echo " OK (took $(( NOW2 - NOW )) secs)"
STATUS=$(curl "http://localhost:9966/instances/test/private/orders/${ORDER_ID}" \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- echo 'should respond ok, after pay. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, after pay. got: $STATUS"
fi
-ORDER_STATUS=`jq -r .order_status < $LAST_RESPONSE`
+ORDER_STATUS=$(jq -r .order_status < "$LAST_RESPONSE")
if [ "$ORDER_STATUS" != "paid" ]
then
- echo 'order should be paid. got:' $ORDER_STATUS `cat $LAST_RESPONSE`
- exit 1
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected '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`
+#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`
+NOW=$(date +%s)
-TO_SLEEP=`echo $(( $WIRE_DEADLINE - $NOW ))`
+TO_SLEEP=$(( WIRE_DEADLINE - NOW ))
echo "waiting $TO_SLEEP secs for wire transfer"
echo -n "Perform wire transfers ..."
@@ -145,124 +243,111 @@ taler-exchange-aggregator -y -c $CONF -T ${TO_SLEEP}000000 -t -L INFO &> aggrega
taler-exchange-transfer -c $CONF -t -L INFO &> transfer.log
echo " DONE"
-echo -n "waiting for Nexus and Sandbox to settle the payment .."
-sleep 3
-echo " DONE"
-
echo -n "Obtaining wire transfer details from bank..."
-# 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.
-export BANKDATA=`get_bankaccount_transactions exchange x | jq '.transactions[0]'`
-export SUBJECT=`echo $BANKDATA | jq -r .subject`
-export WTID=`echo $SUBJECT | awk '{print $1}'`
-export WURL=`echo $SUBJECT | awk '{print $2}'`
-export CREDIT_AMOUNT="`echo $BANKDATA | jq -r .currency`:`echo $BANKDATA | jq -r .amount`"
-export TARGET=`echo $BANKDATA | jq -r .creditorIban`
+
+BANKDATA="$(curl 'http://localhost:8082/accounts/exchange/taler-wire-gateway/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)
+TARGET=$(echo "$TARGET_PAYTO" | awk -F = '{print $2}')
+
# Figure out which account got paid, in order to
# resort the right (and complete: including the receiver-name)
# TARGET_PAYTO
-if `echo $SURVEY_PAYTO | grep -q $TARGET`; then
- export TARGET_PAYTO=$SURVEY_PAYTO;
+if echo "$SURVEY_PAYTO" | grep -q "$TARGET" > /dev/null; then
+ TARGET_PAYTO="$SURVEY_PAYTO";
fi
-if `echo $TUTORIAL_PAYTO | grep -q $TARGET`; then
- export TARGET_PAYTO=$TUTORIAL_PAYTO;
+if echo "$SURVEY_PAYTO" | grep -q "$TARGET" > /dev/null; then
+ TARGET_PAYTO="$SURVEY_PAYTO";
fi
-echo " DONE"
if [ "$EXCHANGE_URL" != "$WURL" ]
then
exit_fail "Wrong exchange URL in subject '$SUBJECT', expected $EXCHANGE_URL"
fi
-
echo " OK"
set +e
-export TARGET_PAYTO
-export WURL
-export WTID
-export CREDIT_AMOUNT
-export LAST_RESPONSE
-
echo -n "Notifying merchant of correct wire transfer, but on wrong instance..."
#issue 6912
#here we are notifying the transfer into a wrong instance (default) and the payto_uri of the default instance
-STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \
+STATUS=$(curl 'http://localhost:9966/private/transfers' \
-d "{\"credit_amount\":\"$CREDIT_AMOUNT\",\"wtid\":\"$WTID\",\"payto_uri\":\"$TOR_PAYTO\",\"exchange_url\":\"$WURL\"}" \
-m 3 \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
-if [ "$STATUS" != "200" ]
+if [ "$STATUS" != "204" ]
then
- jq . < $LAST_RESPONSE
- exit_fail "Expected response ok, after providing transfer data. got: $STATUS"
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected response 204 no content, after providing transfer data. Got: $STATUS"
fi
echo " OK"
echo -n "Fetching wire transfers of DEFAULT instance ..."
-STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl 'http://localhost:9966/private/transfers' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- jq . < $LAST_RESPONSE
+ jq . < "$LAST_RESPONSE"
exit_fail "Expected response 200 Ok. got: $STATUS"
fi
-TRANSFERS_LIST_SIZE=`jq -r '.transfers | length' < $LAST_RESPONSE`
+TRANSFERS_LIST_SIZE=$(jq -r '.transfers | length' < "$LAST_RESPONSE")
if [ "$TRANSFERS_LIST_SIZE" != "1" ]
then
- jq . < $LAST_RESPONSE
+ jq . < "$LAST_RESPONSE"
exit_fail "Expected one transfer. got: $TRANSFERS_LIST_SIZE"
fi
echo "OK"
+echo -n "Fetching running taler-merchant-exchange on bogus transfer ..."
+taler-merchant-exchange -c "$CONF" -L INFO -t &> taler-merchant-exchange-bad.log
+echo "OK"
+
echo -n "Fetching wire transfers of 'test' instance ..."
STATUS=$(curl 'http://localhost:9966/instances/test/private/transfers' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- jq . < $LAST_RESPONSE
+ jq . < "$LAST_RESPONSE"
exit_fail "Expected response 200 Ok. got: $STATUS"
fi
-TRANSFERS_LIST_SIZE=`jq -r '.transfers | length' < $LAST_RESPONSE`
+TRANSFERS_LIST_SIZE=$(jq -r '.transfers | length' < "$LAST_RESPONSE")
if [ "$TRANSFERS_LIST_SIZE" != "0" ]
then
- jq . < $LAST_RESPONSE
- exit_fail "Expected response ok. got: $STATUS"
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected non-empty transfer list size. got: $TRANSFERS_LIST_SIZE"
fi
echo "OK"
-
echo -n "Checking order status ..."
STATUS=$(curl "http://localhost:9966/instances/test/private/orders/${ORDER_ID}?transfer=YES" \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- jq . < $LAST_RESPONSE
- exit_fail 'should response ok, after order inquiry. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, after order inquiry. got: $STATUS"
fi
-WAS_WIRED=`jq -r .wired < $LAST_RESPONSE`
+WAS_WIRED=$(jq -r .wired < "$LAST_RESPONSE")
if [ "$WAS_WIRED" == "true" ]
then
- jq . < $LAST_RESPONSE
- echo '.wired true, expected false'
- exit 1
+ jq . < "$LAST_RESPONSE"
+ exit_fail ".wired is true, expected false"
fi
echo " OK"
@@ -271,58 +356,357 @@ echo -n "Notifying merchant of correct wire transfer in the correct instance..."
#this time in the correct instance so the order will be marked as wired...
STATUS=$(curl 'http://localhost:9966/instances/test/private/transfers' \
- -d '{"credit_amount":"'$CREDIT_AMOUNT'","wtid":"'$WTID'","payto_uri":"'$TARGET_PAYTO'","exchange_url":"'$WURL'"}' \
+ -d '{"credit_amount":"'"$CREDIT_AMOUNT"'","wtid":"'"$WTID"'","payto_uri":"'"$TARGET_PAYTO"'","exchange_url":"'"$WURL"'"}' \
-m 3 \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
-if [ "$STATUS" != "200" ]
+if [ "$STATUS" != "204" ]
then
- jq . < $LAST_RESPONSE
- exit_fail "Expected response ok, after providing transfer data. got: $STATUS"
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected response 204 no content, after providing transfer data. got: $STATUS"
fi
echo " OK"
+echo -n "Fetching running taler-merchant-exchange on good transfer ..."
+taler-merchant-exchange -c $CONF -L INFO -t &> taler-merchant-exchange-bad.log
+echo "OK"
+
echo -n "Fetching wire transfers of TEST instance ..."
STATUS=$(curl 'http://localhost:9966/instances/test/private/transfers' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- jq . < $LAST_RESPONSE
+ jq . < "$LAST_RESPONSE"
exit_fail "Expected response 200 Ok. got: $STATUS"
fi
-TRANSFERS_LIST_SIZE=`jq -r '.transfers | length' < $LAST_RESPONSE`
+TRANSFERS_LIST_SIZE=$(jq -r '.transfers | length' < "$LAST_RESPONSE")
if [ "$TRANSFERS_LIST_SIZE" != "1" ]
then
- jq . < $LAST_RESPONSE
+ jq . < "$LAST_RESPONSE"
exit_fail "Expected one transfer. got: $TRANSFERS_LIST_SIZE"
fi
echo "OK"
echo -n "Checking order status ..."
-STATUS=$(curl "http://localhost:9966/instances/test/private/orders/${ORDER_ID}?transfer=YES" \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+STATUS=$(curl "http://localhost:9966/instances/test/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
- jq . < $LAST_RESPONSE
- exit_fail 'should response ok, after order inquiry. got:' $STATUS `cat $LAST_RESPONSE`
- exit 1
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, after order inquiry. got: $STATUS"
fi
-WAS_WIRED=`jq -r .wired < $LAST_RESPONSE`
+WAS_WIRED=$(jq -r .wired < "$LAST_RESPONSE")
if [ "$WAS_WIRED" != "true" ]
then
- jq . < $LAST_RESPONSE
- echo '.wired false, expected true'
- exit 1
+ jq . < "$LAST_RESPONSE"
+ exit_fail ".wired false, expected true"
+fi
+
+echo " OK"
+
+
+echo "================== 2nd order ====================== "
+
+
+
+# CREATE ORDER AND SELL IT
+echo -n "Creating 2nd order to be paid..."
+STATUS=$(curl 'http://localhost:9966/instances/test/private/orders' \
+ -d '{"order":{"amount":"TESTKUDOS:2","summary":"payme"}}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, 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/test/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, 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 second order ..."
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ handle-uri "${PAY_URL}" \
+ -y \
+ 2> wallet-pay2.err > wallet-pay2.log
+NOW2=$(date +%s)
+echo " OK (took $(( NOW2 - NOW )) secs)"
+
+STATUS=$(curl "http://localhost:9966/instances/test/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, after pay. got: $STATUS"
+fi
+
+ORDER_STATUS=$(jq -r .order_status < "$LAST_RESPONSE")
+
+if [ "$ORDER_STATUS" != "paid" ]
+then
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected '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 "Pre-check for exchange deposit ..."
+taler-merchant-depositcheck -c $CONF -t -L INFO &> depositcheck2a.log
+echo " DONE"
+
+echo -n "Perform wire transfers ..."
+taler-exchange-aggregator -y -c $CONF -T ${TO_SLEEP}000000 -t -L INFO &> aggregator2.log
+taler-exchange-transfer -c $CONF -t -L INFO &> transfer2.log
+echo " DONE"
+
+echo -n "Post-check for exchange deposit ..."
+taler-merchant-depositcheck -c $CONF -t -T ${TO_SLEEP}000000 -L INFO &> depositcheck2b.log
+echo " DONE"
+
+
+echo -n "Obtaining wire transfer details from bank..."
+
+BANKDATA="$(curl 'http://localhost:8082/accounts/exchange/taler-wire-gateway/history/outgoing?delta=2' -s)"
+
+WTID=$(echo "$BANKDATA" | jq -r .outgoing_transactions[1].wtid)
+WURL=$(echo "$BANKDATA" | jq -r .outgoing_transactions[1].exchange_base_url)
+CREDIT_AMOUNT=$(echo "$BANKDATA" | jq -r .outgoing_transactions[1].amount)
+TARGET_PAYTO=$(echo "$BANKDATA" | jq -r .outgoing_transactions[1].credit_account)
+TARGET=$(echo "$TARGET_PAYTO" | awk -F = '{print $2}')
+
+# Figure out which account got paid, in order to
+# resort the right (and complete: including the receiver-name)
+# TARGET_PAYTO
+if echo "$SURVEY_PAYTO" | grep -q "$TARGET" > /dev/null; then
+ TARGET_PAYTO="$SURVEY_PAYTO";
+fi
+if echo "$SURVEY_PAYTO" | grep -q "$TARGET" > /dev/null; then
+ TARGET_PAYTO="$SURVEY_PAYTO";
+fi
+if [ "$EXCHANGE_URL" != "$WURL" ]
+then
+ exit_fail "Wrong exchange URL in subject '$SUBJECT', expected $EXCHANGE_URL"
+fi
echo " OK"
+echo -n "Notifying merchant of correct wire transfer in the correct instance..."
+#this time in the correct instance so the order will be marked as wired...
+
+STATUS=$(curl 'http://localhost:9966/instances/test/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 "Fetching running taler-merchant-exchange on good transfer ..."
+taler-merchant-exchange -c $CONF -L INFO -t &> taler-merchant-exchange2.log
+echo "OK"
+
+echo -n "Fetching wire transfers of TEST instance ..."
+
+STATUS=$(curl 'http://localhost:9966/instances/test/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" != "2" ]
+then
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected two transfers. got: $TRANSFERS_LIST_SIZE"
+fi
+
+echo "OK"
+
+echo -n "Checking order status ..."
+STATUS=$(curl "http://localhost:9966/instances/test/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, after order inquiry. got: $STATUS"
+fi
+
+WAS_WIRED=$(jq -r .wired < "$LAST_RESPONSE")
+
+if [ "$WAS_WIRED" != "true" ]
+then
+ jq . < "$LAST_RESPONSE"
+ exit_fail ".wired false, expected true"
+fi
+
+echo " OK"
+
+echo "================== 3rd order ====================== "
+
+# CREATE ORDER AND SELL IT
+echo -n "Creating 3rd order to be paid..."
+STATUS=$(curl 'http://localhost:9966/instances/test/private/orders' \
+ -d '{"order":{"amount":"TESTKUDOS:3","summary":"payme"}}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, 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/test/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, 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 third order ..."
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ handle-uri "${PAY_URL}" \
+ -y \
+ 2> wallet-pay2.err > wallet-pay2.log
+NOW2=$(date +%s)
+echo " OK (took $(( NOW2 - NOW )) secs)"
+
+STATUS=$(curl "http://localhost:9966/instances/test/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, after pay. got: $STATUS"
+fi
+
+ORDER_STATUS=$(jq -r .order_status < "$LAST_RESPONSE")
+
+if [ "$ORDER_STATUS" != "paid" ]
+then
+ cat "$LAST_RESPONSE"
+ exit_fail "Expected '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 &> aggregator3.log
+taler-exchange-transfer -c $CONF -t -L INFO &> transfer3.log
+echo " DONE"
+
+echo -n "Running taler-merchant-wirewatch to check transfer ..."
+taler-merchant-wirewatch -c $CONF -t -L INFO &> taler-merchant-wirewatch.log
+echo " DONE"
+
+echo -n "Post-wirewatch check for exchange deposit ..."
+taler-merchant-depositcheck -c $CONF -t -T ${TO_SLEEP}000000 -L INFO &> depositcheck2b.log
+echo " DONE"
+
+echo -n "Fetching wire transfers of TEST instance ..."
+
+STATUS=$(curl 'http://localhost:9966/instances/test/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" != "3" ]
+then
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected three transfers. got: $TRANSFERS_LIST_SIZE"
+fi
+
+echo "OK"
+
+echo -n "Fetching running taler-merchant-exchange on good transfer ..."
+taler-merchant-exchange -c $CONF -L INFO -t &> taler-merchant-exchange2.log
+echo "OK"
+
+echo -n "Checking order status ..."
+STATUS=$(curl "http://localhost:9966/instances/test/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok, after order inquiry. got: $STATUS"
+fi
+
+WAS_WIRED=$(jq -r .wired < "$LAST_RESPONSE")
+
+if [ "$WAS_WIRED" != "true" ]
+then
+ jq . < "$LAST_RESPONSE"
+ exit_fail ".wired false, expected true"
+fi
+
+echo " OK"
+
+
exit 0
diff --git a/src/testing/test_merchant_wirewatch.sh b/src/testing/test_merchant_wirewatch.sh
new file mode 100755
index 00000000..61bd2049
--- /dev/null
+++ b/src/testing/test_merchant_wirewatch.sh
@@ -0,0 +1,376 @@
+#!/bin/bash
+# This file is part of TALER
+# Copyright (C) 2014-2023 Taler Systems SA
+#
+# TALER is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 3, or
+# (at your option) any later version.
+#
+# TALER is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with TALER; see the file COPYING. If not, see
+# <http://www.gnu.org/licenses/>
+#
+# Testcase for #6363 (WiP)
+set -eu
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo $1
+ exit 77
+}
+
+echo -n "Testing for taler-harness"
+taler-harness --help >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+
+
+# Replace with 0 for nexus...
+USE_FAKEBANK=1
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ ACCOUNT="exchange-account-2"
+ WIRE_METHOD="x-taler-bank"
+ BANK_FLAGS="-f -d $WIRE_METHOD -u $ACCOUNT"
+ BANK_URL="http://localhost:8082/"
+else
+ echo -n "Testing for libeufin-bank"
+ libeufin-bank --help >/dev/null </dev/null || exit_skip " MISSING"
+ echo " FOUND"
+
+ ACCOUNT="exchange-account-1"
+ WIRE_METHOD="iban"
+ BANK_FLAGS="-ns -d $WIRE_METHOD -u $ACCOUNT"
+ BANK_URL="http://localhost:18082/"
+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/"
+
+
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ FACADE_URL="http://localhost:8082/accounts/gnunet/taler-revenue/"
+ FACADE_USERNAME="gnunet"
+ FACADE_PASSWORD="x"
+else
+ echo "not implemented for current libeufin-bank"
+ exit 1
+ export LIBEUFIN_SANDBOX_DB_CONNECTION='postgresql:///talercheck'
+ export LIBEUFIN_SANDBOX_ADMIN_PASSWORD="secret"
+ export LIBEUFIN_SANDBOX_URL="http://localhost:18082/"
+
+ export EBICS_HOST="talerebics"
+ export LIBEUFIN_SANDBOX_USERNAME="admin"
+ export LIBEUFIN_SANDBOX_PASSWORD="secret"
+ export EBICS_USER_ID="gnunet_ebics"
+ export EBICS_PARTNER="GnunetPartner"
+ export BANK_CONNECTION_NAME="gnunet-connection"
+ export NEXUS_ACCOUNT_NAME="GnunetCredit"
+ # The 'gnunet' account is created by
+ # taler-bank-manage-testing and used for
+ # the 'default' instance, so this must be used here.
+ export SANDBOX_ACCOUNT_NAME="gnunet"
+
+ export LIBEUFIN_NEXUS_URL="http://localhost:8082"
+ # These two are from taler-bank-manage-testing...
+
+ # Define credentials for wirewatch user, will be Merchant client.
+ CREDIT_USERNAME="merchant-wirewatch"
+ CREDIT_PASSWORD="merchant-wirewatch-password"
+
+ echo -n "Create credit user (for gnunet-merchant) at Nexus ..."
+
+ export LIBEUFIN_NEXUS_DB_CONNECTION='postgresql:///talercheck'
+ libeufin-nexus \
+ superuser "$CREDIT_USERNAME" \
+ --password="$CREDIT_PASSWORD" \
+ &> nexus-credit-create.log
+ echo " OK"
+ export LIBEUFIN_NEXUS_USERNAME="$CREDIT_USERNAME"
+ export LIBEUFIN_NEXUS_PASSWORD="$CREDIT_PASSWORD"
+ export GNUNET_CREDIT_FACADE=facade-gnunet-credit
+
+ libeufin-cli sandbox \
+ demobank \
+ new-ebicssubscriber \
+ --host-id ${EBICS_HOST} \
+ --user-id ${NEXUS_ACCOUNT_NAME} \
+ --partner-id ${EBICS_PARTNER} \
+ --bank-account ${SANDBOX_ACCOUNT_NAME} \
+ &> sandbox-subscriber-create.log
+
+ libeufin-cli \
+ connections \
+ new-ebics-connection \
+ --ebics-url="${LIBEUFIN_SANDBOX_URL}ebicsweb" \
+ --host-id=${EBICS_HOST} \
+ --partner-id=${EBICS_PARTNER} \
+ --ebics-user-id=${NEXUS_ACCOUNT_NAME} \
+ ${BANK_CONNECTION_NAME} \
+ &> nexus-connection-create.log
+
+ libeufin-cli \
+ connections \
+ connect \
+ ${BANK_CONNECTION_NAME} \
+ &> nexus-connection-connect.log
+
+ libeufin-cli \
+ connections \
+ download-bank-accounts \
+ ${BANK_CONNECTION_NAME} \
+ &> nexus-account-download.log
+
+ libeufin-cli \
+ connections \
+ import-bank-account \
+ --offered-account-id=${SANDBOX_ACCOUNT_NAME} \
+ --nexus-bank-account-id=${NEXUS_ACCOUNT_NAME} \
+ ${BANK_CONNECTION_NAME} \
+ &> nexus-account-import.log
+
+ libeufin-cli \
+ facades \
+ new-anastasis-facade \
+ --currency=TESTKUDOS \
+ --facade-name=${GNUNET_CREDIT_FACADE} \
+ ${BANK_CONNECTION_NAME} \
+ ${NEXUS_ACCOUNT_NAME} \
+ &> nexus-new-facade.log
+
+ FACADE_URL="http://localhost:18082/accounts/admin/taler-revenue/"
+ # WAS: $(libeufin-cli facades list | jq .facades[0].baseUrl | tr -d \")
+
+ # FIXME: is this correct? Strange to use the super-user
+ # credentials here!
+ FACADE_USERNAME="${CREDIT_USERNAME}"
+ FACADE_PASSWORD="${CREDIT_PASSWORD}"
+fi
+
+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",
+ corebankApiBaseUrl: $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 "."
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ # Fakebank is instant...
+ sleep 0
+else
+ sleep 10
+ # 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
+fi
+echo -n "."
+taler-exchange-wirewatch \
+ -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"
+
+#
+# CREATE INSTANCE FOR TESTING
+#
+
+echo -n "Configuring merchant default instance ..."
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ GNUNET_PAYTO="payto://x-taler-bank/localhost/gnunet?receiver-name=gnunet"
+else
+ GNUNET_PAYTO=$(get_payto_uri gnunet x)
+fi
+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"},"id":"default","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
+ -w "%{http_code}" -s -o /dev/null)
+
+if [ "$STATUS" != "204" ]
+then
+ exit_fail "Expected 204 no content. Got: $STATUS"
+fi
+echo "OK"
+
+echo -n "Configuring bank account..."
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ -H 'Authorization: Bearer secret-token:super_secret' \
+ http://localhost:9966/private/accounts \
+ -d '{"payto_uri":"'"$GNUNET_PAYTO"'","credit_facade_url":"'"${FACADE_URL}"'","credit_facade_credentials":{"type":"basic","username":"'"$FACADE_USERNAME"'","password":"'"$FACADE_PASSWORD"'"}}' \
+ -w "%{http_code}" -s -o /dev/null)
+
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected 200 OK. Got: $STATUS"
+fi
+
+echo "OK"
+
+# CREATE ORDER AND SELL IT
+echo -n "Creating order to be paid..."
+STATUS=$(curl 'http://localhost:9966/private/orders' \
+ -d '{"order":{"amount":"TESTKUDOS:1","summary":"payme"}}' \
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected 200 OK. Got: $STATUS " "$(cat "$LAST_RESPONSE")"
+fi
+
+ORDER_ID=$(jq -e -r .order_id < "$LAST_RESPONSE")
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected 200 ok. Got: $STATUS" "$(cat "$LAST_RESPONSE")"
+fi
+PAY_URL=$(jq -e -r .taler_pay_uri < "$LAST_RESPONSE")
+echo OK
+
+NOW=$(date +%s)
+echo -n "Pay first order ..."
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ handle-uri "${PAY_URL}" \
+ -y 2> wallet-pay1.err > wallet-pay1.log
+NOW2=$(date +%s)
+echo "OK. Took $(( NOW2 - NOW))s."
+
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected 200 Ok. Got: $STATUS" "$(cat "$LAST_RESPONSE")"
+fi
+
+ORDER_STATUS=$(jq -r .order_status < "$LAST_RESPONSE")
+if [ "$ORDER_STATUS" != "paid" ]
+then
+ exit_fail "Expected order status 'paid'. Got: $ORDER_STATUS" "$(cat "$LAST_RESPONSE")"
+fi
+
+#
+# WIRE TRANSFER TO MERCHANT AND NOTIFY BACKEND
+#
+
+WIRE_DEADLINE=$(jq -r .contract_terms.wire_transfer_deadline.t_s < "$LAST_RESPONSE")
+NOW=$(date +%s)
+
+TO_SLEEP="$(( 1 + WIRE_DEADLINE - NOW ))"
+echo -n "Perform wire transfers (with ${TO_SLEEP}s timeshift) ..."
+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"
+
+if [ 1 != "$USE_FAKEBANK" ]
+then
+ echo -n "Waiting for Nexus and Sandbox to settle the payment ..."
+ sleep 3 # FIXME-MS: replace with call to Nexus to right now poll the sandbox ...
+ libeufin-cli \
+ accounts \
+ fetch-transactions \
+ ${NEXUS_ACCOUNT_NAME} \
+ &> libeufin-transfer-fetch.out
+ echo " DONE"
+fi
+
+echo -n "Obtaining wire transfer details from bank..."
+taler-merchant-wirewatch \
+ -c "$CONF" \
+ -t \
+ -L INFO &> merchant-wirewatch.log
+echo " OK"
+
+echo -n "Fetching wire transfers of DEFAULT instance ..."
+STATUS=$(curl 'http://localhost:9966/private/transfers' \
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected response 200 Ok. Got: $STATUS" "$(jq . < "$LAST_RESPONSE")"
+fi
+TRANSFERS_LIST_SIZE=$(jq -r '.transfers | length' < "$LAST_RESPONSE")
+if [ "$TRANSFERS_LIST_SIZE" != "1" ]
+then
+ exit_fail "Expected one transfer. Got: $TRANSFERS_LIST_SIZE" "$(jq . < "$LAST_RESPONSE")"
+fi
+echo " OK"
+
+echo -n "Integrating wire transfer data with exchange..."
+taler-merchant-exchange \
+ -c "$CONF" \
+ -t \
+ -L INFO &> merchant-exchange.log
+echo " OK"
+
+echo -n "Checking order status ..."
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}?transfer=YES" \
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected 200 ok. got: $STATUS" "$(cat "$LAST_RESPONSE")"
+fi
+
+WAS_WIRED=$(jq -r .wired < "$LAST_RESPONSE")
+if [ "$WAS_WIRED" != "true" ]
+then
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Got .wired 'false', expected 'true'"
+fi
+echo " OK"
+
+exit 0
diff --git a/src/testing/test_template.conf b/src/testing/test_template.conf
index 21bb6186..8f3dc4da 100644
--- a/src/testing/test_template.conf
+++ b/src/testing/test_template.conf
@@ -1,18 +1,18 @@
[PATHS]
-TALER_HOME = ${PWD}/test_reducer_home/
-TALER_DATA_HOME = $TALER_HOME/.local/share/taler/
-TALER_CONFIG_HOME = $TALER_HOME/.config/taler/
-TALER_CACHE_HOME = $TALER_HOME/.cache/taler/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/taler-system-runtime/
+TALER_TEST_HOME = test_merchant_api_home/
[taler]
CURRENCY = TESTKUDOS
CURRENCY_ROUND_UNIT = TESTKUDOS:0.01
+[merchant-exchange-kudos]
+DISABLED = YES
+
[exchange]
+AML_THRESHOLD = TESTKUDOS:1000000
MAX_KEYS_CACHING = forever
DB = postgres
-MASTER_PRIV_FILE = ${TALER_DATA_HOME}/exchange/offline-keys/master.priv
+MASTER_PUBLIC_KEY = NKX42KSCQHDQK7CF1PC6X9DMQPXW6KHXKGD3DPQJMP32FKXSWYK0
SERVE = tcp
UNIXPATH = ${TALER_RUNTIME_DIR}/exchange.http
UNIXPATH_MODE = 660
@@ -26,6 +26,9 @@ REVOCATION_DIR = ${TALER_DATA_HOME}/exchange/revocations/
TERMS_ETAG = 0
PRIVACY_ETAG = 0
+[exchangedb-postgres]
+CONFIG = postgres:///talercheck
+
[merchant]
SERVE = tcp
PORT = 9966
@@ -34,17 +37,27 @@ UNIXPATH_MODE = 660
DEFAULT_WIRE_FEE_AMORTIZATION = 1
DB = postgres
WIREFORMAT = default
-# Set very low, so we can be sure that the database generated
-# will contain wire transfers "ready" for the aggregator.
WIRE_TRANSFER_DELAY = 1 minute
DEFAULT_PAY_DEADLINE = 1 day
DEFAULT_MAX_DEPOSIT_FEE = TESTKUDOS:0.1
KEYFILE = ${TALER_DATA_HOME}/merchant/merchant.priv
DEFAULT_MAX_WIRE_FEE = TESTKUDOS:0.10
-
-# Ensure that merchant reports EVERY deposit confirmation to auditor
FORCE_AUDIT = YES
+[merchantdb-postgres]
+CONFIG = postgres:///talercheck
+SQL_DIR = $DATADIR/sql/merchant/
+
+[bank]
+HTTP_PORT = 8082
+
+[libeufin-nexus]
+DB_CONNECTION="postgresql:///talercheck"
+
+[libeufin-sandbox]
+DB_CONNECTION="postgresql:///talercheck"
+
+
[auditor]
DB = postgres
AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}/auditor/offline-keys/auditor.priv
@@ -54,7 +67,6 @@ UNIXPATH_MODE = 660
PORT = 8083
AUDITOR_URL = http://localhost:8083/
TINY_AMOUNT = TESTKUDOS:0.01
-AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}/auditor/offline-keys/auditor.priv
BASE_URL = "http://localhost:8083/"
[exchangedb]
@@ -63,18 +75,38 @@ LEGAL_RESERVE_EXPIRATION_TIME = 7 years
[exchange-account-1]
# PAYTO_URI comes by patching.
-enable_debit = yes
-enable_credit = yes
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
[exchange-accountcredentials-1]
-WIRE_GATEWAY_URL = "http://localhost:8082/facades/test-facade/taler-wire-gateway/"
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/exchange/taler-wire-gateway/"
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = exchange
+PASSWORD = x
+
+[admin-accountcredentials-1]
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/exchange/taler-wire-gateway/"
WIRE_GATEWAY_AUTH_METHOD = basic
USERNAME = exchange
PASSWORD = x
+[exchange-account-2]
+PAYTO_URI = "payto://x-taler-bank/localhost/exchange?receiver-name=exchange"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-2]
+WIRE_GATEWAY_AUTH_METHOD = none
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/exchange/taler-wire-gateway/"
+
+[admin-accountcredentials-2]
+WIRE_GATEWAY_AUTH_METHOD = none
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/exchange/taler-wire-gateway/"
+
[merchant-exchange-default]
EXCHANGE_BASE_URL = http://localhost:8081/
CURRENCY = TESTKUDOS
+MASTER_KEY = NKX42KSCQHDQK7CF1PC6X9DMQPXW6KHXKGD3DPQJMP32FKXSWYK0
[payments-generator]
currency = TESTKUDOS
diff --git a/src/testing/testing_api_cmd_abort_order.c b/src/testing/testing_api_cmd_abort_order.c
index b9f7682b..1e6da35c 100644
--- a/src/testing/testing_api_cmd_abort_order.c
+++ b/src/testing/testing_api_cmd_abort_order.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018, 2020 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -116,7 +116,6 @@ build_coins (struct TALER_MERCHANT_AbortCoin **ac,
}
{
const struct TALER_TESTING_Command *coin_cmd;
- const char **exchange_url;
coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
token);
@@ -142,8 +141,7 @@ build_coins (struct TALER_MERCHANT_AbortCoin **ac,
}
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_exchange_url (coin_cmd,
- &exchange_url));
- icoin->exchange_url = *exchange_url;
+ &icoin->exchange_url));
{
const struct TALER_Amount *denom_value;
@@ -165,48 +163,42 @@ build_coins (struct TALER_MERCHANT_AbortCoin **ac,
* in the state.
*
* @param cls closure.
- * @param hr HTTP response
- * @param merchant_pub public key of the merchant refunding the
- * contract.
- * @param num_aborts length of the @a res array
- * @param res array containing the abort confirmations
+ * @param ar response
*/
static void
abort_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- unsigned int num_aborts,
- const struct TALER_MERCHANT_AbortedCoin res[])
+ const struct TALER_MERCHANT_AbortResponse *ar)
{
struct AbortState *as = cls;
as->oah = NULL;
- if (as->http_status != hr->http_status)
+ if (as->http_status != ar->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u (%d) to command `%s' (expected %u)\n",
- hr->http_status,
- (int) hr->ec,
+ ar->hr.http_status,
+ (int) ar->hr.ec,
TALER_TESTING_interpreter_get_current_label (as->is),
as->http_status);
TALER_TESTING_FAIL (as->is);
}
- if ( (MHD_HTTP_OK == hr->http_status) &&
- (TALER_EC_NONE == hr->ec) )
+ if ( (MHD_HTTP_OK == ar->hr.http_status) &&
+ (TALER_EC_NONE == ar->hr.ec) )
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Received %u refunds\n",
- num_aborts);
- as->acs_length = num_aborts;
- as->acs = GNUNET_new_array (num_aborts,
+ ar->details.ok.num_aborts);
+ as->acs_length = ar->details.ok.num_aborts;
+ as->acs = GNUNET_new_array (as->acs_length,
struct TALER_MERCHANT_AbortedCoin);
- memcpy (as->acs,
- res,
- num_aborts * sizeof (struct TALER_MERCHANT_AbortedCoin));
+ GNUNET_memcpy (as->acs,
+ ar->details.ok.aborts,
+ as->acs_length
+ * sizeof (struct TALER_MERCHANT_AbortedCoin));
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Successful pay-abort (HTTP status: %u)\n",
- hr->http_status);
+ ar->hr.http_status);
TALER_TESTING_interpreter_next (as->is);
}
@@ -225,8 +217,8 @@ abort_run (void *cls,
{
struct AbortState *as = cls;
const struct TALER_TESTING_Command *pay_cmd;
- const char **proposal_reference;
- const char **coin_reference;
+ const char *proposal_reference;
+ const char *coin_reference;
const struct TALER_TESTING_Command *proposal_cmd;
const char *order_id;
const struct TALER_PrivateContractHashP *h_proposal;
@@ -253,7 +245,7 @@ abort_run (void *cls,
&coin_reference))
TALER_TESTING_FAIL (is);
proposal_cmd = TALER_TESTING_interpreter_lookup_command (is,
- *proposal_reference);
+ proposal_reference);
if (NULL == proposal_cmd)
TALER_TESTING_FAIL (is);
@@ -299,7 +291,7 @@ abort_run (void *cls,
}
}
- cr = GNUNET_strdup (*coin_reference);
+ cr = GNUNET_strdup (coin_reference);
abort_coins = NULL;
nabort_coins = 0;
if (GNUNET_OK !=
@@ -320,7 +312,8 @@ abort_run (void *cls,
TALER_TESTING_get_trait_h_contract_terms (proposal_cmd,
&h_proposal))
TALER_TESTING_FAIL (is);
- as->oah = TALER_MERCHANT_order_abort (is->ctx,
+ as->oah = TALER_MERCHANT_order_abort (TALER_TESTING_interpreter_get_context (
+ is),
as->merchant_url,
order_id,
&merchant_pub,
diff --git a/src/testing/testing_api_cmd_checkserver.c b/src/testing/testing_api_cmd_checkserver.c
new file mode 100644
index 00000000..5b10b1fc
--- /dev/null
+++ b/src/testing/testing_api_cmd_checkserver.c
@@ -0,0 +1,270 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file testing/testing_api_cmd_checkserver.c
+ * @brief Implement a CMD to run an Checkserver service for faking the legitimation service
+ * @author Priscilla HUANG
+ */
+#include "platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_testing_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler_merchant_testing_lib.h"
+#include "taler_merchant_service.h"
+#include <taler/taler_exchange_service.h>
+
+
+/**
+ * State for a "checkserver" CMD.
+ */
+struct CheckState
+{
+ /**
+ * Handle to the "testserver" service.
+ */
+ struct MHD_Daemon *mhd;
+
+ /**
+ * Our interpreter.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Index to know which web server we check.
+ */
+ unsigned int index;
+
+ /**
+ * Reference to command to the previous set server status operation.
+ */
+ const char *ref_operation;
+
+ /**
+ * Expected method of the pending webhook.
+ */
+ const char *expected_method;
+
+ /**
+ * Expected url of the pending webhook.
+ */
+ const char *expected_url;
+
+ /**
+ * Expected header of the pending webhook.
+ */
+ const char *expected_header;
+
+ /**
+ * Expected body of the pending webhook.
+ */
+ const char *expected_body;
+
+};
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+checkserver_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct CheckState *cs = cls;
+ const struct TALER_TESTING_Command *ref;
+ const char *url;
+ const char *http_method;
+ const char *header;
+ const void *body;
+ const size_t *body_size;
+
+ (void) cmd;
+ cs->is = is;
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ cs->ref_operation);
+ if (NULL == ref)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "ref NULL\n");
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_urls (ref,
+ cs->index,
+ &url))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Trait url does not work\n");
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (NULL == url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Trait for url is NULL!?\n");
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (0 != strcmp (cs->expected_url,
+ url))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "URL does not match: `%s' != `%s'\n",
+ cs->expected_url,
+ url);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_http_methods (ref,
+ cs->index,
+ &http_method))
+ TALER_TESTING_interpreter_fail (is);
+ if (0 != strcmp (cs->expected_method,
+ http_method))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "http_method does not match\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_http_header (ref,
+ cs->index,
+ &header))
+ TALER_TESTING_interpreter_fail (is);
+ if ( ( (NULL == cs->expected_header) && (NULL != header)) ||
+ ( (NULL != cs->expected_header) && (NULL == header)) ||
+ ( (NULL != cs->expected_header) &&
+ (0 != strcmp (cs->expected_header,
+ header)) ) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "header does not match: `%s' != `%s'\n",
+ cs->expected_header,
+ header);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_http_body (ref,
+ cs->index,
+ &body))
+ TALER_TESTING_interpreter_fail (is);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_http_body_size (ref,
+ cs->index,
+ &body_size))
+ TALER_TESTING_interpreter_fail (is);
+ if ( ( (NULL == cs->expected_body) &&
+ (NULL != body) ) ||
+ ( (NULL != cs->expected_body) &&
+ (NULL == body) ) ||
+ ( (NULL != cs->expected_body) &&
+ ( (*body_size != strlen (cs->expected_body)) ||
+ (0 != memcmp (cs->expected_body,
+ body,
+ *body_size) ) ) ) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "body does not match : `%s' and `%.*s'\n",
+ cs->expected_body,
+ (int) *body_size,
+ (const char *) body);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Free the state of a "checkserver" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+checkserver_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct CheckState *cs = cls;
+
+ GNUNET_free (cs);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_checkserver2 (const char *label,
+ const char *ref_operation,
+ unsigned int index,
+ const char *expected_url,
+ const char *expected_method,
+ const char *expected_header,
+ const char *expected_body)
+{
+ struct CheckState *cs;
+
+ cs = GNUNET_new (struct CheckState);
+ cs->ref_operation = ref_operation;
+ cs->index = index;
+ cs->expected_url = expected_url;
+ cs->expected_method = expected_method;
+ cs->expected_header = expected_header;
+ cs->expected_body = expected_body;
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = cs,
+ .label = label,
+ .run = &checkserver_run,
+ .cleanup = &checkserver_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_checkserver (const char *label,
+ const char *ref_operation,
+ unsigned int index)
+{
+ return TALER_TESTING_cmd_checkserver2 (label,
+ ref_operation,
+ index,
+ "/",
+ "POST",
+ "EFEHYJS-Bakery",
+ "5.0 EUR");
+}
+
+
+/* end of testing_api_cmd_checkserver.c */
diff --git a/src/testing/testing_api_cmd_claim_order.c b/src/testing/testing_api_cmd_claim_order.c
index d7a3ec41..aec03876 100644
--- a/src/testing/testing_api_cmd_claim_order.c
+++ b/src/testing/testing_api_cmd_claim_order.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018, 2020 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -123,34 +123,23 @@ order_claim_cleanup (void *cls,
* response code is as expected.
*
* @param cls closure
- * @param hr HTTP response we got
- * @param contract_terms the contract terms; they are the
- * backend-filled up order minus cryptographic
- * information.
- * @param sig merchant signature over the contract terms.
- * @param hash hash code of the contract terms.
+ * @param ocr response we got
*/
static void
order_claim_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const json_t *contract_terms,
- const struct TALER_MerchantSignatureP *sig,
- const struct TALER_PrivateContractHashP *hash)
+ const struct TALER_MERCHANT_OrderClaimResponse *ocr)
{
struct OrderClaimState *pls = cls;
pls->och = NULL;
- if (pls->http_status != hr->http_status)
+ if (pls->http_status != ocr->hr.http_status)
TALER_TESTING_FAIL (pls->is);
- if (MHD_HTTP_OK == hr->http_status)
+ if (MHD_HTTP_OK == ocr->hr.http_status)
{
- pls->contract_terms = json_object_get (hr->reply,
- "contract_terms");
- if (NULL == pls->contract_terms)
- TALER_TESTING_FAIL (pls->is);
- json_incref (pls->contract_terms);
- pls->contract_terms_hash = *hash;
- pls->merchant_sig = *sig;
+ pls->contract_terms
+ = json_incref ((json_t *) ocr->details.ok.contract_terms);
+ pls->contract_terms_hash = ocr->details.ok.h_contract_terms;
+ pls->merchant_sig = ocr->details.ok.sig;
{
const char *error_name;
unsigned int error_line;
@@ -161,7 +150,7 @@ order_claim_cb (void *cls,
};
if (GNUNET_OK !=
- GNUNET_JSON_parse (contract_terms,
+ GNUNET_JSON_parse (pls->contract_terms,
spec,
&error_name,
&error_line))
@@ -185,7 +174,7 @@ order_claim_run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct OrderClaimState *pls = cls;
- const char **order_id;
+ const char *order_id;
const struct GNUNET_CRYPTO_EddsaPublicKey *nonce;
/* Only used if we do NOT use the nonce/token from traits. */
struct GNUNET_CRYPTO_EddsaPublicKey dummy_nonce;
@@ -194,7 +183,7 @@ order_claim_run (void *cls,
pls->is = is;
if (NULL != pls->order_id)
{
- order_id = &pls->order_id;
+ order_id = pls->order_id;
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
&dummy_nonce,
sizeof (dummy_nonce));
@@ -228,9 +217,10 @@ order_claim_run (void *cls,
&order_id))
TALER_TESTING_FAIL (is);
}
- pls->och = TALER_MERCHANT_order_claim (is->ctx,
+ pls->och = TALER_MERCHANT_order_claim (TALER_TESTING_interpreter_get_context (
+ is),
pls->merchant_url,
- *order_id,
+ order_id,
nonce,
claim_token,
&order_claim_cb,
diff --git a/src/testing/testing_api_cmd_config.c b/src/testing/testing_api_cmd_config.c
index 6487be4e..adde106a 100644
--- a/src/testing/testing_api_cmd_config.c
+++ b/src/testing/testing_api_cmd_config.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -84,24 +84,22 @@ config_cleanup (void *cls,
* Process "GET /public/config" (lookup) response.
*
* @param cls closure
- * @param hr HTTP response we got
- * @param ci basic information about the merchant
- * @param compat protocol compatibility information
+ * @param cr response we got
*/
static void
config_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_MERCHANT_ConfigInformation *ci,
- enum TALER_MERCHANT_VersionCompatibility compat)
+ const struct TALER_MERCHANT_ConfigResponse *cr)
{
struct ConfigState *cs = cls;
- (void) ci;
cs->vgh = NULL;
- if (cs->http_code != hr->http_status)
- TALER_TESTING_FAIL (cs->is);
- if (TALER_MERCHANT_VC_MATCH != compat)
+ if (cs->http_code != cr->hr.http_status)
TALER_TESTING_FAIL (cs->is);
+ if (MHD_HTTP_OK == cr->hr.http_status)
+ {
+ if (TALER_MERCHANT_VC_MATCH != cr->details.ok.compat)
+ TALER_TESTING_FAIL (cs->is);
+ }
TALER_TESTING_interpreter_next (cs->is);
}
@@ -121,7 +119,8 @@ config_run (void *cls,
struct ConfigState *cs = cls;
cs->is = is;
- cs->vgh = TALER_MERCHANT_config_get (is->ctx,
+ cs->vgh = TALER_MERCHANT_config_get (TALER_TESTING_interpreter_get_context (
+ is),
cs->merchant_url,
&config_cb,
cs);
diff --git a/src/testing/testing_api_cmd_delete_account.c b/src/testing/testing_api_cmd_delete_account.c
new file mode 100644
index 00000000..681faa3c
--- /dev/null
+++ b/src/testing/testing_api_cmd_delete_account.c
@@ -0,0 +1,213 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing_api_cmd_delete_account.c
+ * @brief command to test DELETE /account/$H_WIRE
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "DELETE /accounts/$H_WIRE" CMD.
+ */
+struct DeleteAccountState
+{
+
+ /**
+ * Handle for a "DELETE account" request.
+ */
+ struct TALER_MERCHANT_AccountDeleteHandle *adh;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * ID of the command to get account details from.
+ */
+ const char *create_account_ref;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a DELETE /account/$H_WIRE operation.
+ *
+ * @param cls closure for this function
+ * @param adr response being processed
+ */
+static void
+delete_account_cb (void *cls,
+ const struct TALER_MERCHANT_AccountDeleteResponse *adr)
+{
+ struct DeleteAccountState *das = cls;
+
+ das->adh = NULL;
+ if (das->http_status != adr->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ adr->hr.http_status,
+ (int) adr->hr.ec,
+ TALER_TESTING_interpreter_get_current_label (das->is));
+ TALER_TESTING_interpreter_fail (das->is);
+ return;
+ }
+ switch (adr->hr.http_status)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ break;
+ case MHD_HTTP_CONFLICT:
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status %u for DELETE account.\n",
+ adr->hr.http_status);
+ }
+ TALER_TESTING_interpreter_next (das->is);
+}
+
+
+/**
+ * Run the "DELETE account" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+delete_account_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct DeleteAccountState *das = cls;
+ const struct TALER_TESTING_Command *ref;
+ const struct TALER_MerchantWireHashP *h_wire;
+ const char *merchant_url;
+
+ das->is = is;
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ das->create_account_ref);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_FAIL (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_merchant_base_url (ref,
+ &merchant_url))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Command %s lacked merchant base URL\n",
+ das->create_account_ref);
+ GNUNET_break (0);
+ TALER_TESTING_FAIL (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_h_wires (ref,
+ 0,
+ &h_wire))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Command %s did not return H_WIRE\n",
+ das->create_account_ref);
+ GNUNET_break (0);
+ TALER_TESTING_FAIL (is);
+ return;
+ }
+ GNUNET_assert (NULL != h_wire);
+ das->adh = TALER_MERCHANT_account_delete (
+ TALER_TESTING_interpreter_get_context (is),
+ merchant_url,
+ h_wire,
+ &delete_account_cb,
+ das);
+ GNUNET_assert (NULL != das->adh);
+}
+
+
+/**
+ * Free the state of a "DELETE account" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+delete_account_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct DeleteAccountState *das = cls;
+
+ if (NULL != das->adh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "DELETE /accounts/$ID operation did not complete\n");
+ TALER_MERCHANT_account_delete_cancel (das->adh);
+ }
+ GNUNET_free (das);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_account (const char *label,
+ const char *create_account_ref,
+ unsigned int http_status)
+{
+ struct DeleteAccountState *das;
+
+ das = GNUNET_new (struct DeleteAccountState);
+ das->create_account_ref = create_account_ref;
+ das->http_status = http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = das,
+ .label = label,
+ .run = &delete_account_run,
+ .cleanup = &delete_account_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_delete_account.c */
diff --git a/src/testing/testing_api_cmd_delete_instance.c b/src/testing/testing_api_cmd_delete_instance.c
index 9d3bd7d5..36cc2964 100644
--- a/src/testing/testing_api_cmd_delete_instance.c
+++ b/src/testing/testing_api_cmd_delete_instance.c
@@ -123,17 +123,19 @@ delete_instance_run (void *cls,
dis->is = is;
if (dis->purge)
- dis->igh = TALER_MERCHANT_instance_purge (is->ctx,
- dis->merchant_url,
- dis->instance_id,
- &delete_instance_cb,
- dis);
+ dis->igh = TALER_MERCHANT_instance_purge (
+ TALER_TESTING_interpreter_get_context (is),
+ dis->merchant_url,
+ dis->instance_id,
+ &delete_instance_cb,
+ dis);
else
- dis->igh = TALER_MERCHANT_instance_delete (is->ctx,
- dis->merchant_url,
- dis->instance_id,
- &delete_instance_cb,
- dis);
+ dis->igh = TALER_MERCHANT_instance_delete (
+ TALER_TESTING_interpreter_get_context (is),
+ dis->merchant_url,
+ dis->instance_id,
+ &delete_instance_cb,
+ dis);
GNUNET_assert (NULL != dis->igh);
}
diff --git a/src/testing/testing_api_cmd_delete_order.c b/src/testing/testing_api_cmd_delete_order.c
index 7c1ddfff..163538ca 100644
--- a/src/testing/testing_api_cmd_delete_order.c
+++ b/src/testing/testing_api_cmd_delete_order.c
@@ -121,12 +121,13 @@ delete_order_run (void *cls,
struct DeleteOrderState *dos = cls;
dos->is = is;
- dos->odh = TALER_MERCHANT_order_delete (is->ctx,
- dos->merchant_url,
- dos->order_id,
- false, /* FIXME: support testing force... */
- &delete_order_cb,
- dos);
+ dos->odh = TALER_MERCHANT_order_delete (
+ TALER_TESTING_interpreter_get_context (is),
+ dos->merchant_url,
+ dos->order_id,
+ false, /* FIXME: support testing force... */
+ &delete_order_cb,
+ dos);
GNUNET_assert (NULL != dos->odh);
}
diff --git a/src/testing/testing_api_cmd_delete_otp_device.c b/src/testing/testing_api_cmd_delete_otp_device.c
new file mode 100644
index 00000000..3d15c645
--- /dev/null
+++ b/src/testing/testing_api_cmd_delete_otp_device.c
@@ -0,0 +1,181 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing_api_cmd_delete_otp_device.c
+ * @brief command to test DELETE /otp-devices/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "DELETE /otp-devices/$ID" CMD.
+ */
+struct DeleteOtpDeviceState
+{
+
+ /**
+ * Handle for a "DELETE otp_device" request.
+ */
+ struct TALER_MERCHANT_OtpDeviceDeleteHandle *tdh;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * ID of the otp_device to run DELETE for.
+ */
+ const char *otp_device_id;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a /delete/otp-devices/$ID operation.
+ *
+ * @param cls closure for this function
+ * @param hr response being processed
+ */
+static void
+delete_otp_device_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct DeleteOtpDeviceState *dis = cls;
+
+ dis->tdh = NULL;
+ if (dis->http_status != hr->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ hr->http_status,
+ (int) hr->ec,
+ TALER_TESTING_interpreter_get_current_label (dis->is));
+ TALER_TESTING_interpreter_fail (dis->is);
+ return;
+ }
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ break;
+ case MHD_HTTP_CONFLICT:
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status %u for DELETE otp_device.\n",
+ hr->http_status);
+ }
+ TALER_TESTING_interpreter_next (dis->is);
+}
+
+
+/**
+ * Run the "DELETE otp_device" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+delete_otp_device_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct DeleteOtpDeviceState *dis = cls;
+
+ dis->is = is;
+ dis->tdh = TALER_MERCHANT_otp_device_delete (
+ TALER_TESTING_interpreter_get_context (is),
+ dis->merchant_url,
+ dis->otp_device_id,
+ &delete_otp_device_cb,
+ dis);
+ GNUNET_assert (NULL != dis->tdh);
+}
+
+
+/**
+ * Free the state of a "DELETE otp_device" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+delete_otp_device_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct DeleteOtpDeviceState *dis = cls;
+
+ if (NULL != dis->tdh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "DELETE /otp-devices/$ID operation did not complete\n");
+ TALER_MERCHANT_otp_device_delete_cancel (dis->tdh);
+ }
+ GNUNET_free (dis);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_otp_device (const char *label,
+ const char *merchant_url,
+ const char *otp_device_id,
+ unsigned int http_status)
+{
+ struct DeleteOtpDeviceState *dis;
+
+ dis = GNUNET_new (struct DeleteOtpDeviceState);
+ dis->merchant_url = merchant_url;
+ dis->otp_device_id = otp_device_id;
+ dis->http_status = http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = dis,
+ .label = label,
+ .run = &delete_otp_device_run,
+ .cleanup = &delete_otp_device_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_delete_otp_device.c */
diff --git a/src/testing/testing_api_cmd_delete_product.c b/src/testing/testing_api_cmd_delete_product.c
index 6fed8d46..77de9261 100644
--- a/src/testing/testing_api_cmd_delete_product.c
+++ b/src/testing/testing_api_cmd_delete_product.c
@@ -120,11 +120,12 @@ delete_product_run (void *cls,
struct DeleteProductState *dis = cls;
dis->is = is;
- dis->pdh = TALER_MERCHANT_product_delete (is->ctx,
- dis->merchant_url,
- dis->product_id,
- &delete_product_cb,
- dis);
+ dis->pdh = TALER_MERCHANT_product_delete (
+ TALER_TESTING_interpreter_get_context (is),
+ dis->merchant_url,
+ dis->product_id,
+ &delete_product_cb,
+ dis);
GNUNET_assert (NULL != dis->pdh);
}
diff --git a/src/testing/testing_api_cmd_delete_reserve.c b/src/testing/testing_api_cmd_delete_reserve.c
deleted file mode 100644
index 65d27fa6..00000000
--- a/src/testing/testing_api_cmd_delete_reserve.c
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as
- published by the Free Software Foundation; either version 3, or
- (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public
- License along with TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file testing_api_cmd_delete_reserve.c
- * @brief command to test DELETE /reserves/$RESERVE_PUB
- * @author Jonathan Buchanan
- */
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-/**
- * State of a "DELETE /reserves/$RESERVE_PUB" CMD.
- */
-struct DeleteReserveState
-{
-
- /**
- * Handle for a "DELETE reserve" request.
- */
- struct TALER_MERCHANT_ReserveDeleteHandle *rdh;
-
- /**
- * The interpreter state.
- */
- struct TALER_TESTING_Interpreter *is;
-
- /**
- * Base URL of the merchant serving the request.
- */
- const char *merchant_url;
-
- /**
- * Reference to a command that provides a reserve.
- */
- const char *reserve_reference;
-
- /**
- * Expected HTTP response code.
- */
- unsigned int http_status;
-
- /**
- * Use purge, not delete.
- */
- bool purge;
-
-};
-
-
-/**
- * Callback for a DELETE /reserves/$RESERVE_PUB operation.
- *
- * @param cls closure for this function
- * @param hr response being processed
- */
-static void
-delete_reserve_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr)
-{
- struct DeleteReserveState *drs = cls;
-
- drs->rdh = NULL;
- if (drs->http_status != hr->http_status)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
- TALER_TESTING_interpreter_get_current_label (drs->is));
- TALER_TESTING_interpreter_fail (drs->is);
- return;
- }
- switch (hr->http_status)
- {
- case MHD_HTTP_NO_CONTENT:
- break;
- case MHD_HTTP_NOT_FOUND:
- break;
- case MHD_HTTP_CONFLICT:
- break;
- default:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unhandled HTTP status %u for DELETE reserve.\n",
- hr->http_status);
- }
- TALER_TESTING_interpreter_next (drs->is);
-}
-
-
-/**
- * Run the "DELETE reserve" CMD.
- *
- * @param cls closure.
- * @param cmd command being run now.
- * @param is interpreter state.
- */
-static void
-delete_reserve_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct DeleteReserveState *drs = cls;
- const struct TALER_TESTING_Command *reserve_cmd;
- const struct TALER_ReservePublicKeyP *reserve_pub;
-
- reserve_cmd = TALER_TESTING_interpreter_lookup_command (
- is,
- drs->reserve_reference);
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_reserve_pub (reserve_cmd,
- &reserve_pub))
- TALER_TESTING_FAIL (is);
-
- drs->is = is;
- if (drs->purge)
- drs->rdh = TALER_MERCHANT_reserve_purge (is->ctx,
- drs->merchant_url,
- reserve_pub,
- &delete_reserve_cb,
- drs);
- else
- drs->rdh = TALER_MERCHANT_reserve_delete (is->ctx,
- drs->merchant_url,
- reserve_pub,
- &delete_reserve_cb,
- drs);
-
- GNUNET_assert (NULL != drs->rdh);
-}
-
-
-/**
- * Free the state of a "DELETE reserve" CMD, and possibly
- * cancel a pending operation thereof.
- *
- * @param cls closure.
- * @param cmd command being run.
- */
-static void
-delete_reserve_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct DeleteReserveState *drs = cls;
-
- if (NULL != drs->rdh)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "DELETE /reserves/$RESERVE_PUB operation did not complete\n");
- TALER_MERCHANT_reserve_delete_cancel (drs->rdh);
- }
- GNUNET_free (drs);
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_delete_reserve (const char *label,
- const char *merchant_url,
- const char *reserve_reference,
- unsigned int http_status)
-{
- struct DeleteReserveState *drs;
-
- drs = GNUNET_new (struct DeleteReserveState);
- drs->merchant_url = merchant_url;
- drs->reserve_reference = reserve_reference;
- drs->http_status = http_status;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = drs,
- .label = label,
- .run = &delete_reserve_run,
- .cleanup = &delete_reserve_cleanup
- };
-
- return cmd;
- }
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_purge_reserve (const char *label,
- const char *merchant_url,
- const char *reserve_reference,
- unsigned int http_status)
-{
- struct DeleteReserveState *drs;
-
- drs = GNUNET_new (struct DeleteReserveState);
- drs->merchant_url = merchant_url;
- drs->reserve_reference = reserve_reference;
- drs->http_status = http_status;
- drs->purge = true;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = drs,
- .label = label,
- .run = &delete_reserve_run,
- .cleanup = &delete_reserve_cleanup
- };
-
- return cmd;
- }
-}
-
-
-/* end of testing_api_cmd_delete_reserve.c */
diff --git a/src/testing/testing_api_cmd_delete_template.c b/src/testing/testing_api_cmd_delete_template.c
index dd2d38c8..6227c543 100644
--- a/src/testing/testing_api_cmd_delete_template.c
+++ b/src/testing/testing_api_cmd_delete_template.c
@@ -70,19 +70,17 @@ struct DeleteTemplateState
*/
static void
delete_template_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr)
+ const struct TALER_MERCHANT_HttpResponse *hr)
{
struct DeleteTemplateState *dis = cls;
dis->tdh = NULL;
if (dis->http_status != hr->http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
- TALER_TESTING_interpreter_get_current_label (dis->is));
- TALER_TESTING_interpreter_fail (dis->is);
+ TALER_TESTING_unexpected_status_with_body (dis->is,
+ hr->http_status,
+ dis->http_status,
+ hr->reply);
return;
}
switch (hr->http_status)
@@ -114,17 +112,18 @@ delete_template_cb (void *cls,
*/
static void
delete_template_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
{
struct DeleteTemplateState *dis = cls;
dis->is = is;
- dis->tdh = TALER_MERCHANT_template_delete (is->ctx,
- dis->merchant_url,
- dis->template_id,
- &delete_template_cb,
- dis);
+ dis->tdh = TALER_MERCHANT_template_delete (
+ TALER_TESTING_interpreter_get_context (is),
+ dis->merchant_url,
+ dis->template_id,
+ &delete_template_cb,
+ dis);
GNUNET_assert (NULL != dis->tdh);
}
@@ -138,7 +137,7 @@ delete_template_run (void *cls,
*/
static void
delete_template_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
+ const struct TALER_TESTING_Command *cmd)
{
struct DeleteTemplateState *dis = cls;
@@ -154,9 +153,9 @@ delete_template_cleanup (void *cls,
struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_delete_template (const char *label,
- const char *merchant_url,
- const char *template_id,
- unsigned int http_status)
+ const char *merchant_url,
+ const char *template_id,
+ unsigned int http_status)
{
struct DeleteTemplateState *dis;
diff --git a/src/testing/testing_api_cmd_delete_transfer.c b/src/testing/testing_api_cmd_delete_transfer.c
index 6ccef365..ae872dee 100644
--- a/src/testing/testing_api_cmd_delete_transfer.c
+++ b/src/testing/testing_api_cmd_delete_transfer.c
@@ -145,11 +145,12 @@ delete_transfer_run (void *cls,
TALER_TESTING_interpreter_fail (dts->is);
return;
}
- dts->tdh = TALER_MERCHANT_transfer_delete (is->ctx,
- dts->merchant_url,
- *tid,
- &delete_transfer_cb,
- dts);
+ dts->tdh = TALER_MERCHANT_transfer_delete (
+ TALER_TESTING_interpreter_get_context (is),
+ dts->merchant_url,
+ *tid,
+ &delete_transfer_cb,
+ dts);
GNUNET_assert (NULL != dts->tdh);
}
diff --git a/src/testing/testing_api_cmd_delete_webhook.c b/src/testing/testing_api_cmd_delete_webhook.c
index d1060226..12654fe6 100644
--- a/src/testing/testing_api_cmd_delete_webhook.c
+++ b/src/testing/testing_api_cmd_delete_webhook.c
@@ -120,11 +120,12 @@ delete_webhook_run (void *cls,
struct DeleteWebhookState *dis = cls;
dis->is = is;
- dis->wdh = TALER_MERCHANT_webhook_delete (is->ctx,
- dis->merchant_url,
- dis->webhook_id,
- &delete_webhook_cb,
- dis);
+ dis->wdh = TALER_MERCHANT_webhook_delete (
+ TALER_TESTING_interpreter_get_context (is),
+ dis->merchant_url,
+ dis->webhook_id,
+ &delete_webhook_cb,
+ dis);
GNUNET_assert (NULL != dis->wdh);
}
diff --git a/src/testing/testing_api_cmd_depositcheck.c b/src/testing/testing_api_cmd_depositcheck.c
new file mode 100644
index 00000000..ad033d2e
--- /dev/null
+++ b/src/testing/testing_api_cmd_depositcheck.c
@@ -0,0 +1,162 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_depositcheck.c
+ * @brief run the taler-merchant-depositcheck command
+ * @author Priscilla HUANG
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_signatures.h"
+#include "taler/taler_testing_lib.h"
+
+
+/**
+ * State for a "depositcheck" CMD.
+ */
+struct DepositcheckState
+{
+
+ /**
+ * Process for the depositcheck.
+ */
+ struct GNUNET_OS_Process *depositcheck_proc;
+
+ /**
+ * Configuration file used by the depositcheck.
+ */
+ const char *config_filename;
+};
+
+
+/**
+ * Run the command; use the `taler-merchant-depositcheck' program.
+ *
+ * @param cls closure.
+ * @param cmd command currently being executed.
+ * @param is interpreter state.
+ */
+static void
+depositcheck_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct DepositcheckState *ws = cls;
+
+ (void) cmd;
+ ws->depositcheck_proc
+ = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-merchant-depositcheck",
+ "taler-merchant-depositcheck",
+ "-c", ws->config_filename,
+ "-t", /* exit when done */
+ "-L", "DEBUG",
+ NULL);
+ if (NULL == ws->depositcheck_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "depositcheck" CMD, and possibly
+ * kills its process if it did not terminate regularly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+depositcheck_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct DepositcheckState *ws = cls;
+
+ (void) cmd;
+ if (NULL != ws->depositcheck_proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (ws->depositcheck_proc,
+ SIGKILL));
+ GNUNET_OS_process_wait (ws->depositcheck_proc);
+ GNUNET_OS_process_destroy (ws->depositcheck_proc);
+ ws->depositcheck_proc = NULL;
+ }
+ GNUNET_free (ws);
+}
+
+
+/**
+ * Offer "depositcheck" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+depositcheck_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct DepositcheckState *ws = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (&ws->depositcheck_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_depositcheck (const char *label,
+ const char *config_filename)
+{
+ struct DepositcheckState *ws;
+
+ ws = GNUNET_new (struct DepositcheckState);
+ ws->config_filename = config_filename;
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ws,
+ .label = label,
+ .run = &depositcheck_run,
+ .cleanup = &depositcheck_cleanup,
+ .traits = &depositcheck_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_depositcheck.c */
diff --git a/src/testing/testing_api_cmd_forget_order.c b/src/testing/testing_api_cmd_forget_order.c
index 5e6225d4..172ac295 100644
--- a/src/testing/testing_api_cmd_forget_order.c
+++ b/src/testing/testing_api_cmd_forget_order.c
@@ -120,7 +120,13 @@ order_forget_cb (void *cls,
ofs->ofh = NULL;
if (ofs->http_status != hr->http_status)
- TALER_TESTING_FAIL (ofs->is);
+ {
+ TALER_TESTING_unexpected_status_with_body (ofs->is,
+ hr->http_status,
+ ofs->http_status,
+ hr->reply);
+ return;
+ }
TALER_TESTING_interpreter_next (ofs->is);
}
@@ -138,12 +144,12 @@ order_forget_run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct OrderForgetState *ofs = cls;
- const char **order_id;
+ const char *order_id;
ofs->is = is;
if (NULL != ofs->order_id)
{
- order_id = &ofs->order_id;
+ order_id = ofs->order_id;
}
else
{
@@ -159,13 +165,14 @@ order_forget_run (void *cls,
&order_id))
TALER_TESTING_FAIL (is);
}
- ofs->ofh = TALER_MERCHANT_order_forget (is->ctx,
- ofs->merchant_url,
- *order_id,
- ofs->paths_length,
- ofs->paths,
- &order_forget_cb,
- ofs);
+ ofs->ofh = TALER_MERCHANT_order_forget (
+ TALER_TESTING_interpreter_get_context (is),
+ ofs->merchant_url,
+ order_id,
+ ofs->paths_length,
+ ofs->paths,
+ &order_forget_cb,
+ ofs);
GNUNET_assert (NULL != ofs->ofh);
}
@@ -191,7 +198,7 @@ order_forget_traits (void *cls,
traits[0] = TALER_TESTING_make_trait_paths_length (&ofs->paths_length);
for (unsigned int i = 0; i < ofs->paths_length; ++i)
traits[i + 1] = TALER_TESTING_make_trait_paths (i,
- &ofs->paths[i]);
+ ofs->paths[i]);
traits[ofs->paths_length + 1] = TALER_TESTING_trait_end ();
return TALER_TESTING_get_trait (traits,
diff --git a/src/testing/testing_api_cmd_get_instance.c b/src/testing/testing_api_cmd_get_instance.c
index 95dc7282..c3199a7e 100644
--- a/src/testing/testing_api_cmd_get_instance.c
+++ b/src/testing/testing_api_cmd_get_instance.c
@@ -60,31 +60,6 @@ struct GetInstanceState
const char *instance_reference;
/**
- * Whether we should check the instance's accounts or not.
- */
- bool cmp_accounts;
-
- /**
- * The accounts of the merchant we expect to be active.
- */
- const char **active_accounts;
-
- /**
- * The length of @e active_accounts.
- */
- unsigned int active_accounts_length;
-
- /**
- * The accounts of the merchant we expect to be inactive.
- */
- const char **inactive_accounts;
-
- /**
- * The length of @e inactive_accounts.
- */
- unsigned int inactive_accounts_length;
-
- /**
* Expected HTTP response code.
*/
unsigned int http_status;
@@ -96,17 +71,11 @@ struct GetInstanceState
* Callback for a /get/instance/$ID operation.
*
* @param cls closure for this function
- * @param hr HTTP response
- * @param accounts_length how many bank accounts the instance has
- * @param accounts the list of the instance's bank accounts
- * @param details all the details related to this particular instance
+ * @param igr response
*/
static void
get_instance_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int accounts_length,
- const struct TALER_MERCHANT_Account accounts[],
- const struct TALER_MERCHANT_InstanceDetails *details)
+ const struct TALER_MERCHANT_InstanceGetResponse *igr)
{
struct GetInstanceState *gis = cls;
const struct TALER_TESTING_Command *instance_cmd;
@@ -116,208 +85,116 @@ get_instance_cb (void *cls,
gis->instance_reference);
gis->igh = NULL;
- if (gis->http_status != hr->http_status)
+ if (gis->http_status != igr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
+ igr->hr.http_status,
+ (int) igr->hr.ec,
TALER_TESTING_interpreter_get_current_label (gis->is));
TALER_TESTING_interpreter_fail (gis->is);
return;
}
- switch (hr->http_status)
+ switch (igr->hr.http_status)
{
case MHD_HTTP_OK:
{
- const char **name;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_instance_name (instance_cmd,
- &name))
- TALER_TESTING_interpreter_fail (gis->is);
- if (0 != strcmp (details->name,
- *name))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Instance name does not match: Got `%s', wanted `%s'\n",
- details->name,
- *name);
- TALER_TESTING_interpreter_fail (gis->is);
- return;
- }
- }
- {
- const json_t *address;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_address (instance_cmd,
- &address))
- TALER_TESTING_interpreter_fail (gis->is);
- if (1 != json_equal (details->address,
- address))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Instance address does not match\n");
- TALER_TESTING_interpreter_fail (gis->is);
- return;
- }
- }
- {
- const struct json_t *jurisdiction;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_jurisdiction (instance_cmd,
- &jurisdiction))
- TALER_TESTING_interpreter_fail (gis->is);
- if (1 != json_equal (details->jurisdiction,
- jurisdiction))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Instance jurisdiction does not match\n");
- TALER_TESTING_interpreter_fail (gis->is);
- return;
- }
- }
- {
- const struct TALER_Amount *default_max_wire_fee;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_max_wire_fee (instance_cmd,
- &default_max_wire_fee))
- TALER_TESTING_interpreter_fail (gis->is);
- if ((GNUNET_OK != TALER_amount_cmp_currency (
- details->default_max_wire_fee,
- default_max_wire_fee)) ||
- (0 != TALER_amount_cmp (details->default_max_wire_fee,
- default_max_wire_fee)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Instance default max wire fee does not match\n");
- TALER_TESTING_interpreter_fail (gis->is);
- return;
- }
- }
- {
- const uint32_t *default_wire_fee_amortization;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_wire_fee_amortization (instance_cmd,
- &
- default_wire_fee_amortization))
- TALER_TESTING_interpreter_fail (gis->is);
- if (details->default_wire_fee_amortization !=
- *default_wire_fee_amortization)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Instance default wire fee amortization does not match\n");
- TALER_TESTING_interpreter_fail (gis->is);
- return;
- }
- }
- {
- const struct TALER_Amount *default_max_deposit_fee;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_max_deposit_fee (instance_cmd,
- &default_max_deposit_fee))
- TALER_TESTING_interpreter_fail (gis->is);
- if ( (GNUNET_OK !=
- TALER_amount_cmp_currency (
- details->default_max_deposit_fee,
- default_max_deposit_fee)) ||
- (0 != TALER_amount_cmp (details->default_max_deposit_fee,
- default_max_deposit_fee)) )
+ const struct TALER_MERCHANT_InstanceDetails *details =
+ &igr->details.ok.details;
+
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Instance default max deposit fee %s does not match\n",
- TALER_amount2s (details->default_max_deposit_fee));
- TALER_TESTING_interpreter_fail (gis->is);
- return;
+ const char *name;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_instance_name (instance_cmd,
+ &name))
+ TALER_TESTING_interpreter_fail (gis->is);
+ if (0 != strcmp (details->name,
+ name))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Instance name does not match: Got `%s', wanted `%s'\n",
+ details->name,
+ name);
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
}
- }
- {
- const struct GNUNET_TIME_Relative *default_wire_transfer_delay;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_wire_delay (instance_cmd,
- &default_wire_transfer_delay))
- TALER_TESTING_interpreter_fail (gis->is);
- if (details->default_wire_transfer_delay.rel_value_us !=
- default_wire_transfer_delay->rel_value_us)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Instance default wire transfer delay does not match\n");
- TALER_TESTING_interpreter_fail (gis->is);
- return;
+ const json_t *address;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_address (instance_cmd,
+ &address))
+ TALER_TESTING_interpreter_fail (gis->is);
+ if (1 != json_equal (details->address,
+ address))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Instance address does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
}
- }
- {
- const struct GNUNET_TIME_Relative *default_pay_delay;
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_pay_delay (instance_cmd,
- &default_pay_delay))
- TALER_TESTING_interpreter_fail (gis->is);
- if (details->default_pay_delay.rel_value_us !=
- default_pay_delay->rel_value_us)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Instance default pay delay does not match\n");
- TALER_TESTING_interpreter_fail (gis->is);
- return;
- }
- }
- /* We aren't guaranteed an order for the accounts, so we just have to check
- that we can match each account returned with exactly one account
- expected. */
- if (gis->cmp_accounts)
- {
- unsigned int expected_accounts_length =
- gis->active_accounts_length + gis->inactive_accounts_length;
- unsigned int matches[accounts_length];
+ const json_t *jurisdiction;
- if (accounts_length != expected_accounts_length)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Accounts length does not match\n");
- TALER_TESTING_interpreter_fail (gis->is);
- return;
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_jurisdiction (instance_cmd,
+ &jurisdiction))
+ TALER_TESTING_interpreter_fail (gis->is);
+ if (1 != json_equal (details->jurisdiction,
+ jurisdiction))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Instance jurisdiction does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
}
-
- memset (matches,
- 0,
- sizeof (unsigned int) * accounts_length);
-
- /* Compare the accounts */
- for (unsigned int i = 0; i < accounts_length; ++i)
{
- for (unsigned int j = 0; j < gis->active_accounts_length; ++j)
+ const bool *use_stefan;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_use_stefan (instance_cmd,
+ &use_stefan))
+ TALER_TESTING_interpreter_fail (gis->is);
+ if (*use_stefan != details->use_stefan)
{
- if ((0 == strcasecmp (accounts[i].payto_uri,
- gis->active_accounts[j])) &&
- (true == accounts[i].active))
- {
- matches[i] += 1;
- }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Instance use_stefan value does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
}
- for (unsigned int j = 0; j < gis->inactive_accounts_length; ++j)
+ }
+ {
+ const struct GNUNET_TIME_Relative *default_wire_transfer_delay;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_wire_delay (instance_cmd,
+ &default_wire_transfer_delay))
+ TALER_TESTING_interpreter_fail (gis->is);
+ if (details->default_wire_transfer_delay.rel_value_us !=
+ default_wire_transfer_delay->rel_value_us)
{
- if ((0 == strcasecmp (accounts[i].payto_uri,
- gis->inactive_accounts[j])) &&
- (false == accounts[i].active))
- {
- matches[i] += 1;
- }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Instance default wire transfer delay does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
}
}
-
- // Each account should have exactly one match.
- for (unsigned int i = 0; i < accounts_length; ++i)
{
- if (1 != matches[i])
+ const struct GNUNET_TIME_Relative *default_pay_delay;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_pay_delay (instance_cmd,
+ &default_pay_delay))
+ TALER_TESTING_interpreter_fail (gis->is);
+ if (details->default_pay_delay.rel_value_us !=
+ default_pay_delay->rel_value_us)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Instance account does not match\n");
+ "Instance default pay delay does not match\n");
TALER_TESTING_interpreter_fail (gis->is);
return;
}
@@ -331,7 +208,7 @@ get_instance_cb (void *cls,
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Unhandled HTTP status %u for GET instance ID.\n",
- hr->http_status);
+ igr->hr.http_status);
}
TALER_TESTING_interpreter_next (gis->is);
}
@@ -353,11 +230,12 @@ get_instance_run (void *cls,
struct GetInstanceState *gis = cls;
gis->is = is;
- gis->igh = TALER_MERCHANT_instance_get (is->ctx,
- gis->merchant_url,
- gis->instance_id,
- &get_instance_cb,
- gis);
+ gis->igh = TALER_MERCHANT_instance_get (
+ TALER_TESTING_interpreter_get_context (is),
+ gis->merchant_url,
+ gis->instance_id,
+ &get_instance_cb,
+ gis);
GNUNET_assert (NULL != gis->igh);
}
@@ -385,6 +263,35 @@ get_instance_cleanup (void *cls,
}
+/**
+ * Offers information from the GET /instance/$ID CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+get_instance_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct GetInstanceState *pps = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_merchant_base_url (pps->merchant_url),
+ TALER_TESTING_trait_end (),
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_get_instance (const char *label,
const char *merchant_url,
@@ -399,49 +306,13 @@ TALER_TESTING_cmd_merchant_get_instance (const char *label,
gis->instance_id = instance_id;
gis->http_status = http_status;
gis->instance_reference = instance_reference;
- gis->cmp_accounts = false;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = gis,
- .label = label,
- .run = &get_instance_run,
- .cleanup = &get_instance_cleanup
- };
-
- return cmd;
- }
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_instance2 (const char *label,
- const char *merchant_url,
- const char *instance_id,
- unsigned int http_status,
- const char *instance_reference,
- const char *active_accounts[],
- unsigned int active_accounts_length,
- const char *inactive_accounts[],
- unsigned int inactive_accounts_length)
-{
- struct GetInstanceState *gis;
-
- gis = GNUNET_new (struct GetInstanceState);
- gis->merchant_url = merchant_url;
- gis->instance_id = instance_id;
- gis->http_status = http_status;
- gis->instance_reference = instance_reference;
- gis->cmp_accounts = true;
- gis->active_accounts = active_accounts;
- gis->active_accounts_length = active_accounts_length;
- gis->inactive_accounts = inactive_accounts;
- gis->inactive_accounts_length = inactive_accounts_length;
{
struct TALER_TESTING_Command cmd = {
.cls = gis,
.label = label,
.run = &get_instance_run,
- .cleanup = &get_instance_cleanup
+ .cleanup = &get_instance_cleanup,
+ .traits = &get_instance_traits
};
return cmd;
diff --git a/src/testing/testing_api_cmd_get_instances.c b/src/testing/testing_api_cmd_get_instances.c
index 8aee04b1..dbf61fd6 100644
--- a/src/testing/testing_api_cmd_get_instances.c
+++ b/src/testing/testing_api_cmd_get_instances.c
@@ -71,16 +71,13 @@ struct GetInstancesState
* Callback for a GET /instances operation.
*
* @param cls closure for this function
- * @param hr HTTP response
- * @param iis_length how many instances are returned
- * @param iis all the instances details
+ * @param igr response
*/
static void
get_instances_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int iis_length,
- const struct TALER_MERCHANT_InstanceInformation iis[])
+ const struct TALER_MERCHANT_InstancesGetResponse *igr)
{
+ const struct TALER_MERCHANT_HttpResponse *hr = &igr->hr;
struct GetInstancesState *gis = cls;
gis->igh = NULL;
@@ -97,68 +94,75 @@ get_instances_cb (void *cls,
switch (hr->http_status)
{
case MHD_HTTP_OK:
- if (iis_length != gis->instances_length)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Length of instances found does not match\n");
- TALER_TESTING_interpreter_fail (gis->is);
- return;
- }
- for (unsigned int i = 0; i < iis_length; ++i)
- {
- const struct TALER_TESTING_Command *instance_cmd;
-
- instance_cmd = TALER_TESTING_interpreter_lookup_command (
- gis->is,
- gis->instances[i]);
+ unsigned int iis_length
+ = igr->details.ok.iis_length;
+ const struct TALER_MERCHANT_InstanceInformation *iis
+ = igr->details.ok.iis;
+ if (iis_length != gis->instances_length)
{
- const char **name;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_instance_name (instance_cmd,
- &name))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not fetch instance name\n");
- TALER_TESTING_interpreter_fail (gis->is);
- return;
- }
- if (0 != strcmp (iis[i].name,
- *name))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Instance name does not match\n");
- TALER_TESTING_interpreter_fail (gis->is);
- return;
- }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Length of instances found does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
}
-
+ for (unsigned int i = 0; i < iis_length; ++i)
{
- const char **id;
+ const struct TALER_TESTING_Command *instance_cmd;
+
+ instance_cmd = TALER_TESTING_interpreter_lookup_command (
+ gis->is,
+ gis->instances[i]);
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_instance_id (instance_cmd,
- &id))
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not fetch instance id\n");
- TALER_TESTING_interpreter_fail (gis->is);
- return;
+ const char *name;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_instance_name (instance_cmd,
+ &name))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not fetch instance name\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ if (0 != strcmp (iis[i].name,
+ name))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Instance name does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
}
- if (0 != strcmp (iis[i].id,
- *id))
+
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Instance id does not match\n");
- TALER_TESTING_interpreter_fail (gis->is);
- return;
+ const char *id;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_instance_id (instance_cmd,
+ &id))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not fetch instance id\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ if (0 != strcmp (iis[i].id,
+ id))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Instance id does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
}
}
- }
- // FIXME: compare payment_targets
- break;
+ // FIXME: compare payment_targets
+ break;
+ }
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Unhandled HTTP status %u for GET /instances.\n",
@@ -184,10 +188,11 @@ get_instances_run (void *cls,
struct GetInstancesState *gis = cls;
gis->is = is;
- gis->igh = TALER_MERCHANT_instances_get (is->ctx,
- gis->merchant_url,
- &get_instances_cb,
- gis);
+ gis->igh = TALER_MERCHANT_instances_get (
+ TALER_TESTING_interpreter_get_context (is),
+ gis->merchant_url,
+ &get_instances_cb,
+ gis);
GNUNET_assert (NULL != gis->igh);
}
diff --git a/src/testing/testing_api_cmd_get_orders.c b/src/testing/testing_api_cmd_get_orders.c
index 7a271521..0eab0b7f 100644
--- a/src/testing/testing_api_cmd_get_orders.c
+++ b/src/testing/testing_api_cmd_get_orders.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -71,41 +71,39 @@ struct GetOrdersState
* Callback for a GET /orders operation.
*
* @param cls closure for this function
- * @param hr HTTP response
- * @param orders_length how many orders are returned
- * @param orders all the orders' details
+ * @param ogr response
*/
static void
get_orders_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int orders_length,
- const struct TALER_MERCHANT_OrderEntry orders[])
+ const struct TALER_MERCHANT_OrdersGetResponse *ogr)
{
struct GetOrdersState *gos = cls;
gos->ogh = NULL;
- if (gos->http_status != hr->http_status)
+ if (gos->http_status != ogr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
+ ogr->hr.http_status,
+ (int) ogr->hr.ec,
TALER_TESTING_interpreter_get_current_label (gos->is));
TALER_TESTING_interpreter_fail (gos->is);
return;
}
- switch (hr->http_status)
+ switch (ogr->hr.http_status)
{
case MHD_HTTP_OK:
- if (orders_length != gos->orders_length)
+ if (ogr->details.ok.orders_length != gos->orders_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Number of orders found does not match\n");
TALER_TESTING_interpreter_fail (gos->is);
return;
}
- for (unsigned int i = 0; i < orders_length; ++i)
+ for (unsigned int i = 0; i < ogr->details.ok.orders_length; ++i)
{
+ const struct TALER_MERCHANT_OrderEntry *order =
+ &ogr->details.ok.orders[i];
const struct TALER_TESTING_Command *order_cmd;
order_cmd = TALER_TESTING_interpreter_lookup_command (
@@ -113,7 +111,7 @@ get_orders_cb (void *cls,
gos->orders[i]);
{
- const char **order_id;
+ const char *order_id;
if (GNUNET_OK !=
TALER_TESTING_get_trait_order_id (order_cmd,
@@ -124,8 +122,8 @@ get_orders_cb (void *cls,
TALER_TESTING_interpreter_fail (gos->is);
return;
}
- if (0 != strcmp (orders[i].order_id,
- *order_id))
+ if (0 != strcmp (order->order_id,
+ order_id))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Order id does not match\n");
@@ -165,11 +163,11 @@ get_orders_cb (void *cls,
return;
}
if ((0 != strcmp (summary,
- orders[i].summary)) ||
+ order->summary)) ||
(GNUNET_OK != TALER_amount_cmp_currency (&amount,
- &orders[i].amount)) ||
+ &order->amount)) ||
(0 != TALER_amount_cmp (&amount,
- &orders[i].amount)))
+ &order->amount)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Order summary and/or amount does not match\n");
@@ -205,7 +203,8 @@ get_orders_run (void *cls,
struct GetOrdersState *gos = cls;
gos->is = is;
- gos->ogh = TALER_MERCHANT_orders_get (is->ctx,
+ gos->ogh = TALER_MERCHANT_orders_get (TALER_TESTING_interpreter_get_context (
+ is),
gos->merchant_url,
&get_orders_cb,
gos);
@@ -396,31 +395,27 @@ conclude_task (void *cls)
* Callback to process a GET /orders request
*
* @param cls closure
- * @param hr HTTP response details
- * @param orders_length how many orders are returned
- * @param orders the returned orders
+ * @param ogr response details
*/
static void
merchant_poll_orders_cb (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int orders_length,
- const struct TALER_MERCHANT_OrderEntry orders[])
+ const struct TALER_MERCHANT_OrdersGetResponse *ogr)
{
struct MerchantPollOrdersStartState *pos = cls;
pos->ogh = NULL;
- if (MHD_HTTP_OK != hr->http_status)
+ if (MHD_HTTP_OK != ogr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
+ ogr->hr.http_status,
+ (int) ogr->hr.ec,
TALER_TESTING_interpreter_get_current_label (pos->is));
TALER_TESTING_interpreter_fail (pos->is);
return;
}
- switch (hr->http_status)
+ switch (ogr->hr.http_status)
{
case MHD_HTTP_OK:
// FIXME: use order references to check if the data returned matches that from the POST / PATCH
@@ -429,7 +424,7 @@ merchant_poll_orders_cb (
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Unhandled HTTP status.\n");
}
- pos->http_status = hr->http_status;
+ pos->http_status = ogr->hr.http_status;
if (NULL != pos->cs)
{
GNUNET_SCHEDULER_cancel (pos->cs->task);
@@ -459,7 +454,8 @@ merchant_poll_orders_start_run (void *cls,
GNUNET_TIME_relative_add (pos->timeout,
GNUNET_TIME_UNIT_SECONDS));
pos->is = is;
- pos->ogh = TALER_MERCHANT_orders_get2 (is->ctx,
+ pos->ogh = TALER_MERCHANT_orders_get2 (TALER_TESTING_interpreter_get_context (
+ is),
pos->merchant_url,
TALER_EXCHANGE_YNA_ALL,
TALER_EXCHANGE_YNA_ALL,
diff --git a/src/testing/testing_api_cmd_get_otp_device.c b/src/testing/testing_api_cmd_get_otp_device.c
new file mode 100644
index 00000000..3f086529
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_otp_device.c
@@ -0,0 +1,206 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing_api_cmd_get_otp_device.c
+ * @brief command to test GET /otp-devices/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "GET OTP device" CMD.
+ */
+struct GetOtpDeviceState
+{
+
+ /**
+ * Handle for a "GET /otp-device/$ID" request.
+ */
+ struct TALER_MERCHANT_OtpDeviceGetHandle *igh;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * ID of the otp_device to run GET for.
+ */
+ const char *otp_device_id;
+
+ /**
+ * Reference for a POST or PATCH /otp-devices CMD (optional).
+ */
+ const char *otp_device_reference;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a GET /otp-devices/$ID operation.
+ *
+ * @param cls closure for this function
+ * @param tgr HTTP response details
+ */
+static void
+get_otp_device_cb (void *cls,
+ const struct TALER_MERCHANT_OtpDeviceGetResponse *tgr)
+{
+ struct GetOtpDeviceState *gis = cls;
+ const struct TALER_TESTING_Command *otp_device_cmd;
+
+ gis->igh = NULL;
+ if (gis->http_status != tgr->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ tgr->hr.http_status,
+ (int) tgr->hr.ec,
+ TALER_TESTING_interpreter_get_current_label (gis->is));
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ switch (tgr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ const char *expected_description;
+
+ otp_device_cmd = TALER_TESTING_interpreter_lookup_command (
+ gis->is,
+ gis->otp_device_reference);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_otp_device_description (otp_device_cmd,
+ &expected_description))
+ TALER_TESTING_interpreter_fail (gis->is);
+ if (0 != strcmp (tgr->details.ok.otp_device_description,
+ expected_description))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "OtpDevice description does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ }
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status.\n");
+ }
+ TALER_TESTING_interpreter_next (gis->is);
+}
+
+
+/**
+ * Run the "GET /otp-device/$ID" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_otp_device_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct GetOtpDeviceState *gis = cls;
+
+ gis->is = is;
+ gis->igh = TALER_MERCHANT_otp_device_get (
+ TALER_TESTING_interpreter_get_context (is),
+ gis->merchant_url,
+ gis->otp_device_id,
+ &get_otp_device_cb,
+ gis);
+ GNUNET_assert (NULL != gis->igh);
+}
+
+
+/**
+ * Free the state of a "GET /otp-device/$ID" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_otp_device_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct GetOtpDeviceState *gis = cls;
+
+ if (NULL != gis->igh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "GET /otp-devices/$ID operation did not complete\n");
+ TALER_MERCHANT_otp_device_get_cancel (gis->igh);
+ }
+ GNUNET_free (gis);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_otp_device (
+ const char *label,
+ const char *merchant_url,
+ const char *otp_device_id,
+ unsigned int http_status,
+ const char *otp_device_reference)
+{
+ struct GetOtpDeviceState *gis;
+
+ gis = GNUNET_new (struct GetOtpDeviceState);
+ gis->merchant_url = merchant_url;
+ gis->otp_device_id = otp_device_id;
+ gis->http_status = http_status;
+ gis->otp_device_reference = otp_device_reference;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = gis,
+ .label = label,
+ .run = &get_otp_device_run,
+ .cleanup = &get_otp_device_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_get_otp_device.c */
diff --git a/src/testing/testing_api_cmd_get_otp_devices.c b/src/testing/testing_api_cmd_get_otp_devices.c
new file mode 100644
index 00000000..b4f370c5
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_otp_devices.c
@@ -0,0 +1,238 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing_api_cmd_get_otp_devices.c
+ * @brief command to test GET /otp-devices
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "GET /otp-devices" CMD.
+ */
+struct GetOtpDevicesState
+{
+
+ /**
+ * Handle for a "GET /otp-devices" request.
+ */
+ struct TALER_MERCHANT_OtpDevicesGetHandle *igh;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+ /**
+ * The list of otp_device references.
+ */
+ const char **otp_devices;
+
+ /**
+ * Length of @e otp_devices.
+ */
+ unsigned int otp_devices_length;
+
+};
+
+
+/**
+ * Callback for a GET /otp-devices operation.
+ *
+ * @param cls closure for this function
+ * @param tgr response details
+ */
+static void
+get_otp_devices_cb (void *cls,
+ const struct TALER_MERCHANT_OtpDevicesGetResponse *tgr)
+{
+ struct GetOtpDevicesState *gis = cls;
+
+ gis->igh = NULL;
+ if (gis->http_status != tgr->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ tgr->hr.http_status,
+ (int) tgr->hr.ec,
+ TALER_TESTING_interpreter_get_current_label (gis->is));
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ switch (tgr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ if (tgr->details.ok.otp_devices_length != gis->otp_devices_length)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Length of otp_devices found does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ for (unsigned int i = 0; i < gis->otp_devices_length; ++i)
+ {
+ const struct TALER_TESTING_Command *otp_device_cmd;
+
+ otp_device_cmd = TALER_TESTING_interpreter_lookup_command (
+ gis->is,
+ gis->otp_devices[i]);
+
+ {
+ const char *otp_device_id;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_otp_id (otp_device_cmd,
+ &otp_device_id))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not fetch otp_device id\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ if (0 != strcmp (tgr->details.ok.otp_devices[i].otp_device_id,
+ otp_device_id))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "OtpDevice id does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ }
+ }
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* instance does not exist */
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status %u (%d).\n",
+ tgr->hr.http_status,
+ tgr->hr.ec);
+ break;
+ }
+ TALER_TESTING_interpreter_next (gis->is);
+}
+
+
+/**
+ * Run the "GET /otp-devices" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_otp_devices_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct GetOtpDevicesState *gis = cls;
+
+ gis->is = is;
+ gis->igh = TALER_MERCHANT_otp_devices_get (
+ TALER_TESTING_interpreter_get_context (is),
+ gis->merchant_url,
+ &get_otp_devices_cb,
+ gis);
+ GNUNET_assert (NULL != gis->igh);
+}
+
+
+/**
+ * Free the state of a "GET otp_device" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_otp_devices_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct GetOtpDevicesState *gis = cls;
+
+ if (NULL != gis->igh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "GET /otp-devices operation did not complete\n");
+ TALER_MERCHANT_otp_devices_get_cancel (gis->igh);
+ }
+ GNUNET_array_grow (gis->otp_devices,
+ gis->otp_devices_length,
+ 0);
+ GNUNET_free (gis);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_otp_devices (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ ...)
+{
+ struct GetOtpDevicesState *gis;
+
+ gis = GNUNET_new (struct GetOtpDevicesState);
+ gis->merchant_url = merchant_url;
+ gis->http_status = http_status;
+ {
+ const char *clabel;
+ va_list ap;
+
+ va_start (ap, http_status);
+ while (NULL != (clabel = va_arg (ap, const char *)))
+ {
+ GNUNET_array_append (gis->otp_devices,
+ gis->otp_devices_length,
+ clabel);
+ }
+ va_end (ap);
+ }
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = gis,
+ .label = label,
+ .run = &get_otp_devices_run,
+ .cleanup = &get_otp_devices_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_get_otp_devices.c */
diff --git a/src/testing/testing_api_cmd_get_product.c b/src/testing/testing_api_cmd_get_product.c
index ece36e71..a7d8c186 100644
--- a/src/testing/testing_api_cmd_get_product.c
+++ b/src/testing/testing_api_cmd_get_product.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -71,60 +71,31 @@ struct GetProductState
* Callback for a /get/product/$ID operation.
*
* @param cls closure for this function
- * @param hr HTTP response details
- * @param description description of the product
- * @param description_i18n Map from IETF BCP 47 language tags to localized descriptions
- * @param unit unit in which the product is measured (liters, kilograms, packages, etc.)
- * @param price the price for one @a unit of the product, zero is used to imply that
- * this product is not sold separately or that the price is not fixed and
- * must be supplied by the front-end. If non-zero, price must include
- * applicable taxes.
- * @param image base64-encoded product image
- * @param taxes list of taxes paid by the merchant
- * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic books),
- * does NOT indicate remaining stocks, to get remaining stocks,
- * subtract @a total_sold and @a total_lost. Note that this still
- * does not then say how many of the remaining inventory are locked.
- * @param total_sold in @a units, total number of @a unit of product sold
- * @param total_lost in @a units, total number of @a unit of product lost from inventory
- * @param location where the product is in stock
- * @param next_restock when the next restocking is expected to happen, 0 for unknown,
- * #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ * @param pgr response details
*/
static void
get_product_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const char *description,
- const json_t *description_i18n,
- const char *unit,
- const struct TALER_Amount *price,
- const char *image,
- const json_t *taxes,
- int64_t total_stock,
- uint64_t total_sold,
- uint64_t total_lost,
- const json_t *location,
- struct GNUNET_TIME_Timestamp next_restock)
+ const struct TALER_MERCHANT_ProductGetResponse *pgr)
{
struct GetProductState *gis = cls;
const struct TALER_TESTING_Command *product_cmd;
gis->igh = NULL;
- if (gis->http_status != hr->http_status)
+ if (gis->http_status != pgr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
+ pgr->hr.http_status,
+ (int) pgr->hr.ec,
TALER_TESTING_interpreter_get_current_label (gis->is));
TALER_TESTING_interpreter_fail (gis->is);
return;
}
- switch (hr->http_status)
+ switch (pgr->hr.http_status)
{
case MHD_HTTP_OK:
{
- const char **expected_description;
+ const char *expected_description;
product_cmd = TALER_TESTING_interpreter_lookup_command (
gis->is,
@@ -133,8 +104,8 @@ get_product_cb (void *cls,
TALER_TESTING_get_trait_product_description (product_cmd,
&expected_description))
TALER_TESTING_interpreter_fail (gis->is);
- if (0 != strcmp (description,
- *expected_description))
+ if (0 != strcmp (pgr->details.ok.description,
+ expected_description))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Product description does not match\n");
@@ -149,7 +120,7 @@ get_product_cb (void *cls,
TALER_TESTING_get_trait_i18n_description (product_cmd,
&expected_description_i18n))
TALER_TESTING_interpreter_fail (gis->is);
- if (1 != json_equal (description_i18n,
+ if (1 != json_equal (pgr->details.ok.description_i18n,
expected_description_i18n))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -165,9 +136,10 @@ get_product_cb (void *cls,
TALER_TESTING_get_trait_amount (product_cmd,
&expected_price))
TALER_TESTING_interpreter_fail (gis->is);
- if ((GNUNET_OK != TALER_amount_cmp_currency (price,
- expected_price)) ||
- (0 != TALER_amount_cmp (price,
+ if ((GNUNET_OK !=
+ TALER_amount_cmp_currency (&pgr->details.ok.price,
+ expected_price)) ||
+ (0 != TALER_amount_cmp (&pgr->details.ok.price,
expected_price)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -177,14 +149,14 @@ get_product_cb (void *cls,
}
}
{
- const char **expected_image;
+ const char *expected_image;
if (GNUNET_OK !=
TALER_TESTING_get_trait_product_image (product_cmd,
&expected_image))
TALER_TESTING_interpreter_fail (gis->is);
- if (0 != strcmp (image,
- *expected_image))
+ if (0 != strcmp (pgr->details.ok.image,
+ expected_image))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Product image does not match\n");
@@ -199,7 +171,7 @@ get_product_cb (void *cls,
TALER_TESTING_get_trait_taxes (product_cmd,
&expected_taxes))
TALER_TESTING_interpreter_fail (gis->is);
- if (1 != json_equal (taxes,
+ if (1 != json_equal (pgr->details.ok.taxes,
expected_taxes))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -209,14 +181,14 @@ get_product_cb (void *cls,
}
}
{
- const char **expected_unit;
+ const char *expected_unit;
if (GNUNET_OK !=
TALER_TESTING_get_trait_product_unit (product_cmd,
&expected_unit))
TALER_TESTING_interpreter_fail (gis->is);
- if (0 != strcmp (unit,
- *expected_unit))
+ if (0 != strcmp (pgr->details.ok.unit,
+ expected_unit))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Product unit does not match\n");
@@ -231,7 +203,7 @@ get_product_cb (void *cls,
TALER_TESTING_get_trait_address (product_cmd,
&expected_location))
TALER_TESTING_interpreter_fail (gis->is);
- if (1 != json_equal (location,
+ if (1 != json_equal (pgr->details.ok.location,
expected_location))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -247,7 +219,7 @@ get_product_cb (void *cls,
TALER_TESTING_get_trait_product_stock (product_cmd,
&expected_total_stock))
TALER_TESTING_interpreter_fail (gis->is);
- if (total_stock != *expected_total_stock)
+ if (pgr->details.ok.total_stock != *expected_total_stock)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Product total stock does not match\n");
@@ -263,7 +235,7 @@ get_product_cb (void *cls,
0,
&expected_next_restock))
TALER_TESTING_interpreter_fail (gis->is);
- if (GNUNET_TIME_timestamp_cmp (next_restock,
+ if (GNUNET_TIME_timestamp_cmp (pgr->details.ok.next_restock,
!=,
*expected_next_restock))
{
@@ -302,7 +274,8 @@ get_product_run (void *cls,
struct GetProductState *gis = cls;
gis->is = is;
- gis->igh = TALER_MERCHANT_product_get (is->ctx,
+ gis->igh = TALER_MERCHANT_product_get (TALER_TESTING_interpreter_get_context (
+ is),
gis->merchant_url,
gis->product_id,
&get_product_cb,
diff --git a/src/testing/testing_api_cmd_get_products.c b/src/testing/testing_api_cmd_get_products.c
index 190bb4c1..97a105be 100644
--- a/src/testing/testing_api_cmd_get_products.c
+++ b/src/testing/testing_api_cmd_get_products.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -71,17 +71,14 @@ struct GetProductsState
* Callback for a GET /products operation.
*
* @param cls closure for this function
- * @param hr HTTP response details
- * @param products_length length of the @a products array
- * @param products array of products the requested instance offers
+ * @param gpr response details
*/
static void
get_products_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int products_length,
- const struct TALER_MERCHANT_InventoryEntry products[])
+ const struct TALER_MERCHANT_GetProductsResponse *gpr)
{
struct GetProductsState *gis = cls;
+ const struct TALER_MERCHANT_HttpResponse *hr = &gpr->hr;
gis->igh = NULL;
if (gis->http_status != hr->http_status)
@@ -97,40 +94,47 @@ get_products_cb (void *cls,
switch (hr->http_status)
{
case MHD_HTTP_OK:
- if (products_length != gis->products_length)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Length of products found does not match\n");
- TALER_TESTING_interpreter_fail (gis->is);
- return;
- }
- for (unsigned int i = 0; i < gis->products_length; ++i)
- {
- const struct TALER_TESTING_Command *product_cmd;
-
- product_cmd = TALER_TESTING_interpreter_lookup_command (
- gis->is,
- gis->products[i]);
+ unsigned int products_length
+ = gpr->details.ok.products_length;
+ const struct TALER_MERCHANT_InventoryEntry *products
+ = gpr->details.ok.products;
+ if (products_length != gis->products_length)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Length of products found does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ for (unsigned int i = 0; i < gis->products_length; ++i)
{
- const char **product_id;
+ const struct TALER_TESTING_Command *product_cmd;
+
+ product_cmd = TALER_TESTING_interpreter_lookup_command (
+ gis->is,
+ gis->products[i]);
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_product_id (product_cmd,
- &product_id))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not fetch product id\n");
- TALER_TESTING_interpreter_fail (gis->is);
- return;
- }
- if (0 != strcmp (products[i].product_id,
- *product_id))
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Product id does not match\n");
- TALER_TESTING_interpreter_fail (gis->is);
- return;
+ const char *product_id;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_product_id (product_cmd,
+ &product_id))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not fetch product id\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ if (0 != strcmp (products[i].product_id,
+ product_id))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Product id does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
}
}
}
@@ -166,10 +170,11 @@ get_products_run (void *cls,
struct GetProductsState *gis = cls;
gis->is = is;
- gis->igh = TALER_MERCHANT_products_get (is->ctx,
- gis->merchant_url,
- &get_products_cb,
- gis);
+ gis->igh = TALER_MERCHANT_products_get (
+ TALER_TESTING_interpreter_get_context (is),
+ gis->merchant_url,
+ &get_products_cb,
+ gis);
GNUNET_assert (NULL != gis->igh);
}
diff --git a/src/testing/testing_api_cmd_get_reserve.c b/src/testing/testing_api_cmd_get_reserve.c
deleted file mode 100644
index ef7f67e0..00000000
--- a/src/testing/testing_api_cmd_get_reserve.c
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as
- published by the Free Software Foundation; either version 3, or
- (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public
- License along with TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file testing_api_cmd_get_reserve.c
- * @brief command to test GET /private/reserves/$RESERVE_PUB
- * @author Jonathan Buchanan
- */
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-struct GetReserveState
-{
-
- /**
- * Handle for a "GET reserve" request.
- */
- struct TALER_MERCHANT_ReserveGetHandle *rgh;
-
- /**
- * The interpreter state.
- */
- struct TALER_TESTING_Interpreter *is;
-
- /**
- * Base URL of the merchant serving the request.
- */
- const char *merchant_url;
-
- /**
- * Label for a command that created a reserve.
- */
- const char *reserve_reference;
-
- /**
- * Expected HTTP response code.
- */
- unsigned int http_status;
-
- /**
- * Fetch tips
- */
- bool fetch_tips;
-
- /**
- * Length of @e tips.
- */
- unsigned int tips_length;
-
- /**
- * The list of references to tips.
- */
- const char **tips;
-};
-
-
-static void
-get_reserve_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_MERCHANT_ReserveSummary *rs,
- bool active,
- const char *exchange_url,
- const char *payto_uri,
- unsigned int tips_length,
- const struct TALER_MERCHANT_TipDetails tips[])
-{
- struct GetReserveState *grs = cls;
- const struct TALER_TESTING_Command *reserve_cmd;
-
- reserve_cmd = TALER_TESTING_interpreter_lookup_command (
- grs->is,
- grs->reserve_reference);
-
- grs->rgh = NULL;
- if (grs->http_status != hr->http_status)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
- TALER_TESTING_interpreter_get_current_label (grs->is));
- TALER_TESTING_interpreter_fail (grs->is);
- return;
- }
- switch (hr->http_status)
- {
- case MHD_HTTP_OK:
- {
- const struct TALER_Amount *initial_amount;
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_amount (reserve_cmd,
- &initial_amount))
- TALER_TESTING_interpreter_fail (grs->is);
- if ((GNUNET_OK !=
- TALER_amount_cmp_currency (&rs->merchant_initial_amount,
- initial_amount)) ||
- (0 != TALER_amount_cmp (&rs->merchant_initial_amount,
- initial_amount)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Reserve initial amount does not match\n");
- TALER_TESTING_interpreter_fail (grs->is);
- return;
- }
- }
- if (tips_length != grs->tips_length)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Number of tips authorized does not match\n");
- TALER_TESTING_interpreter_fail (grs->is);
- return;
- }
- for (unsigned int i = 0; i < tips_length; ++i)
- {
- const struct TALER_TESTING_Command *tip_cmd;
-
- tip_cmd = TALER_TESTING_interpreter_lookup_command (grs->is,
- grs->tips[i]);
- {
- const struct TALER_TipIdentifierP *tip_id;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_tip_id (tip_cmd,
- &tip_id))
- TALER_TESTING_interpreter_fail (grs->is);
-
- if (0 != GNUNET_memcmp (&tips[i].tip_id,
- tip_id))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Reserve tip id does not match\n");
- TALER_TESTING_interpreter_fail (grs->is);
- return;
- }
- }
- {
- const struct TALER_Amount *total_amount;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_amount (tip_cmd,
- &total_amount))
- TALER_TESTING_interpreter_fail (grs->is);
-
- if ((GNUNET_OK !=
- TALER_amount_cmp_currency (&tips[i].amount,
- total_amount)) ||
- (0 != TALER_amount_cmp (&tips[i].amount,
- total_amount)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Reserve tip amount does not match\n");
- TALER_TESTING_interpreter_fail (grs->is);
- return;
- }
- }
- {
- const char **reason;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_reason (tip_cmd,
- &reason))
- TALER_TESTING_interpreter_fail (grs->is);
-
- if (0 != strcmp (tips[i].reason,
- *reason))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Reserve tip reason does not match\n");
- TALER_TESTING_interpreter_fail (grs->is);
- return;
- }
- }
- }
- break;
- default:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unhandled HTTP status.\n");
- }
- TALER_TESTING_interpreter_next (grs->is);
-}
-
-
-/**
- * Run the "GET /private/reserves/$RESERVE_PUB" CMD.
- *
- * @param cls closure.
- * @param cmd command being run now.
- * @param is interpreter state.
- */
-static void
-get_reserve_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct GetReserveState *grs = cls;
- const struct TALER_TESTING_Command *reserve_cmd;
- const struct TALER_ReservePublicKeyP *reserve_pub;
-
- reserve_cmd = TALER_TESTING_interpreter_lookup_command (
- is,
- grs->reserve_reference);
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_reserve_pub (reserve_cmd,
- &reserve_pub))
- TALER_TESTING_FAIL (is);
-
- grs->is = is;
- grs->rgh = TALER_MERCHANT_reserve_get (is->ctx,
- grs->merchant_url,
- reserve_pub,
- grs->fetch_tips,
- &get_reserve_cb,
- grs);
-
- GNUNET_assert (NULL != grs->rgh);
-}
-
-
-/**
- * Free the state of a "GET reserve" CMD, and possibly
- * cancel a pending operation thereof.
- *
- * @param cls closure.
- * @param cmd command being run.
- */
-static void
-get_reserve_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct GetReserveState *grs = cls;
-
- if (NULL != grs->rgh)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "GET /private/reserve/$RESERVE_PUB operation did not complete\n");
- TALER_MERCHANT_reserve_get_cancel (grs->rgh);
- }
- GNUNET_array_grow (grs->tips,
- grs->tips_length,
- 0);
- GNUNET_free (grs);
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_reserve (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- const char *reserve_reference)
-{
- struct GetReserveState *grs;
-
- grs = GNUNET_new (struct GetReserveState);
- grs->merchant_url = merchant_url;
- grs->http_status = http_status;
- grs->reserve_reference = reserve_reference;
- grs->fetch_tips = false;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = grs,
- .label = label,
- .run = &get_reserve_run,
- .cleanup = &get_reserve_cleanup
- };
-
- return cmd;
- }
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_reserve_with_tips (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- const char *reserve_reference,
- ...)
-{
- struct GetReserveState *grs;
-
- grs = GNUNET_new (struct GetReserveState);
- grs->merchant_url = merchant_url;
- grs->http_status = http_status;
- grs->reserve_reference = reserve_reference;
- grs->fetch_tips = true;
- {
- const char *clabel;
- va_list ap;
-
- va_start (ap, reserve_reference);
- while (NULL != (clabel = va_arg (ap, const char *)))
- {
- GNUNET_array_append (grs->tips,
- grs->tips_length,
- clabel);
- }
- va_end (ap);
- }
- {
- struct TALER_TESTING_Command cmd = {
- .cls = grs,
- .label = label,
- .run = &get_reserve_run,
- .cleanup = &get_reserve_cleanup
- };
-
- return cmd;
- }
-}
-
-
-/* end of testing_api_cmd_get_reserve.c */
diff --git a/src/testing/testing_api_cmd_get_reserves.c b/src/testing/testing_api_cmd_get_reserves.c
deleted file mode 100644
index ea860c27..00000000
--- a/src/testing/testing_api_cmd_get_reserves.c
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as
- published by the Free Software Foundation; either version 3, or
- (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public
- License along with TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file testing_api_cmd_get_reserves.c
- * @brief command to test GET /private/reserves
- * @author Jonathan Buchanan
- */
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-/**
- * State of a "GET reserves" CMD
- */
-struct GetReservesState
-{
-
- /**
- * Handle for a "GET reserves" request.
- */
- struct TALER_MERCHANT_ReservesGetHandle *rgh;
-
- /**
- * The interpreter state.
- */
- struct TALER_TESTING_Interpreter *is;
-
- /**
- * A list of reserves to compare with.
- */
- const char **reserves;
-
- /**
- * Length of @e reserve_refs.
- */
- unsigned int reserves_length;
-
- /**
- * Base URL of the merchant serving the request.
- */
- const char *merchant_url;
-
- /**
- * Expected HTTP response code.
- */
- unsigned int http_status;
-};
-
-
-static void
-get_reserves_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int reserves_length,
- const struct TALER_MERCHANT_ReserveSummary reserves[])
-{
- struct GetReservesState *grs = cls;
- bool matched[reserves_length];
- bool fail = false;
-
- grs->rgh = NULL;
- if (grs->http_status != hr->http_status)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
- TALER_TESTING_interpreter_get_current_label (grs->is));
- TALER_TESTING_interpreter_fail (grs->is);
- return;
- }
- switch (hr->http_status)
- {
- case MHD_HTTP_OK:
- if (reserves_length != grs->reserves_length)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Length of reserves found does not match\n");
- TALER_TESTING_interpreter_fail (grs->is);
- return;
- }
- /* check if the data returned matches that from the POST / PATCH */
- memset (matched, 0, sizeof (matched));
- for (unsigned int i = 0; i < reserves_length; ++i)
- for (unsigned int j = 0; j < reserves_length; ++j)
- {
- const struct TALER_TESTING_Command *reserve_cmd;
- bool match = true;
-
- reserve_cmd = TALER_TESTING_interpreter_lookup_command (
- grs->is,
- grs->reserves[j]);
- {
- const struct TALER_ReservePublicKeyP *reserve_pub;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_reserve_pub (reserve_cmd,
- &reserve_pub))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not fetch reserve public key\n");
- TALER_TESTING_interpreter_fail (grs->is);
- return;
- }
- if (0 != GNUNET_memcmp (&reserves[i].reserve_pub,
- reserve_pub))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Reserve public key does not match, got %s\n",
- TALER_B2S (&reserves[i].reserve_pub));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Reserve public key does not match, expected %s\n",
- TALER_B2S (reserve_pub));
- match = false;
- }
- }
- {
- const struct TALER_Amount *initial;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_amount (reserve_cmd,
- &initial))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not fetch reserve initial balance\n");
- TALER_TESTING_interpreter_fail (grs->is);
- return;
- }
- if ((GNUNET_OK !=
- TALER_amount_cmp_currency (&reserves[i].merchant_initial_amount,
- initial)) ||
- (0 != TALER_amount_cmp (&reserves[i].merchant_initial_amount,
- initial)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Reserve initial amount does not match, got %s\n",
- TALER_amount2s (&reserves[i].merchant_initial_amount));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Reserve initial amount does not match, wanted %s\n",
- TALER_amount2s (initial));
- match = false;
- }
- }
- if (match)
- matched[i] = true;
- }
- for (unsigned int i = 0; i < reserves_length; ++i)
- if (! matched[i])
- fail = true;
- if (fail)
- {
- TALER_TESTING_interpreter_fail (grs->is);
- return;
- }
- break;
- default:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unhandled HTTP status.\n");
- }
- TALER_TESTING_interpreter_next (grs->is);
-}
-
-
-/**
- * Run the "GET /private/reserves" CMD.
- *
- * @param cls closure.
- * @param cmd command being run now.
- * @param is interpreter state.
- */
-static void
-get_reserves_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct GetReservesState *grs = cls;
-
- grs->is = is;
- grs->rgh = TALER_MERCHANT_reserves_get (is->ctx,
- grs->merchant_url,
- GNUNET_TIME_UNIT_ZERO_TS,
- TALER_EXCHANGE_YNA_ALL,
- TALER_EXCHANGE_YNA_ALL,
- &get_reserves_cb,
- grs);
-
- GNUNET_assert (NULL != grs->rgh);
-}
-
-
-/**
- * Free the state of a "GET reserves" CMD, and possibly
- * cancel a pending operation thereof.
- *
- * @param cls closure.
- * @param cmd command being run.
- */
-static void
-get_reserves_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct GetReservesState *grs = cls;
-
- if (NULL != grs->rgh)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "GET /private/reserves operation did not complete\n");
- TALER_MERCHANT_reserves_get_cancel (grs->rgh);
- }
- GNUNET_array_grow (grs->reserves,
- grs->reserves_length,
- 0);
- GNUNET_free (grs);
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_reserves (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- ...)
-{
- struct GetReservesState *grs;
-
- grs = GNUNET_new (struct GetReservesState);
- grs->merchant_url = merchant_url;
- grs->http_status = http_status;
- {
- const char *clabel;
- va_list ap;
-
- va_start (ap, http_status);
- while (NULL != (clabel = va_arg (ap, const char *)))
- {
- GNUNET_array_append (grs->reserves,
- grs->reserves_length,
- clabel);
- }
- va_end (ap);
- }
- {
- struct TALER_TESTING_Command cmd = {
- .cls = grs,
- .label = label,
- .run = &get_reserves_run,
- .cleanup = &get_reserves_cleanup
- };
-
- return cmd;
- }
-}
-
-
-/* end of testing_api_cmd_get_reserves.c */
diff --git a/src/testing/testing_api_cmd_get_template.c b/src/testing/testing_api_cmd_get_template.c
index bd9afff3..377ffe44 100644
--- a/src/testing/testing_api_cmd_get_template.c
+++ b/src/testing/testing_api_cmd_get_template.c
@@ -71,37 +71,31 @@ struct GetTemplateState
* Callback for a /get/templates/$ID operation.
*
* @param cls closure for this function
- * @param hr HTTP response details
- * @param template_description description of the template
- * @param image base64-encoded template image
- * @param template_contract where the contract of the company is
+ * @param tgr HTTP response details
*/
static void
get_template_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const char *template_description,
- const char *image,
- const json_t *template_contract)
+ const struct TALER_MERCHANT_TemplateGetResponse *tgr)
{
struct GetTemplateState *gis = cls;
const struct TALER_TESTING_Command *template_cmd;
gis->igh = NULL;
- if (gis->http_status != hr->http_status)
+ if (gis->http_status != tgr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
+ tgr->hr.http_status,
+ (int) tgr->hr.ec,
TALER_TESTING_interpreter_get_current_label (gis->is));
TALER_TESTING_interpreter_fail (gis->is);
return;
}
- switch (hr->http_status)
+ switch (tgr->hr.http_status)
{
case MHD_HTTP_OK:
{
- const char **expected_description;
+ const char *expected_description;
template_cmd = TALER_TESTING_interpreter_lookup_command (
gis->is,
@@ -110,8 +104,8 @@ get_template_cb (void *cls,
TALER_TESTING_get_trait_template_description (template_cmd,
&expected_description))
TALER_TESTING_interpreter_fail (gis->is);
- if (0 != strcmp (template_description,
- *expected_description))
+ if (0 != strcmp (tgr->details.ok.template_description,
+ expected_description))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Template description does not match\n");
@@ -120,22 +114,22 @@ get_template_cb (void *cls,
}
}
{
- const char **expected_image;
+ const char *expected_otp_id;
if (GNUNET_OK !=
- TALER_TESTING_get_trait_template_image (template_cmd,
- &expected_image))
+ TALER_TESTING_get_trait_otp_id (template_cmd,
+ &expected_otp_id))
TALER_TESTING_interpreter_fail (gis->is);
- if ( ( (NULL == image) && (NULL != *expected_image)) ||
- ( (NULL != image) && (NULL == *expected_image)) ||
- ( (NULL != image) &&
- (0 != strcmp (image,
- *expected_image)) ) )
+ if ( ( (NULL == tgr->details.ok.otp_id) && (NULL != expected_otp_id)) ||
+ ( (NULL != tgr->details.ok.otp_id) && (NULL == expected_otp_id)) ||
+ ( (NULL != tgr->details.ok.otp_id) &&
+ (0 != strcmp (tgr->details.ok.otp_id,
+ expected_otp_id)) ) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Template image `%s' does not match `%s'\n",
- image,
- *expected_image);
+ "Template pos_key `%s' does not match `%s'\n",
+ tgr->details.ok.otp_id,
+ expected_otp_id);
TALER_TESTING_interpreter_fail (gis->is);
return;
}
@@ -147,7 +141,7 @@ get_template_cb (void *cls,
TALER_TESTING_get_trait_template_contract (template_cmd,
&expected_template_contract))
TALER_TESTING_interpreter_fail (gis->is);
- if (1 != json_equal (template_contract,
+ if (1 != json_equal (tgr->details.ok.template_contract,
expected_template_contract))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -185,11 +179,12 @@ get_template_run (void *cls,
struct GetTemplateState *gis = cls;
gis->is = is;
- gis->igh = TALER_MERCHANT_template_get (is->ctx,
- gis->merchant_url,
- gis->template_id,
- &get_template_cb,
- gis);
+ gis->igh = TALER_MERCHANT_template_get (
+ TALER_TESTING_interpreter_get_context (is),
+ gis->merchant_url,
+ gis->template_id,
+ &get_template_cb,
+ gis);
GNUNET_assert (NULL != gis->igh);
}
diff --git a/src/testing/testing_api_cmd_get_templates.c b/src/testing/testing_api_cmd_get_templates.c
index 3e9d59c6..bc971dc2 100644
--- a/src/testing/testing_api_cmd_get_templates.c
+++ b/src/testing/testing_api_cmd_get_templates.c
@@ -71,33 +71,29 @@ struct GetTemplatesState
* Callback for a GET /templates operation.
*
* @param cls closure for this function
- * @param hr HTTP response details
- * @param templates_length length of the @a templates array
- * @param templates array of templates the requested instance offers
+ * @param tgr response details
*/
static void
get_templates_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int templates_length,
- const struct TALER_MERCHANT_TemplateEntry templates[])
+ const struct TALER_MERCHANT_TemplatesGetResponse *tgr)
{
struct GetTemplatesState *gis = cls;
gis->igh = NULL;
- if (gis->http_status != hr->http_status)
+ if (gis->http_status != tgr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
+ tgr->hr.http_status,
+ (int) tgr->hr.ec,
TALER_TESTING_interpreter_get_current_label (gis->is));
TALER_TESTING_interpreter_fail (gis->is);
return;
}
- switch (hr->http_status)
+ switch (tgr->hr.http_status)
{
case MHD_HTTP_OK:
- if (templates_length != gis->templates_length)
+ if (tgr->details.ok.templates_length != gis->templates_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Length of templates found does not match\n");
@@ -113,19 +109,19 @@ get_templates_cb (void *cls,
gis->templates[i]);
{
- const char **template_id;
+ const char *template_id;
if (GNUNET_OK !=
TALER_TESTING_get_trait_template_id (template_cmd,
- &template_id))
+ &template_id))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Could not fetch template id\n");
TALER_TESTING_interpreter_fail (gis->is);
return;
}
- if (0 != strcmp (templates[i].template_id,
- *template_id))
+ if (0 != strcmp (tgr->details.ok.templates[i].template_id,
+ template_id))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Template id does not match\n");
@@ -143,8 +139,9 @@ get_templates_cb (void *cls,
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Unhandled HTTP status %u (%d).\n",
- hr->http_status,
- hr->ec);
+ tgr->hr.http_status,
+ tgr->hr.ec);
+ break;
}
TALER_TESTING_interpreter_next (gis->is);
}
@@ -160,16 +157,17 @@ get_templates_cb (void *cls,
*/
static void
get_templates_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
{
struct GetTemplatesState *gis = cls;
gis->is = is;
- gis->igh = TALER_MERCHANT_templates_get (is->ctx,
- gis->merchant_url,
- &get_templates_cb,
- gis);
+ gis->igh = TALER_MERCHANT_templates_get (
+ TALER_TESTING_interpreter_get_context (is),
+ gis->merchant_url,
+ &get_templates_cb,
+ gis);
GNUNET_assert (NULL != gis->igh);
}
@@ -183,7 +181,7 @@ get_templates_run (void *cls,
*/
static void
get_templates_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
+ const struct TALER_TESTING_Command *cmd)
{
struct GetTemplatesState *gis = cls;
@@ -202,9 +200,9 @@ get_templates_cleanup (void *cls,
struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_get_templates (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- ...)
+ const char *merchant_url,
+ unsigned int http_status,
+ ...)
{
struct GetTemplatesState *gis;
diff --git a/src/testing/testing_api_cmd_get_tips.c b/src/testing/testing_api_cmd_get_tips.c
deleted file mode 100644
index 89a82202..00000000
--- a/src/testing/testing_api_cmd_get_tips.c
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as
- published by the Free Software Foundation; either version 3, or
- (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public
- License along with TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file testing_api_cmd_get_tips.c
- * @brief command to test GET /private/tips
- * @author Jonathan Buchanan
- */
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-/**
- * State of a "GET tips" CMD.
- */
-struct GetTipsState
-{
-
- /**
- * Handle for a "GET tips" request.
- */
- struct TALER_MERCHANT_TipsGetHandle *tgh;
-
- /**
- * The interpreter state.
- */
- struct TALER_TESTING_Interpreter *is;
-
- /**
- * Base URL of the merchant serving the request.
- */
- const char *merchant_url;
-
- /**
- * Row to start querying the database from.
- */
- uint64_t offset;
-
- /**
- * How many rows to return (with direction).
- */
- int64_t limit;
-
- /**
- * Expected HTTP response code.
- */
- unsigned int http_status;
-
- /**
- * Length of @e tips.
- */
- unsigned int tips_length;
-
- /**
- * References to tips that we expect to be found.
- */
- const char **tips;
-
-};
-
-/**
- * Callback for a GET /private/tips operation.
- *
- * @param cls closure for this function
- * @param hr HTTP response details
- * @param tips_length length of the @a tips array
- * @param tips array of tips
- */
-static void
-get_tips_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int tips_length,
- const struct TALER_MERCHANT_TipEntry tips[])
-{
- struct GetTipsState *gts = cls;
-
- gts->tgh = NULL;
- if (gts->http_status != hr->http_status)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
- TALER_TESTING_interpreter_get_current_label (gts->is));
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- switch (hr->http_status)
- {
- case MHD_HTTP_OK:
- if (tips_length != gts->tips_length)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Tips length does not match\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- for (unsigned int i = 0; i < tips_length; ++i)
- {
- const struct TALER_TESTING_Command *tip_cmd;
-
- tip_cmd = TALER_TESTING_interpreter_lookup_command (
- gts->is,
- gts->tips[i]);
- {
- const struct TALER_TipIdentifierP *tip_id;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_tip_id (tip_cmd,
- &tip_id))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not fetch tip id\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- if (0 != GNUNET_memcmp (tip_id,
- &tips[i].tip_id))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Tip id does not match\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- }
- {
- const struct TALER_Amount *tip_amount;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_amount (tip_cmd,
- &tip_amount))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not fetch tip amount\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- if ((GNUNET_OK != TALER_amount_cmp_currency (tip_amount,
- &tips[i].tip_amount)) ||
- (0 != TALER_amount_cmp (tip_amount,
- &tips[i].tip_amount)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Tip amount does not match\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- }
- }
- break;
- default:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unhandled HTTP status.\n");
- }
- TALER_TESTING_interpreter_next (gts->is);
-}
-
-
-/**
- * Run the "GET /private/tips" CMD.
- *
- * @param cls closure.
- * @param cmd command being run now.
- * @param is interpreter state.
- */
-static void
-get_tips_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct GetTipsState *gts = cls;
-
- gts->is = is;
- gts->tgh = TALER_MERCHANT_tips_get2 (is->ctx,
- gts->merchant_url,
- TALER_EXCHANGE_YNA_NO,
- gts->limit,
- gts->offset,
- &get_tips_cb,
- gts);
-
- GNUNET_assert (NULL != gts->tgh);
-}
-
-
-/**
- * Free the state of a "GET tips" CMD, and possibly
- * cancel a pending operation thereof.
- *
- * @param cls closure.
- * @param cmd command being run.
- */
-static void
-get_tips_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct GetTipsState *gts = cls;
-
- if (NULL != gts->tgh)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "GET /private/tips operation did not complete\n");
- TALER_MERCHANT_tips_get_cancel (gts->tgh);
- }
- GNUNET_array_grow (gts->tips,
- gts->tips_length,
- 0);
- GNUNET_free (gts);
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_get_tips (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- ...)
-{
- struct GetTipsState *gts;
-
- gts = GNUNET_new (struct GetTipsState);
- gts->merchant_url = merchant_url;
- gts->offset = INT64_MAX;
- gts->limit = -20;
- gts->http_status = http_status;
- {
- const char *clabel;
- va_list ap;
-
- va_start (ap, http_status);
- while (NULL != (clabel = va_arg (ap, const char *)))
- {
- GNUNET_array_append (gts->tips,
- gts->tips_length,
- clabel);
- }
- va_end (ap);
- }
- {
- struct TALER_TESTING_Command cmd = {
- .cls = gts,
- .label = label,
- .run = &get_tips_run,
- .cleanup = &get_tips_cleanup
- };
-
- return cmd;
- }
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_get_tips2 (const char *label,
- const char *merchant_url,
- uint64_t offset,
- int64_t limit,
- unsigned int http_status,
- ...)
-{
- struct GetTipsState *gts;
-
- gts = GNUNET_new (struct GetTipsState);
- gts->merchant_url = merchant_url;
- gts->offset = offset;
- gts->limit = limit;
- gts->http_status = http_status;
- {
- const char *clabel;
- va_list ap;
-
- va_start (ap, http_status);
- while (NULL != (clabel = va_arg (ap, const char *)))
- {
- GNUNET_array_append (gts->tips,
- gts->tips_length,
- clabel);
- }
- va_end (ap);
- }
- {
- struct TALER_TESTING_Command cmd = {
- .cls = gts,
- .label = label,
- .run = &get_tips_run,
- .cleanup = &get_tips_cleanup
- };
-
- return cmd;
- }
-}
-
-
-/* end of testing_api_cmd_get_tips.c */
diff --git a/src/testing/testing_api_cmd_get_transfers.c b/src/testing/testing_api_cmd_get_transfers.c
index fe50c349..b5b05295 100644
--- a/src/testing/testing_api_cmd_get_transfers.c
+++ b/src/testing/testing_api_cmd_get_transfers.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018, 2020 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -83,42 +83,42 @@ struct GetTransfersState
* Check the result of our GET /transfers request to a merchant
*
* @param cls closure
- * @param hr HTTP response details
- * @param transfers_length length of the @a transfers array
- * @param transfers array with details about the transfers we received
+ * @param gtr response details
*/
static void
get_transfers_cb (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int transfers_length,
- const struct TALER_MERCHANT_TransferData transfers[])
+ const struct TALER_MERCHANT_GetTransfersResponse *gtr)
{
struct GetTransfersState *gts = cls;
gts->gth = NULL;
- if (gts->http_status != hr->http_status)
+ if (gts->http_status != gtr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
+ gtr->hr.http_status,
+ (int) gtr->hr.ec,
TALER_TESTING_interpreter_get_current_label (gts->is));
TALER_TESTING_interpreter_fail (gts->is);
return;
}
- switch (hr->http_status)
+ switch (gtr->hr.http_status)
{
case MHD_HTTP_OK:
- if (transfers_length != gts->transfers_length)
+ if (gtr->details.ok.transfers_length != gts->transfers_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Transfers length does not match\n");
+ "Transfers length does not match (got %u/want %u)\n",
+ gtr->details.ok.transfers_length,
+ gts->transfers_length);
TALER_TESTING_interpreter_fail (gts->is);
return;
}
- for (unsigned int i = 0; i < transfers_length; ++i)
+ for (unsigned int i = 0; i < gtr->details.ok.transfers_length; ++i)
{
+ const struct TALER_MERCHANT_TransferData *transfer
+ = &gtr->details.ok.transfers[i];
const struct TALER_TESTING_Command *transfer_cmd;
transfer_cmd = TALER_TESTING_interpreter_lookup_command (
@@ -145,7 +145,7 @@ get_transfers_cb (
return;
}
if (0 != GNUNET_memcmp (wtid,
- &transfers[i].wtid))
+ &transfer->wtid))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Wire transfer id does not match\n");
@@ -154,10 +154,10 @@ get_transfers_cb (
}
TALER_TESTING_cmd_merchant_post_transfer_set_serial (
(struct TALER_TESTING_Command *) transfer_cmd,
- transfers[i].credit_serial);
+ transfer->credit_serial);
}
{
- const char **payto_uri;
+ const char *payto_uri;
if (GNUNET_OK !=
TALER_TESTING_get_trait_credit_payto_uri (transfer_cmd,
@@ -168,13 +168,13 @@ get_transfers_cb (
TALER_TESTING_interpreter_fail (gts->is);
return;
}
- if (0 != strcmp (*payto_uri,
- transfers[i].payto_uri))
+ if (0 != strcmp (payto_uri,
+ transfer->payto_uri))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Wire transfer payto uri does not match: %s != %s\n",
- *payto_uri,
- transfers[i].payto_uri);
+ payto_uri,
+ transfer->payto_uri);
TALER_TESTING_interpreter_fail (gts->is);
return;
}
@@ -193,9 +193,9 @@ get_transfers_cb (
}
if ( (GNUNET_OK !=
TALER_amount_cmp_currency (credit_amount,
- &transfers[i].credit_amount)) ||
+ &transfer->credit_amount)) ||
(0 != TALER_amount_cmp (credit_amount,
- &transfers[i].credit_amount)))
+ &transfer->credit_amount)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Wire transfer credit amount does not match\n");
@@ -204,7 +204,7 @@ get_transfers_cb (
}
}
{
- const char **exchange_url;
+ const char *exchange_url;
if (GNUNET_OK !=
TALER_TESTING_get_trait_exchange_url (transfer_cmd,
@@ -215,8 +215,8 @@ get_transfers_cb (
TALER_TESTING_interpreter_fail (gts->is);
return;
}
- if (0 != strcmp (*exchange_url,
- transfers[i].exchange_url))
+ if (0 != strcmp (exchange_url,
+ transfer->exchange_url))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Wire transfer exchange url does not match\n");
@@ -224,34 +224,13 @@ get_transfers_cb (
return;
}
}
- {
- const struct GNUNET_TIME_Timestamp *execution_time;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_timestamp (transfer_cmd,
- 0,
- &execution_time))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not fetch wire transfer execution time\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- if (GNUNET_TIME_timestamp_cmp (*execution_time,
- !=,
- transfers[i].execution_time))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Wire transfer execution time does not match\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- }
}
break;
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unhandled HTTP status.\n");
+ "Unhandled HTTP status %u.\n",
+ gtr->hr.http_status);
+ break;
}
TALER_TESTING_interpreter_next (gts->is);
}
@@ -272,16 +251,17 @@ get_transfers_run (void *cls,
struct GetTransfersState *gts = cls;
gts->is = is;
- gts->gth = TALER_MERCHANT_transfers_get (is->ctx,
- gts->merchant_url,
- gts->payto_uri,
- GNUNET_TIME_UNIT_FOREVER_TS,
- GNUNET_TIME_UNIT_ZERO_TS,
- INT64_MAX,
- 0,
- TALER_EXCHANGE_YNA_ALL,
- &get_transfers_cb,
- gts);
+ gts->gth = TALER_MERCHANT_transfers_get (
+ TALER_TESTING_interpreter_get_context (is),
+ gts->merchant_url,
+ gts->payto_uri,
+ GNUNET_TIME_UNIT_FOREVER_TS,
+ GNUNET_TIME_UNIT_ZERO_TS,
+ INT64_MAX,
+ 0,
+ TALER_EXCHANGE_YNA_ALL,
+ &get_transfers_cb,
+ gts);
GNUNET_assert (NULL != gts->gth);
}
@@ -313,11 +293,12 @@ get_transfers_cleanup (void *cls,
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_transfers (const char *label,
- const char *merchant_url,
- const char *payto_uri,
- unsigned int http_code,
- ...)
+TALER_TESTING_cmd_merchant_get_transfers (
+ const char *label,
+ const char *merchant_url,
+ const char *payto_uri,
+ unsigned int http_code,
+ ...)
{
struct GetTransfersState *gts;
diff --git a/src/testing/testing_api_cmd_get_webhook.c b/src/testing/testing_api_cmd_get_webhook.c
index 7acc344e..aef6c555 100644
--- a/src/testing/testing_api_cmd_get_webhook.c
+++ b/src/testing/testing_api_cmd_get_webhook.c
@@ -105,7 +105,7 @@ get_webhook_cb (void *cls,
{
case MHD_HTTP_OK:
{
- const char **expected_event_type;
+ const char *expected_event_type;
webhook_cmd = TALER_TESTING_interpreter_lookup_command (
gis->is,
@@ -114,8 +114,8 @@ get_webhook_cb (void *cls,
TALER_TESTING_get_trait_event_type (webhook_cmd,
&expected_event_type))
TALER_TESTING_interpreter_fail (gis->is);
- if (0 != strcmp (event_type,
- *expected_event_type))
+ if (0 != strcmp (event_type,
+ expected_event_type))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Event type does not match\n");
@@ -124,14 +124,14 @@ get_webhook_cb (void *cls,
}
}
{
- const char **expected_url;
+ const char *expected_url;
if (GNUNET_OK !=
TALER_TESTING_get_trait_url (webhook_cmd,
&expected_url))
TALER_TESTING_interpreter_fail (gis->is);
if (0 != strcmp (url,
- *expected_url))
+ expected_url))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"URL does not match\n");
@@ -140,14 +140,14 @@ get_webhook_cb (void *cls,
}
}
{
- const char **expected_http_method;
+ const char *expected_http_method;
if (GNUNET_OK !=
TALER_TESTING_get_trait_http_method (webhook_cmd,
&expected_http_method))
TALER_TESTING_interpreter_fail (gis->is);
if (0 != strcmp (http_method,
- *expected_http_method))
+ expected_http_method))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"http_method does not match\n");
@@ -156,14 +156,17 @@ get_webhook_cb (void *cls,
}
}
{
- const char **expected_header_template;
+ const char *expected_header_template;
if (GNUNET_OK !=
TALER_TESTING_get_trait_header_template (webhook_cmd,
&expected_header_template))
TALER_TESTING_interpreter_fail (gis->is);
- if (0 != strcmp (header_template,
- *expected_header_template))
+ if ( ( (NULL == header_template) && (NULL != expected_header_template)) ||
+ ( (NULL != header_template) && (NULL == expected_header_template)) ||
+ ( (NULL != header_template) &&
+ (0 != strcmp (header_template,
+ expected_header_template)) ) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"header template does not match\n");
@@ -172,14 +175,17 @@ get_webhook_cb (void *cls,
}
}
{
- const char **expected_body_template;
+ const char *expected_body_template;
if (GNUNET_OK !=
TALER_TESTING_get_trait_body_template (webhook_cmd,
&expected_body_template))
TALER_TESTING_interpreter_fail (gis->is);
- if (0 != strcmp (body_template,
- *expected_body_template))
+ if ( ( (NULL == body_template) && (NULL != expected_body_template)) ||
+ ( (NULL != body_template) && (NULL == expected_body_template)) ||
+ ( (NULL != body_template) &&
+ (0 != strcmp (body_template,
+ expected_body_template)) ) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"body template does not match\n");
@@ -216,7 +222,8 @@ get_webhook_run (void *cls,
struct GetWebhookState *gis = cls;
gis->is = is;
- gis->igh = TALER_MERCHANT_webhook_get (is->ctx,
+ gis->igh = TALER_MERCHANT_webhook_get (TALER_TESTING_interpreter_get_context (
+ is),
gis->merchant_url,
gis->webhook_id,
&get_webhook_cb,
diff --git a/src/testing/testing_api_cmd_get_webhooks.c b/src/testing/testing_api_cmd_get_webhooks.c
index 536512aa..56bf43e8 100644
--- a/src/testing/testing_api_cmd_get_webhooks.c
+++ b/src/testing/testing_api_cmd_get_webhooks.c
@@ -71,33 +71,29 @@ struct GetWebhooksState
* Callback for a GET /webhooks operation.
*
* @param cls closure for this function
- * @param hr HTTP response details
- * @param webhooks_length length of the @a webhooks array
- * @param webhooks array of webhooks the requested instance offers
+ * @param wgr response details
*/
static void
get_webhooks_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int webhooks_length,
- const struct TALER_MERCHANT_WebhookEntry webhooks[])
+ const struct TALER_MERCHANT_WebhooksGetResponse *wgr)
{
struct GetWebhooksState *gis = cls;
gis->igh = NULL;
- if (gis->http_status != hr->http_status)
+ if (gis->http_status != wgr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
+ wgr->hr.http_status,
+ (int) wgr->hr.ec,
TALER_TESTING_interpreter_get_current_label (gis->is));
TALER_TESTING_interpreter_fail (gis->is);
return;
}
- switch (hr->http_status)
+ switch (wgr->hr.http_status)
{
case MHD_HTTP_OK:
- if (webhooks_length != gis->webhooks_length)
+ if (wgr->details.ok.webhooks_length != gis->webhooks_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Length of webhooks found does not match\n");
@@ -113,7 +109,7 @@ get_webhooks_cb (void *cls,
gis->webhooks[i]);
{
- const char **webhook_id;
+ const char *webhook_id;
if (GNUNET_OK !=
TALER_TESTING_get_trait_webhook_id (webhook_cmd,
@@ -124,8 +120,8 @@ get_webhooks_cb (void *cls,
TALER_TESTING_interpreter_fail (gis->is);
return;
}
- if (0 != strcmp (webhooks[i].webhook_id,
- *webhook_id))
+ if (0 != strcmp (wgr->details.ok.webhooks[i].webhook_id,
+ webhook_id))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Webhook id does not match\n");
@@ -143,8 +139,8 @@ get_webhooks_cb (void *cls,
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Unhandled HTTP status %u (%d).\n",
- hr->http_status,
- hr->ec);
+ wgr->hr.http_status,
+ wgr->hr.ec);
}
TALER_TESTING_interpreter_next (gis->is);
}
@@ -166,10 +162,11 @@ get_webhooks_run (void *cls,
struct GetWebhooksState *gis = cls;
gis->is = is;
- gis->igh = TALER_MERCHANT_webhooks_get (is->ctx,
- gis->merchant_url,
- &get_webhooks_cb,
- gis);
+ gis->igh = TALER_MERCHANT_webhooks_get (
+ TALER_TESTING_interpreter_get_context (is),
+ gis->merchant_url,
+ &get_webhooks_cb,
+ gis);
GNUNET_assert (NULL != gis->igh);
}
diff --git a/src/testing/testing_api_cmd_instance_auth.c b/src/testing/testing_api_cmd_instance_auth.c
index f9597464..58f6f9c9 100644
--- a/src/testing/testing_api_cmd_instance_auth.c
+++ b/src/testing/testing_api_cmd_instance_auth.c
@@ -125,12 +125,13 @@ auth_instance_run (void *cls,
struct AuthInstanceState *ais = cls;
ais->is = is;
- ais->iaph = TALER_MERCHANT_instance_auth_post (is->ctx,
- ais->merchant_url,
- ais->instance_id,
- ais->auth_token,
- &auth_instance_cb,
- ais);
+ ais->iaph = TALER_MERCHANT_instance_auth_post (
+ TALER_TESTING_interpreter_get_context (is),
+ ais->merchant_url,
+ ais->instance_id,
+ ais->auth_token,
+ &auth_instance_cb,
+ ais);
GNUNET_assert (NULL != ais->iaph);
}
@@ -175,7 +176,7 @@ auth_instance_traits (void *cls,
{
struct AuthInstanceState *ais = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_auth_token (&ais->auth_token),
+ TALER_TESTING_make_trait_auth_token (ais->auth_token),
TALER_TESTING_trait_end ()
};
diff --git a/src/testing/testing_api_cmd_kyc_get.c b/src/testing/testing_api_cmd_kyc_get.c
index 96d0b58c..a8f29264 100644
--- a/src/testing/testing_api_cmd_kyc_get.c
+++ b/src/testing/testing_api_cmd_kyc_get.c
@@ -73,6 +73,11 @@ struct KycGetState
unsigned int expected_http_status;
/**
+ * Expected AML state.
+ */
+ enum TALER_AmlDecisionState expected_aml_state;
+
+ /**
* Interpreter state.
*/
struct TALER_TESTING_Interpreter *is;
@@ -126,7 +131,21 @@ kyc_get_cb (void *cls,
switch (kr->hr.http_status)
{
case MHD_HTTP_ACCEPTED:
- if (0 != kr->details.kyc_status.pending_kycs_length)
+
+ if ( ( (TALER_AML_NORMAL != cs->expected_aml_state) &&
+ (0 == kr->details.kyc_status.pending_kycs_length) ) ||
+ ( (0 < kr->details.kyc_status.pending_kycs_length) &&
+ (cs->expected_aml_state !=
+ kr->details.kyc_status.pending_kycs[0].aml_status) ) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected AML state %u, got %u/%u\n",
+ cs->expected_aml_state,
+ kr->details.kyc_status.pending_kycs[0].aml_status,
+ kr->details.kyc_status.pending_kycs_length);
+ TALER_TESTING_FAIL (cs->is);
+ }
+ for (unsigned int i = 0; i<kr->details.kyc_status.pending_kycs_length; i++)
{
const char *url;
const char *tok;
@@ -136,7 +155,21 @@ kyc_get_cb (void *cls,
const char *nq;
size_t toklen;
- url = kr->details.kyc_status.pending_kycs[0].kyc_url;
+ url = kr->details.kyc_status.pending_kycs[i].kyc_url;
+ if (NULL == url)
+ {
+ /* AML status here must be either pending or frozne */
+ switch (kr->details.kyc_status.pending_kycs[i].aml_status)
+ {
+ case TALER_AML_NORMAL:
+ TALER_TESTING_FAIL (cs->is);
+ case TALER_AML_PENDING:
+ continue;
+ case TALER_AML_FROZEN:
+ continue;
+ }
+ TALER_TESTING_FAIL (cs->is);
+ }
tok = strstr (url, "&redirect_uri=");
if (NULL == tok)
TALER_TESTING_FAIL (cs->is);
@@ -159,18 +192,18 @@ kyc_get_cb (void *cls,
GNUNET_free (dec);
TALER_TESTING_FAIL (cs->is);
}
- eq += strlen ("/kyc-proof/");
- eq = strstr (eq, "state=");
+ GNUNET_free (dec);
+
+ eq = strstr (url, "&state=");
if (NULL == eq)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Received unexpected 'state'-less KYC URL `%s' (%s)\n",
url,
dec);
- GNUNET_free (dec);
TALER_TESTING_FAIL (cs->is);
}
- eq += strlen ("state=");
+ eq += strlen ("&state=");
nq = strchr (eq, '&');
if (NULL == nq)
nq = eq + strlen (eq);
@@ -183,11 +216,9 @@ kyc_get_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Received unexpected KYC URL `%s' (%s) - no h_payto in state\n",
url,
- dec);
- GNUNET_free (dec);
+ eq);
TALER_TESTING_FAIL (cs->is);
}
- GNUNET_free (dec);
}
break;
}
@@ -234,7 +265,8 @@ kyc_get_run (void *cls,
}
}
if (NULL == cs->instance_id)
- cs->kgh = TALER_MERCHANT_kyc_get (is->ctx,
+ cs->kgh = TALER_MERCHANT_kyc_get (TALER_TESTING_interpreter_get_context (
+ is),
cs->merchant_url,
h_wire,
cs->exchange_url,
@@ -242,14 +274,15 @@ kyc_get_run (void *cls,
&kyc_get_cb,
cs);
else
- cs->kgh = TALER_MERCHANT_management_kyc_get (is->ctx,
- cs->merchant_url,
- cs->instance_id,
- h_wire,
- cs->exchange_url,
- GNUNET_TIME_UNIT_ZERO,
- &kyc_get_cb,
- cs);
+ cs->kgh = TALER_MERCHANT_management_kyc_get (
+ TALER_TESTING_interpreter_get_context (is),
+ cs->merchant_url,
+ cs->instance_id,
+ h_wire,
+ cs->exchange_url,
+ GNUNET_TIME_UNIT_ZERO,
+ &kyc_get_cb,
+ cs);
GNUNET_assert (NULL != cs->kgh);
}
@@ -285,12 +318,14 @@ kyc_get_traits (void *cls,
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_kyc_get (const char *label,
- const char *merchant_url,
- const char *instance_id,
- const char *h_wire_ref,
- const char *exchange_url,
- unsigned int expected_http_status)
+TALER_TESTING_cmd_merchant_kyc_get (
+ const char *label,
+ const char *merchant_url,
+ const char *instance_id,
+ const char *h_wire_ref,
+ const char *exchange_url,
+ unsigned int expected_http_status,
+ enum TALER_AmlDecisionState expected_aml_state)
{
struct KycGetState *cs;
@@ -300,6 +335,7 @@ TALER_TESTING_cmd_merchant_kyc_get (const char *label,
cs->h_wire_ref = h_wire_ref;
cs->exchange_url = exchange_url;
cs->expected_http_status = expected_http_status;
+ cs->expected_aml_state = expected_aml_state;
{
struct TALER_TESTING_Command cmd = {
.cls = cs,
diff --git a/src/testing/testing_api_cmd_lock_product.c b/src/testing/testing_api_cmd_lock_product.c
index da9e8832..5703b9c2 100644
--- a/src/testing/testing_api_cmd_lock_product.c
+++ b/src/testing/testing_api_cmd_lock_product.c
@@ -135,14 +135,15 @@ lock_product_run (void *cls,
struct LockProductState *pis = cls;
pis->is = is;
- pis->iph = TALER_MERCHANT_product_lock (is->ctx,
- pis->merchant_url,
- pis->product_id,
- pis->uuid,
- pis->duration,
- pis->quantity,
- &lock_product_cb,
- pis);
+ pis->iph = TALER_MERCHANT_product_lock (
+ TALER_TESTING_interpreter_get_context (is),
+ pis->merchant_url,
+ pis->product_id,
+ pis->uuid,
+ pis->duration,
+ pis->quantity,
+ &lock_product_cb,
+ pis);
GNUNET_assert (NULL != pis->iph);
}
@@ -188,8 +189,7 @@ lock_product_traits (void *cls,
{
struct LockProductState *lps = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_lock_uuid (
- (const char **) &lps->uuid),
+ TALER_TESTING_make_trait_lock_uuid (lps->uuid),
TALER_TESTING_trait_end ()
};
diff --git a/src/testing/testing_api_cmd_merchant_get_order.c b/src/testing/testing_api_cmd_merchant_get_order.c
index 467b6d61..6301c9f6 100644
--- a/src/testing/testing_api_cmd_merchant_get_order.c
+++ b/src/testing/testing_api_cmd_merchant_get_order.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -94,6 +94,28 @@ struct MerchantGetOrderState
unsigned int forgets_length;
/**
+ * Set to a session ID, if we should pass one as part
+ * of the request.
+ */
+ const char *session_id;
+
+ /**
+ * Set if we expect to be referred to another equivalent order which was
+ * already paid by the wallet under this @e session_id.
+ */
+ const char *repurchase_order_ref;
+
+ /**
+ * Expected minimum age.
+ */
+ unsigned int expected_min_age;
+
+ /**
+ * True if we should pass the 'allow_refunded_for_repurchase' flag.
+ */
+ bool allow_refunded_for_repurchase;
+
+ /**
* Whether the order was refunded or not.
*/
bool refunded;
@@ -156,20 +178,33 @@ merchant_get_order_cb (
switch (osr->hr.http_status)
{
case MHD_HTTP_OK:
- if (gos->osc != osr->details.success.status)
+ if (gos->osc != osr->details.ok.status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Order paid does not match\n");
+ "Order paid does not match: %d vs %d\n",
+ gos->osc,
+ osr->details.ok.status);
TALER_TESTING_interpreter_fail (gos->is);
return;
}
- switch (osr->details.success.status)
+ switch (osr->details.ok.status)
{
case TALER_MERCHANT_OSC_PAID:
{
const struct TALER_TESTING_Command *order_cmd;
struct TALER_Amount refunded_total;
+ if ( (0 != gos->expected_min_age) &&
+ (gos->expected_min_age !=
+ json_integer_value (
+ json_object_get (
+ osr->details.ok.details.paid.contract_terms,
+ "minimum_age"))) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (gos->is);
+ return;
+ }
order_cmd = TALER_TESTING_interpreter_lookup_command (
gos->is,
gos->order_reference);
@@ -212,7 +247,7 @@ merchant_get_order_cb (
for (unsigned int j = 0; j < *paths_length; ++j)
{
- const char **path;
+ const char *path;
int res = GNUNET_OK;
if (GNUNET_OK !=
@@ -228,7 +263,7 @@ merchant_get_order_cb (
GNUNET_assert (GNUNET_OK ==
TALER_JSON_expand_path (ct,
- *path,
+ path,
&apply_forget,
&res));
GNUNET_assert (GNUNET_OK == res);
@@ -236,7 +271,7 @@ merchant_get_order_cb (
}
if (1 != json_equal (ct,
- osr->details.success.details.paid.contract_terms))
+ osr->details.ok.details.paid.contract_terms))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Order contract terms do not match\n");
@@ -246,14 +281,14 @@ merchant_get_order_cb (
json_decref (ct);
}
- if (gos->wired != osr->details.success.details.paid.wired)
+ if (gos->wired != osr->details.ok.details.paid.wired)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Order wired does not match\n");
TALER_TESTING_interpreter_fail (gos->is);
return;
}
- if (gos->transfers_length != osr->details.success.details.paid.wts_len)
+ if (gos->transfers_length != osr->details.ok.details.paid.wts_len)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Number of transfers found does not match\n");
@@ -280,7 +315,7 @@ merchant_get_order_cb (
return;
}
if (0 != GNUNET_memcmp (wtid,
- &osr->details.success.details.paid.wts[i].
+ &osr->details.ok.details.paid.wts[i].
wtid))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -290,7 +325,7 @@ merchant_get_order_cb (
}
}
{
- const char **exchange_url;
+ const char *exchange_url;
if (GNUNET_OK !=
TALER_TESTING_get_trait_exchange_url (transfer_cmd,
@@ -301,9 +336,9 @@ merchant_get_order_cb (
TALER_TESTING_interpreter_fail (gos->is);
return;
}
- if (0 != strcmp (*exchange_url,
- osr->details.success.details.paid.wts[i].
- exchange_url))
+ if (0 != strcmp (
+ exchange_url,
+ osr->details.ok.details.paid.wts[i].exchange_url))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Wire transfer exchange url does not match\n");
@@ -311,48 +346,8 @@ merchant_get_order_cb (
return;
}
}
- {
- struct TALER_Amount transfer_total;
- const struct TALER_Amount *transfer_amount;
- const struct TALER_Amount *transfer_fee;
-
- if ((GNUNET_OK !=
- TALER_TESTING_get_trait_amount (transfer_cmd,
- &transfer_amount)) ||
- (GNUNET_OK !=
- TALER_TESTING_get_trait_fee (transfer_cmd,
- &transfer_fee)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not fetch wire transfer amount/fee\n");
- TALER_TESTING_interpreter_fail (gos->is);
- return;
- }
- if (0 > TALER_amount_add (&transfer_total,
- transfer_amount,
- transfer_fee))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not total wire transfer\n");
- TALER_TESTING_interpreter_fail (gos->is);
- return;
- }
- if ((GNUNET_OK !=
- TALER_amount_cmp_currency (
- &transfer_total,
- &osr->details.success.details.paid.wts[i].total_amount)) ||
- (0 != TALER_amount_cmp (
- &transfer_total,
- &osr->details.success.details.paid.wts[i].total_amount)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Wire transfer total does not match\n");
- TALER_TESTING_interpreter_fail (gos->is);
- return;
- }
- }
}
- if (gos->refunded != osr->details.success.details.paid.refunded)
+ if (gos->refunded != osr->details.ok.details.paid.refunded)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Order refunded does not match\n");
@@ -360,7 +355,7 @@ merchant_get_order_cb (
return;
}
if (gos->refunds_length !=
- osr->details.success.details.paid.refunds_len)
+ osr->details.ok.details.paid.refunds_len)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Number of refunds found does not match\n");
@@ -371,7 +366,7 @@ merchant_get_order_cb (
GNUNET_assert (
GNUNET_OK ==
TALER_amount_set_zero (
- osr->details.success.details.paid.refund_amount.currency,
+ osr->details.ok.details.paid.refund_amount.currency,
&refunded_total));
for (unsigned int i = 0; i < gos->refunds_length; ++i)
{
@@ -383,7 +378,7 @@ merchant_get_order_cb (
{
const struct TALER_Amount *expected_amount;
struct TALER_Amount *amount_found =
- &osr->details.success.details.paid.refunds[i].refund_amount;
+ &osr->details.ok.details.paid.refunds[i].refund_amount;
if (GNUNET_OK !=
TALER_TESTING_get_trait_amount (refund_cmd,
@@ -410,7 +405,7 @@ merchant_get_order_cb (
}
}
{
- const char **expected_reason;
+ const char *expected_reason;
if (GNUNET_OK !=
TALER_TESTING_get_trait_reason (refund_cmd,
@@ -421,9 +416,10 @@ merchant_get_order_cb (
TALER_TESTING_interpreter_fail (gos->is);
return;
}
- if (0 != strcmp (
- *expected_reason,
- osr->details.success.details.paid.refunds[i].reason))
+ if (0 !=
+ strcmp (
+ expected_reason,
+ osr->details.ok.details.paid.refunds[i].reason))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Refund reason does not match\n");
@@ -433,7 +429,7 @@ merchant_get_order_cb (
}
}
- if (gos->wired != osr->details.success.details.paid.wired)
+ if (gos->wired != osr->details.ok.details.paid.wired)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Order wired does not match\n");
@@ -444,22 +440,58 @@ merchant_get_order_cb (
break;
case TALER_MERCHANT_OSC_CLAIMED:
/* FIXME: Check contract terms... */
+ if ( (0 != gos->expected_min_age) &&
+ (gos->expected_min_age !=
+ json_integer_value (
+ json_object_get (
+ osr->details.ok.details.claimed.contract_terms,
+ "minimum_age"))) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (gos->is);
+ return;
+ }
break;
case TALER_MERCHANT_OSC_UNPAID:
{
struct TALER_MERCHANT_PayUriData pud;
const struct TALER_TESTING_Command *order_cmd;
- const char **order_id;
+ const char *order_id;
const struct TALER_ClaimTokenP *claim_token;
+ if (NULL != gos->repurchase_order_ref)
+ {
+ const struct TALER_TESTING_Command *rep_cmd;
+ const char *rep_id;
+ const char *ri;
+
+ rep_cmd = TALER_TESTING_interpreter_lookup_command (
+ gos->is,
+ gos->repurchase_order_ref);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_order_id (rep_cmd,
+ &rep_id))
+ {
+ TALER_TESTING_FAIL (gos->is);
+ }
+ ri = osr->details.ok.details.unpaid.already_paid_order_id;
+ if ( (NULL == ri) ||
+ (0 !=
+ strcmp (ri,
+ rep_id)) )
+ {
+ TALER_TESTING_FAIL (gos->is);
+ }
+ }
+
if (GNUNET_OK !=
TALER_MERCHANT_parse_pay_uri (
- osr->details.success.details.unpaid.taler_pay_uri,
+ osr->details.ok.details.unpaid.taler_pay_uri,
&pud))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Taler pay uri `%s' is malformed\n",
- osr->details.success.details.unpaid.taler_pay_uri);
+ osr->details.ok.details.unpaid.taler_pay_uri);
TALER_TESTING_interpreter_fail (gos->is);
return;
}
@@ -484,37 +516,21 @@ merchant_get_order_cb (
TALER_TESTING_FAIL (gos->is);
}
{
- char *port;
char *host;
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (gos->is->cfg,
- "merchant",
- "PORT",
- &port))
- {
- /* How did we get here without a configured port? */
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (gos->is);
- TALER_MERCHANT_parse_pay_uri_free (&pud);
- return;
- }
- GNUNET_asprintf (&host,
- "localhost:%s",
- port);
- GNUNET_free (port);
+ host = TALER_MERCHANT_TESTING_extract_host (gos->merchant_url);
if ((0 != strcmp (host,
pud.merchant_host)) ||
(NULL != pud.merchant_prefix_path) ||
- (0 != strcmp (*order_id,
+ (0 != strcmp (order_id,
pud.order_id)) ||
(NULL != pud.ssid))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Order pay uri `%s' does not match, wanted %s/%s\n",
- osr->details.success.details.unpaid.taler_pay_uri,
+ osr->details.ok.details.unpaid.taler_pay_uri,
host,
- *order_id);
+ order_id);
TALER_TESTING_interpreter_fail (gos->is);
TALER_MERCHANT_parse_pay_uri_free (&pud);
GNUNET_free (host);
@@ -568,7 +584,7 @@ merchant_get_order_run (void *cls,
{
struct MerchantGetOrderState *gos = cls;
const struct TALER_TESTING_Command *order_cmd;
- const char **order_id;
+ const char *order_id;
const struct TALER_PrivateContractHashP *h_contract;
order_cmd = TALER_TESTING_interpreter_lookup_command (
@@ -586,14 +602,14 @@ merchant_get_order_run (void *cls,
TALER_TESTING_FAIL (is);
gos->is = is;
- gos->ogh = TALER_MERCHANT_merchant_order_get (is->ctx,
- gos->merchant_url,
- *order_id,
- NULL,
- true,
- GNUNET_TIME_UNIT_ZERO,
- &merchant_get_order_cb,
- gos);
+ gos->ogh = TALER_MERCHANT_merchant_order_get (
+ TALER_TESTING_interpreter_get_context (is),
+ gos->merchant_url,
+ order_id,
+ gos->session_id,
+ GNUNET_TIME_UNIT_ZERO,
+ &merchant_get_order_cb,
+ gos);
}
@@ -612,7 +628,7 @@ merchant_get_order_cleanup (void *cls,
if (NULL != gos->ogh)
{
- TALER_LOG_WARNING ("Get tip operation did not complete\n");
+ TALER_LOG_WARNING ("Get order operation did not complete\n");
TALER_MERCHANT_merchant_order_get_cancel (gos->ogh);
}
GNUNET_array_grow (gos->transfers,
@@ -629,13 +645,14 @@ merchant_get_order_cleanup (void *cls,
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_order (const char *label,
- const char *merchant_url,
- const char *order_reference,
- enum TALER_MERCHANT_OrderStatusCode osc,
- bool refunded,
- unsigned int http_status,
- ...)
+TALER_TESTING_cmd_merchant_get_order (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ enum TALER_MERCHANT_OrderStatusCode osc,
+ bool refunded,
+ unsigned int http_status,
+ ...)
{
struct MerchantGetOrderState *gos;
@@ -673,16 +690,17 @@ TALER_TESTING_cmd_merchant_get_order (const char *label,
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_order2 (const char *label,
- const char *merchant_url,
- const char *order_reference,
- enum TALER_MERCHANT_OrderStatusCode osc,
- bool wired,
- const char **transfers,
- bool refunded,
- const char **refunds,
- const char **forgets,
- unsigned int http_status)
+TALER_TESTING_cmd_merchant_get_order2 (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ enum TALER_MERCHANT_OrderStatusCode osc,
+ bool wired,
+ const char **transfers,
+ bool refunded,
+ const char **refunds,
+ const char **forgets,
+ unsigned int http_status)
{
struct MerchantGetOrderState *gos;
@@ -733,6 +751,68 @@ TALER_TESTING_cmd_merchant_get_order2 (const char *label,
}
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_order3 (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ enum TALER_MERCHANT_OrderStatusCode osc,
+ const char *session_id,
+ const char *repurchase_order_ref,
+ unsigned int expected_http_status)
+{
+ struct MerchantGetOrderState *gos;
+
+ gos = GNUNET_new (struct MerchantGetOrderState);
+ gos->merchant_url = merchant_url;
+ gos->order_reference = order_reference;
+ gos->osc = osc;
+ gos->session_id = session_id;
+ gos->repurchase_order_ref = repurchase_order_ref;
+ gos->http_status = expected_http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = gos,
+ .label = label,
+ .run = &merchant_get_order_run,
+ .cleanup = &merchant_get_order_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_order4 (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ enum TALER_MERCHANT_OrderStatusCode osc,
+ uint32_t expected_min_age,
+ unsigned int expected_http_status)
+{
+ struct MerchantGetOrderState *gos;
+
+ gos = GNUNET_new (struct MerchantGetOrderState);
+ gos->merchant_url = merchant_url;
+ gos->order_reference = order_reference;
+ gos->osc = osc;
+ gos->expected_min_age = expected_min_age;
+ gos->http_status = expected_http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = gos,
+ .label = label,
+ .run = &merchant_get_order_run,
+ .cleanup = &merchant_get_order_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
struct MerchantPollOrderConcludeState
{
/**
@@ -907,14 +987,14 @@ merchant_poll_order_start_run (void *cls,
= GNUNET_TIME_absolute_add (GNUNET_TIME_relative_to_absolute (pos->timeout),
GNUNET_TIME_UNIT_SECONDS);
pos->is = is;
- pos->ogh = TALER_MERCHANT_merchant_order_get (is->ctx,
- pos->merchant_url,
- pos->order_id,
- NULL,
- false,
- pos->timeout,
- &merchant_poll_order_cb,
- pos);
+ pos->ogh = TALER_MERCHANT_merchant_order_get (
+ TALER_TESTING_interpreter_get_context (is),
+ pos->merchant_url,
+ pos->order_id,
+ NULL,
+ pos->timeout,
+ &merchant_poll_order_cb,
+ pos);
GNUNET_assert (NULL != pos->ogh);
/* We CONTINUE to run the interpreter while the long-polled command
completes asynchronously! */
@@ -947,14 +1027,12 @@ merchant_poll_order_start_cleanup (void *cls,
}
-/**
- * Start a long poll for GET /private/orders/$ORDER_ID.
- */
struct TALER_TESTING_Command
-TALER_TESTING_cmd_poll_order_start (const char *label,
- const char *merchant_url,
- const char *order_id,
- struct GNUNET_TIME_Relative timeout)
+TALER_TESTING_cmd_poll_order_start (
+ const char *label,
+ const char *merchant_url,
+ const char *order_id,
+ struct GNUNET_TIME_Relative timeout)
{
struct MerchantPollOrderStartState *pos;
@@ -1042,9 +1120,6 @@ merchant_poll_order_conclude_cleanup (void *cls,
}
-/**
- * Complete a long poll for GET /private/orders/$ORDER_ID.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_poll_order_conclude (const char *label,
unsigned int http_status,
diff --git a/src/testing/testing_api_cmd_merchant_get_tip.c b/src/testing/testing_api_cmd_merchant_get_tip.c
deleted file mode 100644
index a8e7a67e..00000000
--- a/src/testing/testing_api_cmd_merchant_get_tip.c
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as
- published by the Free Software Foundation; either version 3, or
- (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public
- License along with TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file testing_api_cmd_merchant_get_tip.c
- * @brief command to test GET /private/tips/$TIP_ID.
- * @author Jonathan Buchanan
- */
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-/**
- * State for a GET /private/tips/$TIP_ID CMD.
- */
-struct MerchantTipGetState
-{
-
- /**
- * The merchant base URL.
- */
- const char *merchant_url;
-
- /**
- * Expected HTTP response code for this CMD.
- */
- unsigned int http_status;
-
- /**
- * Whether to fetch and compare pickups.
- */
- bool fetch_pickups;
-
- /**
- * The length of @e pickups.
- */
- unsigned int pickups_length;
-
- /**
- * The NULL-terminated list of pickup commands associated with the tip.
- */
- const char **pickups;
-
- /**
- * The handle to the current GET /tips/$TIP_ID request.
- */
- struct TALER_MERCHANT_TipMerchantGetHandle *tgh;
-
- /**
- * The interpreter state.
- */
- struct TALER_TESTING_Interpreter *is;
-
- /**
- * Reference to a command that created a tip.
- */
- const char *tip_reference;
-};
-
-
-/**
- * Callback for a GET /private/tips/$TIP_ID operation.
- *
- * @param cls closure for this function
- * @param tsr response
- */
-static void
-merchant_get_tip_cb (void *cls,
- const struct TALER_MERCHANT_TipStatusResponse *tsr)
-{
- struct MerchantTipGetState *gts = cls;
- const struct TALER_TESTING_Command *authorize_cmd;
- struct TALER_Amount expected_total_picked_up;
-
- authorize_cmd = TALER_TESTING_interpreter_lookup_command (gts->is,
- gts->tip_reference);
-
- gts->tgh = NULL;
- if (gts->http_status != tsr->hr.http_status)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u (%d) to command %s\n",
- tsr->hr.http_status,
- (int) tsr->hr.ec,
- TALER_TESTING_interpreter_get_current_label (gts->is));
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- switch (tsr->hr.http_status)
- {
- case MHD_HTTP_OK:
- {
- const struct TALER_Amount *initial_amount;
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (tsr->details.success.total_picked_up.currency,
- &expected_total_picked_up));
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_amount (authorize_cmd,
- &initial_amount))
- TALER_TESTING_FAIL (gts->is);
- if ((GNUNET_OK !=
- TALER_amount_cmp_currency (&tsr->details.success.total_authorized,
- initial_amount)) ||
- (0 != TALER_amount_cmp (&tsr->details.success.total_authorized,
- initial_amount)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Tip authorized amount does not match\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- }
- {
- const char **justification;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_reason (authorize_cmd,
- &justification))
- TALER_TESTING_FAIL (gts->is);
- if (0 != strcmp (tsr->details.success.reason,
- *justification))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Tip authorized reason does not match\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- }
- {
- const struct GNUNET_TIME_Timestamp *tip_expiration;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_timestamp (authorize_cmd,
- 0,
- &tip_expiration))
- TALER_TESTING_FAIL (gts->is);
- if (GNUNET_TIME_timestamp_cmp (*tip_expiration,
- !=,
- tsr->details.success.expiration))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Tip authorized expiration does not match\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- }
- if (tsr->details.success.pickups_length != gts->pickups_length)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Length of pickups array does not match\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- {
- for (unsigned int i = 0; i < gts->pickups_length; ++i)
- {
- const struct TALER_TESTING_Command *pickup_cmd;
-
- pickup_cmd = TALER_TESTING_interpreter_lookup_command (gts->is,
- gts->pickups[i]);
- {
- const uint32_t *num_planchets;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_num_planchets (pickup_cmd,
- &num_planchets))
- TALER_TESTING_FAIL (gts->is);
-
- if (*num_planchets != tsr->details.success.pickups[i].num_planchets)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Pickup planchet count does not match\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- }
- {
- const struct TALER_Amount *total;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_amount (pickup_cmd,
- &total))
- TALER_TESTING_FAIL (gts->is);
-
- if ( (GNUNET_OK !=
- TALER_amount_cmp_currency (total,
- &tsr->details.success.pickups[i].requested_amount)) ||
- (0 != TALER_amount_cmp (total,
- &tsr->details.success.pickups[i].requested_amount)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Pickup planchet sum does not match\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- GNUNET_assert (0 < TALER_amount_add (&expected_total_picked_up,
- &expected_total_picked_up,
- total));
- }
- }
- if ( (GNUNET_OK !=
- TALER_amount_cmp_currency (&expected_total_picked_up,
- &tsr->details.success.total_picked_up)) ||
- (0 !=
- TALER_amount_cmp (&expected_total_picked_up,
- &tsr->details.success.total_picked_up)) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Tip picked up amount does not match\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- }
- break;
- default:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unhandled HTTP status.\n");
- }
- TALER_TESTING_interpreter_next (gts->is);
-}
-
-
-/**
- * Run the "GET tip" CMD.
- *
- * @param cls closure.
- * @param cmd command being run now.
- * @param is interpreter state.
- */
-static void
-merchant_get_tip_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct MerchantTipGetState *tgs = cls;
- const struct TALER_TESTING_Command *tip_cmd;
- const struct TALER_TipIdentifierP *tip_id;
-
- tip_cmd = TALER_TESTING_interpreter_lookup_command (is,
- tgs->tip_reference);
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_tip_id (tip_cmd,
- &tip_id))
- TALER_TESTING_FAIL (is);
-
- tgs->is = is;
- tgs->tgh = TALER_MERCHANT_merchant_tip_get (is->ctx,
- tgs->merchant_url,
- tip_id,
- NULL,
- GNUNET_TIME_UNIT_ZERO,
- tgs->fetch_pickups,
- &merchant_get_tip_cb,
- tgs);
- GNUNET_assert (NULL != tgs->tgh);
-}
-
-
-/**
-* Free the state of a "GET tip" CMD, and possibly
-* cancel a pending operation thereof.
-*
-* @param cls closure.
-* @param cmd command being run.
-*/
-static void
-merchant_get_tip_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct MerchantTipGetState *tgs = cls;
-
- if (NULL != tgs->tgh)
- {
- TALER_LOG_WARNING ("Get tip operation did not complete\n");
- TALER_MERCHANT_merchant_tip_get_cancel (tgs->tgh);
- }
- GNUNET_array_grow (tgs->pickups,
- tgs->pickups_length,
- 0);
- GNUNET_free (tgs);
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_tip (const char *label,
- const char *merchant_url,
- const char *tip_reference,
- unsigned int http_status)
-{
- struct MerchantTipGetState *tgs;
-
- tgs = GNUNET_new (struct MerchantTipGetState);
- tgs->merchant_url = merchant_url;
- tgs->tip_reference = tip_reference;
- tgs->http_status = http_status;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = tgs,
- .label = label,
- .run = &merchant_get_tip_run,
- .cleanup = &merchant_get_tip_cleanup
- };
-
- return cmd;
- }
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_get_tip_with_pickups (const char *label,
- const char *merchant_url,
- const char *tip_reference,
- unsigned int http_status,
- ...)
-{
- struct MerchantTipGetState *tgs;
-
- tgs = GNUNET_new (struct MerchantTipGetState);
- tgs->merchant_url = merchant_url;
- tgs->tip_reference = tip_reference;
- tgs->fetch_pickups = true;
- tgs->http_status = http_status;
- {
- const char *clabel;
- va_list ap;
-
- va_start (ap, http_status);
- while (NULL != (clabel = va_arg (ap, const char *)))
- {
- GNUNET_array_append (tgs->pickups,
- tgs->pickups_length,
- clabel);
- }
- va_end (ap);
- }
- {
- struct TALER_TESTING_Command cmd = {
- .cls = tgs,
- .label = label,
- .run = &merchant_get_tip_run,
- .cleanup = &merchant_get_tip_cleanup
- };
-
- return cmd;
- }
-}
-
-
-/* end of testing_api_cmd_merchant_get_tip.c */
diff --git a/src/testing/testing_api_cmd_patch_instance.c b/src/testing/testing_api_cmd_patch_instance.c
index 348163af..cef38bec 100644
--- a/src/testing/testing_api_cmd_patch_instance.c
+++ b/src/testing/testing_api_cmd_patch_instance.c
@@ -55,16 +55,6 @@ struct PatchInstanceState
const char *instance_id;
/**
- * Length of the @payto_uris array
- */
- unsigned int payto_uris_length;
-
- /**
- * Array of payto URIs.
- */
- const char **payto_uris;
-
- /**
* Name of the instance.
*/
const char *name;
@@ -80,19 +70,9 @@ struct PatchInstanceState
json_t *jurisdiction;
/**
- * Wire fee to use.
- */
- struct TALER_Amount default_max_wire_fee;
-
- /**
- * Amortization to use.
- */
- uint32_t default_wire_fee_amortization;
-
- /**
- * Deposit fee ceiling to use.
+ * Use STEFAN curve?
*/
- struct TALER_Amount default_max_deposit_fee;
+ bool use_stefan;
/**
* Wire transfer delay to use.
@@ -173,21 +153,19 @@ patch_instance_run (void *cls,
struct PatchInstanceState *pis = cls;
pis->is = is;
- pis->iph = TALER_MERCHANT_instance_patch (is->ctx,
- pis->merchant_url,
- pis->instance_id,
- pis->payto_uris_length,
- pis->payto_uris,
- pis->name,
- pis->address,
- pis->jurisdiction,
- &pis->default_max_wire_fee,
- pis->default_wire_fee_amortization,
- &pis->default_max_deposit_fee,
- pis->default_wire_transfer_delay,
- pis->default_pay_delay,
- &patch_instance_cb,
- pis);
+ pis->iph = TALER_MERCHANT_instance_patch (
+ TALER_TESTING_interpreter_get_context (is),
+ pis->merchant_url,
+ pis->instance_id,
+ pis->name,
+ TALER_KYCLOGIC_KYC_UT_BUSINESS,
+ pis->address,
+ pis->jurisdiction,
+ pis->use_stefan,
+ pis->default_wire_transfer_delay,
+ pis->default_pay_delay,
+ &patch_instance_cb,
+ pis);
GNUNET_assert (NULL != pis->iph);
}
@@ -202,44 +180,23 @@ patch_instance_run (void *cls,
* @param index index number of the object to extract.
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
patch_instance_traits (void *cls,
const void **ret,
const char *trait,
unsigned int index)
{
struct PatchInstanceState *pis = cls;
- #define NUM_TRAITS (pis->payto_uris_length) + 11
- struct TALER_TESTING_Trait traits[NUM_TRAITS];
- traits[0] =
- TALER_TESTING_make_trait_instance_name (&pis->name);
- traits[1] =
- TALER_TESTING_make_trait_instance_id (&pis->instance_id);
- traits[2] =
- TALER_TESTING_make_trait_address (pis->address);
- traits[3] =
- TALER_TESTING_make_trait_jurisdiction (pis->jurisdiction);
- traits[4] =
- TALER_TESTING_make_trait_max_wire_fee (&pis->default_max_wire_fee);
- traits[5] =
- TALER_TESTING_make_trait_wire_fee_amortization (
- &pis->default_wire_fee_amortization);
- traits[6] =
- TALER_TESTING_make_trait_max_deposit_fee (&pis->default_max_deposit_fee);
- traits[7] =
- TALER_TESTING_make_trait_wire_delay (&pis->default_wire_transfer_delay);
- traits[8] =
- TALER_TESTING_make_trait_pay_delay (&pis->default_pay_delay);
- traits[9] =
- TALER_TESTING_make_trait_payto_length (&pis->payto_uris_length);
- traits[NUM_TRAITS - 1] =
- TALER_TESTING_trait_end ();
- for (unsigned int i = 0; i < pis->payto_uris_length; ++i)
- {
- traits[10 + i] =
- TALER_TESTING_make_trait_payto_uris (i,
- &pis->payto_uris[i]);
- }
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_instance_name (pis->name),
+ TALER_TESTING_make_trait_instance_id (pis->instance_id),
+ TALER_TESTING_make_trait_address (pis->address),
+ TALER_TESTING_make_trait_jurisdiction (pis->jurisdiction),
+ TALER_TESTING_make_trait_use_stefan (&pis->use_stefan),
+ TALER_TESTING_make_trait_wire_delay (&pis->default_wire_transfer_delay),
+ TALER_TESTING_make_trait_pay_delay (&pis->default_pay_delay),
+ TALER_TESTING_trait_end ()
+ };
return TALER_TESTING_get_trait (traits,
ret,
@@ -267,9 +224,8 @@ patch_instance_cleanup (void *cls,
"PATCH /instance/$ID operation did not complete\n");
TALER_MERCHANT_instance_patch_cancel (pis->iph);
}
- json_decref (pis->address);
json_decref (pis->jurisdiction);
- GNUNET_free (pis->payto_uris);
+ json_decref (pis->address);
GNUNET_free (pis);
}
@@ -279,14 +235,10 @@ TALER_TESTING_cmd_merchant_patch_instance (
const char *label,
const char *merchant_url,
const char *instance_id,
- unsigned int payto_uris_length,
- const char *payto_uris[],
const char *name,
json_t *address,
json_t *jurisdiction,
- const char *default_max_wire_fee,
- uint32_t default_wire_fee_amortization,
- const char *default_max_deposit_fee,
+ bool use_stefan,
struct GNUNET_TIME_Relative default_wire_transfer_delay,
struct GNUNET_TIME_Relative default_pay_delay,
unsigned int http_status)
@@ -297,22 +249,10 @@ TALER_TESTING_cmd_merchant_patch_instance (
pis->merchant_url = merchant_url;
pis->instance_id = instance_id;
pis->http_status = http_status;
- pis->payto_uris_length = payto_uris_length;
- pis->payto_uris = GNUNET_new_array (payto_uris_length,
- const char *);
- memcpy (pis->payto_uris,
- payto_uris,
- sizeof (const char *) * payto_uris_length);
pis->name = name;
pis->address = address; /* ownership transfer! */
pis->jurisdiction = jurisdiction; /* ownership transfer! */
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (default_max_wire_fee,
- &pis->default_max_wire_fee));
- pis->default_wire_fee_amortization = default_wire_fee_amortization;
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (default_max_deposit_fee,
- &pis->default_max_deposit_fee));
+ pis->use_stefan = use_stefan;
pis->default_wire_transfer_delay = default_wire_transfer_delay;
pis->default_pay_delay = default_pay_delay;
{
diff --git a/src/testing/testing_api_cmd_patch_otp_device.c b/src/testing/testing_api_cmd_patch_otp_device.c
new file mode 100644
index 00000000..ce263908
--- /dev/null
+++ b/src/testing/testing_api_cmd_patch_otp_device.c
@@ -0,0 +1,250 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing_api_cmd_patch_otp_device.c
+ * @brief command to test PATCH /otp-device
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "PATCH /otp-device" CMD.
+ */
+struct PatchOtpDeviceState
+{
+
+ /**
+ * Handle for a "GET otp_device" request.
+ */
+ struct TALER_MERCHANT_OtpDevicePatchHandle *iph;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * ID of the otp_device to run GET for.
+ */
+ const char *otp_device_id;
+
+ /**
+ * description of the otp_device
+ */
+ const char *otp_device_description;
+
+ /**
+ * base64-encoded key
+ */
+ char *otp_key;
+
+ /**
+ * Algorithm used by the OTP device
+ */
+ enum TALER_MerchantConfirmationAlgorithm otp_alg;
+
+ /**
+ * Counter of the device (if in counter mode).
+ */
+ uint64_t otp_ctr;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a PATCH /otp-devices/$ID operation.
+ *
+ * @param cls closure for this function
+ * @param hr response being processed
+ */
+static void
+patch_otp_device_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct PatchOtpDeviceState *pis = cls;
+
+ pis->iph = NULL;
+ if (pis->http_status != hr->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ hr->http_status,
+ (int) hr->ec,
+ TALER_TESTING_interpreter_get_current_label (pis->is));
+ TALER_TESTING_interpreter_fail (pis->is);
+ return;
+ }
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ break;
+ case MHD_HTTP_CONFLICT:
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status %u for PATCH /otp-devices/ID.\n",
+ hr->http_status);
+ }
+ TALER_TESTING_interpreter_next (pis->is);
+}
+
+
+/**
+ * Run the "PATCH /otp-devices/$ID" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+patch_otp_device_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PatchOtpDeviceState *pis = cls;
+
+ pis->is = is;
+ pis->iph = TALER_MERCHANT_otp_device_patch (
+ TALER_TESTING_interpreter_get_context (is),
+ pis->merchant_url,
+ pis->otp_device_id,
+ pis->otp_device_description,
+ pis->otp_key,
+ pis->otp_alg,
+ pis->otp_ctr,
+ &patch_otp_device_cb,
+ pis);
+ GNUNET_assert (NULL != pis->iph);
+}
+
+
+/**
+ * Offers information from the PATCH /otp-devices CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+patch_otp_device_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct PatchOtpDeviceState *pts = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_otp_device_description (pts->otp_device_description),
+ TALER_TESTING_make_trait_otp_key (pts->otp_key),
+ TALER_TESTING_make_trait_otp_alg (&pts->otp_alg),
+ TALER_TESTING_make_trait_otp_id (pts->otp_device_id),
+ TALER_TESTING_trait_end (),
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Free the state of a "GET otp_device" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+patch_otp_device_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PatchOtpDeviceState *pis = cls;
+
+ if (NULL != pis->iph)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "PATCH /otp-devices/$ID operation did not complete\n");
+ TALER_MERCHANT_otp_device_patch_cancel (pis->iph);
+ }
+ GNUNET_free (pis->otp_key);
+ GNUNET_free (pis);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_patch_otp_device (
+ const char *label,
+ const char *merchant_url,
+ const char *otp_device_id,
+ const char *otp_device_description,
+ const char *otp_key,
+ const enum TALER_MerchantConfirmationAlgorithm otp_alg,
+ uint64_t otp_ctr,
+ unsigned int http_status)
+{
+ struct PatchOtpDeviceState *pis;
+
+ pis = GNUNET_new (struct PatchOtpDeviceState);
+ pis->merchant_url = merchant_url;
+ pis->otp_device_id = otp_device_id;
+ pis->http_status = http_status;
+ pis->otp_device_description = otp_device_description;
+ pis->otp_key = GNUNET_strdup (otp_key);
+ pis->otp_alg = otp_alg;
+ pis->otp_ctr = otp_ctr;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pis,
+ .label = label,
+ .run = &patch_otp_device_run,
+ .cleanup = &patch_otp_device_cleanup,
+ .traits = &patch_otp_device_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_patch_otp_device.c */
diff --git a/src/testing/testing_api_cmd_patch_product.c b/src/testing/testing_api_cmd_patch_product.c
index 4715ce1f..702ef85a 100644
--- a/src/testing/testing_api_cmd_patch_product.c
+++ b/src/testing/testing_api_cmd_patch_product.c
@@ -172,21 +172,22 @@ patch_product_run (void *cls,
struct PatchProductState *pis = cls;
pis->is = is;
- pis->iph = TALER_MERCHANT_product_patch (is->ctx,
- pis->merchant_url,
- pis->product_id,
- pis->description,
- pis->description_i18n,
- pis->unit,
- &pis->price,
- pis->image,
- pis->taxes,
- pis->total_stock,
- pis->total_lost,
- pis->address,
- pis->next_restock,
- &patch_product_cb,
- pis);
+ pis->iph = TALER_MERCHANT_product_patch (
+ TALER_TESTING_interpreter_get_context (is),
+ pis->merchant_url,
+ pis->product_id,
+ pis->description,
+ pis->description_i18n,
+ pis->unit,
+ &pis->price,
+ pis->image,
+ pis->taxes,
+ pis->total_stock,
+ pis->total_lost,
+ pis->address,
+ pis->next_restock,
+ &patch_product_cb,
+ pis);
GNUNET_assert (NULL != pis->iph);
}
@@ -209,18 +210,17 @@ patch_product_traits (void *cls,
{
struct PatchProductState *pps = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_product_description (&pps->description),
+ TALER_TESTING_make_trait_product_description (pps->description),
TALER_TESTING_make_trait_i18n_description (pps->description_i18n),
- TALER_TESTING_make_trait_product_unit (&pps->unit),
+ TALER_TESTING_make_trait_product_unit (pps->unit),
TALER_TESTING_make_trait_amount (&pps->price),
- TALER_TESTING_make_trait_product_image (
- (const char **) &pps->image),
+ TALER_TESTING_make_trait_product_image (pps->image),
TALER_TESTING_make_trait_taxes (pps->taxes),
TALER_TESTING_make_trait_product_stock (&pps->total_stock),
TALER_TESTING_make_trait_address (pps->address),
TALER_TESTING_make_trait_timestamp (0,
&pps->next_restock),
- TALER_TESTING_make_trait_product_id (&pps->product_id),
+ TALER_TESTING_make_trait_product_id (pps->product_id),
TALER_TESTING_trait_end (),
};
diff --git a/src/testing/testing_api_cmd_patch_template.c b/src/testing/testing_api_cmd_patch_template.c
index a2a75b89..8ad9d9dc 100644
--- a/src/testing/testing_api_cmd_patch_template.c
+++ b/src/testing/testing_api_cmd_patch_template.c
@@ -60,9 +60,9 @@ struct PatchTemplateState
const char *template_description;
/**
- * base64-encoded template image
+ * OTP device ID
*/
- char *image;
+ char *otp_id;
/**
* Contract of the company
@@ -85,7 +85,7 @@ struct PatchTemplateState
*/
static void
patch_template_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr)
+ const struct TALER_MERCHANT_HttpResponse *hr)
{
struct PatchTemplateState *pis = cls;
@@ -131,20 +131,21 @@ patch_template_cb (void *cls,
*/
static void
patch_template_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
{
struct PatchTemplateState *pis = cls;
pis->is = is;
- pis->iph = TALER_MERCHANT_template_patch (is->ctx,
- pis->merchant_url,
- pis->template_id,
- pis->template_description,
- pis->image,
- pis->template_contract,
- &patch_template_cb,
- pis);
+ pis->iph = TALER_MERCHANT_template_patch (
+ TALER_TESTING_interpreter_get_context (is),
+ pis->merchant_url,
+ pis->template_id,
+ pis->template_description,
+ pis->otp_id,
+ pis->template_contract,
+ &patch_template_cb,
+ pis);
GNUNET_assert (NULL != pis->iph);
}
@@ -161,17 +162,16 @@ patch_template_run (void *cls,
*/
static enum GNUNET_GenericReturnValue
patch_template_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
+ const void **ret,
+ const char *trait,
+ unsigned int index)
{
struct PatchTemplateState *pts = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_template_description (&pts->template_description),
- TALER_TESTING_make_trait_template_image (
- (const char **) &pts->image),
+ TALER_TESTING_make_trait_template_description (pts->template_description),
+ TALER_TESTING_make_trait_otp_id (pts->otp_id),
TALER_TESTING_make_trait_template_contract (pts->template_contract),
- TALER_TESTING_make_trait_template_id (&pts->template_id),
+ TALER_TESTING_make_trait_template_id (pts->template_id),
TALER_TESTING_trait_end (),
};
@@ -191,7 +191,7 @@ patch_template_traits (void *cls,
*/
static void
patch_template_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
+ const struct TALER_TESTING_Command *cmd)
{
struct PatchTemplateState *pis = cls;
@@ -201,7 +201,7 @@ patch_template_cleanup (void *cls,
"PATCH /templates/$ID operation did not complete\n");
TALER_MERCHANT_template_patch_cancel (pis->iph);
}
- GNUNET_free (pis->image);
+ GNUNET_free (pis->otp_id);
json_decref (pis->template_contract);
GNUNET_free (pis);
}
@@ -213,7 +213,7 @@ TALER_TESTING_cmd_merchant_patch_template (
const char *merchant_url,
const char *template_id,
const char *template_description,
- const char *image,
+ const char *otp_id,
json_t *template_contract,
unsigned int http_status)
{
@@ -224,7 +224,7 @@ TALER_TESTING_cmd_merchant_patch_template (
pis->template_id = template_id;
pis->http_status = http_status;
pis->template_description = template_description;
- pis->image = (NULL == image) ? NULL : GNUNET_strdup (image);
+ pis->otp_id = (NULL == otp_id) ? NULL : GNUNET_strdup (otp_id);
pis->template_contract = template_contract; /* ownership taken */
{
struct TALER_TESTING_Command cmd = {
diff --git a/src/testing/testing_api_cmd_patch_webhook.c b/src/testing/testing_api_cmd_patch_webhook.c
index a2fea433..0b066371 100644
--- a/src/testing/testing_api_cmd_patch_webhook.c
+++ b/src/testing/testing_api_cmd_patch_webhook.c
@@ -147,16 +147,17 @@ patch_webhook_run (void *cls,
struct PatchWebhookState *pis = cls;
pis->is = is;
- pis->iph = TALER_MERCHANT_webhook_patch (is->ctx,
- pis->merchant_url,
- pis->webhook_id,
- pis->event_type,
- pis->url,
- pis->http_method,
- pis->header_template,
- pis->body_template,
- &patch_webhook_cb,
- pis);
+ pis->iph = TALER_MERCHANT_webhook_patch (
+ TALER_TESTING_interpreter_get_context (is),
+ pis->merchant_url,
+ pis->webhook_id,
+ pis->event_type,
+ pis->url,
+ pis->http_method,
+ pis->header_template,
+ pis->body_template,
+ &patch_webhook_cb,
+ pis);
GNUNET_assert (NULL != pis->iph);
}
@@ -179,12 +180,12 @@ patch_webhook_traits (void *cls,
{
struct PatchWebhookState *pws = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_event_type (&pws->event_type),
- TALER_TESTING_make_trait_url (&pws->url),
- TALER_TESTING_make_trait_http_method (&pws->http_method),
- TALER_TESTING_make_trait_header_template (&pws->header_template),
- TALER_TESTING_make_trait_body_template (&pws->body_template),
- TALER_TESTING_make_trait_webhook_id (&pws->webhook_id),
+ TALER_TESTING_make_trait_event_type (pws->event_type),
+ TALER_TESTING_make_trait_url (pws->url),
+ TALER_TESTING_make_trait_http_method (pws->http_method),
+ TALER_TESTING_make_trait_header_template (pws->header_template),
+ TALER_TESTING_make_trait_body_template (pws->body_template),
+ TALER_TESTING_make_trait_webhook_id (pws->webhook_id),
TALER_TESTING_trait_end (),
};
@@ -239,8 +240,8 @@ TALER_TESTING_cmd_merchant_patch_webhook (
pis->event_type = event_type;
pis->url = url;
pis->http_method = http_method;
- pis->header_template = header_template;
- pis->body_template = body_template;
+ pis->header_template = (NULL == header_template) ? NULL : header_template;
+ pis->body_template = (NULL == body_template) ? NULL : body_template;
{
struct TALER_TESTING_Command cmd = {
.cls = pis,
diff --git a/src/testing/testing_api_cmd_pay_order.c b/src/testing/testing_api_cmd_pay_order.c
index cd589965..0b84c8a6 100644
--- a/src/testing/testing_api_cmd_pay_order.c
+++ b/src/testing/testing_api_cmd_pay_order.c
@@ -68,6 +68,11 @@ struct PayState
const char *merchant_url;
/**
+ * Total amount to be paid.
+ */
+ struct TALER_Amount total_amount;
+
+ /**
* Amount to be paid, plus the deposit fee.
*/
const char *amount_with_fee;
@@ -91,6 +96,17 @@ struct PayState
* The session for which the payment is made.
*/
const char *session_id;
+
+ /**
+ * base64-encoded key
+ */
+ const char *pos_key;
+
+ /**
+ * Option that add amount of the order
+ */
+ enum TALER_MerchantConfirmationAlgorithm pos_alg;
+
};
@@ -117,6 +133,14 @@ build_coins (struct TALER_MERCHANT_PayCoin **pc,
const char *amount_without_fee)
{
char *token;
+ struct TALER_EXCHANGE_Keys *keys;
+
+ keys = TALER_TESTING_get_keys (is);
+ if (NULL == keys)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
for (token = strtok (coins, ";");
NULL != token;
@@ -127,7 +151,7 @@ build_coins (struct TALER_MERCHANT_PayCoin **pc,
unsigned int ci;
struct TALER_MERCHANT_PayCoin *icoin;
const struct TALER_EXCHANGE_DenomPublicKey *dpk;
- const char **exchange_url;
+ const char *exchange_url;
/* Token syntax is "LABEL[/NUMBER]" */
ctok = strchr (token, '/');
@@ -194,7 +218,7 @@ build_coins (struct TALER_MERCHANT_PayCoin **pc,
icoin->h_age_commitment = h_age_commitment;
}
GNUNET_assert (NULL != (dpk =
- TALER_TESTING_find_pk (is->keys,
+ TALER_TESTING_find_pk (keys,
&icoin->denom_value,
false)));
@@ -205,7 +229,7 @@ build_coins (struct TALER_MERCHANT_PayCoin **pc,
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_exchange_url (coin_cmd,
&exchange_url));
- icoin->exchange_url = *exchange_url;
+ icoin->exchange_url = exchange_url;
}
return GNUNET_OK;
@@ -238,7 +262,43 @@ pay_cb (void *cls,
}
if (MHD_HTTP_OK == pr->hr.http_status)
{
- ps->merchant_sig = pr->details.success.merchant_sig;
+ ps->merchant_sig = pr->details.ok.merchant_sig;
+ if (NULL != ps->pos_key)
+ {
+ char *pc;
+ bool found = false;
+
+ if (NULL == pr->details.ok.pos_confirmation)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ps->is);
+ return;
+ }
+ pc = TALER_build_pos_confirmation (ps->pos_key,
+ ps->pos_alg,
+ &ps->total_amount,
+ GNUNET_TIME_timestamp_get ());
+ /* Check if *any* of our TOTP codes overlaps
+ with any of the returned TOTP codes. */
+ for (const char *tok = strtok (pc, "\n");
+ NULL != tok;
+ tok = strtok (NULL, "\n"))
+ {
+ if (NULL != strstr (pr->details.ok.pos_confirmation,
+ tok))
+ {
+ found = true;
+ break;
+ }
+ }
+ GNUNET_free (pc);
+ if (! found)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ps->is);
+ return;
+ }
+ }
}
TALER_TESTING_interpreter_next (ps->is);
}
@@ -266,13 +326,13 @@ pay_run (void *cls,
struct TALER_MerchantPublicKeyP merchant_pub;
struct TALER_MerchantWireHashP h_wire;
const struct TALER_PrivateContractHashP *h_proposal;
- struct TALER_Amount total_amount;
struct TALER_Amount max_fee;
- const char *error_name;
- unsigned int error_line;
+ const char *error_name = NULL;
+ unsigned int error_line = 0;
struct TALER_MERCHANT_PayCoin *pay_coins;
unsigned int npay_coins;
const struct TALER_MerchantSignatureP *merchant_sig;
+ const enum TALER_MerchantConfirmationAlgorithm *alg_ptr;
ps->is = is;
proposal_cmd = TALER_TESTING_interpreter_lookup_command (
@@ -286,6 +346,17 @@ pay_run (void *cls,
TALER_TESTING_get_trait_contract_terms (proposal_cmd,
&contract_terms))
TALER_TESTING_FAIL (is);
+ if (NULL == contract_terms)
+ TALER_TESTING_FAIL (is);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_otp_key (proposal_cmd,
+ &ps->pos_key))
+ ps->pos_key = NULL;
+ if ( (GNUNET_OK ==
+ TALER_TESTING_get_trait_otp_alg (proposal_cmd,
+ &alg_ptr)) &&
+ (NULL != alg_ptr) )
+ ps->pos_alg = *alg_ptr;
{
/* Get information that needs to be put verbatim in the
* deposit permission */
@@ -303,7 +374,7 @@ pay_run (void *cls,
GNUNET_JSON_spec_fixed_auto ("h_wire",
&h_wire),
TALER_JSON_spec_amount_any ("amount",
- &total_amount),
+ &ps->total_amount),
TALER_JSON_spec_amount_any ("max_fee",
&max_fee),
/* FIXME oec: parse minimum age, use data later? */
@@ -362,11 +433,13 @@ pay_run (void *cls,
&h_proposal))
TALER_TESTING_FAIL (is);
ps->h_contract_terms = *h_proposal;
- ps->oph = TALER_MERCHANT_order_pay (is->ctx,
+ ps->oph = TALER_MERCHANT_order_pay (TALER_TESTING_interpreter_get_context (
+ is),
ps->merchant_url,
ps->session_id,
h_proposal,
- &total_amount,
+ NULL,
+ &ps->total_amount,
&max_fee,
&merchant_pub,
merchant_sig,
@@ -429,7 +502,7 @@ pay_traits (void *cls,
{
struct PayState *ps = cls;
- const char **order_id;
+ const char *order_id;
const struct TALER_TESTING_Command *proposal_cmd;
const struct TALER_MerchantPublicKeyP *merchant_pub;
@@ -465,13 +538,15 @@ pay_traits (void *cls,
&amount_with_fee));
{
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_proposal_reference (&ps->proposal_reference),
+ TALER_TESTING_make_trait_proposal_reference (ps->proposal_reference),
TALER_TESTING_make_trait_coin_reference (0,
- &ps->coin_reference),
+ ps->coin_reference),
TALER_TESTING_make_trait_order_id (order_id),
TALER_TESTING_make_trait_merchant_pub (merchant_pub),
TALER_TESTING_make_trait_merchant_sig (&ps->merchant_sig),
TALER_TESTING_make_trait_amount (&amount_with_fee),
+ TALER_TESTING_make_trait_otp_key (ps->pos_key),
+ TALER_TESTING_make_trait_otp_alg (&ps->pos_alg),
TALER_TESTING_trait_end ()
};
diff --git a/src/testing/testing_api_cmd_post_account.c b/src/testing/testing_api_cmd_post_account.c
new file mode 100644
index 00000000..8ddad94c
--- /dev/null
+++ b/src/testing/testing_api_cmd_post_account.c
@@ -0,0 +1,250 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing_api_cmd_post_account.c
+ * @brief command to test POST /account
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "POST /account" CMD.
+ */
+struct PostAccountState
+{
+
+ /**
+ * Handle for a "GET product" request.
+ */
+ struct TALER_MERCHANT_AccountsPostHandle *aph;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * Wire hash of the created account, set on success.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * RFC 8905 URI for the account to create.
+ */
+ char *payto_uri;
+
+ /**
+ * Credit facade URL for the account to create.
+ */
+ char *credit_facade_url;
+
+ /**
+ * Credit facade credentials for the account to create.
+ */
+ json_t *credit_facade_credentials;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a POST /account operation.
+ *
+ * @param cls closure for this function
+ * @param apr response being processed
+ */
+static void
+post_account_cb (void *cls,
+ const struct TALER_MERCHANT_AccountsPostResponse *apr)
+{
+ struct PostAccountState *pas = cls;
+
+ pas->aph = NULL;
+ if (pas->http_status != apr->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ apr->hr.http_status,
+ (int) apr->hr.ec,
+ TALER_TESTING_interpreter_get_current_label (pas->is));
+ TALER_TESTING_interpreter_fail (pas->is);
+ return;
+ }
+ switch (apr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ pas->h_wire = apr->details.ok.h_wire;
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ break;
+ case MHD_HTTP_CONFLICT:
+ break;
+ default:
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status %u for POST /account.\n",
+ apr->hr.http_status);
+ }
+ TALER_TESTING_interpreter_next (pas->is);
+}
+
+
+/**
+ * Run the "POST /account" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+post_account_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PostAccountState *pas = cls;
+
+ pas->is = is;
+ pas->aph = TALER_MERCHANT_accounts_post (
+ TALER_TESTING_interpreter_get_context (is),
+ pas->merchant_url,
+ pas->payto_uri,
+ pas->credit_facade_url,
+ pas->credit_facade_credentials,
+ &post_account_cb,
+ pas);
+ GNUNET_assert (NULL != pas->aph);
+}
+
+
+/**
+ * Offers information from the POST /account CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+post_account_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct PostAccountState *pps = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_h_wires (
+ 0,
+ &pps->h_wire),
+ TALER_TESTING_make_trait_payto_uris (
+ 0,
+ pps->payto_uri),
+ TALER_TESTING_make_trait_merchant_base_url (
+ pps->merchant_url),
+ TALER_TESTING_trait_end (),
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Free the state of a "POST product" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+post_account_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PostAccountState *pas = cls;
+
+ if (NULL != pas->aph)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "POST /account operation did not complete\n");
+ TALER_MERCHANT_accounts_post_cancel (pas->aph);
+ }
+ GNUNET_free (pas->payto_uri);
+ GNUNET_free (pas->credit_facade_url);
+ json_decref (pas->credit_facade_credentials);
+ GNUNET_free (pas);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_account (
+ const char *label,
+ const char *merchant_url,
+ const char *payto_uri,
+ const char *credit_facade_url,
+ const json_t *credit_facade_credentials,
+ unsigned int http_status)
+{
+ struct PostAccountState *pas;
+
+ pas = GNUNET_new (struct PostAccountState);
+ pas->merchant_url = merchant_url;
+ pas->payto_uri = GNUNET_strdup (payto_uri);
+ if (NULL != credit_facade_url)
+ pas->credit_facade_url = GNUNET_strdup (credit_facade_url);
+ if (NULL != credit_facade_credentials)
+ pas->credit_facade_credentials
+ = json_incref ((json_t *) credit_facade_credentials);
+ pas->http_status = http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pas,
+ .label = label,
+ .run = &post_account_run,
+ .cleanup = &post_account_cleanup,
+ .traits = &post_account_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_post_account.c */
diff --git a/src/testing/testing_api_cmd_post_instances.c b/src/testing/testing_api_cmd_post_instances.c
index 4d1f0d26..0d081026 100644
--- a/src/testing/testing_api_cmd_post_instances.c
+++ b/src/testing/testing_api_cmd_post_instances.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -55,16 +55,6 @@ struct PostInstancesState
const char *instance_id;
/**
- * Length of the @payto_uris array
- */
- unsigned int payto_uris_length;
-
- /**
- * Array of payto URIs.
- */
- const char **payto_uris;
-
- /**
* Name of the instance.
*/
const char *name;
@@ -85,19 +75,9 @@ struct PostInstancesState
const char *auth_token;
/**
- * Wire fee to use.
- */
- struct TALER_Amount default_max_wire_fee;
-
- /**
- * Amortization to use.
+ * Use STEFAN curves?
*/
- uint32_t default_wire_fee_amortization;
-
- /**
- * Deposit fee ceiling to use.
- */
- struct TALER_Amount default_max_deposit_fee;
+ bool use_stefan;
/**
* Wire transfer delay to use.
@@ -132,11 +112,10 @@ post_instances_cb (void *cls,
pis->iph = NULL;
if (pis->http_status != hr->http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
- TALER_TESTING_interpreter_get_current_label (pis->is));
+ TALER_TESTING_unexpected_status_with_body (pis->is,
+ hr->http_status,
+ pis->http_status,
+ hr->reply);
TALER_TESTING_interpreter_fail (pis->is);
return;
}
@@ -180,22 +159,20 @@ post_instances_run (void *cls,
struct PostInstancesState *pis = cls;
pis->is = is;
- pis->iph = TALER_MERCHANT_instances_post (is->ctx,
- pis->merchant_url,
- pis->instance_id,
- pis->payto_uris_length,
- pis->payto_uris,
- pis->name,
- pis->address,
- pis->jurisdiction,
- &pis->default_max_wire_fee,
- pis->default_wire_fee_amortization,
- &pis->default_max_deposit_fee,
- pis->default_wire_transfer_delay,
- pis->default_pay_delay,
- pis->auth_token,
- &post_instances_cb,
- pis);
+ pis->iph = TALER_MERCHANT_instances_post (
+ TALER_TESTING_interpreter_get_context (is),
+ pis->merchant_url,
+ pis->instance_id,
+ pis->name,
+ TALER_KYCLOGIC_KYC_UT_BUSINESS,
+ pis->address,
+ pis->jurisdiction,
+ pis->use_stefan,
+ pis->default_wire_transfer_delay,
+ pis->default_pay_delay,
+ pis->auth_token,
+ &post_instances_cb,
+ pis);
if (NULL == pis->iph)
{
GNUNET_break (0);
@@ -222,37 +199,16 @@ post_instances_traits (void *cls,
unsigned int index)
{
struct PostInstancesState *pis = cls;
- #define NUM_TRAITS (pis->payto_uris_length) + 11
- struct TALER_TESTING_Trait traits[NUM_TRAITS];
- traits[0] =
- TALER_TESTING_make_trait_instance_name (&pis->name);
- traits[1] =
- TALER_TESTING_make_trait_instance_id (&pis->instance_id);
- traits[2] =
- TALER_TESTING_make_trait_address (pis->address);
- traits[3] =
- TALER_TESTING_make_trait_jurisdiction (pis->jurisdiction);
- traits[4] =
- TALER_TESTING_make_trait_max_wire_fee (&pis->default_max_wire_fee);
- traits[5] =
- TALER_TESTING_make_trait_wire_fee_amortization (
- &pis->default_wire_fee_amortization);
- traits[6] =
- TALER_TESTING_make_trait_max_deposit_fee (&pis->default_max_deposit_fee);
- traits[7] =
- TALER_TESTING_make_trait_wire_delay (&pis->default_wire_transfer_delay);
- traits[8] =
- TALER_TESTING_make_trait_pay_delay (&pis->default_pay_delay);
- traits[9] =
- TALER_TESTING_make_trait_payto_length (&pis->payto_uris_length);
- traits[NUM_TRAITS - 1] =
- TALER_TESTING_trait_end ();
- for (unsigned int i = 0; i < pis->payto_uris_length; ++i)
- {
- traits[10 + i] =
- TALER_TESTING_make_trait_payto_uris (i,
- &pis->payto_uris[i]);
- }
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_instance_name (pis->name),
+ TALER_TESTING_make_trait_instance_id (pis->instance_id),
+ TALER_TESTING_make_trait_address (pis->address),
+ TALER_TESTING_make_trait_jurisdiction (pis->jurisdiction),
+ TALER_TESTING_make_trait_use_stefan (&pis->use_stefan),
+ TALER_TESTING_make_trait_wire_delay (&pis->default_wire_transfer_delay),
+ TALER_TESTING_make_trait_pay_delay (&pis->default_pay_delay),
+ TALER_TESTING_trait_end ()
+ };
return TALER_TESTING_get_trait (traits,
ret,
@@ -282,7 +238,6 @@ post_instances_cleanup (void *cls,
}
json_decref (pis->address);
json_decref (pis->jurisdiction);
- GNUNET_free (pis->payto_uris);
GNUNET_free (pis);
}
@@ -292,14 +247,10 @@ TALER_TESTING_cmd_merchant_post_instances2 (
const char *label,
const char *merchant_url,
const char *instance_id,
- unsigned int payto_uris_length,
- const char *payto_uris[],
const char *name,
json_t *address,
json_t *jurisdiction,
- const char *default_max_wire_fee,
- uint32_t default_wire_fee_amortization,
- const char *default_max_deposit_fee,
+ bool use_stefan,
struct GNUNET_TIME_Relative default_wire_transfer_delay,
struct GNUNET_TIME_Relative default_pay_delay,
const char *auth_token,
@@ -311,22 +262,10 @@ TALER_TESTING_cmd_merchant_post_instances2 (
pis->merchant_url = merchant_url;
pis->instance_id = instance_id;
pis->http_status = http_status;
- pis->payto_uris_length = payto_uris_length;
- pis->payto_uris = GNUNET_new_array (payto_uris_length,
- const char *);
- memcpy (pis->payto_uris,
- payto_uris,
- sizeof (const char *) * payto_uris_length);
pis->name = name;
pis->address = address; /* ownership transfer! */
pis->jurisdiction = jurisdiction; /* ownership transfer! */
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (default_max_wire_fee,
- &pis->default_max_wire_fee));
- pis->default_wire_fee_amortization = default_wire_fee_amortization;
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (default_max_deposit_fee,
- &pis->default_max_deposit_fee));
+ pis->use_stefan = use_stefan;
pis->default_wire_transfer_delay = default_wire_transfer_delay;
pis->default_pay_delay = default_pay_delay;
pis->auth_token = auth_token;
@@ -348,34 +287,16 @@ struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_instances (const char *label,
const char *merchant_url,
const char *instance_id,
- const char *payto_uri,
- const char *currency,
unsigned int http_status)
{
- const char *payto_uris[] = {
- payto_uri
- };
- struct TALER_Amount default_max_fee;
- const char *default_max_fee_s;
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (currency,
- &default_max_fee));
- default_max_fee.value = 1;
- default_max_fee_s = TALER_amount2s (&default_max_fee);
-
return TALER_TESTING_cmd_merchant_post_instances2 (
label,
merchant_url,
instance_id,
- 1,
- payto_uris,
instance_id,
json_pack ("{s:s}", "city", "shopcity"),
json_pack ("{s:s}", "city", "lawyercity"),
- default_max_fee_s,
- 10,
- default_max_fee_s,
+ true,
GNUNET_TIME_UNIT_ZERO, /* no wire transfer delay */
GNUNET_TIME_UNIT_MINUTES,
NULL,
diff --git a/src/testing/testing_api_cmd_post_orders.c b/src/testing/testing_api_cmd_post_orders.c
index b818bd8c..d5cfdddc 100644
--- a/src/testing/testing_api_cmd_post_orders.c
+++ b/src/testing/testing_api_cmd_post_orders.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
+ Copyright (C) 2014-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -46,6 +46,11 @@ struct OrdersState
const char *order_id;
/**
+ * Our configuration.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
* The order id we expect the merchant to assign (if not NULL).
*/
const char *expected_order_id;
@@ -157,7 +162,7 @@ orders_traits (void *cls,
{
struct OrdersState *ps = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_order_id (&ps->order_id),
+ TALER_TESTING_make_trait_order_id (ps->order_id),
TALER_TESTING_make_trait_contract_terms (ps->contract_terms),
TALER_TESTING_make_trait_order_terms (ps->order_terms),
TALER_TESTING_make_trait_h_contract_terms (&ps->h_contract_terms),
@@ -181,43 +186,41 @@ orders_traits (void *cls,
* created.
*
* @param cls closure
- * @param hr HTTP response we got
- * @param contract_terms contract terms of this order
- * @param sig merchant's signature
- * @param hash hash over the contract
+ * @param ocr response we got
*/
static void
orders_claim_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const json_t *contract_terms,
- const struct TALER_MerchantSignatureP *sig,
- const struct TALER_PrivateContractHashP *hash)
+ const struct TALER_MERCHANT_OrderClaimResponse *ocr)
{
struct OrdersState *ps = cls;
- struct TALER_MerchantPublicKeyP merchant_pub;
const char *error_name;
unsigned int error_line;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &merchant_pub),
+ &ps->merchant_pub),
GNUNET_JSON_spec_end ()
};
ps->och = NULL;
- if (ps->http_status != hr->http_status)
+ if (ps->http_status != ocr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Expected status %u, got %u\n",
ps->http_status,
- hr->http_status);
+ ocr->hr.http_status);
TALER_TESTING_FAIL (ps->is);
}
-
- ps->contract_terms = json_deep_copy (contract_terms);
- ps->h_contract_terms = *hash;
- ps->merchant_sig = *sig;
+ if (MHD_HTTP_OK != ocr->hr.http_status)
+ {
+ TALER_TESTING_interpreter_next (ps->is);
+ return;
+ }
+ ps->contract_terms = json_deep_copy (
+ (json_t *) ocr->details.ok.contract_terms);
+ ps->h_contract_terms = ocr->details.ok.h_contract_terms;
+ ps->merchant_sig = ocr->details.ok.sig;
if (GNUNET_OK !=
- GNUNET_JSON_parse (contract_terms,
+ GNUNET_JSON_parse (ps->contract_terms,
spec,
&error_name,
&error_line))
@@ -236,16 +239,14 @@ orders_claim_cb (void *cls,
free (log);
TALER_TESTING_FAIL (ps->is);
}
- ps->merchant_pub = merchant_pub;
TALER_TESTING_interpreter_next (ps->is);
}
/**
- * Callback that processes the response following a
- * POST /orders. NOTE: no contract terms are included
- * here; they need to be taken via the "orders lookup"
- * method.
+ * Callback that processes the response following a POST /orders. NOTE: no
+ * contract terms are included here; they need to be taken via the "orders
+ * lookup" method.
*
* @param cls closure.
* @param por details about the response
@@ -259,20 +260,19 @@ order_cb (void *cls,
ps->po = NULL;
if (ps->http_status != por->hr.http_status)
{
- TALER_LOG_ERROR ("Given vs expected: %u(%d) vs %u\n",
- por->hr.http_status,
- (int) por->hr.ec,
- ps->http_status);
- TALER_TESTING_FAIL (ps->is);
+ TALER_TESTING_unexpected_status_with_body (ps->is,
+ por->hr.http_status,
+ ps->http_status,
+ por->hr.reply);
+ TALER_TESTING_interpreter_fail (ps->is);
+ return;
}
- if (0 == ps->http_status)
+ switch (por->hr.http_status)
{
+ case 0:
TALER_LOG_DEBUG ("/orders, expected 0 status code\n");
TALER_TESTING_interpreter_next (ps->is);
return;
- }
- switch (por->hr.http_status)
- {
case MHD_HTTP_OK:
if (NULL != por->details.ok.token)
ps->claim_token = *por->details.ok.token;
@@ -351,13 +351,14 @@ order_cb (void *cls,
return;
}
if (NULL ==
- (ps->och = TALER_MERCHANT_order_claim (ps->is->ctx,
- ps->merchant_url,
- ps->order_id,
- &ps->nonce,
- &ps->claim_token,
- &orders_claim_cb,
- ps)))
+ (ps->och = TALER_MERCHANT_order_claim (
+ TALER_TESTING_interpreter_get_context (ps->is),
+ ps->merchant_url,
+ ps->order_id,
+ &ps->nonce,
+ &ps->claim_token,
+ &orders_claim_cb,
+ ps)))
TALER_TESTING_FAIL (ps->is);
}
@@ -383,7 +384,7 @@ orders_run (void *cls,
struct GNUNET_TIME_Absolute now;
char *order_id;
- now = GNUNET_TIME_absolute_get_monotonic (is->cfg);
+ now = GNUNET_TIME_absolute_get_monotonic (ps->cfg);
order_id = GNUNET_STRINGS_data_to_string_alloc (
&now,
sizeof (now));
@@ -396,7 +397,8 @@ orders_run (void *cls,
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
&ps->nonce,
sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
- ps->po = TALER_MERCHANT_orders_post (is->ctx,
+ ps->po = TALER_MERCHANT_orders_post (TALER_TESTING_interpreter_get_context (
+ is),
ps->merchant_url,
ps->order_terms,
GNUNET_TIME_UNIT_ZERO,
@@ -456,7 +458,7 @@ orders_run2 (void *cls,
struct GNUNET_TIME_Absolute now;
char *order_id;
- now = GNUNET_TIME_absolute_get_monotonic (is->cfg);
+ now = GNUNET_TIME_absolute_get_monotonic (ps->cfg);
order_id = GNUNET_STRINGS_data_to_string_alloc (
&now.abs_value_us,
sizeof (now.abs_value_us));
@@ -514,7 +516,7 @@ orders_run2 (void *cls,
token = strtok (NULL, ";"))
{
const struct TALER_TESTING_Command *lock_cmd;
- const char **uuid;
+ const char *uuid;
lock_cmd = TALER_TESTING_interpreter_lookup_command (
is,
@@ -532,20 +534,22 @@ orders_run2 (void *cls,
GNUNET_array_append (locks,
locks_length,
- *uuid);
+ uuid);
}
- ps->po = TALER_MERCHANT_orders_post2 (is->ctx,
- ps->merchant_url,
- order,
- GNUNET_TIME_UNIT_ZERO,
- ps->payment_target,
- products_length,
- products,
- locks_length,
- locks,
- ps->make_claim_token,
- &order_cb,
- ps);
+ ps->po = TALER_MERCHANT_orders_post2 (
+ TALER_TESTING_interpreter_get_context (
+ is),
+ ps->merchant_url,
+ order,
+ GNUNET_TIME_UNIT_ZERO,
+ ps->payment_target,
+ products_length,
+ products,
+ locks_length,
+ locks,
+ ps->make_claim_token,
+ &order_cb,
+ ps);
GNUNET_free (products_string);
GNUNET_free (locks_string);
GNUNET_array_grow (products,
@@ -684,7 +688,6 @@ TALER_TESTING_cmd_merchant_post_orders_no_claim (
ps->http_status = http_status;
ps->expected_order_id = order_id;
ps->merchant_url = merchant_url;
- ps->with_claim = false;
{
struct TALER_TESTING_Command cmd = {
.cls = ps,
@@ -702,6 +705,7 @@ TALER_TESTING_cmd_merchant_post_orders_no_claim (
struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_orders (
const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *merchant_url,
unsigned int http_status,
const char *order_id,
@@ -712,6 +716,7 @@ TALER_TESTING_cmd_merchant_post_orders (
struct OrdersState *ps;
ps = GNUNET_new (struct OrdersState);
+ ps->cfg = cfg;
make_order_json (order_id,
refund_deadline,
pay_deadline,
@@ -738,6 +743,7 @@ TALER_TESTING_cmd_merchant_post_orders (
struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_orders2 (
const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *merchant_url,
unsigned int http_status,
const char *order_id,
@@ -753,6 +759,7 @@ TALER_TESTING_cmd_merchant_post_orders2 (
struct OrdersState *ps;
ps = GNUNET_new (struct OrdersState);
+ ps->cfg = cfg;
make_order_json (order_id,
refund_deadline,
pay_deadline,
@@ -779,3 +786,45 @@ TALER_TESTING_cmd_merchant_post_orders2 (
return cmd;
}
}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_orders3 (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *merchant_url,
+ unsigned int expected_http_status,
+ const char *order_id,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ const char *fulfillment_url,
+ const char *amount)
+{
+ struct OrdersState *ps;
+
+ ps = GNUNET_new (struct OrdersState);
+ ps->cfg = cfg;
+ make_order_json (order_id,
+ refund_deadline,
+ pay_deadline,
+ amount,
+ &ps->order_terms);
+ GNUNET_assert (0 ==
+ json_object_set_new (ps->order_terms,
+ "fulfillment_url",
+ json_string (fulfillment_url)));
+ ps->http_status = expected_http_status;
+ ps->merchant_url = merchant_url;
+ ps->with_claim = true;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ps,
+ .label = label,
+ .run = &orders_run,
+ .cleanup = &orders_cleanup,
+ .traits = &orders_traits
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_post_orders_paid.c b/src/testing/testing_api_cmd_post_orders_paid.c
index 6c4e41cd..f4806788 100644
--- a/src/testing/testing_api_cmd_post_orders_paid.c
+++ b/src/testing/testing_api_cmd_post_orders_paid.c
@@ -55,7 +55,7 @@ struct PostOrdersPaidState
const char *pay_reference;
/**
- * The session to use for the requet.
+ * The session to use for the request.
*/
const char *session_id;
@@ -71,21 +71,21 @@ struct PostOrdersPaidState
* Response from the merchant after POST /paid.
*
* @param cls pointer to `struct PostOrdersPaidState`.
- * @param hr the http response.
+ * @param opr the response.
*/
static void
paid_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr)
+ const struct TALER_MERCHANT_OrderPaidResponse *opr)
{
struct PostOrdersPaidState *ops = cls;
ops->oph = NULL;
- if (ops->http_status != hr->http_status)
+ if (ops->http_status != opr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
+ opr->hr.http_status,
+ (int) opr->hr.ec,
TALER_TESTING_interpreter_get_current_label (ops->is));
TALER_TESTING_FAIL (ops->is);
}
@@ -110,7 +110,7 @@ paid_run (void *cls,
{
struct PostOrdersPaidState *ops = cls;
const struct TALER_TESTING_Command *pay_cmd;
- const char **proposal_reference;
+ const char *proposal_reference;
const struct TALER_TESTING_Command *proposal_cmd;
const char *order_id;
const struct TALER_PrivateContractHashP *h_contract_terms;
@@ -130,7 +130,7 @@ paid_run (void *cls,
&proposal_reference))
TALER_TESTING_FAIL (is);
proposal_cmd = TALER_TESTING_interpreter_lookup_command (is,
- *proposal_reference);
+ proposal_reference);
if (NULL == proposal_cmd)
TALER_TESTING_FAIL (is);
@@ -179,11 +179,13 @@ paid_run (void *cls,
&h_contract_terms))
TALER_TESTING_FAIL (is);
- ops->oph = TALER_MERCHANT_order_paid (is->ctx,
+ ops->oph = TALER_MERCHANT_order_paid (TALER_TESTING_interpreter_get_context (
+ is),
ops->merchant_url,
order_id,
ops->session_id,
h_contract_terms,
+ NULL,
merchant_sig,
&paid_cb,
ops);
diff --git a/src/testing/testing_api_cmd_post_otp_devices.c b/src/testing/testing_api_cmd_post_otp_devices.c
new file mode 100644
index 00000000..09358274
--- /dev/null
+++ b/src/testing/testing_api_cmd_post_otp_devices.c
@@ -0,0 +1,256 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing_api_cmd_post_otp_devices.c
+ * @brief command to test POST /otp-devices
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "POST /otp-devices" CMD.
+ */
+struct PostOtpDevicesState
+{
+
+ /**
+ * Handle for a "GET otp_device" request.
+ */
+ struct TALER_MERCHANT_OtpDevicesPostHandle *iph;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * ID of the otp_device to run POST for.
+ */
+ const char *otp_device_id;
+
+ /**
+ * description of the otp_device
+ */
+ const char *otp_device_description;
+
+ /**
+ * base64-encoded key
+ */
+ char *otp_key;
+
+ /**
+ * Option that add amount of the order
+ */
+ enum TALER_MerchantConfirmationAlgorithm otp_alg;
+
+ /**
+ * Counter at the OTP device.
+ */
+ uint64_t otp_ctr;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a POST /otp-devices operation.
+ *
+ * @param cls closure for this function
+ * @param hr response being processed
+ */
+static void
+post_otp_devices_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct PostOtpDevicesState *tis = cls;
+
+ tis->iph = NULL;
+ if (tis->http_status != hr->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ hr->http_status,
+ (int) hr->ec,
+ TALER_TESTING_interpreter_get_current_label (tis->is));
+ TALER_TESTING_interpreter_fail (tis->is);
+ return;
+ }
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ break;
+ case MHD_HTTP_CONFLICT:
+ break;
+ default:
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status %u for POST /otp-devices.\n",
+ hr->http_status);
+ }
+ TALER_TESTING_interpreter_next (tis->is);
+}
+
+
+/**
+ * Run the "POST /otp-devices" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+post_otp_devices_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PostOtpDevicesState *tis = cls;
+
+ tis->is = is;
+ tis->iph = TALER_MERCHANT_otp_devices_post (
+ TALER_TESTING_interpreter_get_context (is),
+ tis->merchant_url,
+ tis->otp_device_id,
+ tis->otp_device_description,
+ tis->otp_key,
+ tis->otp_alg,
+ tis->otp_ctr,
+ &post_otp_devices_cb,
+ tis);
+ if (NULL == tis->iph)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tis->is);
+ return;
+ }
+}
+
+
+/**
+ * Offers information from the POST /otp-devices CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+post_otp_devices_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct PostOtpDevicesState *pts = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_otp_device_description (pts->otp_device_description),
+ TALER_TESTING_make_trait_otp_key (pts->otp_key),
+ TALER_TESTING_make_trait_otp_alg (&pts->otp_alg),
+ TALER_TESTING_make_trait_otp_id (pts->otp_device_id),
+ TALER_TESTING_trait_end (),
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Free the state of a "POST otp_device" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+post_otp_devices_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PostOtpDevicesState *tis = cls;
+
+ if (NULL != tis->iph)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "POST /otp-devices operation did not complete\n");
+ TALER_MERCHANT_otp_devices_post_cancel (tis->iph);
+ }
+ GNUNET_free (tis->otp_key);
+ GNUNET_free (tis);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_otp_devices (
+ const char *label,
+ const char *merchant_url,
+ const char *otp_device_id,
+ const char *otp_device_description,
+ const char *otp_key,
+ const enum TALER_MerchantConfirmationAlgorithm otp_alg,
+ uint64_t otp_ctr,
+ unsigned int http_status)
+{
+ struct PostOtpDevicesState *tis;
+
+ tis = GNUNET_new (struct PostOtpDevicesState);
+ tis->merchant_url = merchant_url;
+ tis->otp_device_id = otp_device_id;
+ tis->http_status = http_status;
+ tis->otp_device_description = otp_device_description;
+ tis->otp_key = GNUNET_strdup (otp_key);
+ tis->otp_alg = otp_alg;
+ tis->otp_ctr = otp_ctr;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = tis,
+ .label = label,
+ .run = &post_otp_devices_run,
+ .cleanup = &post_otp_devices_cleanup,
+ .traits = &post_otp_devices_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_post_otp_devices.c */
diff --git a/src/testing/testing_api_cmd_post_products.c b/src/testing/testing_api_cmd_post_products.c
index be3c3071..4ffafddc 100644
--- a/src/testing/testing_api_cmd_post_products.c
+++ b/src/testing/testing_api_cmd_post_products.c
@@ -95,6 +95,11 @@ struct PostProductsState
json_t *address;
/**
+ * Minimum age requirement to use for the product.
+ */
+ unsigned int minimum_age;
+
+ /**
* when the next restocking is expected to happen, 0 for unknown,
*/
struct GNUNET_TIME_Timestamp next_restock;
@@ -168,20 +173,22 @@ post_products_run (void *cls,
struct PostProductsState *pis = cls;
pis->is = is;
- pis->iph = TALER_MERCHANT_products_post (is->ctx,
- pis->merchant_url,
- pis->product_id,
- pis->description,
- pis->description_i18n,
- pis->unit,
- &pis->price,
- pis->image,
- pis->taxes,
- pis->total_stock,
- pis->address,
- pis->next_restock,
- &post_products_cb,
- pis);
+ pis->iph = TALER_MERCHANT_products_post2 (
+ TALER_TESTING_interpreter_get_context (is),
+ pis->merchant_url,
+ pis->product_id,
+ pis->description,
+ pis->description_i18n,
+ pis->unit,
+ &pis->price,
+ pis->image,
+ pis->taxes,
+ pis->total_stock,
+ pis->address,
+ pis->next_restock,
+ pis->minimum_age,
+ &post_products_cb,
+ pis);
GNUNET_assert (NULL != pis->iph);
}
@@ -196,7 +203,7 @@ post_products_run (void *cls,
* @param index index number of the object to extract.
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
post_products_traits (void *cls,
const void **ret,
const char *trait,
@@ -204,18 +211,17 @@ post_products_traits (void *cls,
{
struct PostProductsState *pps = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_product_description (&pps->description),
+ TALER_TESTING_make_trait_product_description (pps->description),
TALER_TESTING_make_trait_i18n_description (pps->description_i18n),
- TALER_TESTING_make_trait_product_unit (&pps->unit),
+ TALER_TESTING_make_trait_product_unit (pps->unit),
TALER_TESTING_make_trait_amount (&pps->price),
- TALER_TESTING_make_trait_product_image (
- (const char **) &pps->image),
+ TALER_TESTING_make_trait_product_image (pps->image),
TALER_TESTING_make_trait_taxes (pps->taxes),
TALER_TESTING_make_trait_product_stock (&pps->total_stock),
TALER_TESTING_make_trait_address (pps->address),
TALER_TESTING_make_trait_timestamp (0,
&pps->next_restock),
- TALER_TESTING_make_trait_product_id (&pps->product_id),
+ TALER_TESTING_make_trait_product_id (pps->product_id),
TALER_TESTING_trait_end (),
};
@@ -265,6 +271,7 @@ TALER_TESTING_cmd_merchant_post_products2 (
const char *image,
json_t *taxes,
int64_t total_stock,
+ uint32_t minimum_age,
json_t *address,
struct GNUNET_TIME_Timestamp next_restock,
unsigned int http_status)
@@ -288,6 +295,7 @@ TALER_TESTING_cmd_merchant_post_products2 (
pis->image = GNUNET_strdup (image);
pis->taxes = taxes; /* ownership taken */
pis->total_stock = total_stock;
+ pis->minimum_age = minimum_age;
pis->address = address; /* ownership taken */
pis->next_restock = next_restock;
{
@@ -305,12 +313,13 @@ TALER_TESTING_cmd_merchant_post_products2 (
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_post_products (const char *label,
- const char *merchant_url,
- const char *product_id,
- const char *description,
- const char *price,
- unsigned int http_status)
+TALER_TESTING_cmd_merchant_post_products (
+ const char *label,
+ const char *merchant_url,
+ const char *product_id,
+ const char *description,
+ const char *price,
+ unsigned int http_status)
{
return TALER_TESTING_cmd_merchant_post_products2 (
label,
@@ -322,7 +331,8 @@ TALER_TESTING_cmd_merchant_post_products (const char *label,
price,
"",
json_array (),
- 4,
+ 4, /* total stock */
+ 0, /* minimum age */
json_pack ("{s:s}", "street", "my street"),
GNUNET_TIME_UNIT_ZERO_TS,
http_status);
diff --git a/src/testing/testing_api_cmd_post_reserves.c b/src/testing/testing_api_cmd_post_reserves.c
deleted file mode 100644
index b2167534..00000000
--- a/src/testing/testing_api_cmd_post_reserves.c
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as
- published by the Free Software Foundation; either version 3, or
- (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public
- License along with TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file testing_api_cmd_post_reserves.c
- * @brief command to test POST /reserves
- * @author Jonathan Buchanan
- */
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-/**
- * State of a "POST /reserves" CMD.
- */
-struct PostReservesState
-{
- /**
- * Handle for a "POST /reserves" request.
- */
- struct TALER_MERCHANT_PostReservesHandle *prh;
-
- /**
- * The interpreter state.
- */
- struct TALER_TESTING_Interpreter *is;
-
- /**
- * Base URL of the merchant
- */
- const char *merchant_url;
-
- /**
- * Base URL of the exchange.
- */
- const char *exchange_url;
-
- /**
- * Wire method for the reserve.
- */
- const char *wire_method;
-
- /**
- * The initial balance of the reserve.
- */
- struct TALER_Amount initial_balance;
-
- /**
- * Expected HTTP response code.
- */
- unsigned int http_status;
-
- /**
- * Public key assigned to the reserve
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-};
-
-
-/**
- * Callbacks of this type are used to work the result of submitting a
- * POST /reserves request to a merchant
- *
- * @param cls closure
- * @param hr HTTP response details
- * @param reserve_pub public key of the created reserve, NULL on error
- * @param payto_uri where to make the payment to for filling the reserve, NULL on error
- */
-static void
-post_reserves_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const char *payto_uri)
-{
- struct PostReservesState *prs = cls;
-
- prs->prh = NULL;
- if (prs->http_status != hr->http_status)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
- TALER_TESTING_interpreter_get_current_label (prs->is));
- TALER_TESTING_interpreter_fail (prs->is);
- return;
- }
- switch (hr->http_status)
- {
- case MHD_HTTP_OK:
- break;
- case MHD_HTTP_ACCEPTED:
- break;
- case MHD_HTTP_UNAUTHORIZED:
- break;
- case MHD_HTTP_NOT_FOUND:
- break;
- default:
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unhandled HTTP status %u for POST /reserves.\n",
- hr->http_status);
- }
- prs->reserve_pub = *reserve_pub;
- TALER_TESTING_interpreter_next (prs->is);
-}
-
-
-/**
- * Offers information from the POST /reserves CMD state to other
- * commands.
- *
- * @param cls closure
- * @param[out] ret result (could be anything)
- * @param trait name of the trait
- * @param index index number of the object to extract.
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-post_reserves_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
-{
- struct PostReservesState *prs = cls;
- struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_reserve_pub (&prs->reserve_pub),
- TALER_TESTING_make_trait_amount (&prs->initial_balance),
- TALER_TESTING_trait_end (),
- };
-
- return TALER_TESTING_get_trait (traits,
- ret,
- trait,
- index);
-}
-
-
-/**
- * Run the "POST /reserves" CMD.
- *
- * @param cls closure.
- * @param cmd command being run now.
- * @param is interpreter state.
- */
-static void
-post_reserves_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct PostReservesState *prs = cls;
-
- prs->is = is;
- prs->prh = TALER_MERCHANT_reserves_post (is->ctx,
- prs->merchant_url,
- &prs->initial_balance,
- prs->exchange_url,
- prs->wire_method,
- &post_reserves_cb,
- prs);
- GNUNET_assert (NULL != prs->prh);
-}
-
-
-/**
- * Run the fake "POST /reserves" CMD.
- *
- * @param cls closure.
- * @param cmd command being run now.
- * @param is interpreter state.
- */
-static void
-post_reserves_fake_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct PostReservesState *prs = cls;
- struct TALER_ReservePrivateKeyP reserve_priv;
-
- prs->is = is;
- GNUNET_CRYPTO_eddsa_key_create (&reserve_priv.eddsa_priv);
- GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv.eddsa_priv,
- &prs->reserve_pub.eddsa_pub);
-
- GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:100.00",
- &prs->initial_balance));
- TALER_TESTING_interpreter_next (prs->is);
-}
-
-
-/**
- * Free the state of a "POST /reserves" CMD, and possibly
- * cancel a pending operation thereof.
- *
- * @param cls closure.
- * @param cmd command being run.
- */
-static void
-post_reserves_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct PostReservesState *prs = cls;
-
- if (NULL != prs->prh)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "POST /reserves operation did not complete\n");
- TALER_MERCHANT_reserves_post_cancel (prs->prh);
- }
- GNUNET_free (prs);
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_post_reserves (const char *label,
- const char *merchant_url,
- const char *initial_balance,
- const char *exchange_url,
- const char *wire_method,
- unsigned int http_status)
-{
- struct PostReservesState *prs;
-
- prs = GNUNET_new (struct PostReservesState);
- prs->merchant_url = merchant_url;
- prs->exchange_url = exchange_url;
- prs->wire_method = wire_method;
- prs->http_status = http_status;
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (initial_balance,
- &prs->initial_balance));
- {
- struct TALER_TESTING_Command cmd = {
- .cls = prs,
- .label = label,
- .run = &post_reserves_run,
- .cleanup = &post_reserves_cleanup,
- .traits = &post_reserves_traits
- };
-
- return cmd;
- }
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_post_reserves_fake (const char *label)
-{
- struct PostReservesState *prs;
-
- prs = GNUNET_new (struct PostReservesState);
- {
- struct TALER_TESTING_Command cmd = {
- .cls = prs,
- .label = label,
- .run = &post_reserves_fake_run,
- .cleanup = &post_reserves_cleanup,
- .traits = &post_reserves_traits
- };
-
- return cmd;
- }
-}
diff --git a/src/testing/testing_api_cmd_post_templates.c b/src/testing/testing_api_cmd_post_templates.c
index 32b8d627..f0b6d713 100644
--- a/src/testing/testing_api_cmd_post_templates.c
+++ b/src/testing/testing_api_cmd_post_templates.c
@@ -60,9 +60,9 @@ struct PostTemplatesState
const char *template_description;
/**
- * base64-encoded product image
+ * OTP device ID.
*/
- char *image;
+ char *otp_id;
/**
* Contract of the company
@@ -92,12 +92,10 @@ post_templates_cb (void *cls,
tis->iph = NULL;
if (tis->http_status != hr->http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
- TALER_TESTING_interpreter_get_current_label (tis->is));
- TALER_TESTING_interpreter_fail (tis->is);
+ TALER_TESTING_unexpected_status_with_body (tis->is,
+ hr->http_status,
+ tis->http_status,
+ hr->reply);
return;
}
switch (hr->http_status)
@@ -110,6 +108,8 @@ post_templates_cb (void *cls,
break;
case MHD_HTTP_NOT_FOUND:
break;
+ case MHD_HTTP_CONFLICT:
+ break;
default:
GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@@ -136,14 +136,15 @@ post_templates_run (void *cls,
struct PostTemplatesState *tis = cls;
tis->is = is;
- tis->iph = TALER_MERCHANT_templates_post (is->ctx,
- tis->merchant_url,
- tis->template_id,
- tis->template_description,
- tis->image,
- tis->template_contract,
- &post_templates_cb,
- tis);
+ tis->iph = TALER_MERCHANT_templates_post (
+ TALER_TESTING_interpreter_get_context (is),
+ tis->merchant_url,
+ tis->template_id,
+ tis->template_description,
+ tis->otp_id,
+ tis->template_contract,
+ &post_templates_cb,
+ tis);
if (NULL == tis->iph)
{
GNUNET_break (0);
@@ -171,11 +172,10 @@ post_templates_traits (void *cls,
{
struct PostTemplatesState *pts = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_template_description (&pts->template_description),
- TALER_TESTING_make_trait_template_image (
- (const char **) &pts->image),
+ TALER_TESTING_make_trait_template_description (pts->template_description),
+ TALER_TESTING_make_trait_otp_id (pts->otp_id),
TALER_TESTING_make_trait_template_contract (pts->template_contract),
- TALER_TESTING_make_trait_template_id (&pts->template_id),
+ TALER_TESTING_make_trait_template_id (pts->template_id),
TALER_TESTING_trait_end (),
};
@@ -205,7 +205,7 @@ post_templates_cleanup (void *cls,
"POST /templates operation did not complete\n");
TALER_MERCHANT_templates_post_cancel (tis->iph);
}
- GNUNET_free (tis->image);
+ GNUNET_free (tis->otp_id);
json_decref (tis->template_contract);
GNUNET_free (tis);
}
@@ -217,7 +217,7 @@ TALER_TESTING_cmd_merchant_post_templates2 (
const char *merchant_url,
const char *template_id,
const char *template_description,
- const char *image,
+ const char *otp_id,
json_t *template_contract,
unsigned int http_status)
{
@@ -231,7 +231,7 @@ TALER_TESTING_cmd_merchant_post_templates2 (
tis->template_id = template_id;
tis->http_status = http_status;
tis->template_description = template_description;
- tis->image = (NULL == image) ? NULL : GNUNET_strdup (image);
+ tis->otp_id = (NULL == otp_id) ? NULL : GNUNET_strdup (otp_id);
tis->template_contract = template_contract;
{
struct TALER_TESTING_Command cmd = {
diff --git a/src/testing/testing_api_cmd_post_transfers.c b/src/testing/testing_api_cmd_post_transfers.c
index dee1b183..c194bd1e 100644
--- a/src/testing/testing_api_cmd_post_transfers.c
+++ b/src/testing/testing_api_cmd_post_transfers.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020, 2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -70,6 +70,11 @@ struct PostTransfersState
const char *payto_uri;
/**
+ * Set to the hash of the @e payto_uri.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
* Authentication details to authenticate to the bank.
*/
struct TALER_BANK_AuthenticationData auth;
@@ -85,11 +90,6 @@ struct PostTransfersState
struct TALER_Amount credit_amount;
/**
- * The fee incurred on the wire transfer.
- */
- struct TALER_Amount wire_fee;
-
- /**
* Expected HTTP response code.
*/
unsigned int http_status;
@@ -110,10 +110,6 @@ struct PostTransfersState
*/
unsigned int deposits_length;
- /**
- * When the exchange executed the transfer.
- */
- struct GNUNET_TIME_Timestamp execution_time;
};
@@ -121,185 +117,29 @@ struct PostTransfersState
* Callback for a POST /transfers operation.
*
* @param cls closure for this function
- * @param hr HTTP response details
- * @param execution_time when did the transfer happen (according to the exchange),
- * #GNUNET_TIME_UNIT_FOREVER_ABS if the transfer did not yet happen or if
- * we have no data from the exchange about it
- * @param total_amount total amount of the wire transfer, or NULL if the exchange did
- * not provide any details
- * @param wire_fee how much did the exchange charge in terms of wire fees, or NULL
- * if the exchange did not provide any details
- * @param details_length length of the @a details array
- * @param details array with details about the combined transactions
+ * @param ptr response details
*/
static void
transfers_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- struct GNUNET_TIME_Timestamp execution_time,
- const struct TALER_Amount *total_amount,
- const struct TALER_Amount *wire_fee,
- unsigned int details_length,
- const struct TALER_MERCHANT_TrackTransferDetail details[])
+ const struct TALER_MERCHANT_PostTransfersResponse *ptr)
{
struct PostTransfersState *pts = cls;
pts->pth = NULL;
- if (pts->http_status != hr->http_status)
+ if (pts->http_status != ptr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
+ ptr->hr.http_status,
+ (int) ptr->hr.ec,
TALER_TESTING_interpreter_get_current_label (pts->is));
+ GNUNET_break (0);
TALER_TESTING_interpreter_fail (pts->is);
return;
}
- switch (hr->http_status)
+ switch (ptr->hr.http_status)
{
- case MHD_HTTP_OK:
- {
- pts->execution_time = execution_time;
- pts->wire_fee = *wire_fee;
- fprintf (stderr,
- "FIXME");
- json_dumpf (hr->reply,
- stderr,
- 0);
-#if FIXME_WRITE_PROPPER_CHECK_OF_RETURNED_DATA_HERE
- /* this code is some legacy logic that is close to what we
- need but needs to be updated to the current API */
- struct TALER_Amount total;
-
- if (0 >
- TALER_amount_subtract (&total,
- total_amount,
- wire_fee))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (pts->is);
- return;
- }
- if (0 !=
- TALER_amount_cmp (&total,
- &pts->credit_amount))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (pts->is);
- return;
- }
- TALER_amount_set_zero (total.currency,
- &total);
- for (unsigned int i = 0; i<details_length; i++)
- {
- const struct TALER_MERCHANT_TrackTransferDetail *tdd = &details[i];
- struct TALER_Amount sum;
- struct TALER_Amount fees;
-
- TALER_amount_set_zero (tdd->deposit_value.currency,
- &sum);
- TALER_amount_set_zero (tdd->deposit_fee.currency,
- &fees);
- for (unsigned int j = 0; j<pts->deposits_length; j++)
- {
- const char *label = pts->deposits[j];
- const struct TALER_TESTING_Command *cmd;
- const json_t *contract_terms;
- const struct TALER_Amount *deposit_value;
- const struct TALER_Amount *deposit_fee;
- const char *order_id;
-
- cmd = TALER_TESTING_interpreter_lookup_command (pts->is,
- label);
- if (NULL == cmd)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (pts->is);
- return;
- }
- if ( (GNUNET_OK !=
- TALER_TESTING_get_trait_contract_terms (cmd,
- 0,
- &contract_terms)) ||
- (GNUNET_OK !=
- TALER_TESTING_get_trait_amount_obj (cmd,
- TALER_TESTING_CMD_DEPOSIT_TRAIT_IDX_DEPOSIT_VALUE,
- &deposit_value)) ||
- (GNUNET_OK !=
- TALER_TESTING_get_trait_amount_obj (cmd,
- TALER_TESTING_CMD_DEPOSIT_TRAIT_IDX_DEPOSIT_FEE,
- &deposit_fee)) )
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (pts->is);
- return;
- }
- order_id = json_string_value (json_object_get (contract_terms,
- "order_id"));
- if (NULL == order_id)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (pts->is);
- return;
- }
- if (0 != strcmp (tdd->order_id,
- order_id))
- continue;
- if (0 >
- TALER_amount_add (&sum,
- &sum,
- deposit_value))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (pts->is);
- return;
- }
- if (0 >
- TALER_amount_add (&fees,
- &fees,
- deposit_fee))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (pts->is);
- return;
- }
- }
- if (0 !=
- TALER_amount_cmp (&sum,
- &tdd->deposit_value))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (pts->is);
- return;
- }
- if (0 !=
- TALER_amount_cmp (&fees,
- &tdd->deposit_fee))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (pts->is);
- return;
- }
- GNUNET_assert (0 <=
- TALER_amount_add (&total,
- &total,
- &tdd->deposit_value));
- GNUNET_assert (0 <=
- TALER_amount_subtract (&total,
- &total,
- &tdd->deposit_fee));
- }
- if (0 !=
- TALER_amount_cmp (&total,
- &pts->credit_amount))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (pts->is);
- return;
- }
-#endif
- break;
- }
- case MHD_HTTP_ACCEPTED:
+ case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_UNAUTHORIZED:
break;
@@ -311,7 +151,7 @@ transfers_cb (void *cls,
GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Unhandled HTTP status %u for POST /transfers.\n",
- hr->http_status);
+ ptr->hr.http_status);
}
TALER_TESTING_interpreter_next (pts->is);
}
@@ -336,14 +176,10 @@ post_transfers_traits (void *cls,
struct PostTransfersState *pts = cls;
struct TALER_TESTING_Trait traits[] = {
TALER_TESTING_make_trait_wtid (&pts->wtid),
- TALER_TESTING_make_trait_credit_payto_uri (
- (const char **) &pts->credit_account),
+ TALER_TESTING_make_trait_credit_payto_uri (pts->credit_account),
+ TALER_TESTING_make_trait_h_payto (&pts->h_payto),
TALER_TESTING_make_trait_amount (&pts->credit_amount),
- TALER_TESTING_make_trait_fee (&pts->wire_fee),
- TALER_TESTING_make_trait_exchange_url (
- (const char **) &pts->exchange_url),
- TALER_TESTING_make_trait_timestamp (0,
- &pts->execution_time),
+ TALER_TESTING_make_trait_exchange_url (pts->exchange_url),
TALER_TESTING_make_trait_bank_row (&pts->serial),
TALER_TESTING_trait_end (),
};
@@ -371,14 +207,15 @@ post_transfers_run2 (void *cls,
struct PostTransfersState *pts = cls;
pts->is = is;
- pts->pth = TALER_MERCHANT_transfers_post (pts->is->ctx,
- pts->merchant_url,
- &pts->credit_amount,
- &pts->wtid,
- pts->credit_account,
- pts->exchange_url,
- &transfers_cb,
- pts);
+ pts->pth = TALER_MERCHANT_transfers_post (
+ TALER_TESTING_interpreter_get_context (pts->is),
+ pts->merchant_url,
+ &pts->credit_amount,
+ &pts->wtid,
+ pts->credit_account,
+ pts->exchange_url,
+ &transfers_cb,
+ pts);
GNUNET_assert (NULL != pts->pth);
}
@@ -400,22 +237,22 @@ debit_cb (
pts->dhh = NULL;
switch (reply->http_status)
{
+ case MHD_HTTP_OK:
+ /* handled below */
+ break;
case MHD_HTTP_NO_CONTENT:
GNUNET_break (0);
TALER_TESTING_interpreter_fail (pts->is);
return;
- case MHD_HTTP_OK:
- /* handled below */
- break;
default:
GNUNET_break (0);
TALER_TESTING_interpreter_fail (pts->is);
return;
}
- for (unsigned int i = 0; i<reply->details.success.details_length; i++)
+ for (unsigned int i = 0; i<reply->details.ok.details_length; i++)
{
const struct TALER_BANK_DebitDetails *details
- = &reply->details.success.details[i];
+ = &reply->details.ok.details[i];
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Bank reports transfer of %s to %s\n",
@@ -434,17 +271,24 @@ debit_cb (
pts->payto_uri,
pts->exchange_url,
TALER_B2S (&pts->wtid));
- pts->pth = TALER_MERCHANT_transfers_post (pts->is->ctx,
- pts->merchant_url,
- &pts->credit_amount,
- &pts->wtid,
- pts->credit_account,
- pts->exchange_url,
- &transfers_cb,
- pts);
+ pts->pth = TALER_MERCHANT_transfers_post (
+ TALER_TESTING_interpreter_get_context (pts->is),
+ pts->merchant_url,
+ &pts->credit_amount,
+ &pts->wtid,
+ pts->credit_account,
+ pts->exchange_url,
+ &transfers_cb,
+ pts);
GNUNET_assert (NULL != pts->pth);
break;
}
+ if (NULL == pts->pth)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pts->is);
+ return;
+ }
}
@@ -468,7 +312,8 @@ post_transfers_run (void *cls,
"Looking for transfer of %s from %s at bank\n",
TALER_amount2s (&pts->credit_amount),
pts->payto_uri);
- pts->dhh = TALER_BANK_debit_history (is->ctx,
+ pts->dhh = TALER_BANK_debit_history (TALER_TESTING_interpreter_get_context (
+ is),
&pts->auth,
UINT64_MAX,
-INT64_MAX,
@@ -529,6 +374,8 @@ TALER_TESTING_cmd_merchant_post_transfer (
pts->merchant_url = merchant_url;
pts->auth = *auth;
pts->payto_uri = payto_uri;
+ TALER_payto_hash (payto_uri,
+ &pts->h_payto);
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (credit_amount,
&pts->credit_amount));
diff --git a/src/testing/testing_api_cmd_post_using_templates.c b/src/testing/testing_api_cmd_post_using_templates.c
index 956a421a..7aeec33d 100644
--- a/src/testing/testing_api_cmd_post_using_templates.c
+++ b/src/testing/testing_api_cmd_post_using_templates.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -27,6 +27,7 @@
#include "taler_merchant_service.h"
#include "taler_merchant_testing_lib.h"
+
/**
* State of a "POST /templates" CMD.
*/
@@ -44,11 +45,23 @@ struct PostUsingTemplatesState
struct TALER_TESTING_Interpreter *is;
/**
+ * The (initial) POST /orders/$ID/claim operation handle.
+ * The logic is such that after an order creation,
+ * we immediately claim the order.
+ */
+ struct TALER_MERCHANT_OrderClaimHandle *och;
+
+ /**
* Base URL of the merchant serving the request.
*/
const char *merchant_url;
/**
+ * ID of the using template to run.
+ */
+ const char *using_template_id;
+
+ /**
* Summary given by the customer.
*/
const char *summary;
@@ -64,12 +77,145 @@ struct PostUsingTemplatesState
const char *template_ref;
/**
+ * Order id.
+ */
+ char *order_id;
+
+ /**
+ * The order id we expect the merchant to assign (if not NULL).
+ */
+ const char *expected_order_id;
+
+ /**
+ * Contract terms obtained from the backend.
+ */
+ json_t *contract_terms;
+
+ /**
+ * Order submitted to the backend.
+ */
+ json_t *order_terms;
+
+ /**
+ * Contract terms hash code.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Merchant signature over the orders.
+ */
+ struct TALER_MerchantSignatureP merchant_sig;
+
+ /**
+ * Merchant public key.
+ */
+ struct TALER_MerchantPublicKeyP merchant_pub;
+
+ /**
+ * The nonce.
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey nonce;
+
+ /**
+ * The claim token
+ */
+ struct TALER_ClaimTokenP claim_token;
+
+ /**
+ * Should the command also CLAIM the order?
+ */
+ bool with_claim;
+
+ /**
+ * If not NULL, the command should duplicate the request and verify the
+ * response is the same as in this command.
+ */
+ const char *duplicate_of;
+
+ /**
+ * Label of command creating/updating OTP device, or NULL.
+ */
+ const char *otp_ref;
+
+ /**
+ * Encoded key for the payment verification.
+ */
+ const char *otp_key;
+
+ /**
+ * Option that add amount of the order
+ */
+ const enum TALER_MerchantConfirmationAlgorithm *otp_alg;
+
+ /**
* Expected HTTP response code.
*/
unsigned int http_status;
};
+/**
+ * Used to fill the "using_template" CMD state with backend-provided
+ * values. Also double-checks that the using_template was correctly
+ * created.
+ *
+ * @param cls closure
+ * @param ocr response we got
+ */
+static void
+using_claim_cb (void *cls,
+ const struct TALER_MERCHANT_OrderClaimResponse *ocr)
+{
+ struct PostUsingTemplatesState *tis = cls;
+ const char *error_name;
+ unsigned int error_line;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &tis->merchant_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ tis->och = NULL;
+ if (tis->http_status != ocr->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected status %u, got %u\n",
+ tis->http_status,
+ ocr->hr.http_status);
+ TALER_TESTING_FAIL (tis->is);
+ }
+ if (MHD_HTTP_OK != ocr->hr.http_status)
+ {
+ TALER_TESTING_interpreter_next (tis->is);
+ return;
+ }
+ tis->contract_terms = json_deep_copy (
+ (json_t *) ocr->details.ok.contract_terms);
+ tis->h_contract_terms = ocr->details.ok.h_contract_terms;
+ tis->merchant_sig = ocr->details.ok.sig;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (tis->contract_terms,
+ spec,
+ &error_name,
+ &error_line))
+ {
+ char *log;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Parser failed on %s:%u\n",
+ error_name,
+ error_line);
+ log = json_dumps (tis->contract_terms,
+ JSON_INDENT (1));
+ fprintf (stderr,
+ "%s\n",
+ log);
+ free (log);
+ TALER_TESTING_FAIL (tis->is);
+ }
+ TALER_TESTING_interpreter_next (tis->is);
+}
+
/**
* Callback for a POST /using-templates operation.
@@ -94,22 +240,102 @@ post_using_templates_cb (void *cls,
TALER_TESTING_interpreter_fail (tis->is);
return;
}
+ if (0 == tis->http_status)
+ {
+ TALER_LOG_DEBUG ("/using_templates, expected 0 status code\n");
+ TALER_TESTING_interpreter_next (tis->is);
+ return;
+ }
+ // check for order
switch (por->hr.http_status)
{
case MHD_HTTP_OK:
- break;
- case MHD_HTTP_CONFLICT:
+ if (NULL != por->details.ok.token)
+ tis->claim_token = *por->details.ok.token;
+ tis->order_id = GNUNET_strdup (por->details.ok.order_id);
+ if ((NULL != tis->expected_order_id) &&
+ (0 != strcmp (por->details.ok.order_id,
+ tis->expected_order_id)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Order id assigned does not match\n");
+ TALER_TESTING_interpreter_fail (tis->is);
+ return;
+ }
+ if (NULL != tis->duplicate_of)
+ {
+ const struct TALER_TESTING_Command *order_cmd;
+ const struct TALER_ClaimTokenP *prev_token;
+ struct TALER_ClaimTokenP zero_token = {0};
+
+ order_cmd = TALER_TESTING_interpreter_lookup_command (
+ tis->is,
+ tis->duplicate_of);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_claim_token (order_cmd,
+ &prev_token))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not fetch previous order claim token\n");
+ TALER_TESTING_interpreter_fail (tis->is);
+ return;
+ }
+ if (NULL == por->details.ok.token)
+ prev_token = &zero_token;
+ if (0 != GNUNET_memcmp (prev_token,
+ por->details.ok.token))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Claim tokens for identical requests do not match\n");
+ TALER_TESTING_interpreter_fail (tis->is);
+ return;
+ }
+ }
break;
case MHD_HTTP_NOT_FOUND:
- break;
+ TALER_TESTING_interpreter_next (tis->is);
+ return;
+ case MHD_HTTP_GONE:
+ TALER_TESTING_interpreter_next (tis->is);
+ return;
+ case MHD_HTTP_CONFLICT:
+ TALER_TESTING_interpreter_next (tis->is);
+ return;
default:
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unhandled HTTP status %u for POST /templates/$ID.\n",
- por->hr.http_status);
- break;
+ {
+ char *s = json_dumps (por->hr.reply,
+ JSON_COMPACT);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected status code from /orders: %u (%d) at %s; JSON: %s\n",
+ por->hr.http_status,
+ (int) por->hr.ec,
+ TALER_TESTING_interpreter_get_current_label (tis->is),
+ s);
+ GNUNET_free (s);
+ /**
+ * Not failing, as test cases are _supposed_
+ * to create non 200 OK situations.
+ */
+ TALER_TESTING_interpreter_next (tis->is);
+ }
+ return;
}
- TALER_TESTING_interpreter_next (tis->is);
+
+ if (! tis->with_claim)
+ {
+ TALER_TESTING_interpreter_next (tis->is);
+ return;
+ }
+ if (NULL ==
+ (tis->och = TALER_MERCHANT_order_claim (
+ TALER_TESTING_interpreter_get_context (tis->is),
+ tis->merchant_url,
+ tis->order_id,
+ &tis->nonce,
+ &tis->claim_token,
+ &using_claim_cb,
+ tis)))
+ TALER_TESTING_FAIL (tis->is);
}
@@ -128,7 +354,7 @@ post_using_templates_run (void *cls,
{
struct PostUsingTemplatesState *tis = cls;
const struct TALER_TESTING_Command *ref;
- const char **template_id;
+ const char *template_id;
tis->is = is;
ref = TALER_TESTING_interpreter_lookup_command (is,
@@ -137,10 +363,23 @@ post_using_templates_run (void *cls,
TALER_TESTING_get_trait_template_id (ref,
&template_id))
TALER_TESTING_FAIL (is);
+ if (NULL != tis->otp_ref)
+ {
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ tis->otp_ref);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_otp_key (ref,
+ &tis->otp_key))
+ TALER_TESTING_FAIL (is);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_otp_alg (ref,
+ &tis->otp_alg))
+ TALER_TESTING_FAIL (is);
+ }
tis->iph = TALER_MERCHANT_using_templates_post (
- is->ctx,
+ TALER_TESTING_interpreter_get_context (is),
tis->merchant_url,
- *template_id,
+ template_id,
tis->summary,
TALER_amount_is_valid (&tis->amount)
? &tis->amount
@@ -169,6 +408,16 @@ post_using_templates_traits (void *cls,
{
struct PostUsingTemplatesState *pts = cls;
struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_order_id (pts->order_id),
+ TALER_TESTING_make_trait_contract_terms (pts->contract_terms),
+ TALER_TESTING_make_trait_order_terms (pts->order_terms),
+ TALER_TESTING_make_trait_h_contract_terms (&pts->h_contract_terms),
+ TALER_TESTING_make_trait_merchant_sig (&pts->merchant_sig),
+ TALER_TESTING_make_trait_merchant_pub (&pts->merchant_pub),
+ TALER_TESTING_make_trait_claim_nonce (&pts->nonce),
+ TALER_TESTING_make_trait_claim_token (&pts->claim_token),
+ TALER_TESTING_make_trait_otp_key (pts->otp_key),
+ TALER_TESTING_make_trait_otp_alg (pts->otp_alg),
TALER_TESTING_trait_end (),
};
@@ -199,25 +448,144 @@ post_using_templates_cleanup (void *cls,
"POST /using-templates operation did not complete\n");
TALER_MERCHANT_using_templates_post_cancel (tis->iph);
}
+ json_decref (tis->order_terms);
+ json_decref (tis->contract_terms);
+ GNUNET_free (tis->order_id);
GNUNET_free (tis);
}
+/**
+ * Mark part of the contract terms as possible to forget.
+ *
+ * @param cls pointer to the result of the forget operation.
+ * @param object_id name of the object to forget.
+ * @param parent parent of the object at @e object_id.
+ */
+static void
+mark_forgettable (void *cls,
+ const char *object_id,
+ json_t *parent)
+{
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_mark_forgettable (parent,
+ object_id));
+}
+
+
+/**
+ * Constructs the json for a POST using template request.
+ *
+ * @param using_template_id the name of the using_template to add, can be NULL.
+ * @param refund_deadline the deadline for refunds on this using template.
+ * @param pay_deadline the deadline for payment on this using template.
+ * @param amount the amount this using template is for.
+ * @param[out] using_template where to write the json string.
+ */
+static void
+make_order_json (const char *using_template_id,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ const char *amount,
+ json_t **using_template)
+{
+ struct GNUNET_TIME_Timestamp refund = refund_deadline;
+ struct GNUNET_TIME_Timestamp pay = pay_deadline;
+ json_t *contract_terms;
+ struct TALER_Amount tamount;
+ json_t *arr;
+
+ if (NULL != amount)
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (amount,
+ &tamount));
+ /* Include required fields and some dummy objects to test forgetting. */
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ arr,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string (
+ "item", "speakers"))));
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ arr,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string (
+ "item", "headphones"))));
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ arr,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string (
+ "item", "earbuds"))));
+ contract_terms = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("summary",
+ "merchant-lib testcase"),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string (
+ "using_template_id", using_template_id)),
+ NULL == amount
+ ? GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("amount",
+ NULL))
+ : TALER_JSON_pack_amount ("amount",
+ &tamount),
+ GNUNET_JSON_pack_string ("fulfillment_url",
+ "https://example.com"),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("refund_deadline",
+ refund)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("pay_deadline",
+ pay)),
+ GNUNET_JSON_pack_string ("dummy_obj",
+ "EUR:1.0"),
+ GNUNET_JSON_pack_array_steal ("dummy_array",
+ arr));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_expand_path (contract_terms,
+ "$.dummy_obj",
+ &mark_forgettable,
+ NULL));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_expand_path (contract_terms,
+ "$.dummy_array[*].item",
+ &mark_forgettable,
+ NULL));
+ *using_template = contract_terms;
+}
+
+
struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_using_templates (
const char *label,
const char *template_ref,
+ const char *otp_ref,
const char *merchant_url,
+ const char *using_template_id,
const char *summary,
- const char *amount, unsigned int http_status)
+ const char *amount,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ unsigned int http_status)
{
struct PostUsingTemplatesState *tis;
tis = GNUNET_new (struct PostUsingTemplatesState);
tis->template_ref = template_ref;
+ tis->otp_ref = otp_ref;
tis->merchant_url = merchant_url;
+ tis->using_template_id = using_template_id;
tis->http_status = http_status;
tis->summary = summary;
+ tis->with_claim = true;
+ make_order_json (using_template_id,
+ refund_deadline,
+ pay_deadline,
+ amount,
+ &tis->order_terms);
if (NULL != amount)
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (amount,
diff --git a/src/testing/testing_api_cmd_post_webhooks.c b/src/testing/testing_api_cmd_post_webhooks.c
index d1628429..c3a8d1b3 100644
--- a/src/testing/testing_api_cmd_post_webhooks.c
+++ b/src/testing/testing_api_cmd_post_webhooks.c
@@ -78,7 +78,7 @@ struct PostWebhooksState
* body of the webhook
*/
const char *body_template;
-
+
/**
* Expected HTTP response code.
*/
@@ -148,16 +148,17 @@ post_webhooks_run (void *cls,
struct PostWebhooksState *wis = cls;
wis->is = is;
- wis->iph = TALER_MERCHANT_webhooks_post (is->ctx,
- wis->merchant_url,
- wis->webhook_id,
- wis->event_type,
- wis->url,
- wis->http_method,
- wis->header_template,
- wis->body_template,
- &post_webhooks_cb,
- wis);
+ wis->iph = TALER_MERCHANT_webhooks_post (
+ TALER_TESTING_interpreter_get_context (is),
+ wis->merchant_url,
+ wis->webhook_id,
+ wis->event_type,
+ wis->url,
+ wis->http_method,
+ wis->header_template,
+ wis->body_template,
+ &post_webhooks_cb,
+ wis);
GNUNET_assert (NULL != wis->iph);
}
@@ -180,12 +181,12 @@ post_webhooks_traits (void *cls,
{
struct PostWebhooksState *pws = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_event_type (&pws->event_type),
- TALER_TESTING_make_trait_url (&pws->url),
- TALER_TESTING_make_trait_http_method (&pws->http_method),
- TALER_TESTING_make_trait_header_template (&pws->header_template),
- TALER_TESTING_make_trait_body_template (&pws->body_template),
- TALER_TESTING_make_trait_webhook_id (&pws->webhook_id),
+ TALER_TESTING_make_trait_event_type (pws->event_type),
+ TALER_TESTING_make_trait_url (pws->url),
+ TALER_TESTING_make_trait_http_method (pws->http_method),
+ TALER_TESTING_make_trait_header_template (pws->header_template),
+ TALER_TESTING_make_trait_body_template (pws->body_template),
+ TALER_TESTING_make_trait_webhook_id (pws->webhook_id),
TALER_TESTING_trait_end (),
};
@@ -240,8 +241,8 @@ TALER_TESTING_cmd_merchant_post_webhooks2 (
wis->event_type = event_type;
wis->url = url;
wis->http_method = http_method;
- wis->header_template = header_template;
- wis->body_template = body_template;
+ wis->header_template = (NULL==header_template) ? NULL : header_template;
+ wis->body_template = (NULL==body_template) ? NULL : body_template;
{
struct TALER_TESTING_Command cmd = {
.cls = wis,
@@ -268,10 +269,10 @@ TALER_TESTING_cmd_merchant_post_webhooks (const char *label,
merchant_url,
webhook_id,
event_type,
- "https://example.com",
+ "http://localhost:12345/",
"POST",
- "Authorization:EFEHYJS",
- "$amount",
+ "Taler-test-header: EFEHYJS-Bakery",
+ "5.0 EUR",
http_status);
}
diff --git a/src/testing/testing_api_cmd_refund_order.c b/src/testing/testing_api_cmd_refund_order.c
index 0f94622c..7cc71e21 100644
--- a/src/testing/testing_api_cmd_refund_order.c
+++ b/src/testing/testing_api_cmd_refund_order.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2019 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -76,39 +76,34 @@ struct RefundState
* if the HTTP response code is the one expected.
*
* @param cls closure
- * @param hr HTTP response
- * @param taler_refund_uri the refund uri offered to the wallet
- * @param h_contract hash of the contract a Browser may need to authorize
- * obtaining the HTTP response.
+ * @param rr response
*/
static void
refund_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const char *taler_refund_uri,
- const struct TALER_PrivateContractHashP *h_contract)
+ const struct TALER_MERCHANT_RefundResponse *rr)
{
struct RefundState *ris = cls;
- (void) h_contract;
ris->orh = NULL;
- if (ris->http_code != hr->http_status)
+ if (ris->http_code != rr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Expected status %u, got %u(%d) for refund increase\n",
ris->http_code,
- hr->http_status,
- (int) hr->ec);
+ rr->hr.http_status,
+ (int) rr->hr.ec);
TALER_TESTING_FAIL (ris->is);
}
- switch (hr->http_status)
+ switch (rr->hr.http_status)
{
case MHD_HTTP_OK:
{
struct TALER_MERCHANT_RefundUriData rud;
if (GNUNET_OK !=
- TALER_MERCHANT_parse_refund_uri (taler_refund_uri,
- &rud))
+ TALER_MERCHANT_parse_refund_uri (
+ rr->details.ok.taler_refund_uri,
+ &rud))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Taler refund uri is malformed\n");
@@ -116,25 +111,9 @@ refund_cb (void *cls,
return;
}
{
- char *port;
char *host;
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (ris->is->cfg,
- "merchant",
- "PORT",
- &port))
- {
- /* How did we get here without a configured port? */
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (ris->is);
- TALER_MERCHANT_parse_refund_uri_free (&rud);
- return;
- }
- GNUNET_asprintf (&host,
- "localhost:%s",
- port);
- GNUNET_free (port);
+ host = TALER_MERCHANT_TESTING_extract_host (ris->merchant_url);
if ((0 != strcmp (host,
rud.merchant_host)) ||
(NULL != rud.merchant_prefix_path) ||
@@ -165,7 +144,7 @@ refund_cb (void *cls,
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Unhandled HTTP status %u for refund order.\n",
- hr->http_status);
+ rr->hr.http_status);
}
TALER_TESTING_interpreter_next (ris->is);
}
@@ -186,13 +165,14 @@ refund_increase_run (void *cls,
struct RefundState *ris = cls;
ris->is = is;
- ris->orh = TALER_MERCHANT_post_order_refund (is->ctx,
- ris->merchant_url,
- ris->order_id,
- &ris->refund_amount,
- ris->reason,
- &refund_cb,
- ris);
+ ris->orh = TALER_MERCHANT_post_order_refund (
+ TALER_TESTING_interpreter_get_context (is),
+ ris->merchant_url,
+ ris->order_id,
+ &ris->refund_amount,
+ ris->reason,
+ &refund_cb,
+ ris);
if (NULL == ris->orh)
TALER_TESTING_FAIL (is);
}
@@ -217,7 +197,7 @@ refund_increase_traits (void *cls,
struct RefundState *ris = cls;
struct TALER_TESTING_Trait traits[] = {
TALER_TESTING_make_trait_amount (&ris->refund_amount),
- TALER_TESTING_make_trait_reason (&ris->reason),
+ TALER_TESTING_make_trait_reason (ris->reason),
TALER_TESTING_trait_end ()
};
diff --git a/src/testing/testing_api_cmd_testserver.c b/src/testing/testing_api_cmd_testserver.c
new file mode 100644
index 00000000..f47502a6
--- /dev/null
+++ b/src/testing/testing_api_cmd_testserver.c
@@ -0,0 +1,374 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file testing/testing_api_cmd_testserver.c
+ * @brief Implement a CMD to run an Testserver service for faking the legitimation service
+ * @author Priscilla HUANG
+ */
+#include "platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_testing_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler_merchant_testing_lib.h"
+#include "taler_merchant_service.h"
+#include <taler/taler_exchange_service.h>
+
+/**
+ * State for the testserver CMD.
+ */
+struct TestserverState
+{
+
+ /**
+ * Handle to the "testserver" service.
+ */
+ struct MHD_Daemon *mhd;
+
+ /**
+ * Port to listen on.
+ */
+ uint16_t port;
+
+ /**
+ * Array where we remember all of the requests this
+ * server answered.
+ */
+ struct RequestCtx **rcs;
+
+ /**
+ * Length of the @a rcs array
+ */
+ unsigned int rcs_length;
+};
+
+
+struct RequestCtx
+{
+ /**
+ * URL where we are redirect.
+ */
+ char *url;
+
+ /**
+ * http method of the webhook.
+ */
+ char *http_method;
+
+ /**
+ * header of the webhook.
+ */
+ char *header;
+
+ /**
+ * body of the webhook.
+ */
+ void *body;
+
+ /**
+ * size of the body
+ */
+ size_t body_size;
+
+ /**
+ * Set to true when we are done with the request.
+ */
+ bool done;
+};
+
+
+/**
+ * A client has requested the given url using the given method
+ * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
+ * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback
+ * must call MHD callbacks to provide content to give back to the
+ * client and return an HTTP status code (i.e. #MHD_HTTP_OK,
+ * #MHD_HTTP_NOT_FOUND, etc.).
+ *
+ * @param cls argument given together with the function
+ * pointer when the handler was registered with MHD
+ * @param connection the connection being handled
+ * @param url the requested url
+ * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
+ * #MHD_HTTP_METHOD_PUT, etc.)
+ * @param version the HTTP version string (i.e.
+ * MHD_HTTP_VERSION_1_1)
+ * @param upload_data the data being uploaded (excluding HEADERS,
+ * for a POST that fits into memory and that is encoded
+ * with a supported encoding, the POST data will NOT be
+ * given in upload_data and is instead available as
+ * part of MHD_get_connection_values(); very large POST
+ * data *will* be made available incrementally in
+ * @a upload_data)
+ * @param[in,out] upload_data_size set initially to the size of the
+ * @a upload_data provided; the method must update this
+ * value to the number of bytes NOT processed;
+ * @param[in,out] con_cls pointer that the callback can set to some
+ * address and that will be preserved by MHD for future
+ * calls for this request; since the access handler may
+ * be called many times (i.e., for a PUT/POST operation
+ * with plenty of upload data) this allows the application
+ * to easily associate some request-specific state.
+ * If necessary, this state can be cleaned up in the
+ * global MHD_RequestCompletedCallback (which
+ * can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
+ * Initially, `*con_cls` will be NULL.
+ * @return #MHD_YES if the connection was handled successfully,
+ * #MHD_NO if the socket must be closed due to a serious
+ * error while handling the request
+ */
+static MHD_RESULT
+handler_cb (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ struct TestserverState *ts = cls;
+ struct RequestCtx *rc = *con_cls;
+
+ (void) version;
+ if (NULL == rc)
+ {
+ const char *hdr;
+
+ rc = GNUNET_new (struct RequestCtx);
+ *con_cls = rc;
+ rc->http_method = GNUNET_strdup (method);
+ hdr = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ "Taler-test-header");
+ if (NULL != hdr)
+ rc->header = GNUNET_strdup (hdr);
+ if (NULL != url)
+ rc->url = GNUNET_strdup (url);
+ GNUNET_array_append (ts->rcs,
+ ts->rcs_length,
+ rc);
+ fprintf (stderr,
+ "Webhook called server at `%s' with header `%s'\n",
+ url,
+ hdr);
+ return MHD_YES;
+ }
+ if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET))
+ {
+ json_t *reply;
+
+ reply = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string (
+ "status",
+ "success"));
+ return TALER_MHD_reply_json_steal (connection,
+ reply,
+ MHD_HTTP_OK);
+ }
+ if (0 != strcasecmp (method,
+ MHD_HTTP_METHOD_POST))
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (0 != *upload_data_size)
+ {
+ void *body;
+
+ body = GNUNET_malloc (rc->body_size + *upload_data_size);
+ GNUNET_memcpy (body,
+ rc->body,
+ rc->body_size);
+ GNUNET_free (rc->body);
+ GNUNET_memcpy (body + rc->body_size,
+ upload_data,
+ *upload_data_size);
+ rc->body = body;
+ rc->body_size += *upload_data_size;
+ *upload_data_size = 0;
+ return MHD_YES;
+ }
+
+ {
+ json_t *reply;
+
+ reply = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("something",
+ "good"));
+ return TALER_MHD_reply_json_steal (connection,
+ reply,
+ MHD_HTTP_OK);
+ }
+}
+
+
+static void
+cleanup (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct RequestCtx *rc = *con_cls;
+
+ (void) cls;
+ (void) connection;
+ (void) toe;
+ if (NULL == rc)
+ return;
+ rc->done = true;
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+testserver_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct TestserverState *ser = cls;
+
+ (void) cmd;
+ ser->mhd = MHD_start_daemon (MHD_USE_AUTO_INTERNAL_THREAD,
+ ser->port,
+ NULL, NULL,
+ &handler_cb, ser,
+ MHD_OPTION_NOTIFY_COMPLETED, &cleanup, NULL,
+ NULL);
+ if (NULL == ser->mhd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Cleanup the state from a "testserver" CMD, and possibly cancel a operation
+ * thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+testserver_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct TestserverState *ser = cls;
+
+ (void) cmd;
+ if (NULL != ser->mhd)
+ {
+ MHD_stop_daemon (ser->mhd);
+ ser->mhd = NULL;
+ }
+ for (unsigned int i = 0; i<ser->rcs_length; i++)
+ {
+ struct RequestCtx *rc = ser->rcs[i];
+
+ GNUNET_assert (rc->done);
+ GNUNET_free (rc->url);
+ GNUNET_free (rc->http_method);
+ GNUNET_free (rc->header);
+ GNUNET_free (rc->body);
+ GNUNET_free (rc);
+ }
+ GNUNET_array_grow (ser->rcs,
+ ser->rcs_length,
+ 0);
+ GNUNET_free (ser);
+}
+
+
+static enum GNUNET_GenericReturnValue
+traits_testserver (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct TestserverState *ser = cls;
+
+ if (index >= ser->rcs_length)
+ return GNUNET_NO;
+
+ {
+ const struct RequestCtx *rc = ser->rcs[index];
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_urls (index,
+ rc->url),
+ TALER_TESTING_make_trait_http_methods (index,
+ rc->http_method),
+ TALER_TESTING_make_trait_http_header (index,
+ rc->header),
+ TALER_TESTING_make_trait_http_body (index,
+ rc->body),
+ TALER_TESTING_make_trait_http_body_size (index,
+ &rc->body_size),
+ TALER_TESTING_trait_end (),
+ };
+
+ if (! rc->done)
+ return GNUNET_NO;
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+ }
+}
+
+
+/**
+ * This function is used to start the web server.
+ *
+ * @param label command label
+ * @param port is the port of the web server
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_testserver (const char *label,
+ uint16_t port)
+{
+ struct TestserverState *ser;
+
+ ser = GNUNET_new (struct TestserverState);
+ ser->port = port;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ser,
+ .label = label,
+ .run = &testserver_run,
+ .cleanup = &testserver_cleanup,
+ .traits = &traits_testserver
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_checkserver.c */
diff --git a/src/testing/testing_api_cmd_tip_authorize.c b/src/testing/testing_api_cmd_tip_authorize.c
deleted file mode 100644
index 3d6893d9..00000000
--- a/src/testing/testing_api_cmd_tip_authorize.c
+++ /dev/null
@@ -1,489 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as
- published by the Free Software Foundation; either version 3, or
- (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public
- License along with TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-
-/**
- * @file testing_api_cmd_tip_authorize.c
- * @brief command to test the tipping.
- * @author Marcello Stanisci
- */
-
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-/**
- * State for a /tip-authorize CMD.
- */
-struct TipAuthorizeState
-{
-
- /**
- * Merchant base URL.
- */
- const char *merchant_url;
-
- /**
- * Expected HTTP response code.
- */
- unsigned int http_status;
-
- /**
- * Reference to the reserv to authorize the tip
- * from (if NULL, the merchant decides).
- */
- const char *reserve_reference;
-
- /**
- * Human-readable justification for the
- * tip authorization carried on by this CMD.
- */
- const char *justification;
-
- /**
- * Amount that should be authorized for tipping.
- */
- struct TALER_Amount amount;
-
- /**
- * Expected Taler error code for this CMD.
- */
- enum TALER_ErrorCode expected_ec;
-
- /**
- * Tip taler:// URI.
- */
- char *tip_uri;
-
- /**
- * The tip id; set when the CMD succeeds.
- */
- struct TALER_TipIdentifierP tip_id;
-
- /**
- * Expiration date for this tip.
- */
- struct GNUNET_TIME_Timestamp tip_expiration;
-
- /**
- * Handle to the on-going /tip-authorize request.
- */
- struct TALER_MERCHANT_TipAuthorizeHandle *tao;
-
- /**
- * The interpreter state.
- */
- struct TALER_TESTING_Interpreter *is;
-
- /**
- * Task used for retries.
- */
- struct GNUNET_SCHEDULER_Task *retry_task;
-
- /**
- * How long do we wait between retries?
- */
- struct GNUNET_TIME_Relative backoff;
-
- /**
- * How many retries are left?
- */
- unsigned int retries_left;
-
-};
-
-
-/**
- * Run the main logic of talking to the merchant.
- *
- * @param cls a `struct TipAuthorizeState`.
- */
-static void
-do_retry (void *cls);
-
-
-/**
- * Callback for a /tip-authorize request. Set into the state
- * what was returned from the backend (@a tip_id and @a
- * tip_expiration).
- *
- * @param cls closure
- * @param hr HTTP response we got
- * @param tip_id unique identifier for the tip
- * @param taler_tip_uri URI to let the wallet know about the tip
- * @param expiration when the tip expires
- */
-static void
-tip_authorize_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- struct TALER_TipIdentifierP *tip_id,
- const char *taler_tip_uri,
- struct GNUNET_TIME_Timestamp expiration)
-{
- struct TipAuthorizeState *tas = cls;
-
- tas->tao = NULL;
- if (tas->http_status != hr->http_status)
- {
- if ( (MHD_HTTP_NOT_FOUND == hr->http_status) &&
- (0 < tas->retries_left) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Reserve authorization failed. Reserve may not yet be ready, retrying %u more times.\n",
- tas->retries_left);
- tas->retries_left--;
- tas->backoff = GNUNET_TIME_randomized_backoff (tas->backoff,
- GNUNET_TIME_UNIT_SECONDS);
- tas->retry_task = GNUNET_SCHEDULER_add_delayed (tas->backoff,
- &do_retry,
- tas);
- return;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- hr->ec,
- TALER_TESTING_interpreter_get_current_label (tas->is));
- TALER_TESTING_interpreter_fail (tas->is);
- return;
- }
-
- if (tas->expected_ec != hr->ec)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected error code %d (%u) to command %s\n",
- (int) hr->ec,
- hr->http_status,
- TALER_TESTING_interpreter_get_current_label (tas->is));
- TALER_TESTING_interpreter_fail (tas->is);
- return;
- }
- if ( (MHD_HTTP_OK == hr->http_status) &&
- (TALER_EC_NONE == hr->ec) )
- {
- tas->tip_uri = strdup (taler_tip_uri);
- tas->tip_id = *tip_id;
- tas->tip_expiration = expiration;
- }
- TALER_TESTING_interpreter_next (tas->is);
-}
-
-
-/**
- * Offers information from the /tip-authorize CMD state to other
- * commands.
- *
- * @param cls closure
- * @param[out] ret result (could be anything)
- * @param trait name of the trait
- * @param index index number of the object to extract.
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-tip_authorize_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
-{
- struct TipAuthorizeState *tas = cls;
- struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_tip_id (&tas->tip_id),
- TALER_TESTING_make_trait_amount (&tas->amount),
- TALER_TESTING_make_trait_reason (&tas->justification),
- TALER_TESTING_make_trait_timestamp (0,
- &tas->tip_expiration),
- TALER_TESTING_trait_end (),
- };
-
- return TALER_TESTING_get_trait (traits,
- ret,
- trait,
- index);
-}
-
-
-/**
- * Runs the /tip-authorize CMD
- *
- * @param cls closure
- * @param cmd the CMD representing _this_ command
- * @param is interpreter state
- */
-static void
-tip_authorize_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct TipAuthorizeState *tas = cls;
-
- tas->retries_left = 16;
- tas->is = is;
- tas->retry_task = GNUNET_SCHEDULER_add_now (&do_retry,
- tas);
-}
-
-
-static void
-do_retry (void *cls)
-{
- struct TipAuthorizeState *tas = cls;
-
- tas->retry_task = NULL;
- if (NULL == tas->reserve_reference)
- {
- tas->tao = TALER_MERCHANT_tip_authorize (tas->is->ctx,
- tas->merchant_url,
- "http://merchant.com/pickup",
- &tas->amount,
- tas->justification,
- &tip_authorize_cb,
- tas);
- }
- else
- {
- const struct TALER_TESTING_Command *reserve_cmd;
- const struct TALER_ReservePublicKeyP *reserve_pub;
-
- reserve_cmd = TALER_TESTING_interpreter_lookup_command (
- tas->is,
- tas->reserve_reference);
- GNUNET_assert (GNUNET_OK ==
- TALER_TESTING_get_trait_reserve_pub (reserve_cmd,
- &reserve_pub));
- tas->tao = TALER_MERCHANT_tip_authorize2 (tas->is->ctx,
- tas->merchant_url,
- reserve_pub,
- "http://merchant.com/pickup",
- &tas->amount,
- tas->justification,
- &tip_authorize_cb,
- tas);
- }
- GNUNET_assert (NULL != tas->tao);
-}
-
-
-/**
- * Run the /tip-authorize CMD, the "fake" version of it.
- *
- * @param cls closure
- * @param cmd the CMD representing _this_ command
- * @param is interpreter state *
- */
-static void
-tip_authorize_fake_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct TipAuthorizeState *tas = cls;
-
- /* Make up a tip id. */
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
- &tas->tip_id,
- sizeof (struct TALER_TipIdentifierP));
- TALER_TESTING_interpreter_next (is);
-}
-
-
-/**
- * Free the state from a /tip-authorize CMD, and possibly
- * cancel any pending operation.
- *
- * @param cls closure
- * @param cmd the /tip-authorize CMD that is about to be freed.
- */
-static void
-tip_authorize_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct TipAuthorizeState *tas = cls;
-
- if (NULL != tas->tao)
- {
- TALER_LOG_WARNING ("Tip-autorize operation"
- " did not complete\n");
- TALER_MERCHANT_tip_authorize_cancel (tas->tao);
- }
- if (NULL != tas->retry_task)
- {
- GNUNET_SCHEDULER_cancel (tas->retry_task);
- tas->retry_task = NULL;
- }
- GNUNET_free (tas->tip_uri);
- GNUNET_free (tas);
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_authorize_with_ec (const char *label,
- const char *merchant_url,
- const char *exchange_url,
- unsigned int http_status,
- const char *justification,
- const char *amount,
- enum TALER_ErrorCode ec)
-{
- struct TipAuthorizeState *tas;
-
- tas = GNUNET_new (struct TipAuthorizeState);
- tas->merchant_url = merchant_url;
- tas->justification = justification;
- tas->http_status = http_status;
- tas->expected_ec = ec;
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (amount,
- &tas->amount));
- {
- struct TALER_TESTING_Command cmd = {
- .label = label,
- .cls = tas,
- .run = &tip_authorize_run,
- .cleanup = &tip_authorize_cleanup,
- .traits = &tip_authorize_traits
- };
-
- return cmd;
- }
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_authorize_from_reserve_with_ec (
- const char *label,
- const char *merchant_url,
- const char *exchange_url,
- const char *reserve_reference,
- unsigned int http_status,
- const char *justification,
- const char *amount,
- enum TALER_ErrorCode ec)
-{
- struct TipAuthorizeState *tas;
-
- tas = GNUNET_new (struct TipAuthorizeState);
- tas->merchant_url = merchant_url;
- tas->justification = justification;
- tas->http_status = http_status;
- tas->expected_ec = ec;
- tas->reserve_reference = reserve_reference;
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (amount,
- &tas->amount));
- {
- struct TALER_TESTING_Command cmd = {
- .label = label,
- .cls = tas,
- .run = &tip_authorize_run,
- .cleanup = &tip_authorize_cleanup,
- .traits = &tip_authorize_traits
- };
-
- return cmd;
- }
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_authorize (const char *label,
- const char *merchant_url,
- const char *exchange_url,
- unsigned int http_status,
- const char *justification,
- const char *amount)
-{
- struct TipAuthorizeState *tas;
-
- tas = GNUNET_new (struct TipAuthorizeState);
- tas->merchant_url = merchant_url;
- tas->justification = justification;
- tas->http_status = http_status;
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (amount,
- &tas->amount));
- {
- struct TALER_TESTING_Command cmd = {
- .label = label,
- .cls = tas,
- .run = &tip_authorize_run,
- .cleanup = &tip_authorize_cleanup,
- .traits = &tip_authorize_traits
- };
-
- return cmd;
- }
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_authorize_from_reserve (const char *label,
- const char *merchant_url,
- const char *exchange_url,
- const char *reserve_reference,
- unsigned int http_status,
- const char *justification,
- const char *amount)
-{
- struct TipAuthorizeState *tas;
-
- tas = GNUNET_new (struct TipAuthorizeState);
- tas->merchant_url = merchant_url;
- tas->reserve_reference = reserve_reference;
- tas->justification = justification;
- tas->http_status = http_status;
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (amount,
- &tas->amount));
- {
- struct TALER_TESTING_Command cmd = {
- .label = label,
- .cls = tas,
- .run = &tip_authorize_run,
- .cleanup = &tip_authorize_cleanup,
- .traits = &tip_authorize_traits
- };
-
- return cmd;
- }
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_authorize_fake (const char *label)
-{
- struct TipAuthorizeState *tas;
-
- tas = GNUNET_new (struct TipAuthorizeState);
- {
- struct TALER_TESTING_Command cmd = {
- .label = label,
- .cls = tas,
- .run = &tip_authorize_fake_run,
- .cleanup = &tip_authorize_cleanup,
- .traits = &tip_authorize_traits
- };
-
- return cmd;
- }
-}
-
-
-/* end of testing_api_cmd_tip_authorize.c */
diff --git a/src/testing/testing_api_cmd_tip_pickup.c b/src/testing/testing_api_cmd_tip_pickup.c
deleted file mode 100644
index 76eac167..00000000
--- a/src/testing/testing_api_cmd_tip_pickup.c
+++ /dev/null
@@ -1,413 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as
- published by the Free Software Foundation; either version 3, or
- (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public
- License along with TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-
-/**
- * @file testing_api_cmd_tip_pickup.c
- * @brief command to test the tipping.
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-/**
- * State for a /tip-pickup CMD.
- */
-struct TipPickupState
-{
- /**
- * Merchant base URL.
- */
- const char *merchant_url;
-
- /**
- * Exchange base URL.
- */
- const char *exchange_url;
-
- /**
- * Expected HTTP response code.
- */
- unsigned int http_status;
-
- /**
- * Reference to a /tip/authorize CMD. This will be used to
- * get the tip id to make the request with.
- */
- const char *authorize_reference;
-
- /**
- * If set to non NULL, it references another pickup CMD
- * that will provide all the data which is needed to issue
- * the request (like planchet secrets, denomination keys..).
- */
- const char *replay_reference;
-
- /**
- * Handle to a on-going /tip/pickup request.
- */
- struct TALER_MERCHANT_TipPickupHandle *tpo;
-
- /**
- * The interpreter state.
- */
- struct TALER_TESTING_Interpreter *is;
-
- /**
- * An array of string-defined amounts that indicates
- * which denominations are going to be used to receive
- * tips.
- */
- const char **amounts;
-
- /**
- * The object version of the above @a amounts.
- */
- struct TALER_Amount *amounts_obj;
-
- /**
- * The sum of the the amounts above.
- */
- struct TALER_Amount total_amount;
-
- /**
- * The array of denomination keys, in the same order of @a
- * amounts.
- */
- const struct TALER_EXCHANGE_DenomPublicKey **dks;
-
- /**
- * The array of planchet secrets, in the same order of @a
- * amounts.
- */
- struct TALER_PlanchetMasterSecretP *psa;
-
- /**
- * Set (by the interpreter) to an array of @a num_coins
- * details on coins created from the (successful) tip operation.
- */
- struct TALER_EXCHANGE_PrivateCoinDetails *pcds;
-
- /**
- * How many coins are involved in the tipping operation.
- */
- uint32_t num_coins;
-
- /**
- * Expected Taler error code (NOTE: this is NOT the HTTP
- * response code).
- */
- enum TALER_ErrorCode expected_ec;
-};
-
-
-/**
- * Callback for a /tip-pickup request, it mainly checks if
- * values returned from the backend are as expected, and if so
- * (and if the status was 200 OK) proceede with the withdrawal.
- *
- * @param cls closure
- * @param pd details about the result of the operation
- */
-static void
-pickup_cb (void *cls,
- const struct TALER_MERCHANT_PickupDetails *pd)
-{
- struct TipPickupState *tps = cls;
-
- tps->tpo = NULL;
- if (pd->hr.http_status != tps->http_status)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u (%d) to command %s\n",
- pd->hr.http_status,
- (int) pd->hr.ec,
- TALER_TESTING_interpreter_get_current_label (tps->is));
- TALER_TESTING_FAIL (tps->is);
- }
-
- if (pd->hr.ec != tps->expected_ec)
- TALER_TESTING_FAIL (tps->is);
-
- /* Safe to go ahead: http status was expected. */
- if ( (MHD_HTTP_OK != pd->hr.http_status) ||
- (TALER_EC_NONE != pd->hr.ec) )
- {
- TALER_TESTING_interpreter_next (tps->is);
- return;
- }
- if (pd->details.success.num_sigs != tps->num_coins)
- TALER_TESTING_FAIL (tps->is);
- tps->pcds = GNUNET_new_array (tps->num_coins,
- struct TALER_EXCHANGE_PrivateCoinDetails);
- for (unsigned int i = 0; i<tps->num_coins; i++)
- {
- struct TALER_EXCHANGE_PrivateCoinDetails *pcd =
- &pd->details.success.pcds[i];
-
- tps->pcds[i] = *pcd;
- TALER_denom_sig_deep_copy (&tps->pcds[i].sig,
- &pcd->sig);
- }
- TALER_TESTING_interpreter_next (tps->is);
-}
-
-
-/**
- * Run a /tip-pickup CMD.
- *
- * @param cls closure
- * @param cmd the current /tip-pickup CMD.
- * @param is interpreter state.
- */
-static void
-tip_pickup_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct TipPickupState *tps = cls;
- unsigned int num_planchets;
- const struct TALER_TESTING_Command *replay_cmd;
- const struct TALER_TESTING_Command *authorize_cmd;
- const struct TALER_TipIdentifierP *tip_id;
-
- tps->is = is;
- tps->exchange_url = TALER_EXCHANGE_get_base_url (is->exchange);
- if (NULL == tps->replay_reference)
- {
- replay_cmd = NULL;
-
- /* Count planchets. */
- for (num_planchets = 0;
- NULL != tps->amounts[num_planchets];
- num_planchets++)
- ;
- }
- else
- {
- const uint32_t *np;
-
- if (NULL == /* looking for "parent" tip-pickup command */
- (replay_cmd
- = TALER_TESTING_interpreter_lookup_command (is,
- tps->replay_reference)) )
- TALER_TESTING_FAIL (is);
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_num_planchets (replay_cmd,
- &np))
- TALER_TESTING_FAIL (is);
- num_planchets = *np;
- }
-
- if (NULL ==
- (authorize_cmd
- = TALER_TESTING_interpreter_lookup_command (is,
- tps->authorize_reference)) )
- TALER_TESTING_FAIL (is);
-
- tps->num_coins = num_planchets;
- {
- struct TALER_MERCHANT_PlanchetData planchets[num_planchets];
-
- tps->psa = GNUNET_new_array (num_planchets,
- struct TALER_PlanchetMasterSecretP);
- tps->dks = GNUNET_new_array (num_planchets,
- const struct TALER_EXCHANGE_DenomPublicKey *);
- tps->amounts_obj = GNUNET_new_array (num_planchets,
- struct TALER_Amount);
- for (unsigned int i = 0; i<num_planchets; i++)
- {
- if (NULL == replay_cmd)
- {
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (tps->amounts[i],
- &tps->amounts_obj[i]));
- if (0 == i)
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (tps->amounts_obj[i].currency,
- &tps->total_amount));
-
- GNUNET_assert (0 <
- TALER_amount_add (&tps->total_amount,
- &tps->total_amount,
- &tps->amounts_obj[i]));
- tps->dks[i] = TALER_TESTING_find_pk (is->keys,
- &tps->amounts_obj[i],
- false);
- if (NULL == tps->dks[i])
- TALER_TESTING_FAIL (is);
- TALER_planchet_master_setup_random (&tps->psa[i]);
- }
- else
- {
- const struct TALER_PlanchetMasterSecretP *ps;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_denom_pub (replay_cmd,
- i,
- &tps->dks[i]))
- TALER_TESTING_FAIL (is);
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_planchet_secrets (replay_cmd,
- i,
- &ps))
- TALER_TESTING_FAIL (is);
- tps->psa[i] = *ps;
- }
- planchets[i].pk = tps->dks[i];
- planchets[i].ps = tps->psa[i];
- }
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_tip_id (authorize_cmd,
- &tip_id))
- TALER_TESTING_FAIL (is);
- tps->tpo = TALER_MERCHANT_tip_pickup (is->ctx,
- is->exchange,
- tps->merchant_url,
- tip_id,
- num_planchets,
- planchets,
- &pickup_cb,
- tps);
- GNUNET_assert (NULL != tps->tpo);
- }
-}
-
-
-/**
- * Free a /tip-pickup CMD state, and possibly cancel a
- * pending /tip-pickup request.
- *
- * @param cls closure.
- * @param cmd current CMD to be freed.
- */
-static void
-tip_pickup_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct TipPickupState *tps = cls;
-
- GNUNET_free (tps->amounts_obj);
- GNUNET_free (tps->dks);
- GNUNET_free (tps->psa);
- if (NULL != tps->pcds)
- {
- for (unsigned int i = 0; i<tps->num_coins; i++)
- TALER_denom_sig_free (&tps->pcds[i].sig);
- GNUNET_free (tps->pcds);
- }
- if (NULL != tps->tpo)
- {
- TALER_LOG_WARNING ("Tip-pickup operation did not complete\n");
- TALER_MERCHANT_tip_pickup_cancel (tps->tpo);
- }
- GNUNET_free (tps);
-}
-
-
-static enum GNUNET_GenericReturnValue
-tip_pickup_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
-{
- struct TipPickupState *tps = cls;
-
- if (index >= tps->num_coins)
- return GNUNET_SYSERR;
- {
- struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_planchet_secrets (index,
- &tps->psa[index]),
- TALER_TESTING_make_trait_coin_priv (index,
- &tps->pcds[index].coin_priv),
- TALER_TESTING_make_trait_denom_pub (index,
- tps->dks[index]),
- TALER_TESTING_make_trait_denom_sig (index,
- &tps->pcds[index].sig),
- TALER_TESTING_make_trait_amounts (index,
- &tps->amounts_obj[index]),
- TALER_TESTING_make_trait_amount (&tps->total_amount),
- TALER_TESTING_make_trait_num_planchets (&tps->num_coins),
- TALER_TESTING_make_trait_exchange_url (&tps->exchange_url),
- TALER_TESTING_trait_end ()
- };
-
- return TALER_TESTING_get_trait (traits,
- ret,
- trait,
- index);
- }
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_pickup (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- const char *authorize_reference,
- const char **amounts)
-{
- struct TipPickupState *tps;
-
- tps = GNUNET_new (struct TipPickupState);
- tps->merchant_url = merchant_url;
- tps->authorize_reference = authorize_reference;
- tps->amounts = amounts;
- tps->http_status = http_status;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = tps,
- .label = label,
- .run = &tip_pickup_run,
- .cleanup = &tip_pickup_cleanup,
- .traits = &tip_pickup_traits
- };
-
- return cmd;
- }
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_pickup_with_ec (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- const char *authorize_reference,
- const char **amounts,
- enum TALER_ErrorCode ec)
-{
- struct TALER_TESTING_Command cmd;
- struct TipPickupState *tps;
-
- cmd = TALER_TESTING_cmd_tip_pickup (label,
- merchant_url,
- http_status,
- authorize_reference,
- amounts);
- tps = cmd.cls;
- tps->expected_ec = ec;
- return cmd;
-}
-
-
-/* end of testing_api_cmd_tip_pickup.c */
diff --git a/src/testing/testing_api_cmd_tme.c b/src/testing/testing_api_cmd_tme.c
new file mode 100644
index 00000000..b84e1df5
--- /dev/null
+++ b/src/testing/testing_api_cmd_tme.c
@@ -0,0 +1,161 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_tme.c
+ * @brief run the taler-merchant-exchange command
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_signatures.h"
+#include "taler/taler_testing_lib.h"
+
+
+/**
+ * State for a "taler-merchant-exchange" CMD.
+ */
+struct MerchantExchangeState
+{
+
+ /**
+ * Process for taler-merchant-exchange
+ */
+ struct GNUNET_OS_Process *merchant_exchange_proc;
+
+ /**
+ * Configuration file used by the program.
+ */
+ const char *config_filename;
+};
+
+
+/**
+ * Run the command; use the `taler-merchant-exchange' program.
+ *
+ * @param cls closure.
+ * @param cmd command currently being executed.
+ * @param is interpreter state.
+ */
+static void
+tme_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct MerchantExchangeState *ws = cls;
+
+ (void) cmd;
+ ws->merchant_exchange_proc
+ = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-merchant-exchange",
+ "taler-merchant-exchange",
+ "-c", ws->config_filename,
+ "-t", /* exit when done */
+ "-L", "DEBUG",
+ NULL);
+ if (NULL == ws->merchant_exchange_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "exchange" CMD, and possibly
+ * kills its process if it did not terminate regularly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+tme_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct MerchantExchangeState *ws = cls;
+
+ (void) cmd;
+ if (NULL != ws->merchant_exchange_proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (ws->merchant_exchange_proc,
+ SIGKILL));
+ GNUNET_OS_process_wait (ws->merchant_exchange_proc);
+ GNUNET_OS_process_destroy (ws->merchant_exchange_proc);
+ ws->merchant_exchange_proc = NULL;
+ }
+ GNUNET_free (ws);
+}
+
+
+/**
+ * Offer "tme" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+tme_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct MerchantExchangeState *ws = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (&ws->merchant_exchange_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_run_tme (const char *label,
+ const char *config_filename)
+{
+ struct MerchantExchangeState *ws;
+
+ ws = GNUNET_new (struct MerchantExchangeState);
+ ws->config_filename = config_filename;
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ws,
+ .label = label,
+ .run = &tme_run,
+ .cleanup = &tme_cleanup,
+ .traits = &tme_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_tme.c */
diff --git a/src/testing/testing_api_cmd_wallet_get_order.c b/src/testing/testing_api_cmd_wallet_get_order.c
index ac448499..d55ff0a7 100644
--- a/src/testing/testing_api_cmd_wallet_get_order.c
+++ b/src/testing/testing_api_cmd_wallet_get_order.c
@@ -59,6 +59,18 @@ struct WalletGetOrderState
const char *order_reference;
/**
+ * Reference to a command that created a paid
+ * equivalent order that we expect to be referred
+ * to during repurchase detection, or NULL.
+ */
+ const char *repurchase_order_ref;
+
+ /**
+ * Session Id the order needs to be bound to.
+ */
+ const char *session_id;
+
+ /**
* Whether the order was paid or not.
*/
bool paid;
@@ -103,14 +115,14 @@ wallet_get_order_cb (
switch (hr->http_status)
{
case MHD_HTTP_OK:
- if (gos->refunded != owgr->details.success.refunded)
+ if (gos->refunded != owgr->details.ok.refunded)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Order refunded does not match\n");
TALER_TESTING_interpreter_fail (gos->is);
return;
}
- if (gos->refund_pending != owgr->details.success.refund_pending)
+ if (gos->refund_pending != owgr->details.ok.refund_pending)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Order refund pending does not match\n");
@@ -122,9 +134,34 @@ wallet_get_order_cb (
{
struct TALER_MERCHANT_PayUriData pud;
const struct TALER_TESTING_Command *order_cmd;
- const char **order_id;
+ const char *order_id;
const struct TALER_ClaimTokenP *claim_token;
+ if (NULL != gos->repurchase_order_ref)
+ {
+ const struct TALER_TESTING_Command *rep_cmd;
+ const char *rep_id;
+ const char *ri;
+
+ rep_cmd = TALER_TESTING_interpreter_lookup_command (
+ gos->is,
+ gos->repurchase_order_ref);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_order_id (rep_cmd,
+ &rep_id))
+ {
+ TALER_TESTING_FAIL (gos->is);
+ }
+ ri = owgr->details.payment_required.already_paid_order_id;
+ if ( (NULL == ri) ||
+ (0 !=
+ strcmp (ri,
+ rep_id)) )
+ {
+ TALER_TESTING_FAIL (gos->is);
+ }
+ }
+
if (GNUNET_OK !=
TALER_MERCHANT_parse_pay_uri (
owgr->details.payment_required.taler_pay_uri,
@@ -158,29 +195,13 @@ wallet_get_order_cb (
}
{
- char *port;
char *host;
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (gos->is->cfg,
- "merchant",
- "PORT",
- &port))
- {
- /* How did we get here without a configured port? */
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (gos->is);
- TALER_MERCHANT_parse_pay_uri_free (&pud);
- return;
- }
- GNUNET_asprintf (&host,
- "localhost:%s",
- port);
- GNUNET_free (port);
+ host = TALER_MERCHANT_TESTING_extract_host (gos->merchant_url);
if ((0 != strcmp (host,
pud.merchant_host)) ||
(NULL != pud.merchant_prefix_path) ||
- (0 != strcmp (*order_id,
+ (0 != strcmp (order_id,
pud.order_id)) ||
(NULL != pud.ssid))
{
@@ -235,7 +256,7 @@ wallet_get_order_run (void *cls,
{
struct WalletGetOrderState *gos = cls;
const struct TALER_TESTING_Command *order_cmd;
- const char **order_id;
+ const char *order_id;
const struct TALER_PrivateContractHashP *h_contract;
order_cmd = TALER_TESTING_interpreter_lookup_command (
@@ -253,16 +274,17 @@ wallet_get_order_run (void *cls,
TALER_TESTING_FAIL (is);
gos->is = is;
- gos->ogh = TALER_MERCHANT_wallet_order_get (is->ctx,
- gos->merchant_url,
- *order_id,
- h_contract,
- GNUNET_TIME_UNIT_ZERO,
- NULL,
- NULL,
- false,
- &wallet_get_order_cb,
- gos);
+ gos->ogh = TALER_MERCHANT_wallet_order_get (
+ TALER_TESTING_interpreter_get_context (is),
+ gos->merchant_url,
+ order_id,
+ h_contract,
+ GNUNET_TIME_UNIT_ZERO,
+ gos->session_id,
+ NULL,
+ false,
+ &wallet_get_order_cb,
+ gos);
}
@@ -281,7 +303,7 @@ wallet_get_order_cleanup (void *cls,
if (NULL != gos->ogh)
{
- TALER_LOG_WARNING ("Get tip operation did not complete\n");
+ TALER_LOG_WARNING ("Get order operation did not complete\n");
TALER_MERCHANT_wallet_order_get_cancel (gos->ogh);
}
GNUNET_free (gos);
@@ -289,13 +311,48 @@ wallet_get_order_cleanup (void *cls,
struct TALER_TESTING_Command
-TALER_TESTING_cmd_wallet_get_order (const char *label,
- const char *merchant_url,
- const char *order_reference,
- bool paid,
- bool refunded,
- bool refund_pending,
- unsigned int http_status)
+TALER_TESTING_cmd_wallet_get_order (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ bool paid,
+ bool refunded,
+ bool refund_pending,
+ unsigned int http_status)
+{
+ struct WalletGetOrderState *gos;
+
+ gos = GNUNET_new (struct WalletGetOrderState);
+ gos->merchant_url = merchant_url;
+ gos->order_reference = order_reference;
+ gos->http_status = http_status;
+ gos->paid = paid;
+ gos->refunded = refunded;
+ gos->refund_pending = refund_pending;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = gos,
+ .label = label,
+ .run = &wallet_get_order_run,
+ .cleanup = &wallet_get_order_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wallet_get_order2 (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ const char *session_id,
+ bool paid,
+ bool refunded,
+ bool refund_pending,
+ const char *repurchase_order_ref,
+ unsigned int http_status)
{
struct WalletGetOrderState *gos;
@@ -304,8 +361,10 @@ TALER_TESTING_cmd_wallet_get_order (const char *label,
gos->order_reference = order_reference;
gos->http_status = http_status;
gos->paid = paid;
+ gos->session_id = session_id;
gos->refunded = refunded;
gos->refund_pending = refund_pending;
+ gos->repurchase_order_ref = repurchase_order_ref;
{
struct TALER_TESTING_Command cmd = {
.cls = gos,
@@ -554,10 +613,10 @@ wallet_poll_order_cb (
{
case MHD_HTTP_OK:
pos->paid = true;
- pos->refunded = owgr->details.success.refunded;
- pos->refund_pending = owgr->details.success.refund_pending;
- if (owgr->details.success.refunded)
- pos->refund_available = owgr->details.success.refund_amount;
+ pos->refunded = owgr->details.ok.refunded;
+ pos->refund_pending = owgr->details.ok.refund_pending;
+ if (owgr->details.ok.refunded)
+ pos->refund_available = owgr->details.ok.refund_amount;
break;
case MHD_HTTP_PAYMENT_REQUIRED:
if (NULL != owgr->details.payment_required.already_paid_order_id)
@@ -593,7 +652,7 @@ wallet_poll_order_start_run (void *cls,
{
struct WalletPollOrderStartState *pos = cls;
const struct TALER_TESTING_Command *order_cmd;
- const char **order_id;
+ const char *order_id;
const struct TALER_PrivateContractHashP *h_contract;
order_cmd = TALER_TESTING_interpreter_lookup_command (
@@ -615,18 +674,19 @@ wallet_poll_order_start_run (void *cls,
= GNUNET_TIME_absolute_add (GNUNET_TIME_relative_to_absolute (pos->timeout),
GNUNET_TIME_UNIT_SECONDS);
pos->is = is;
- pos->ogh = TALER_MERCHANT_wallet_order_get (is->ctx,
- pos->merchant_url,
- *order_id,
- h_contract,
- pos->timeout,
- pos->session_id,
- pos->wait_for_refund
- ? &pos->refund_threshold
- : NULL,
- false, /* await_refund_obtained */
- &wallet_poll_order_cb,
- pos);
+ pos->ogh = TALER_MERCHANT_wallet_order_get (
+ TALER_TESTING_interpreter_get_context (is),
+ pos->merchant_url,
+ order_id,
+ h_contract,
+ pos->timeout,
+ pos->session_id,
+ pos->wait_for_refund
+ ? &pos->refund_threshold
+ : NULL,
+ false, /* await_refund_obtained */
+ &wallet_poll_order_cb,
+ pos);
GNUNET_assert (NULL != pos->ogh);
/* We CONTINUE to run the interpreter while the long-polled command
completes asynchronously! */
diff --git a/src/testing/testing_api_cmd_wallet_get_tip.c b/src/testing/testing_api_cmd_wallet_get_tip.c
deleted file mode 100644
index 0c19a5b9..00000000
--- a/src/testing/testing_api_cmd_wallet_get_tip.c
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as
- published by the Free Software Foundation; either version 3, or
- (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public
- License along with TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file testing_api_cmd_wallet_get_tip.c
- * @brief command to test the tipping.
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-/**
- * State for a GET /tips/$TIP_ID CMD.
- */
-struct WalletTipGetState
-{
-
- /**
- * The merchant base URL.
- */
- const char *merchant_url;
-
- /**
- * Expected HTTP response code for this CMD.
- */
- unsigned int http_status;
-
- /**
- * Whether to compare amounts or not.
- */
- bool cmp_amounts;
-
- /**
- * The expected amount remaining.
- */
- struct TALER_Amount amount_remaining;
-
- /**
- * The handle to the current GET /tips/$TIP_ID request.
- */
- struct TALER_MERCHANT_TipWalletGetHandle *tgh;
-
- /**
- * The interpreter state.
- */
- struct TALER_TESTING_Interpreter *is;
-
- /**
- * Reference to a command that created a tip.
- */
- const char *tip_reference;
-};
-
-
-/**
- * Callback to process a GET /tips/$TIP_ID request, it mainly
- * checks that what the backend returned matches the command's
- * expectations.
- *
- * @param cls closure
- * @param hr HTTP response
- * @param reserve_expiration when the tip reserve will expire
- * @param exchange_url from where to pick up the tip
- * @param amount_remaining how much is remaining
- */
-static void
-wallet_tip_get_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- struct GNUNET_TIME_Timestamp reserve_expiration,
- const char *exchange_url,
- const struct TALER_Amount *amount_remaining)
-{
- struct WalletTipGetState *gts = cls;
- const struct TALER_TESTING_Command *tip_cmd;
-
- tip_cmd = TALER_TESTING_interpreter_lookup_command (
- gts->is,
- gts->tip_reference);
-
- gts->tgh = NULL;
- if (gts->http_status != hr->http_status)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
- TALER_TESTING_interpreter_get_current_label (gts->is));
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- switch (hr->http_status)
- {
- case MHD_HTTP_OK:
- if (gts->cmp_amounts)
- {
- if ((GNUNET_OK != TALER_amount_cmp_currency (&gts->amount_remaining,
- amount_remaining)) ||
- (0 != TALER_amount_cmp (&gts->amount_remaining,
- amount_remaining)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Amount remaining on tip does not match\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- }
- {
- const struct GNUNET_TIME_Timestamp *expiration;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_timestamp (tip_cmd,
- 0,
- &expiration))
- TALER_TESTING_interpreter_fail (gts->is);
- if (GNUNET_TIME_timestamp_cmp (*expiration,
- !=,
- reserve_expiration))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Tip expiration does not match\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- }
- break;
- default:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unhandled HTTP status.\n");
- }
- TALER_TESTING_interpreter_next (gts->is);
-}
-
-
-/**
- * Run the "GET tip" CMD.
- *
- * @param cls closure.
- * @param cmd command being run now.
- * @param is interpreter state.
- */
-static void
-wallet_get_tip_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct WalletTipGetState *tgs = cls;
- const struct TALER_TESTING_Command *tip_cmd;
- const struct TALER_TipIdentifierP *tip_id;
-
- tip_cmd = TALER_TESTING_interpreter_lookup_command (is,
- tgs->tip_reference);
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_tip_id (tip_cmd,
- &tip_id))
- TALER_TESTING_FAIL (is);
-
- tgs->is = is;
- tgs->tgh = TALER_MERCHANT_wallet_tip_get (is->ctx,
- tgs->merchant_url,
- tip_id,
- &wallet_tip_get_cb,
- tgs);
-}
-
-
-/**
- * Free the state of a "GET tip" CMD, and possibly
- * cancel a pending operation thereof.
- *
- * @param cls closure.
- * @param cmd command being run.
- */
-static void
-wallet_get_tip_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct WalletTipGetState *tgs = cls;
-
- if (NULL != tgs->tgh)
- {
- TALER_LOG_WARNING ("Get tip operation did not complete\n");
- TALER_MERCHANT_wallet_tip_get_cancel (tgs->tgh);
- }
- GNUNET_free (tgs);
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_wallet_get_tip (const char *label,
- const char *merchant_url,
- const char *tip_reference,
- unsigned int http_status)
-{
- struct WalletTipGetState *tgs;
-
- tgs = GNUNET_new (struct WalletTipGetState);
- tgs->merchant_url = merchant_url;
- tgs->tip_reference = tip_reference;
- tgs->http_status = http_status;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = tgs,
- .label = label,
- .run = &wallet_get_tip_run,
- .cleanup = &wallet_get_tip_cleanup
- };
-
- return cmd;
- }
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_wallet_get_tip2 (const char *label,
- const char *merchant_url,
- const char *tip_reference,
- const char *amount_remaining,
- unsigned int http_status)
-{
- struct WalletTipGetState *tgs;
-
- tgs = GNUNET_new (struct WalletTipGetState);
- tgs->merchant_url = merchant_url;
- tgs->tip_reference = tip_reference;
- tgs->cmp_amounts = true;
- GNUNET_assert (GNUNET_OK == TALER_string_to_amount (amount_remaining,
- &tgs->amount_remaining));
- tgs->http_status = http_status;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = tgs,
- .label = label,
- .run = &wallet_get_tip_run,
- .cleanup = &wallet_get_tip_cleanup
- };
-
- return cmd;
- }
-}
-
-
-/* end of testing_api_cmd_wallet_get_tip.c */
diff --git a/src/testing/testing_api_cmd_wallet_post_orders_refund.c b/src/testing/testing_api_cmd_wallet_post_orders_refund.c
index b9e7981f..617d33fb 100644
--- a/src/testing/testing_api_cmd_wallet_post_orders_refund.c
+++ b/src/testing/testing_api_cmd_wallet_post_orders_refund.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -76,44 +76,39 @@ struct WalletRefundState
* if the HTTP response code is the one expected.
*
* @param cls closure
- * @param hr HTTP response
- * @param refund_amount refund amount
- * @param merchant_pub public key of the merchant giving the refund
- * @param refunds the given refunds
- * @param refunds_length how many refunds were given
+ * @param wrr response
*/
static void
refund_cb (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_Amount *refund_amount,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- struct TALER_MERCHANT_RefundDetail refunds[],
- unsigned int refunds_length)
+ const struct TALER_MERCHANT_WalletRefundResponse *wrr)
{
struct WalletRefundState *wrs = cls;
wrs->orh = NULL;
- if (wrs->http_code != hr->http_status)
+ if (wrs->http_code != wrr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Expected status %u, got %u(%d) for refund increase\n",
wrs->http_code,
- hr->http_status,
- (int) hr->ec);
+ wrr->hr.http_status,
+ (int) wrr->hr.ec);
TALER_TESTING_FAIL (wrs->is);
}
- switch (hr->http_status)
+ switch (wrr->hr.http_status)
{
case MHD_HTTP_OK:
{
struct TALER_Amount refunded_total;
- if (refunds_length > 0)
+ if (wrr->details.ok.refunds_length > 0)
GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (refunds[0].refund_amount.currency,
- &refunded_total));
- for (unsigned int i = 0; i < refunds_length; ++i)
+ TALER_amount_set_zero (
+ wrr->details.ok.refunds[0].refund_amount.currency,
+ &refunded_total));
+ for (unsigned int i = 0; i < wrr->details.ok.refunds_length; ++i)
{
+ const struct TALER_MERCHANT_RefundDetail *refund
+ = &wrr->details.ok.refunds[wrr->details.ok.refunds_length - 1 - i];
const struct TALER_TESTING_Command *refund_cmd;
const struct TALER_Amount *expected_amount;
@@ -133,13 +128,12 @@ refund_cb (
/* The most recent refunds are returned first */
GNUNET_assert (0 <= TALER_amount_add (&refunded_total,
&refunded_total,
- &refunds[refunds_length - 1
- - i].refund_amount));
- if ((GNUNET_OK !=
- TALER_amount_cmp_currency (expected_amount,
- &refunded_total)) ||
- (0 != TALER_amount_cmp (expected_amount,
- &refunded_total)))
+ &refund->refund_amount));
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (expected_amount,
+ &refunded_total)) ||
+ (0 != TALER_amount_cmp (expected_amount,
+ &refunded_total)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Refund amounts do not match\n");
@@ -150,7 +144,6 @@ refund_cb (
}
break;
default:
-
break;
}
TALER_TESTING_interpreter_next (wrs->is);
@@ -224,7 +217,7 @@ obtain_refunds_run (void *cls,
wrs->is = is;
wrs->orh = TALER_MERCHANT_wallet_post_order_refund (
- is->ctx,
+ TALER_TESTING_interpreter_get_context (is),
wrs->merchant_url,
order_id,
h_contract_terms,
diff --git a/src/testing/testing_api_cmd_webhook.c b/src/testing/testing_api_cmd_webhook.c
new file mode 100644
index 00000000..8c5df5b9
--- /dev/null
+++ b/src/testing/testing_api_cmd_webhook.c
@@ -0,0 +1,161 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_webhook.c
+ * @brief run the taler-merchant-webhook command
+ * @author Priscilla HUANG
+ */
+#include "platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_signatures.h"
+#include "taler/taler_testing_lib.h"
+
+
+/**
+ * State for a "webhook" CMD.
+ */
+struct WebhookState
+{
+
+ /**
+ * Process for the webhook.
+ */
+ struct GNUNET_OS_Process *webhook_proc;
+
+ /**
+ * Configuration file used by the webhook.
+ */
+ const char *config_filename;
+};
+
+
+/**
+ * Run the command; use the `taler-merchant-webhook' program.
+ *
+ * @param cls closure.
+ * @param cmd command currently being executed.
+ * @param is interpreter state.
+ */
+static void
+webhook_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct WebhookState *ws = cls;
+
+ (void) cmd;
+ ws->webhook_proc
+ = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-merchant-webhook",
+ "taler-merchant-webhook",
+ "-c", ws->config_filename,
+ "-t", /* exit when done */
+ "-L", "DEBUG",
+ NULL);
+ if (NULL == ws->webhook_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "webhook" CMD, and possibly
+ * kills its process if it did not terminate regularly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+webhook_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct WebhookState *ws = cls;
+
+ (void) cmd;
+ if (NULL != ws->webhook_proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (ws->webhook_proc,
+ SIGKILL));
+ GNUNET_OS_process_wait (ws->webhook_proc);
+ GNUNET_OS_process_destroy (ws->webhook_proc);
+ ws->webhook_proc = NULL;
+ }
+ GNUNET_free (ws);
+}
+
+
+/**
+ * Offer "webhook" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+webhook_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct WebhookState *ws = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (&ws->webhook_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_webhook (const char *label,
+ const char *config_filename)
+{
+ struct WebhookState *ws;
+
+ ws = GNUNET_new (struct WebhookState);
+ ws->config_filename = config_filename;
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ws,
+ .label = label,
+ .run = &webhook_run,
+ .cleanup = &webhook_cleanup,
+ .traits = &webhook_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_webhook.c */
diff --git a/src/testing/testing_api_helpers.c b/src/testing/testing_api_helpers.c
index ea7f2dea..dbc7a6eb 100644
--- a/src/testing/testing_api_helpers.c
+++ b/src/testing/testing_api_helpers.c
@@ -1,4 +1,3 @@
-
/*
This file is part of TALER
Copyright (C) 2014-2018 Taler Systems SA
@@ -22,153 +21,51 @@
* @file testing_api_helpers.c
* @brief helper functions for test library.
* @author Christian Grothoff
- * @author Marcello Stanisci
*/
-
#include "platform.h"
#include <taler/taler_exchange_service.h>
#include <taler/taler_testing_lib.h>
#include "taler_merchant_testing_lib.h"
-struct GNUNET_OS_Process *
-TALER_TESTING_run_merchant (const char *config_filename,
- const char *merchant_url)
-{
- struct GNUNET_OS_Process *merchant_proc;
- unsigned int iter;
- char *wget_cmd;
-
- merchant_proc
- = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-merchant-httpd",
- "taler-merchant-httpd",
- "--log=INFO",
- "-c", config_filename,
- NULL);
- if (NULL == merchant_proc)
- MERCHANT_FAIL ();
-
- GNUNET_asprintf (&wget_cmd,
- "wget -q -t 1 -T 1"
- " --header='Authorization: ApiKey sandbox'"
- " %s"
- " -o /dev/null -O /dev/null",
- merchant_url);
-
- /* give child time to start and bind against the socket */
- fprintf (stderr,
- "Waiting for `taler-merchant-httpd' to be ready\n");
- iter = 0;
- do
- {
- if (10 == iter)
- {
- fprintf (stderr,
- "Failed to launch"
- " `taler-merchant-httpd' (or `wget')\n");
- GNUNET_OS_process_kill (merchant_proc,
- SIGTERM);
- GNUNET_OS_process_wait (merchant_proc);
- GNUNET_OS_process_destroy (merchant_proc);
- MERCHANT_FAIL ();
- }
- fprintf (stderr, ".\n");
- sleep (1);
- iter++;
- }
- while (0 != system (wget_cmd));
- GNUNET_free (wget_cmd);
- fprintf (stderr, "\n");
-
- return merchant_proc;
-}
-
-
char *
-TALER_TESTING_prepare_merchant (const char *config_filename)
+TALER_MERCHANT_TESTING_extract_host (const char *merchant_url)
{
- struct GNUNET_CONFIGURATION_Handle *cfg;
- unsigned long long port;
- struct GNUNET_OS_Process *dbinit_proc;
- enum GNUNET_OS_ProcessStatusType type;
- unsigned long code;
- char *base_url;
+ const char *hosts = strchr (merchant_url, '/');
+ const char *hend;
+ const char *pstr;
+ const char *pend;
+ char *host;
- cfg = GNUNET_CONFIGURATION_create ();
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_load (cfg,
- config_filename))
- MERCHANT_FAIL ();
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_number (cfg,
- "merchant",
- "PORT",
- &port))
+ if (NULL == hosts)
{
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "merchant",
- "PORT");
- GNUNET_CONFIGURATION_destroy (cfg);
- MERCHANT_FAIL ();
+ GNUNET_break (0);
+ return NULL;
}
-
- GNUNET_CONFIGURATION_destroy (cfg);
-
- if (GNUNET_OK !=
- GNUNET_NETWORK_test_port_free (IPPROTO_TCP,
- (uint16_t) port))
+ if (hosts[1] != '/')
{
- fprintf (stderr,
- "Required port %llu not available, skipping.\n",
- port);
- MERCHANT_FAIL ();
+ GNUNET_break (0);
+ return NULL;
}
-
- /* DB preparation */
- if (NULL == (dbinit_proc = GNUNET_OS_start_process (
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-merchant-dbinit",
- "taler-merchant-dbinit",
- "-c", config_filename,
- "-r",
- NULL)))
+ hosts += 2;
+ pstr = strchr (hosts, ':');
+ if (NULL == pstr)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to run taler-merchant-dbinit. Check your PATH.\n");
- MERCHANT_FAIL ();
+ hend = &hosts[strlen (hosts)];
+ pstr = "80";
+ pend = &pstr[2];
}
-
- if (GNUNET_SYSERR ==
- GNUNET_OS_process_wait_status (dbinit_proc,
- &type,
- &code))
+ else
{
- GNUNET_OS_process_destroy (dbinit_proc);
- MERCHANT_FAIL ();
+ hend = pstr;
+ pstr++;
+ pend = strchr (pstr, '/');
}
- if ( (type == GNUNET_OS_PROCESS_EXITED) &&
- (0 != code) )
- {
- fprintf (stderr,
- "Failed to setup database\n");
- MERCHANT_FAIL ();
- }
- if ( (type != GNUNET_OS_PROCESS_EXITED) ||
- (0 != code) )
- {
- fprintf (stderr,
- "Unexpected error running"
- " `taler-merchant-dbinit'!\n");
- MERCHANT_FAIL ();
- }
- GNUNET_OS_process_destroy (dbinit_proc);
-
-
- GNUNET_asprintf (&base_url,
- "http://localhost:%llu/",
- port);
- return base_url;
+ GNUNET_asprintf (&host,
+ "%.*s:%.*s",
+ (int) (hend - hosts),
+ hosts,
+ (int) (pend - pstr),
+ pstr);
+ return host;
}