summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/backend/.gitignore4
-rw-r--r--src/backend/Makefile.am177
-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.c1304
-rw-r--r--src/backend/taler-merchant-httpd.c937
-rw-r--r--src/backend/taler-merchant-httpd.h133
-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.c89
-rw-r--r--src/backend/taler-merchant-httpd_contract.c47
-rw-r--r--src/backend/taler-merchant-httpd_contract.h592
-rw-r--r--src/backend/taler-merchant-httpd_exchanges.c2045
-rw-r--r--src/backend/taler-merchant-httpd_exchanges.h154
-rw-r--r--src/backend/taler-merchant-httpd_get-orders-ID.c1967
-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.c290
-rw-r--r--src/backend/taler-merchant-httpd_get-tips-ID.h67
-rw-r--r--src/backend/taler-merchant-httpd_helper.c817
-rw-r--r--src/backend/taler-merchant-httpd_helper.h160
-rw-r--r--src/backend/taler-merchant-httpd_mhd.c1
-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.c8
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-paid.c45
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-pay.c3029
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-refund.c86
-rw-r--r--src/backend/taler-merchant-httpd_post-tips-ID-pickup.c998
-rw-r--r--src/backend/taler-merchant-httpd_post-tips-ID-pickup.h49
-rw-r--r--src/backend/taler-merchant-httpd_post-using-templates.c301
-rw-r--r--src/backend/taler-merchant-httpd_post-using-templates.h (renamed from src/backend/taler-merchant-httpd_private-get-reserves-ID.h)21
-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.c14
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-orders-ID.c63
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.c78
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.h41
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-products-ID.c1
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-templates-ID.c78
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-templates-ID.h41
-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-delete-transfers-ID.c9
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-webhooks-ID.c (renamed from src/backend/taler-merchant-httpd_private-delete-reserves-ID.c)56
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-webhooks-ID.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.h41
-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.c416
-rw-r--r--src/backend/taler-merchant-httpd_private-get-instances-ID.c17
-rw-r--r--src/backend/taler-merchant-httpd_private-get-instances.c8
-rw-r--r--src/backend/taler-merchant-httpd_private-get-orders-ID.c2058
-rw-r--r--src/backend/taler-merchant-httpd_private-get-orders.c297
-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-tips-ID.h)24
-rw-r--r--src/backend/taler-merchant-httpd_private-get-products.c28
-rw-r--r--src/backend/taler-merchant-httpd_private-get-reserves-ID.c227
-rw-r--r--src/backend/taler-merchant-httpd_private-get-reserves.c145
-rw-r--r--src/backend/taler-merchant-httpd_private-get-templates-ID.c83
-rw-r--r--src/backend/taler-merchant-httpd_private-get-templates-ID.h42
-rw-r--r--src/backend/taler-merchant-httpd_private-get-templates.c79
-rw-r--r--src/backend/taler-merchant-httpd_private-get-templates.h41
-rw-r--r--src/backend/taler-merchant-httpd_private-get-tips-ID.c155
-rw-r--r--src/backend/taler-merchant-httpd_private-get-tips.c156
-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.c64
-rw-r--r--src/backend/taler-merchant-httpd_private-get-webhooks-ID.c92
-rw-r--r--src/backend/taler-merchant-httpd_private-get-webhooks-ID.h41
-rw-r--r--src/backend/taler-merchant-httpd_private-get-webhooks.c80
-rw-r--r--src/backend/taler-merchant-httpd_private-get-webhooks.h41
-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.c236
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-orders-ID-forget.c22
-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.c243
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-templates-ID.h43
-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.c188
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-webhooks-ID.h43
-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.c9
-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.c216
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders.c3219
-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.h44
-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.c396
-rw-r--r--src/backend/taler-merchant-httpd_private-post-templates.c284
-rw-r--r--src/backend/taler-merchant-httpd_private-post-templates.h43
-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.c1370
-rw-r--r--src/backend/taler-merchant-httpd_private-post-transfers.h9
-rw-r--r--src/backend/taler-merchant-httpd_private-post-webhooks.c215
-rw-r--r--src/backend/taler-merchant-httpd_private-post-webhooks.h (renamed from src/backend/taler-merchant-httpd_private-post-reserves.h)23
-rw-r--r--src/backend/taler-merchant-httpd_qr.c6
-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.c255
-rw-r--r--src/backend/taler-merchant-httpd_spa.h10
-rw-r--r--src/backend/taler-merchant-httpd_statics.c18
-rw-r--r--src/backend/taler-merchant-httpd_statics.h4
-rw-r--r--src/backend/taler-merchant-httpd_templating.c452
-rw-r--r--src/backend/taler-merchant-httpd_templating.h56
-rw-r--r--src/backend/taler-merchant-webhook.c591
-rw-r--r--src/backend/taler-merchant-wirewatch.c756
-rw-r--r--src/backend/test.conf172
-rw-r--r--src/backenddb/.gitignore1
-rw-r--r--src/backenddb/Makefile.am136
-rw-r--r--src/backenddb/drop.sql (renamed from src/backenddb/drop0002.sql)15
-rw-r--r--src/backenddb/drop0001.sql68
-rw-r--r--src/backenddb/merchant-0001.sql523
-rw-r--r--src/backenddb/merchant-0002.sql159
-rw-r--r--src/backenddb/merchant-0003.sql30
-rw-r--r--src/backenddb/merchant-0004.sql30
-rw-r--r--src/backenddb/merchant-0005.sql36
-rw-r--r--src/backenddb/merchantdb-postgres.conf2
-rw-r--r--src/backenddb/merchantdb_helper.c47
-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_token_family_key.c97
-rw-r--r--src/backenddb/pg_insert_token_family_key.h46
-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.c328
-rw-r--r--src/backenddb/pg_lookup_deposits_by_contract_and_coin.h48
-rw-r--r--src/backenddb/pg_lookup_deposits_by_order.c166
-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.h46
-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_token_family_key.c161
-rw-r--r--src/backenddb/pg_lookup_token_family_key.h50
-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.c9643
-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.c3287
-rw-r--r--src/backenddb/versioning.sql (renamed from src/backenddb/merchant-0000.sql)5
-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.am3
-rw-r--r--src/include/gettext.h71
-rw-r--r--src/include/platform.h253
-rw-r--r--src/include/taler_merchant_bank_lib.h247
-rw-r--r--src/include/taler_merchant_service.h3641
-rw-r--r--src/include/taler_merchant_testing_lib.h1131
-rw-r--r--src/include/taler_merchantdb_lib.h38
-rw-r--r--src/include/taler_merchantdb_plugin.h1811
-rw-r--r--src/lib/Makefile.am47
-rw-r--r--src/lib/merchant_api_common.c167
-rw-r--r--src/lib/merchant_api_common.h61
-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_order.c10
-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.c184
-rw-r--r--src/lib/merchant_api_delete_webhook.c184
-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.c187
-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.c190
-rw-r--r--src/lib/merchant_api_get_orders.c246
-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.c317
-rw-r--r--src/lib/merchant_api_get_reserves.c282
-rw-r--r--src/lib/merchant_api_get_template.c201
-rw-r--r--src/lib/merchant_api_get_templates.c247
-rw-r--r--src/lib/merchant_api_get_tips.c307
-rw-r--r--src/lib/merchant_api_get_tokenfamily.c210
-rw-r--r--src/lib/merchant_api_get_transfers.c74
-rw-r--r--src/lib/merchant_api_get_webhook.c221
-rw-r--r--src/lib/merchant_api_get_webhooks.c246
-rw-r--r--src/lib/merchant_api_lock_product.c1
-rw-r--r--src/lib/merchant_api_merchant_get_order.c483
-rw-r--r--src/lib/merchant_api_merchant_get_tip.c324
-rw-r--r--src/lib/merchant_api_patch_account.c254
-rw-r--r--src/lib/merchant_api_patch_instance.c38
-rw-r--r--src/lib/merchant_api_patch_order_forget.c22
-rw-r--r--src/lib/merchant_api_patch_otp_device.c252
-rw-r--r--src/lib/merchant_api_patch_product.c1
-rw-r--r--src/lib/merchant_api_patch_template.c249
-rw-r--r--src/lib/merchant_api_patch_webhook.c254
-rw-r--r--src/lib/merchant_api_post_account.c250
-rw-r--r--src/lib/merchant_api_post_instances.c50
-rw-r--r--src/lib/merchant_api_post_order_abort.c121
-rw-r--r--src/lib/merchant_api_post_order_claim.c66
-rw-r--r--src/lib/merchant_api_post_order_paid.c63
-rw-r--r--src/lib/merchant_api_post_order_pay.c285
-rw-r--r--src/lib/merchant_api_post_order_refund.c66
-rw-r--r--src/lib/merchant_api_post_orders.c173
-rw-r--r--src/lib/merchant_api_post_otp_devices.c237
-rw-r--r--src/lib/merchant_api_post_products.c59
-rw-r--r--src/lib/merchant_api_post_reserves.c246
-rw-r--r--src/lib/merchant_api_post_templates.c279
-rw-r--r--src/lib/merchant_api_post_tokenfamilies.c246
-rw-r--r--src/lib/merchant_api_post_transfers.c157
-rw-r--r--src/lib/merchant_api_post_using_templates.c177
-rw-r--r--src/lib/merchant_api_post_webhooks.c240
-rw-r--r--src/lib/merchant_api_tip_authorize.c375
-rw-r--r--src/lib/merchant_api_tip_pickup.c446
-rw-r--r--src/lib/merchant_api_tip_pickup2.c350
-rw-r--r--src/lib/merchant_api_wallet_get_order.c137
-rw-r--r--src/lib/merchant_api_wallet_get_template.c195
-rw-r--r--src/lib/merchant_api_wallet_get_tip.c226
-rw-r--r--src/lib/merchant_api_wallet_post_order_refund.c288
-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.c197
-rw-r--r--src/merchant-tools/taler-merchant-setup-reserve.c147
-rw-r--r--src/mustach/.gitignore1
-rw-r--r--src/mustach/AUTHORS23
-rw-r--r--src/mustach/LICENSE-2.0.txt202
-rw-r--r--src/mustach/Makefile.am33
-rw-r--r--src/mustach/ORIGIN9
-rw-r--r--src/mustach/README.md214
-rw-r--r--src/mustach/meson.build12
-rw-r--r--src/mustach/mustach-jansson.c417
-rw-r--r--src/mustach/mustach-jansson.h82
-rw-r--r--src/mustach/mustach-tool.c155
-rw-r--r--src/mustach/mustach.c472
-rw-r--r--src/mustach/mustach.h241
-rwxr-xr-xsrc/mustach/run-original-tests.sh10
-rw-r--r--src/mustach/test1/.gitignore2
-rw-r--r--src/mustach/test1/Makefile11
-rw-r--r--src/mustach/test1/json23
-rw-r--r--src/mustach/test1/must43
-rw-r--r--src/mustach/test1/resu.ref49
-rw-r--r--src/mustach/test2/.gitignore2
-rw-r--r--src/mustach/test2/Makefile10
-rw-r--r--src/mustach/test2/json9
-rw-r--r--src/mustach/test2/must17
-rw-r--r--src/mustach/test2/resu.ref22
-rw-r--r--src/mustach/test3/.gitignore2
-rw-r--r--src/mustach/test3/Makefile11
-rw-r--r--src/mustach/test3/json7
-rw-r--r--src/mustach/test3/must15
-rw-r--r--src/mustach/test3/resu.ref15
-rw-r--r--src/mustach/test4/.gitignore2
-rw-r--r--src/mustach/test4/Makefile11
-rw-r--r--src/mustach/test4/json13
-rw-r--r--src/mustach/test4/must58
-rw-r--r--src/mustach/test4/resu.ref100
-rw-r--r--src/mustach/test5/.gitignore2
-rw-r--r--src/mustach/test5/Makefile11
-rw-r--r--src/mustach/test5/json23
-rw-r--r--src/mustach/test5/must23
-rw-r--r--src/mustach/test5/must214
-rw-r--r--src/mustach/test5/must2.mustache1
-rw-r--r--src/mustach/test5/must3.mustache17
-rw-r--r--src/mustach/test5/resu.ref60
-rw-r--r--src/mustach/test5/special1
-rw-r--r--src/mustach/test5/special.mustache1
-rw-r--r--src/mustach/test6/.gitignore4
-rw-r--r--src/mustach/test6/Makefile15
-rw-r--r--src/mustach/test6/json23
-rw-r--r--src/mustach/test6/must43
-rw-r--r--src/mustach/test6/resu.ref147
-rw-r--r--src/mustach/test6/test-custom-write.c145
-rw-r--r--src/mustach/test_mustach_jansson.c163
-rw-r--r--src/testing/.gitignore4
-rw-r--r--src/testing/Makefile.am64
-rwxr-xr-xsrc/testing/initialize_taler_system.sh249
-rwxr-xr-xsrc/testing/setup.sh67
-rwxr-xr-xsrc/testing/test-merchant-walletharness.sh19
-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.c537
-rw-r--r--src/testing/test_kyc_api.conf132
-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.c1582
-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.c196
-rwxr-xr-xsrc/testing/test_merchant_instance_auth.sh238
-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.sh676
-rwxr-xr-xsrc/testing/test_merchant_product_creation.sh253
-rwxr-xr-xsrc/testing/test_merchant_reserve_creation.sh172
-rwxr-xr-xsrc/testing/test_merchant_transfer_tracking.sh612
-rwxr-xr-xsrc/testing/test_merchant_wirewatch.sh376
-rw-r--r--src/testing/test_template.conf74
-rw-r--r--src/testing/testing_api_cmd_abort_order.c56
-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.c12
-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.c179
-rw-r--r--src/testing/testing_api_cmd_delete_transfer.c11
-rw-r--r--src/testing/testing_api_cmd_delete_webhook.c181
-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.c378
-rw-r--r--src/testing/testing_api_cmd_get_instances.c125
-rw-r--r--src/testing/testing_api_cmd_get_orders.c64
-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.c333
-rw-r--r--src/testing/testing_api_cmd_get_reserves.c273
-rw-r--r--src/testing/testing_api_cmd_get_template.c242
-rw-r--r--src/testing/testing_api_cmd_get_templates.c238
-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.c285
-rw-r--r--src/testing/testing_api_cmd_get_webhooks.c237
-rw-r--r--src/testing/testing_api_cmd_instance_auth.c15
-rw-r--r--src/testing/testing_api_cmd_kyc_get.c101
-rw-r--r--src/testing/testing_api_cmd_lock_product.c20
-rw-r--r--src/testing/testing_api_cmd_merchant_get_order.c378
-rw-r--r--src/testing/testing_api_cmd_merchant_get_tip.c383
-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.c243
-rw-r--r--src/testing/testing_api_cmd_patch_webhook.c259
-rw-r--r--src/testing/testing_api_cmd_pay_order.c123
-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.c338
-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.c66
-rw-r--r--src/testing/testing_api_cmd_post_reserves.c280
-rw-r--r--src/testing/testing_api_cmd_post_templates.c271
-rw-r--r--src/testing/testing_api_cmd_post_tokenfamilies.c272
-rw-r--r--src/testing/testing_api_cmd_post_transfers.c328
-rw-r--r--src/testing/testing_api_cmd_post_using_templates.c607
-rw-r--r--src/testing/testing_api_cmd_post_webhooks.c280
-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.c258
-rw-r--r--src/testing/testing_api_cmd_wallet_get_tip.c265
-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.c160
983 files changed, 66130 insertions, 40895 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index cb071d95..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 mustach 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 073fff5e..dbc7cde8 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -16,34 +16,49 @@ EXTRA_DIST = \
$(pkgcfg_DATA)
bin_PROGRAMS = \
- taler-merchant-httpd
+ taler-merchant-depositcheck \
+ taler-merchant-exchange \
+ taler-merchant-httpd \
+ 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_contract.c taler-merchant-httpd_contract.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_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 \
@@ -58,36 +73,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-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-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 \
@@ -98,23 +143,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_templating.c \
- taler-merchant-httpd_templating.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 \
@@ -123,9 +168,79 @@ taler_merchant_httpd_LDADD = \
-lgnunetcurl \
-lgnunetjson \
-lgnunetutil \
- $(top_builddir)/src/mustach/libmustach.a \
@QR_LIBS@ \
$(XLIB)
taler_merchant_httpd_CFLAGS = \
@QR_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 = \
+ $(top_builddir)/src/backenddb/libtalermerchantdb.la \
+ -ltalertemplating \
+ -ltalermhd \
+ -ltalerjson \
+ -ltalerutil \
+ -ltalerpq \
+ -ljansson \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lcurl \
+ $(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..7945cb50
--- /dev/null
+++ b/src/backend/taler-merchant-exchange.c
@@ -0,0 +1,1304 @@
+/*
+ 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;
+
+/**
+ * Set to true if we ever encountered any problem.
+ */
+static bool found_problem;
+
+/**
+ * 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;
+
+ if (failed)
+ found_problem = true;
+ 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;
+ GNUNET_assert (NULL == task);
+ 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;
+ }
+ if (NULL != task)
+ {
+ GNUNET_SCHEDULER_cancel (task);
+ task = 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... */
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Checking coin with value %s\n",
+ TALER_amount2s (amount_with_fee));
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (amount_with_fee,
+ &ttd->coin_value)) ||
+ (0 != TALER_amount_cmp (amount_with_fee,
+ &ttd->coin_value)) )
+ {
+ /* Disagreement between the exchange and us about how much this
+ coin is worth! */
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Disagreement about coin value %s\n",
+ TALER_amount2s (amount_with_fee));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Exchange gave it a value of %s\n",
+ TALER_amount2s (&ttd->coin_value));
+ 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;
+ }
+ if ( (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);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Expected fee is %s\n",
+ TALER_amount2s (&ttd->coin_fee));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Fee claimed by exchange is %s\n",
+ TALER_amount2s (deposit_fee));
+ 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);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Wire transfer total value was %s\n",
+ TALER_amount2s (&w->total));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Exchange claimed total value to be %s\n",
+ TALER_amount2s (&td->total_amount));
+ 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);
+ }
+ GNUNET_assert (NULL == task);
+ 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;
+ if ( (found_problem) &&
+ (0 == global_ret) )
+ global_ret = 7;
+ 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 d4d8bc11..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
@@ -20,23 +20,31 @@
* @author Marcello Stanisci
* @author Christian Grothoff
* @author Florian Dold
+ * @author Priscilla HUANG
*/
#include "platform.h"
#include <taler/taler_dbevents.h>
#include <taler/taler_bank_service.h>
#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-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-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"
@@ -44,33 +52,45 @@
#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-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-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"
-#include "taler-merchant-httpd_templating.h"
+
/**
* Fixme: document.
@@ -94,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;
@@ -123,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;
@@ -133,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.
@@ -148,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,
@@ -212,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;
@@ -225,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);
@@ -241,15 +367,7 @@ TMH_instance_decref (struct TMH_MerchantInstance *mi)
}
-/**
- * Callback that frees an instances removing
- * it from the global hashmap.
- *
- * @param cls closure, NULL
- * @param key current key
- * @param value a `struct TMH_MerchantInstance`
- */
-int
+enum GNUNET_GenericReturnValue
TMH_instance_free_cb (void *cls,
const struct GNUNET_HashCode *key,
void *value)
@@ -277,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 ();
{
@@ -293,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,
@@ -314,6 +428,17 @@ do_shutdown (void *cls)
GNUNET_CONTAINER_multihashmap_destroy (TMH_by_id_map);
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;
+ }
}
@@ -339,6 +464,7 @@ handle_mhd_completion_callback (void *cls,
{
struct TMH_HandlerContext *hc = *con_cls;
+ (void) cls;
if (NULL == hc)
return;
GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
@@ -372,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;
}
@@ -446,11 +573,76 @@ handle_server_options (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
+ (void) rh;
+ (void) hc;
return TALER_MHD_reply_cors_preflight (connection);
}
/**
+ * 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,
+ struct TMH_HandlerContext *hc)
+{
+ const char *text = "Redirecting to /webui/";
+ struct MHD_Response *response;
+ char *dst;
+
+ response = MHD_create_response_from_buffer (strlen (text),
+ (void *) text,
+ MHD_RESPMEM_PERSISTENT);
+ if (NULL == response)
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ TALER_MHD_add_global_headers (response);
+ GNUNET_break (MHD_YES ==
+ 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,
+ dst))
+ {
+ GNUNET_break (0);
+ MHD_destroy_response (response);
+ GNUNET_free (dst);
+ return MHD_NO;
+ }
+ GNUNET_free (dst);
+
+ {
+ MHD_RESULT ret;
+
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_FOUND,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+ }
+}
+
+
+/**
* Extract the token from authorization header value @a auth.
*
* @param auth pointer to authorization header value,
@@ -463,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,
@@ -544,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
@@ -578,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
@@ -659,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",
@@ -670,7 +888,7 @@ url_handler (void *cls,
.handler = &TMH_private_get_instances_default_ID_kyc,
},
{
- NULL
+ .url_prefix = NULL
}
};
@@ -828,111 +1046,276 @@ 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: */
+ /* GET /transfers: */
+ {
+ .url_prefix = "/transfers",
+ .method = MHD_HTTP_METHOD_GET,
+ .allow_deleted_instance = true,
+ .handler = &TMH_private_get_transfers
+ },
+ /* POST /otp-devices: */
{
- .url_prefix = "/tips",
+ .url_prefix = "/otp-devices",
.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
+ .handler = &TMH_private_post_otp_devices
},
- /* GET /tips: */
+ /* GET /otp-devices: */
{
- .url_prefix = "/tips",
- .allow_deleted_instance = true,
+ .url_prefix = "/otp-devices",
.method = MHD_HTTP_METHOD_GET,
- .handler = &TMH_private_get_tips
+ .handler = &TMH_private_get_otp_devices
},
- /* GET /tips/$ID: */
+ /* GET /otp-devices/$ID/: */
{
- .url_prefix = "/tips/",
+ .url_prefix = "/otp-devices/",
.method = MHD_HTTP_METHOD_GET,
- .allow_deleted_instance = true,
.have_id_segment = true,
- .handler = &TMH_private_get_tips_ID
+ .handler = &TMH_private_get_otp_devices_ID
},
- /* GET /reserves: */
+ /* DELETE /otp-devices/$ID/: */
{
- .url_prefix = "/reserves",
- .allow_deleted_instance = true,
+ .url_prefix = "/otp-devices/",
+ .method = MHD_HTTP_METHOD_DELETE,
+ .have_id_segment = true,
+ .handler = &TMH_private_delete_otp_devices_ID
+ },
+ /* PATCH /otp-devices/$ID/: */
+ {
+ .url_prefix = "/otp-devices/",
+ .method = MHD_HTTP_METHOD_PATCH,
+ .have_id_segment = true,
+ .handler = &TMH_private_patch_otp_devices_ID
+ },
+ /* POST /templates: */
+ {
+ .url_prefix = "/templates",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler = &TMH_private_post_templates,
+ /* allow template data of up to 8 MB, that should be plenty;
+ note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
+ would require further changes to the allocation logic
+ in the code... */
+ .max_upload = 1024 * 1024 * 8
+ },
+ /* GET /templates: */
+ {
+ .url_prefix = "/templates",
.method = MHD_HTTP_METHOD_GET,
- .handler = &TMH_private_get_reserves
+ .handler = &TMH_private_get_templates
},
- /* GET /reserves/$ID: */
+ /* GET /templates/$ID/: */
{
- .url_prefix = "/reserves/",
+ .url_prefix = "/templates/",
+ .method = MHD_HTTP_METHOD_GET,
+ .have_id_segment = true,
+ .allow_deleted_instance = true,
+ .handler = &TMH_private_get_templates_ID
+ },
+ /* DELETE /templates/$ID/: */
+ {
+ .url_prefix = "/templates/",
+ .method = MHD_HTTP_METHOD_DELETE,
+ .have_id_segment = true,
.allow_deleted_instance = true,
+ .handler = &TMH_private_delete_templates_ID
+ },
+ /* PATCH /templates/$ID/: */
+ {
+ .url_prefix = "/templates/",
+ .method = MHD_HTTP_METHOD_PATCH,
.have_id_segment = true,
+ .allow_deleted_instance = true,
+ .handler = &TMH_private_patch_templates_ID,
+ /* allow template data of up to 8 MB, that should be plenty;
+ note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
+ would require further changes to the allocation logic
+ in the code... */
+ .max_upload = 1024 * 1024 * 8
+ },
+ /* GET /webhooks: */
+ {
+ .url_prefix = "/webhooks",
.method = MHD_HTTP_METHOD_GET,
- .handler = &TMH_private_get_reserves_ID
+ .handler = &TMH_private_get_webhooks
},
- /* POST /transfers: */
+ /* POST /webhooks: */
{
- .url_prefix = "/transfers",
+ .url_prefix = "/webhooks",
.method = MHD_HTTP_METHOD_POST,
+ .handler = &TMH_private_post_webhooks,
+ /* allow webhook data of up to 8 MB, that should be plenty;
+ note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
+ would require further changes to the allocation logic
+ in the code... */
+ .max_upload = 1024 * 1024 * 8
+ },
+ /* GET /webhooks/$ID/: */
+ {
+ .url_prefix = "/webhooks/",
+ .method = MHD_HTTP_METHOD_GET,
+ .have_id_segment = true,
.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
+ .handler = &TMH_private_get_webhooks_ID
},
- /* DELETE /transfers/$ID: */
+ /* DELETE /webhooks/$ID/: */
{
- .url_prefix = "/transfers/",
+ .url_prefix = "/webhooks/",
.method = MHD_HTTP_METHOD_DELETE,
+ .have_id_segment = true,
.allow_deleted_instance = true,
- .handler = &TMH_private_delete_transfers_ID,
+ .handler = &TMH_private_delete_webhooks_ID
+ },
+ /* PATCH /webhooks/$ID/: */
+ {
+ .url_prefix = "/webhooks/",
+ .method = MHD_HTTP_METHOD_PATCH,
.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
+ .allow_deleted_instance = true,
+ .handler = &TMH_private_patch_webhooks_ID,
+ /* allow webhook data of up to 8 MB, that should be plenty;
+ note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
+ would require further changes to the allocation logic
+ in the code... */
+ .max_upload = 1024 * 1024 * 8
},
- /* GET /transfers: */
+ /* POST /accounts: */
{
- .url_prefix = "/transfers",
+ .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,
- .allow_deleted_instance = true,
- .handler = &TMH_private_get_transfers
+ .handler = &TMH_private_get_accounts
},
+ /* GET /accounts/$H_WIRE: */
{
- NULL
+ .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
+ },
+ {
+ .url_prefix = "/webui/",
+ .method = MHD_HTTP_METHOD_GET,
+ .mime_type = "text/html",
+ .skip_instance = true,
+ .have_id_segment = true,
.handler = &TMH_return_spa,
.response_code = MHD_HTTP_OK
},
@@ -1022,32 +1405,27 @@ 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
+ .handler = &TMH_return_static
},
- /* POST /tips/$ID/pickup: */
+ /* GET /templates/$ID/: */
{
- .url_prefix = "/tips/",
- .method = MHD_HTTP_METHOD_POST,
+ .url_prefix = "/templates/",
+ .method = MHD_HTTP_METHOD_GET,
.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_get_templates_ID
},
- /* GET /static/ *: */
+ /* POST /templates/$ID: */
{
- .url_prefix = "/static/",
- .method = MHD_HTTP_METHOD_GET,
+ .url_prefix = "/templates/",
+ .method = MHD_HTTP_METHOD_POST,
.have_id_segment = true,
- .handler = &TMH_return_static
+ .handler = &TMH_post_using_templates_ID,
+ .max_upload = 1024 * 1024
},
{
.url_prefix = "*",
@@ -1055,7 +1433,7 @@ url_handler (void *cls,
.handler = &handle_server_options
},
{
- NULL
+ .url_prefix = NULL
}
};
struct TMH_HandlerContext *hc = *con_cls;
@@ -1064,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 */
@@ -1112,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;
@@ -1148,6 +1522,7 @@ url_handler (void *cls,
MHD_HTTP_METHOD_HEAD))
method = MHD_HTTP_METHOD_GET; /* MHD will deal with the rest */
+
/* Find out the merchant backend instance for the request.
* If there is an instance, remove the instance specification
* from the beginning of the request URL. */
@@ -1168,7 +1543,56 @@ 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",
+ instance_id)) )
+ hc->instance = TMH_lookup_instance (NULL);
+ if ( (0 == strcmp ("default",
+ instance_id)) &&
+ (NULL != TMH_default_auth) &&
+ (NULL != hc->instance) )
+ {
+ /* Override default instance access control */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Command-line override of access control\n");
+ TMH_compute_auth (TMH_default_auth,
+ &hc->instance->auth.auth_salt,
+ &hc->instance->auth.auth_hash);
+ hc->instance->auth_override = true;
+ GNUNET_free (TMH_default_auth);
+ }
GNUNET_free (instance_id);
if (NULL == slash)
url = "";
@@ -1184,8 +1608,8 @@ url_handler (void *cls,
(NULL != hc->instance) )
{
/* Override default instance access control */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Overriding access control\n");
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Command-line override of access control\n");
TMH_compute_auth (TMH_default_auth,
&hc->instance->auth.auth_salt,
&hc->instance->auth.auth_hash);
@@ -1218,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
{
@@ -1252,7 +1680,7 @@ url_handler (void *cls,
{
prefix_strlen = slash - url + 1; /* includes both '/'-es if present! */
infix_url = slash + 1;
- slash = strchr (&infix_url[1], '/');
+ slash = strchr (infix_url, '/');
if (NULL == slash)
{
/* the infix was the rest */
@@ -1366,10 +1794,15 @@ url_handler (void *cls,
return ret;
}
if (NULL == hc->rh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Endpoint `%s' not known\n",
+ hc->url);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
hc->url);
+ }
}
}
/* At this point, we must have found a handler */
@@ -1379,6 +1812,9 @@ url_handler (void *cls,
if ( (NULL == hc->instance) &&
(! hc->rh->skip_instance) )
{
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "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,
@@ -1424,15 +1860,46 @@ url_handler (void *cls,
&hc->instance->auth.auth_salt,
&hc->instance->auth.auth_hash));
else /* Are the credentials provided OK for CLI override? */
- auth_ok |= ( (use_default) &&
- (NULL != TMH_default_auth) &&
- (NULL != auth) &&
- (! auth_malformed) &&
- (0 == strcmp (auth,
- TMH_default_auth)) );
- if (! auth_ok)
- {
- if (auth_malformed)
+ auth_ok |= (use_default &&
+ (NULL != TMH_default_auth) &&
+ (NULL != auth) &&
+ (! auth_malformed) &&
+ (0 == strcmp (auth,
+ TMH_default_auth)) );
+ if (auth_ok)
+ {
+ 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,
@@ -1448,18 +1915,28 @@ url_handler (void *cls,
if ( (NULL == hc->instance) &&
(! hc->rh->skip_instance) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Instance for URL `%s' not known\n",
+ url);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
url);
+ }
if ( (NULL != hc->instance) && /* make static analysis happy */
(! hc->rh->skip_instance) &&
(hc->instance->deleted) &&
(! hc->rh->allow_deleted_instance) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Instance `%s' was deleted\n",
+ hc->instance->settings.id);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_MERCHANT_GENERIC_INSTANCE_DELETED,
hc->instance->settings.id);
+ }
/* parse request body */
hc->has_body = ( (0 == strcasecmp (method,
MHD_HTTP_METHOD_POST)) ||
@@ -1468,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 */
@@ -1508,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.
*
@@ -1516,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);
@@ -1558,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));
@@ -1595,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]) ) )
@@ -1638,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;
}
@@ -1646,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
*/
@@ -1690,7 +2152,6 @@ run (void *cls,
int fh;
enum TALER_MHD_GlobalOptions go;
int elen;
- int alen;
const char *tok;
(void) cls;
@@ -1705,14 +2166,12 @@ run (void *cls,
RFC_8959_PREFIX,
strlen (RFC_8959_PREFIX))) )
{
- char *tmp;
-
- GNUNET_asprintf (&tmp,
- "%s%s",
- RFC_8959_PREFIX,
- TMH_default_auth);
- GNUNET_free (TMH_default_auth);
- TMH_default_auth = tmp;
+ 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;
}
cfg = config;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -1722,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",
@@ -1744,12 +2233,28 @@ 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",
"FORCE_AUDIT"))
TMH_force_audit = GNUNET_YES;
- TMH_templating_init ();
+ TALER_TEMPLATING_init ("merchant");
if (GNUNET_OK !=
TMH_spa_init ())
{
@@ -1758,47 +2263,43 @@ run (void *cls,
GNUNET_SCHEDULER_shutdown ();
return;
}
- TMH_statics_init ();
- elen = TMH_EXCHANGES_init (config);
- if (GNUNET_SYSERR == elen)
+ /* /static/ is currently not used */
+ /* (void) TMH_statics_init (); */
+ 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)),
@@ -1814,8 +2315,6 @@ run (void *cls,
load_instances (NULL,
NULL,
0);
- /* start watching reserves */
- TMH_RESERVES_init ();
fh = TALER_MHD_bind (cfg,
"merchant",
&port);
@@ -1835,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,
@@ -1847,7 +2348,7 @@ run (void *cls,
GNUNET_SCHEDULER_shutdown ();
return;
}
- result = GNUNET_OK;
+ global_ret = EXIT_SUCCESS;
TALER_MHD_daemon_start (mhd);
}
}
@@ -1891,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 8d009b26..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.
@@ -51,7 +52,7 @@ struct TMH_WireMethod
struct TMH_WireMethod *prev;
/**
- * Which wire method / payment target identifier is @e j_wire using?
+ * Which wire method / payment target identifier is @e payto_uri using?
*/
char *wire_method;
@@ -66,11 +67,22 @@ struct TMH_WireMethod
struct TALER_WireSaltP wire_salt;
/**
- * Hash of our wire format details as given in #j_wire.
+ * Hash of our wire format details as given in @e payto_uri
*/
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
@@ -381,7 +448,7 @@ struct TMH_RequestHandler
/**
* URL prefix the handler is for, includes the '/',
- * so "/orders" or "/products". Does *not* include
+ * so "/orders", "/templates", "/webhooks" or "/products". Does *not* include
* "/private", that is controlled by the array in which
* the handler is defined. Must not contain any
* '/' except for the leading '/'.
@@ -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) 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;
@@ -626,11 +733,12 @@ extern char *TMH_default_auth;
* Callback that frees an instances removing
* it from the global hashmap.
*
- * @param cls closure, NULL
- * @param key current key
+ * @param cls closure, pass NULL
+ * @param key current key (ignored)
* @param value a `struct TMH_MerchantInstance`
+ * @return #GNUNET_YES (always)
*/
-int
+enum GNUNET_GenericReturnValue
TMH_instance_free_cb (void *cls,
const struct GNUNET_HashCode *key,
void *value);
@@ -656,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..d1340249 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 "14:1:10"
+
+
+/**
+ * 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
@@ -51,18 +82,72 @@ MH_handler_config (struct TMH_RequestHandler *rh,
struct TMH_HandlerContext *hc)
{
static struct MHD_Response *response;
+ static struct GNUNET_TIME_Absolute a;
(void) rh;
(void) hc;
+ if ( (GNUNET_TIME_absolute_is_past (a)) &&
+ (NULL != response) )
+ {
+ MHD_destroy_response (response);
+ response = NULL;
+ }
if (NULL == response)
{
+ json_t *specs = json_object ();
+ json_t *exchanges = json_array ();
+ struct GNUNET_TIME_Timestamp km;
+ char dat[128];
+
+ GNUNET_assert (NULL != specs);
+ GNUNET_assert (NULL != exchanges);
+ a = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS);
+ /* Round up to next full day to ensure the expiration
+ time does not become a fingerprint! */
+ a = GNUNET_TIME_absolute_round_down (a,
+ GNUNET_TIME_UNIT_DAYS);
+ a = GNUNET_TIME_absolute_add (a,
+ GNUNET_TIME_UNIT_DAYS);
+ /* => /config response stays at most 48h in caches! */
+ km = GNUNET_TIME_absolute_to_timestamp (a);
+ TALER_MHD_get_date_string (km.abs_time,
+ dat);
+ 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:taler-merchant:c-reference"),
GNUNET_JSON_pack_string ("name",
"taler-merchant"),
GNUNET_JSON_pack_string ("version",
MERCHANT_PROTOCOL_VERSION));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_EXPIRES,
+ dat));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "public,max-age=21600")); /* 6h */
}
return MHD_queue_response (connection,
MHD_HTTP_OK,
diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c
new file mode 100644
index 00000000..38c82e70
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_contract.c
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ (C) 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
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received 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_contract.c
+ * @brief shared logic for contract terms handling
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <jansson.h>
+#include "taler-merchant-httpd_contract.h"
+
+enum TALER_MerchantContractInputType
+TMH_string_to_contract_input_type (const char *str)
+{
+ /* For now, only 'token' is the only supported option. */
+ if (0 == strcmp("token", str))
+ {
+ return TALER_MCIT_TOKEN;
+ }
+
+ return TALER_MCIT_INVALID;
+}
+
+enum TALER_MerchantContractOutputType
+TMH_string_to_contract_output_type (const char *str)
+{
+ /* For now, only 'token' is the only supported option. */
+ if (0 == strcmp("token", str))
+ {
+ return TALER_MCOT_TOKEN;
+ }
+
+ return TALER_MCOT_INVALID;
+}
diff --git a/src/backend/taler-merchant-httpd_contract.h b/src/backend/taler-merchant-httpd_contract.h
new file mode 100644
index 00000000..b1e3938c
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_contract.h
@@ -0,0 +1,592 @@
+/*
+ This file is part of TALER
+ (C) 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
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received 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_contract.h
+ * @brief shared logic for contract terms handling
+ * @author Christian Blättler
+ */
+#include "taler-merchant-httpd.h"
+#include <gnunet/gnunet_time_lib.h>
+#include <jansson.h>
+
+
+/**
+ * Possible versions of the contract terms.
+ */
+enum TALER_MerchantContractVersion
+{
+
+ /**
+ * Version 0
+ */
+ TALER_MCV_V0 = 0,
+
+ /**
+ * Version 1
+ */
+ TALER_MCV_V1 = 1
+};
+
+/**
+ * Possible token kinds.
+ */
+enum TALER_MerchantContractTokenKind
+{
+
+ /**
+ * Subscription token kind
+ */
+ TALER_MCTK_SUBSCRIPTION = 0,
+
+ /**
+ * Discount token kind
+ */
+ TALER_MCTK_DISCOUNT = 1
+};
+
+/**
+ * Possible input types for the contract terms.
+ */
+enum TALER_MerchantContractInputType
+{
+
+ /**
+ * Input type invalid
+ */
+ TALER_MCIT_INVALID = 0,
+
+ /**
+ * Input type coin
+ */
+ TALER_MCIT_COIN = 1,
+
+ /**
+ * Input type token
+ */
+ TALER_MCIT_TOKEN = 2
+};
+
+/**
+ * Contract input (part of the v1 contract terms).
+ */
+struct TALER_MerchantContractInput
+{
+ /**
+ * Type of the input.
+ */
+ enum TALER_MerchantContractInputType type;
+
+ union
+ {
+ /**
+ * Coin-based input (ration). (Future work, only here for reference)
+ */
+ // struct
+ // {
+ // /**
+ // * Price to be paid.
+ // */
+ // struct TALER_Amount price;
+
+ // /**
+ // * Base URL of the ration authority.
+ // */
+ // const char *ration_authority_url;
+ // } coin;
+
+ /**
+ * Token-based input.
+ */
+ struct
+ {
+ /**
+ * Slug of the token family to be used.
+ */
+ const char *token_family_slug;
+
+ /**
+ * Start time of the validity period of the token. Base on this timestamp
+ * the wallet can find the correct key for this token in token_authorities.
+ */
+ struct GNUNET_TIME_Timestamp valid_after;
+
+ /**
+ * Number of tokens of this type required. Defaults to one if the
+ * field is not provided.
+ */
+ unsigned int count;
+ } token;
+ } details;
+};
+
+/**
+ * Possible output types for the contract terms.
+ */
+enum TALER_MerchantContractOutputType
+{
+
+ /**
+ * Invalid output type
+ */
+ TALER_MCOT_INVALID = 0,
+
+ /**
+ * Output type coin
+ */
+ TALER_MCOT_COIN = 1,
+
+ /**
+ * Output type token
+ */
+ TALER_MCOT_TOKEN = 2,
+
+ /**
+ * Output type tax-receipt
+ */
+ TALER_MCOT_TAX_RECEIPT = 3
+
+};
+
+/**
+ * Contract output (part of the v1 contract terms).
+ */
+struct TALER_MerchantContractOutput
+{
+ /**
+ * Type of the output.
+ */
+ enum TALER_MerchantContractOutputType type;
+
+ union
+ {
+ /**
+ * Coin-based output.
+ */
+ struct {
+ /**
+ * Coins that will be yielded. This excludes any applicable withdraw fees.
+ */
+ struct TALER_Amount brutto_yield;
+
+ /**
+ * Base URL of the exchange that will issue the coins.
+ */
+ const char *exchange_url;
+ } coin;
+
+ /**
+ * Tax-receipt output.
+ */
+ struct
+ {
+ /**
+ * Base URL of the donation authority that will issue the tax receipt.
+ */
+ const char *donau_url;
+ } tax_receipt;
+
+ /**
+ * Token-based output.
+ */
+ struct
+ {
+ /**
+ * Slug of the token family to be issued.
+ */
+ const char *token_family_slug;
+
+ /**
+ * Start time of the validity period of the token. Base on this timestamp
+ * the wallet can find the correct key for this token in token_authorities.
+ */
+ struct GNUNET_TIME_Timestamp valid_after;
+
+ /**
+ * Number of tokens of this type required. Defaults to one if the
+ * field is not provided.
+ */
+ unsigned int count;
+ } token;
+ } details;
+};
+
+/**
+ * Contract choice (part of the v1 contract terms).
+ */
+struct TALER_MerchantContractChoice
+{
+ /**
+ * List of inputs the wallet must provision (all of them) to satisfy the
+ * conditions for the contract.
+ */
+ struct TALER_MerchantContractInput *inputs;
+
+ /**
+ * Length of the @e inputs array.
+ */
+ unsigned int inputs_len;
+
+ /**
+ * List of outputs the merchant promises to yield (all of them) once
+ * the contract is paid.
+ */
+ struct TALER_MerchantContractOutput *outputs;
+
+ /**
+ * Length of the @e outputs array.
+ */
+ unsigned int outputs_len;
+};
+
+struct TALER_MerchantContractLimits
+{
+ /**
+ * Currency these limits are for.
+ */
+ char currency[TALER_CURRENCY_LEN];
+
+ /**
+ * The hash of the merchant instance's wire details.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * Wire transfer method identifier for the wire method associated with ``h_wire``.
+ * The wallet may only select exchanges via a matching auditor if the
+ * exchange also supports this wire method.
+ * The wire transfer fees must be added based on this wire transfer method.
+ */
+ char *wire_method;
+
+ /**
+ * Maximum total deposit fee accepted by the merchant for this contract.
+ */
+ struct TALER_Amount max_fee;
+};
+
+struct TALER_MerchantContractTokenAuthority
+{
+ /**
+ * Label of the token authority.
+ */
+ const char *label;
+
+ /**
+ * Human-readable description of the semantics of the tokens issued by
+ * this authority.
+ */
+ char *description;
+
+ /**
+ * Map from IETF BCP 47 language tags to localized description.
+ */
+ json_t *description_i18n;
+
+ /**
+ * Public key used to validate tokens signed by this authority.
+ */
+ struct TALER_TokenFamilyPublicKey *pub;
+
+ /**
+ * When will tokens signed by this key expire?
+ */
+ struct GNUNET_TIME_Timestamp token_expiration;
+
+ /**
+ * Must a wallet understand this token type to process contracts that
+ * consume or yield it?
+ */
+ bool critical;
+
+ /**
+ * Kind of the token.
+ */
+ enum TALER_MerchantContractTokenKind kind;
+
+ /**
+ * Kind-specific information about the token.
+ */
+ union
+ {
+ /**
+ * Subscription token.
+ */
+ struct
+ {
+ /**
+ * When does the subscription period start?
+ */
+ struct GNUNET_TIME_Timestamp start_date;
+
+ /**
+ * When does the subscription period end?
+ */
+ struct GNUNET_TIME_Timestamp end_date;
+
+ /**
+ * Array of domain names where this subscription can be safely used
+ * (e.g. the issuer warrants that these sites will re-issue tokens of
+ * this type if the respective contract says so). May contain "*" for
+ * any domain or subdomain.
+ */
+ const char **trusted_domains;
+
+ /**
+ * Length of the @e trusted_domains array.
+ */
+ unsigned int trusted_domains_len;
+ } subscription;
+
+ /**
+ * Discount token.
+ */
+ struct
+ {
+ /**
+ * Array of domain names where this discount token is intended to be
+ * used. May contain "*" for any domain or subdomain. Users should be
+ * warned about sites proposing to consume discount tokens of this
+ * type that are not in this list that the merchant is accepting a
+ * coupon from a competitor and thus may be attaching different
+ * semantics (like get 20% discount for my competitors 30% discount
+ * token).
+ */
+ const char **expected_domains;
+
+ /**
+ * Length of the @e expected_domains array.
+ */
+ unsigned int expected_domains_len;
+
+ } discount;
+ } details;
+};
+
+/**
+ * Struct to hold contract terms in v0 and v1 format. v0 contracts are modelled
+ * as a v1 contract with a single choice and no inputs and outputs. Use the
+ * version field to explicitly differentiate between v0 and v1 contracts.
+ */
+struct TALER_MerchantContract
+{
+ /**
+ * URL where the same contract could be ordered again (if available).
+ */
+ const char *public_reorder_url;
+
+ /**
+ * Our order ID.
+ */
+ const char *order_id;
+
+ /**
+ * Merchant base URL.
+ */
+ char *merchant_base_url;
+
+ /**
+ * Merchant information.
+ */
+ struct
+ {
+ /**
+ * Legal name of the instance
+ */
+ char *name;
+
+ /**
+ * Merchant's site url
+ */
+ char *website;
+
+ /**
+ * Email contact for customers
+ */
+ char *email;
+
+ /**
+ * merchant's logo data uri
+ */
+ char *logo;
+
+ /**
+ * Merchant address
+ */
+ json_t *address;
+
+ /**
+ * Jurisdiction of the business
+ */
+ json_t *jurisdiction;
+ } merchant;
+
+ /**
+ * Price to be paid for the transaction. Could be 0. The price is in addition
+ * to other instruments, such as rations and tokens.
+ * The exchange will subtract deposit fees from that amount
+ * before transferring it to the merchant.
+ */
+ struct TALER_Amount brutto;
+
+ /**
+ * Summary of the contract.
+ */
+ const char *summary;
+
+ /**
+ * Internationalized summary.
+ */
+ json_t *summary_i18n;
+
+ /**
+ * URL that will show that the contract was successful
+ * after it has been paid for.
+ */
+ const char *fulfillment_url;
+
+ /**
+ * Message shown to the customer after paying for the contract.
+ * 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;
+
+ /**
+ * Array of products that are part of the purchase.
+ */
+ const json_t *products;
+
+ /**
+ * Timestamp of the contract.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Deadline for refunds.
+ */
+ struct GNUNET_TIME_Timestamp refund_deadline;
+
+ /**
+ * Specifies for how long the wallet should try to get an
+ * automatic refund for the purchase.
+ */
+ struct GNUNET_TIME_Relative auto_refund;
+
+ /**
+ * 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;
+
+ /**
+ * 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;
+
+ /**
+ * Specified version of the contract.
+ */
+ enum TALER_MerchantContractVersion version;
+
+ /**
+ * Array of possible specific contracts the wallet/customer may choose
+ * from by selecting the respective index when signing the deposit
+ * confirmation.
+ */
+ struct TALER_MerchantContractChoice *choices;
+
+ /**
+ * Length of the @e choices array.
+ */
+ unsigned int choices_len;
+
+ /**
+ * Array of token authorities.
+ */
+ struct TALER_MerchantContractTokenAuthority *token_authorities;
+
+ /**
+ * Length of the @e token_authorities array.
+ */
+ unsigned int token_authorities_len;
+
+ /**
+ * Maximum fee as given by the client request.
+ */
+ struct TALER_Amount max_fee;
+
+ // TODO: Add exchanges array
+};
+
+enum TALER_MerchantContractInputType
+TMH_string_to_contract_input_type (const char *str);
+
+enum TALER_MerchantContractOutputType
+TMH_string_to_contract_output_type (const char *str);
+
+/**
+ * Serialize @a contract to a JSON object, ready to be stored in the database.
+ * The @a contract can be of v0 or v1.
+ *
+ * @param[in] contract contract struct to serialize
+ * @param[in] instance merchant instance for this contract
+ * @param[in] exchanges JSON array of exchanges
+ * @param[out] out serialized contract as JSON object
+ * @return #GNUNET_OK on success
+ * #GNUNET_NO if @a contract was not valid
+ * #GNUNET_SYSERR on failure
+ */
+enum GNUNET_GenericReturnValue
+TMH_serialize_contract (const struct TALER_MerchantContract *contract,
+ const struct TMH_MerchantInstance *instance,
+ json_t *exchanges,
+ json_t **out);
+
+enum GNUNET_GenericReturnValue
+TMH_serialize_contract_v0 (const struct TALER_MerchantContract *contract,
+ const struct TMH_MerchantInstance *instance,
+ json_t *exchanges,
+ json_t **out);
+
+enum GNUNET_GenericReturnValue
+TMH_serialize_contract_v1 (const struct TALER_MerchantContract *contract,
+ const struct TMH_MerchantInstance *instance,
+ json_t *exchanges,
+ json_t **out); \ No newline at end of file
diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c
index be29cfcc..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
+ * 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.
*/
-#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));
+#define LONG_POLL_THRESHOLD GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_SECONDS, 120)
/**
- * Exchange
+ * Information we keep for a pending #MMH_EXCHANGES_keys4exchange() operation.
*/
-struct Exchange;
-
-
-/**
- * Information we keep for a pending #MMH_EXCHANGES_find_exchange() 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.
+ */
+ struct TALER_EXCHANGE_WireAggregateFees *af;
+
+};
+
+
+/**
+ * Restriction that applies to an exchange account.
+ */
+struct Restriction
+{
+ /**
+ * Kept in a DLL.
*/
- char *payto_uri;
+ struct Restriction *next;
/**
- * Applicable fees, NULL if unknown/error.
+ * Kept in a DLL.
*/
- struct TALER_EXCHANGE_WireAggregateFees *af;
+ 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 TMH_Exchange *next;
/**
* Kept in a DLL.
*/
- struct Exchange *prev;
+ 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
@@ -242,519 +339,629 @@ struct Exchange
/**
- * 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.
- *
- * @param cls the exchange
+ * Free data structures within @a ea, but not @a ea
+ * pointer itself.
*
+ * @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))
{
- TALER_EXCHANGE_disconnect (exchange->conn);
- exchange->conn = NULL;
+ GNUNET_CONTAINER_DLL_remove (ea->d_head,
+ ea->d_tail,
+ r);
+ switch (r->type)
+ {
+ 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);
}
- exchange->conn = TALER_EXCHANGE_connect (merchant_curl_ctx,
- exchange->url,
- &keys_mgmt_cb,
- exchange,
- TALER_EXCHANGE_OPTION_END);
- /* 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)) )
+ while (NULL != (acc = exchange->acc_head))
{
- /* Hole in the fee structure, not allowed! */
- GNUNET_break_op (0);
- return TALER_EC_MERCHANT_GENERIC_HOLE_IN_WIRE_FEE_STRUCTURE;
+ GNUNET_CONTAINER_DLL_remove (exchange->acc_head,
+ exchange->acc_tail,
+ acc);
+ free_exchange_account (acc);
+ GNUNET_free (acc);
}
- while (NULL != fees)
- {
- 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;
- }
- 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 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)
{
- struct FeesByWireMethod *fbw;
+ while ( (NULL != fbw->af) &&
+ (GNUNET_TIME_timestamp_cmp (fbw->af->end_date,
+ <,
+ now)) )
+ {
+ struct TALER_EXCHANGE_WireAggregateFees *af = fbw->af;
- fn = fo->next;
- if (NULL != fo->wire_method)
+ fbw->af = af->next;
+ GNUNET_free (af);
+ }
+ if (NULL == fbw->af)
{
- /* Find fee structure for our wire method */
- fbw = get_wire_fees (exchange,
- now,
- fo->wire_method);
- if (NULL == fbw)
- {
- need_wire = true;
- /* 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;
- }
+ /* 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
+ else if (GNUNET_TIME_timestamp_cmp (fbw->af->start_date,
+ >,
+ now))
{
- /* no wire transfer method given, so we yield no fee */
- fbw = NULL;
+ /* 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 */
+
+ {
+ 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)
{
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = MHD_HTTP_OK,
- };
-
- fo->fc (fo->fc_cls,
- &hr,
- exchange->conn,
- (NULL != fbw) ? fbw->payto_uri : NULL,
- (NULL != fbw) ? &fbw->af->fees.wire : NULL,
- exchange->trusted);
+ 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;
- if (MHD_HTTP_OK != hr->http_status)
+ 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,
+ "Fetching /keys from exchange %s in retry_exchange()\n",
+ exchange->url);
+ switch (exchange->state)
{
- struct TMH_EXCHANGES_FindOperation *fo;
-
- 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);
- 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;
}
- if ( (process_find_operations (exchange)) &&
- (NULL == exchange->wire_task) &&
- (NULL == exchange->wire_request) )
+ 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)) )
{
- /* 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);
+ /* 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;
}
+ if ( (NULL == exchange->conn) &&
+ ( (ESTATE_FAILED == exchange->state) ||
+ (ESTATE_REDOWNLOADING_FAILURE == exchange->state) ) )
+ {
+ 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;
-
- exchange->wire_task = NULL;
- GNUNET_assert (! exchange->pending);
- if (! process_find_operations (exchange))
- return; /* no more need */
- GNUNET_assert (NULL == exchange->wire_request);
- exchange->wire_request = TALER_EXCHANGE_wire (exchange->conn,
- &handle_wire_data,
- exchange);
+ 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;
+
+ /* 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;
}
@@ -764,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;
@@ -784,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);
}
@@ -819,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))
+ exchange->state = ESTATE_FAILED;
+ while (NULL != (keys = exchange->keys_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) )
- {
- /* 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++)
@@ -968,225 +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
- delay = GNUNET_TIME_absolute_get_remaining (expire.abs_time);
- if (GNUNET_TIME_relative_is_zero (delay))
+ qs = TMH_db->insert_exchange_keys (TMH_db->cls,
+ keys);
+ 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) )
+
+ qs = TMH_db->delete_exchange_accounts (TMH_db->cls,
+ &keys->master_pub);
+ if (0 > qs)
{
- 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);
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
}
-}
+ for (unsigned int i = 0; i<keys->accounts_len; i++)
+ {
+ 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' */
-/**
- * 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;
+ 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;
+
+ 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));
- 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_DEBUG,
- "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
@@ -1198,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,
@@ -1246,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);
}
@@ -1267,70 +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 ()
{
+ if (NULL != keys_eh)
+ {
+ TMH_db->event_listen_cancel (keys_eh);
+ keys_eh = NULL;
+ }
while (NULL != exchange_head)
free_exchange_entry (exchange_head);
- GNUNET_CURL_fini (merchant_curl_ctx);
- merchant_curl_ctx = NULL;
- GNUNET_CURL_gnunet_rc_destroy (merchant_curl_rc);
- merchant_curl_rc = NULL;
- json_decref (TMH_trusted_exchanges);
- TMH_trusted_exchanges = NULL;
}
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 76dfefd5..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
@@ -25,12 +25,13 @@
#include <taler/taler_signatures.h>
#include <taler/taler_dbevents.h>
#include <taler/taler_json_lib.h>
+#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"
-#include "taler-merchant-httpd_templating.h"
/**
* How often do we retry DB transactions on serialization failures?
@@ -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 = TMH_return_from_template (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,550 +1061,55 @@ send_pay_request (struct GetOrderData *god,
}
GNUNET_free (taler_pay_uri);
GNUNET_free (order_status_url);
- return ret;
+ phase_end (god,
+ ret);
+ return false;
}
/**
- * Function called with detailed information about a refund.
- * It is responsible for packing up the data to return.
+ * Check if the order has been paid.
*
- * @param cls closure
- * @param refund_serial unique serial number of the refund
- * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
- * @param coin_pub public coin from which the refund comes from
- * @param exchange_url URL of the exchange that issued @a coin_pub
- * @param rtransaction_id identificator of the refund
- * @param reason human-readable explanation of the refund
- * @param refund_amount refund amount which is being taken from @a coin_pub
- * @param pending true if the this refund was not yet processed by the wallet/exchange
+ * @param[in,out] god request context
*/
static void
-process_refunds_cb (void *cls,
- uint64_t refund_serial,
- struct GNUNET_TIME_Timestamp timestamp,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const char *exchange_url,
- uint64_t rtransaction_id,
- const char *reason,
- const struct TALER_Amount *refund_amount,
- bool pending)
+phase_check_paid (struct GetOrderData *god)
{
- struct GetOrderData *god = cls;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Found refund of %s for coin %s with reason `%s' in database\n",
- TALER_amount2s (refund_amount),
- TALER_B2S (coin_pub),
- reason);
- god->refund_pending |= pending;
- if (!pending)
- {
- GNUNET_assert (0 <=
- TALER_amount_add (&god->refund_taken,
- &god->refund_taken,
- refund_amount));
+ 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;
}
- GNUNET_assert (0 <=
- TALER_amount_add (&god->refund_amount,
- &god->refund_amount,
- refund_amount));
- god->refunded = true;
+ god->phase++;
}
/**
- * Clean up the session state for a GET /orders/$ID request.
+ * Check if the client already paid for an equivalent
+ * order under this session, and if so redirect to
+ * that order.
*
- * @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)
-{
- 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);
-}
-
-
-MHD_RESULT
-TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
+static bool
+phase_redirect_to_paid_order (struct GetOrderData *god)
{
- 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;
-
- if (NULL == god)
- {
- god = GNUNET_new (struct GetOrderData);
- hc->ctx = god;
- 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);
-
-
- /* 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;
- }
- god->session_id = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "session_id");
-
- /* process await_refund_obtained argument */
- {
- const char *await_refund_obtained_s;
-
- await_refund_obtained_s =
- MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "await_refund_obtained");
- god->sc.awaiting_refund_obtained =
- (NULL != await_refund_obtained_s)
- ? 0 == strcasecmp (await_refund_obtained_s,
- "yes")
- : false;
- if (god->sc.awaiting_refund_obtained)
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Awaiting refund obtained\n");
- }
-
- {
- 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);
- }
- }
-
-
- /* 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 */
-
- if (GNUNET_SYSERR == god->suspended)
- return MHD_NO; /* we are in shutdown */
- if (GNUNET_YES == god->suspended)
- {
- god->suspended = GNUNET_NO;
- GNUNET_CONTAINER_DLL_remove (god_head,
- god_tail,
- god);
- }
-
- /* Convert order_id to h_contract_terms */
- TMH_db->preflight (TMH_db->cls);
- if (NULL == god->contract_terms)
- {
- uint64_t order_serial;
- 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,
- &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);
- /* FIXME: use better error code */
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
- "authentication with h_contract provided but wrong");
- }
-
- 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) )
{
@@ -1283,16 +1122,19 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
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_DEBUG,
+ 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,
- hc->instance->settings.id,
- god->fulfillment_url,
- god->session_id,
- &already_paid_order_id);
+ 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
@@ -1300,20 +1142,22 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
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");
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "order by fulfillment");
+ return false;
}
- if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
- (0 != strcmp (order_id,
- already_paid_order_id)) )
+ if ( (! god->paid) &&
+ ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
+ (0 != strcmp (god->order_id,
+ already_paid_order_id)) ) )
{
- MHD_RESULT ret;
+ bool ret;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Sending pay request for order %s (already paid: %s)\n",
- order_id,
+ god->order_id,
already_paid_order_id);
ret = send_pay_request (god,
already_paid_order_id);
@@ -1323,70 +1167,161 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
GNUNET_free (already_paid_order_id);
}
+ god->phase++;
+ return false;
+}
+
- if (! god->claimed)
+/**
+ * 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
{
- /* 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);
+ god->order_id);
}
+ return send_pay_request (god,
+ NULL);
+}
+
+/**
+ * Function called with detailed information about a refund.
+ * It is responsible for packing up the data to return.
+ *
+ * @param cls closure
+ * @param refund_serial unique serial number of the refund
+ * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
+ * @param coin_pub public coin from which the refund comes from
+ * @param exchange_url URL of the exchange that issued @a coin_pub
+ * @param rtransaction_id identificator of the refund
+ * @param reason human-readable explanation of the refund
+ * @param refund_amount refund amount which is being taken from @a coin_pub
+ * @param pending true if the this refund was not yet processed by the wallet/exchange
+ */
+static void
+process_refunds_cb (void *cls,
+ uint64_t refund_serial,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const char *exchange_url,
+ uint64_t rtransaction_id,
+ const char *reason,
+ const struct TALER_Amount *refund_amount,
+ bool pending)
+{
+ struct GetOrderData *god = cls;
+
+ (void) refund_serial;
+ (void) timestamp;
+ (void) exchange_url;
+ (void) rtransaction_id;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Found refund of %s for coin %s with reason `%s' in database\n",
+ TALER_amount2s (refund_amount),
+ 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)) )
{
- /* 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);
- }
+ god->bad_refund_currency_in_db = true;
+ return;
+ }
+ if (! pending)
+ {
+ GNUNET_assert (0 <=
+ TALER_amount_add (&god->refund_taken,
+ &god->refund_taken,
+ refund_amount));
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&god->refund_amount,
+ &god->refund_amount,
+ refund_amount));
+ god->refunded = true;
+}
+
+
+/**
+ * Check if the order has been refunded.
+ *
+ * @param[in,out] god request context
+ * @return true to exit due to suspension
+ */
+static bool
+phase_check_refunded (struct GetOrderData *god)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ if ( (god->sc.awaiting_refund) &&
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&god->contract_total,
+ &god->sc.refund_expected)) )
+ {
+ GNUNET_break (0);
+ phase_fail (god,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ god->contract_total.currency);
+ return false;
}
/* 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,
+ TALER_amount_set_zero (god->contract_total.currency,
&god->refund_amount));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TMH_currency,
+ TALER_amount_set_zero (god->contract_total.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);
+ 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)
{
GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_refunds_detailed");
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_refunds_detailed");
+ return false;
+ }
+ if (god->bad_refund_currency_in_db)
+ {
+ 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,
@@ -1399,7 +1334,8 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
struct GNUNET_TIME_Relative remaining;
remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
- if (! GNUNET_TIME_relative_is_zero (remaining))
+ if ( (! GNUNET_TIME_relative_is_zero (remaining)) &&
+ (! god->generate_html) )
{
/* yes, indeed suspend */
if (god->sc.awaiting_refund)
@@ -1410,108 +1346,315 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Awaiting pending refunds\n");
suspend_god (god);
- return MHD_YES;
+ return true;
}
}
+ god->phase++;
+ return false;
+}
- /* All operations done, build final response */
- if (god->generate_html)
+
+/**
+ * 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))
{
- enum GNUNET_GenericReturnValue res;
+ 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);
+}
- 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;
+/**
+ * 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;
+ }
- 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 = TMH_return_from_template (god->sc.con,
- MHD_HTTP_OK,
- "offer_refund",
- hc->instance->settings.id,
- uri,
- context);
- json_decref (context);
- }
+ 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);
- GNUNET_free (qr);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ "qr code");
+ return;
}
- else
+
{
+ 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 = TMH_return_from_template (god->sc.con,
- MHD_HTTP_OK,
- "show_order_details",
- hc->instance->settings.id,
- NULL,
- context);
+ &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);
}
- if (GNUNET_SYSERR == res)
+ 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);
+ }
+}
+
+
+MHD_RESULT
+TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct GetOrderData *god = hc->ctx;
+
+ (void) rh;
+ if (NULL == god)
+ {
+ god = GNUNET_new (struct GetOrderData);
+ hc->ctx = god;
+ hc->cc = &god_cleanup;
+ god->sc.con = connection;
+ god->hc = hc;
+ god->order_id = hc->infix;
+ god->generate_html
+ = TMH_MHD_test_html_desired (connection);
+
+ /* first-time initialization / sanity checks */
+ 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");
+
+ /* process await_refund_obtained argument */
{
- GNUNET_break (0);
+ const char *await_refund_obtained_s;
+
+ await_refund_obtained_s =
+ MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "await_refund_obtained");
+ god->sc.awaiting_refund_obtained =
+ (NULL != await_refund_obtained_s)
+ ? 0 == strcasecmp (await_refund_obtained_s,
+ "yes")
+ : false;
+ if (god->sc.awaiting_refund_obtained)
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Awaiting refund obtained\n");
+ }
+
+ TALER_MHD_parse_request_amount (connection,
+ "refund",
+ &god->sc.refund_expected);
+ if (TALER_amount_is_valid (&god->sc.refund_expected))
+ {
+ god->sc.awaiting_refund = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Awaiting minimum refund of %s\n",
+ TALER_amount2s (&god->sc.refund_expected));
+ }
+ TALER_MHD_parse_request_timeout (connection,
+ &god->sc.long_poll_timeout);
+ }
+
+ if (GNUNET_SYSERR == god->suspended)
+ return MHD_NO; /* we are in shutdown */
+ if (GNUNET_YES == god->suspended)
+ {
+ god->suspended = GNUNET_NO;
+ GNUNET_CONTAINER_DLL_remove (god_head,
+ god_tail,
+ god);
+ }
+
+ while (1)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling request in phase %d\n",
+ (int) god->phase);
+ switch (god->phase)
+ {
+ 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;
+ 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 f427b168..00000000
--- a/src/backend/taler-merchant-httpd_get-tips-ID.c
+++ /dev/null
@@ -1,290 +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-merchant-httpd_get-tips-ID.h"
-#include "taler-merchant-httpd_mhd.h"
-#include "taler-merchant-httpd_qr.h"
-#include "taler-merchant-httpd_templating.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;
-
- 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 = TMH_return_from_template (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 595b82cf..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,72 +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;
}
@@ -101,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",
@@ -140,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 ()
};
@@ -164,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))
@@ -202,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),
@@ -229,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",
@@ -271,12 +369,6 @@ TMH_products_array_valid (const json_t *products)
if ( (NULL != taxes) &&
(! TMH_taxes_array_valid (taxes)) )
{
- GNUNET_break_op (0);
- valid = false;
- }
- if ( (NULL != description_i18n) &&
- (! TALER_JSON_check_i18n (description_i18n)) )
- {
GNUNET_break_op (0);
valid = false;
}
@@ -318,6 +410,51 @@ TMH_image_data_url_valid (const char *image_data_url)
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 };
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("summary",
+ &summary),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ 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),
+ GNUNET_JSON_spec_relative_time ("pay_duration",
+ &pay_duration),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *ename;
+ unsigned int eline;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (template_contract,
+ spec,
+ &ename,
+ &eline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid template_contract for field %s\n",
+ ename);
+ return false;
+ }
+ return true;
+}
+
+
+bool
TMH_taxes_array_valid (const json_t *taxes)
{
json_t *tax;
@@ -352,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);
@@ -368,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));
@@ -428,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)
@@ -451,7 +592,501 @@ 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));
+}
+
+
+/**
+ * Closure for #trigger_webhook_cb.
+ *
+ * @param instance which is the instance we work with
+ * @param root JSON data to fill into the template
+ * @param rv, query of the TALER_TEMPLATEING_fill
+ */
+struct Trigger
+{
+ const char *instance;
+
+ const json_t *root;
+
+ enum GNUNET_DB_QueryStatus rv;
+
+};
+
+/**
+ * Typically called by `TMH_trigger_webhook`.
+ *
+ * @param[in,out] cls a `struct Trigger` with information about the webhook
+ * @param webhook_serial reference to the configured webhook template.
+ * @param event_type is the event/action of the webhook
+ * @param url to make request to
+ * @param http_method use for the webhook
+ * @param header_template of the webhook
+ * @param body_template of the webhook
+ */
+static void
+trigger_webhook_cb (void *cls,
+ uint64_t webhook_serial,
+ const char *event_type,
+ const char *url,
+ const char *http_method,
+ const char *header_template,
+ const char *body_template)
+{
+ struct Trigger *t = cls;
+ void *header = NULL;
+ void *body = NULL;
+ size_t header_size;
+ size_t body_size;
+
+ if (NULL != header_template)
+ {
+ int ret;
+
+ ret = TALER_TEMPLATING_fill (header_template,
+ t->root,
+ &header,
+ &header_size);
+ if (0 != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to expand webhook header template for webhook %llu (%d)\n",
+ (unsigned long long) webhook_serial,
+ ret);
+ t->rv = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ /* 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)
+ {
+ int ret;
+ ret = TALER_TEMPLATING_fill (body_template,
+ t->root,
+ &body,
+ &body_size);
+ if (0 != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "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;
+ }
+ /* 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,
+ webhook_serial,
+ url,
+ 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);
+}
+
+
+/**
+ * TMH_trigger_webhook is a function that need to be use when someone
+ * pay. Merchant need to have a notification.
+ *
+ * @param instance that we need to send the webhook as a notification
+ * @param event of the webhook
+ * @param args argument of the function
+ */
+enum GNUNET_DB_QueryStatus
+TMH_trigger_webhook (const char *instance,
+ const char *event,
+ const json_t *args)
+{
+ struct Trigger t = {
+ .instance = instance,
+ .root = args
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->lookup_webhook_by_event (TMH_db->cls,
+ instance,
+ event,
+ &trigger_webhook_cb,
+ &t);
+ if (qs < 0)
+ 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 659bddc2..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);
/**
@@ -88,16 +87,59 @@ TMH_image_data_url_valid (const char *image_data_url);
/**
+ * Check if @a template_contract is a valid template_contract object in the sense of Taler's API
+ * definition.
+ *
+ * @param template_contract object to check
+ * @return true if @a template_location is an object
+ * representing a template_location.
+ */
+bool
+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);
+
+
+/**
+ * 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);
+
/**
- * FIXME: document
+ * 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,
@@ -117,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
*
@@ -130,4 +245,33 @@ TMH_uuid_from_string (const char *uuids,
GNUNET_JSON_pack_allow_null ( \
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.
+ *
+ * @param instance that we need to send the webhook as a notification
+ * @param event of the webhook
+ * @param args argument of the function
+ */
+enum GNUNET_DB_QueryStatus
+TMH_trigger_webhook (const char *instance,
+ const char *action,
+ 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_mhd.c b/src/backend/taler-merchant-httpd_mhd.c
index e96acca5..0bb22b35 100644
--- a/src/backend/taler-merchant-httpd_mhd.c
+++ b/src/backend/taler-merchant-httpd_mhd.c
@@ -59,6 +59,7 @@ TMH_MHD_test_html_desired (struct MHD_Connection *connection)
bool ret = false;
const char *accept;
+ // FIXME: use TALER_MHD_check_accept here!
accept = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_ACCEPT);
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 07a3fd8a..16b69c53 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-claim.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-claim.c
@@ -170,8 +170,9 @@ claim_order (const char *instance_id,
qs = TMH_db->insert_contract_terms (TMH_db->cls,
instance_id,
order_id,
- *contract_terms);
- if (0 > qs)
+ *contract_terms,
+ &order_serial);
+ if (0 >= qs)
{
TMH_db->rollback (TMH_db->cls);
json_decref (*contract_terms);
@@ -219,6 +220,9 @@ TMH_post_orders_ID_claim (const struct TMH_RequestHandler *rh,
if (GNUNET_OK != res)
{
GNUNET_break_op (0);
+ json_dumpf (hc->request_body,
+ stderr,
+ JSON_INDENT (2));
return (GNUNET_NO == res)
? MHD_YES
: MHD_NO;
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 bc8ccfd9..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,
@@ -171,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"));
@@ -202,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 c64eaebf..14edfd55 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-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 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"
@@ -58,68 +57,94 @@
*/
struct PayContext;
+
/**
- * Information kept during a pay request for each coin.
+ * Different phases of processing the /pay request.
*/
-struct DepositConfirmation
+enum PayPhase
{
+ /**
+ * Initial phase where the request is parsed.
+ */
+ PP_INIT = 0,
/**
- * Reference to the main PayContext
+ * Check database state for the given order.
*/
- struct PayContext *pc;
+ PP_CHECK_CONTRACT,
/**
- * Handle to the deposit operation we are performing for
- * this coin, NULL after the operation is done.
+ * Contract has been paid.
*/
- struct TALER_EXCHANGE_DepositHandle *dh;
+ PP_CONTRACT_PAID,
/**
- * URL of the exchange that issued this coin.
+ * Execute payment transaction.
*/
- char *exchange_url;
+ PP_PAY_TRANSACTION,
/**
- * Hash of the denomination of this coin.
+ * Notify other processes about successful payment.
*/
- struct TALER_DenominationHashP h_denom;
+ PP_PAYMENT_NOTIFICATION,
/**
- * Amount this coin contributes to the total purchase price.
- * This amount includes the deposit fee.
+ * Create final success response.
*/
- struct TALER_Amount amount_with_fee;
+ PP_SUCCESS_RESPONSE,
/**
- * Fee charged by the exchange for the deposit operation of this coin.
+ * Perform batch deposits with exchange(s).
*/
- struct TALER_Amount deposit_fee;
+ PP_BATCH_DEPOSITS,
/**
- * Fee charged by the exchange for the refund operation of this coin.
+ * Return response in payment context.
*/
- struct TALER_Amount refund_fee;
+ PP_RETURN_RESPONSE,
/**
- * Wire fee charged by the exchange of this coin.
+ * Return #MHD_YES to end processing.
*/
- struct TALER_Amount wire_fee;
+ PP_END_YES,
/**
- * Public key of the coin.
+ * Return #MHD_NO to end processing.
*/
- struct TALER_CoinSpendPublicKeyP coin_pub;
+ PP_END_NO
+};
+
+
+/**
+ * Information kept during a pay request for each coin.
+ */
+struct DepositConfirmation
+{
/**
- * Signature using the @e denom key over the @e coin_pub.
+ * Reference to the main PayContext
*/
- struct TALER_DenominationSignature ub_sig;
+ struct PayContext *pc;
/**
- * Signature of the coin's private key over the contract.
+ * URL of the exchange that issued this coin.
*/
- struct TALER_CoinSpendSignatureP coin_sig;
+ char *exchange_url;
+
+ /**
+ * Details about the coin being deposited.
+ */
+ struct TALER_EXCHANGE_CoinDepositDetail cdd;
+
+ /**
+ * Fee charged by the exchange for the deposit operation of this coin.
+ */
+ struct TALER_Amount deposit_fee;
+
+ /**
+ * Fee charged by the exchange for the refund operation of this coin.
+ */
+ struct TALER_Amount refund_fee;
/**
* If a minimum age was required (i. e. pc->minimum_age is large enough),
@@ -129,16 +154,19 @@ struct DepositConfirmation
*/
struct TALER_AgeAttestation minimum_age_sig;
- /* If a minimum age was required (i. e. pc->minimum_age is large enought),
+ /**
+ * If a minimum age was required (i. e. pc->minimum_age is large enough),
* this is the age commitment (i. e. age mask and vector of EdDSA public
* keys, one per age group) that went into the mining of the coin. The
* SHA256 hash of the mask and the vector of public keys was bound to the
* key.
*/
- struct TALER_AgeCommitment *age_commitment;
+ struct TALER_AgeCommitment age_commitment;
- /* Age mask in the denomination that defines the age groups. Only
- * applicable, if minimum age was required. */
+ /**
+ * Age mask in the denomination that defines the age groups. Only
+ * applicable, if minimum age was required.
+ */
struct TALER_AgeMask age_mask;
/**
@@ -148,10 +176,73 @@ struct DepositConfirmation
unsigned int index;
/**
+ * true, if no field "age_commitment" was found in the JSON blob
+ */
+ bool no_age_commitment;
+
+ /**
+ * True, if no field "minimum_age_sig" was found in the JSON blob
+ */
+ bool no_minimum_age_sig;
+
+ /**
+ * true, if no field "h_age_commitment" was found in the JSON blob
+ */
+ bool no_h_age_commitment;
+
+ /**
* true if we found this coin in the database.
*/
bool found_in_db;
+ /**
+ * true if we #deposit_paid_check() matched this coin in the database.
+ */
+ bool matched_in_db;
+
+};
+
+
+/**
+ * Information kept during a pay request for each exchange.
+ */
+struct ExchangeGroup
+{
+
+ /**
+ * Payment context this group is part of.
+ */
+ struct PayContext *pc;
+
+ /**
+ * Handle to the batch deposit operation we are performing for this
+ * exchange, NULL after the operation is done.
+ */
+ struct TALER_EXCHANGE_BatchDepositHandle *bdh;
+
+ /**
+ * Handle for operation to lookup /keys (and auditors) from
+ * the exchange used for this transaction; NULL if no operation is
+ * pending.
+ */
+ struct TMH_EXCHANGES_KeysOperation *fo;
+
+ /**
+ * URL of the exchange that issued this coin. Aliases
+ * the exchange URL of one of the coins, do not free!
+ */
+ 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;
};
@@ -172,6 +263,12 @@ struct PayContext
struct PayContext *prev;
/**
+ * Array with @e num_exchange exchanges we are depositing
+ * coins into.
+ */
+ struct ExchangeGroup **egs;
+
+ /**
* Array with @e coins_cnt coins we are despositing.
*/
struct DepositConfirmation *dc;
@@ -188,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;
@@ -205,16 +302,9 @@ struct PayContext
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;
-
- /**
- * URL of the exchange used for the last @e fo.
+ * Our contract (or NULL if not available).
*/
- const char *current_exchange;
+ json_t *contract_terms;
/**
* Placeholder for #TALER_MHD_parse_post_json() to keep its internal state.
@@ -258,36 +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;
-
-
- /**
- * Minimum age required for this purchase.
- */
- unsigned int minimum_age;
-
- /**
* Amount from @e root. This is the amount the merchant expects
* to make, minus @e max_fee.
*/
@@ -335,19 +400,31 @@ 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.
+ */
+ unsigned int minimum_age;
/**
* 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
+ * of the @e eg array.
+ */
+ unsigned int num_exchanges;
/**
* How often have we retried the 'main' transaction?
@@ -355,19 +432,14 @@ struct PayContext
unsigned int retry_counter;
/**
- * Number of transactions still pending. Initially set to
- * @e coins_cnt, decremented on each transaction that
- * successfully finished.
+ * Number of batch transactions pending.
*/
- unsigned int pending;
+ unsigned int pending_at_eg;
/**
- * Number of transactions still pending for the currently selected
- * exchange. Initially set to the number of coins started at the
- * exchange, decremented on each transaction that successfully
- * finished. Once it hits zero, we pick the next exchange.
+ * Number of coin deposits pending.
*/
- unsigned int pending_at_ce;
+ unsigned int pending;
/**
* HTTP status code to use for the reply, i.e 200 for "OK".
@@ -377,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
@@ -385,72 +462,16 @@ struct PayContext
enum GNUNET_GenericReturnValue suspended;
/**
- * true if we already tried a forced /keys download.
- */
- bool tried_force_keys;
-
-};
-
-
-/**
- * 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.
- */
- struct TMH_EXCHANGES_FindOperation *fo;
-
- /**
- * Exchange this is about.
- */
- char *exchange_url;
-
- /**
- * Merchant instance this is for.
+ * Set to true if the deposit currency of a coin
+ * does not match the contract currency.
*/
- struct TMH_MerchantInstance *mi;
+ bool deposit_currency_mismatch;
/**
- * Wire method we are checking the status of.
+ * Set to true if the database contains a (bogus)
+ * refund for a different currency.
*/
- 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;
};
@@ -465,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);
@@ -582,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.
@@ -595,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);
}
@@ -617,364 +574,272 @@ 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));
+ resume_pay_with_response (
+ pc,
+ TALER_ErrorCode_get_http_status_safe (ec),
+ TALER_MHD_make_error (ec,
+ msg));
}
/**
- * Custom cleanup routine for a `struct PayContext`.
+ * Conclude payment processing for @a pc with the
+ * given @a res MHD status code.
*
- * @param cls the `struct PayContext` to clean up.
+ * @param[in,out] pc payment context for final state transition
+ * @param res MHD return code to end with
*/
static void
-pay_context_cleanup (void *cls)
+pay_end (struct PayContext *pc,
+ MHD_RESULT res)
{
- struct PayContext *pc = cls;
-
- if (NULL != pc->timeout_task)
- {
- GNUNET_SCHEDULER_cancel (pc->timeout_task);
- pc->timeout_task = 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->ub_sig);
- GNUNET_free (dc->exchange_url);
- }
- GNUNET_free (pc->dc);
- if (NULL != pc->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (pc->fo);
- pc->fo = NULL;
- }
- 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);
+ pc->phase = (MHD_YES == res)
+ ? PP_END_YES
+ : PP_END_NO;
}
/**
- * Find the exchange we need to talk to for the next
- * pending deposit permission.
+ * Return response stored in @a pc.
*
- * @param pc payment context we are processing
+ * @param[in,out] pc payment context we are processing
*/
static void
-find_next_exchange (struct PayContext *pc);
-
-
-/**
- * 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.
- *
- * @param cls a `struct KycContext *`
- * @param hr HTTP response data
- * @param dd details about the deposit (NULL on errors)
- */
-static void
-deposit_get_callback (
- void *cls,
- const struct TALER_EXCHANGE_GetDepositResponse *dr)
+phase_return_response (struct PayContext *pc)
{
- 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)
+ GNUNET_assert (0 != pc->response_code);
+ /* We are *done* processing the request, just queue the response (!) */
+ if (UINT_MAX == pc->response_code)
{
- 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,
- dr->details.success.payment_target_uuid,
- 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.payment_target_uuid,
- 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);
+ GNUNET_break (0);
+ pay_end (pc,
+ MHD_NO); /* hard error */
+ return;
}
- destroy_kc (kc);
+ pay_end (pc,
+ MHD_queue_response (pc->connection,
+ pc->response_code,
+ pc->response));
}
/**
- * Function called with the result of our exchange lookup.
+ * Do database transaction for a completed batch deposit.
*
- * @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 eg group that completed
+ * @param dr response from the server
+ * @return transaction status
*/
-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)
+static enum GNUNET_DB_QueryStatus
+batch_deposit_transaction (const struct ExchangeGroup *eg,
+ const struct TALER_EXCHANGE_BatchDepositResult *dr)
{
- struct KycContext *kc = cls;
-
- kc->fo = NULL;
- if (NULL == exchange_handle)
+ const struct PayContext *pc = eg->pc;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_Amount total_without_fees;
+ uint64_t b_dep_serial;
+ uint32_t off = 0;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pc->amount.currency,
+ &total_without_fees));
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
- destroy_kc (kc);
- return;
+ 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,
+ dc->exchange_url))
+ continue;
+ 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));
}
- 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)
+ 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);
- }
-}
-
-
-/**
- * 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;
+ struct DepositConfirmation *dc = &pc->dc[i];
- kc->kyc_timestamp = last_check;
- kc->kyc_ok = kyc_ok;
+ /* 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;
}
/**
- * 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.
+ * Handle case where the batch deposit completed
+ * with a status of #MHD_HTTP_OK.
*
- * @param pc payment context to use as starting point
- * @param dc deposit confirmation we are triggering on
+ * @param eg group that completed
+ * @param dr response from the server
*/
static void
-check_kyc (struct PayContext *pc,
- const struct DepositConfirmation *dc)
+handle_batch_deposit_ok (struct ExchangeGroup *eg,
+ const struct TALER_EXCHANGE_BatchDepositResult *dr)
{
- enum GNUNET_DB_QueryStatus qs;
- struct KycContext *kc;
+ struct PayContext *pc = eg->pc;
+ enum GNUNET_DB_QueryStatus qs
+ = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- kc = GNUNET_new (struct KycContext);
- qs = TMH_db->account_kyc_get_status (TMH_db->cls,
- pc->hc->instance->settings.id,
- &pc->wm->h_wire,
- dc->exchange_url,
- &kyc_cb,
- kc);
- if (qs < 0)
- {
- GNUNET_break (0);
- GNUNET_free (kc);
- return;
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ /* store result to DB */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Storing successful payment %s (%s) at instance `%s'\n",
+ pc->hc->infix,
+ GNUNET_h2s (&pc->h_contract_terms.hash),
+ pc->hc->instance->settings.id);
+ for (unsigned int r = 0; r<MAX_RETRIES; r++)
{
- if (kc->kyc_ok)
+ TMH_db->preflight (TMH_db->cls);
+ if (GNUNET_OK !=
+ TMH_db->start (TMH_db->cls,
+ "batch-deposit-insert-confirmation"))
{
- GNUNET_free (kc);
- return; /* we are done */
+ resume_pay_with_response (
+ pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_MHD_MAKE_JSON_PACK (
+ TALER_JSON_pack_ec (
+ TALER_EC_GENERIC_DB_START_FAILED),
+ TMH_pack_exchange_reply (&dr->hr)));
+ return;
}
- if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration (
- kc->kyc_timestamp.abs_time),
- <,
- KYC_RETRY_FREQUENCY))
+ qs = batch_deposit_transaction (eg,
+ dr);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Not re-checking KYC status at `%s', as we already recently asked\n",
- dc->exchange_url);
- GNUNET_free (kc);
- 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)
+ {
+ 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,
+ "insert_deposit");
+ }
+ break; /* DB transaction succeeded */
}
- kc->mi = pc->hc->instance;
- kc->mi->rc++;
- kc->wm = pc->wm;
- kc->exchange_url = GNUNET_strdup (dc->exchange_url);
- kc->h_contract_terms = pc->h_contract_terms;
- kc->coin_pub = dc->coin_pub;
- GNUNET_CONTAINER_DLL_insert (kc_head,
- kc_tail,
- kc);
- kc->fo = TMH_EXCHANGES_find_exchange (dc->exchange_url,
- NULL,
- GNUNET_NO,
- &process_kyc_with_exchange,
- kc);
- if (NULL == kc->fo)
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
- GNUNET_break (0);
- destroy_kc (kc);
+ resume_pay_with_error (pc,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "insert_deposit");
+ return;
+ }
+
+ /* Transaction is done, mark affected coins as complete as well. */
+ for (size_t i = 0; i<pc->coins_cnt; i++)
+ {
+ struct DepositConfirmation *dc = &pc->dc[i];
+
+ if (0 != strcmp (eg->exchange_url,
+ pc->dc[i].exchange_url))
+ continue;
+ if (dc->found_in_db)
+ continue;
+ dc->found_in_db = true; /* well, at least NOW it'd be true ;-) */
+ pc->pending--;
}
}
/**
- * Callback to handle a deposit permission's response.
+ * Callback to handle a batch deposit permission's response.
*
- * @param cls a `struct DepositConfirmation` (i.e. a pointer
- * into the global array of confirmations and an index for this call
- * in that array). That way, the last executed callback can detect
- * that no other confirmations are on the way, and can pack a response
- * for the wallet
- * @param hr HTTP response code details
- * @param deposit_timestamp time when the exchange generated the deposit confirmation
- * @param exchange_sig signature from the exchange over the deposit confirmation
- * @param exchange_pub which key did the exchange use to create the @a exchange_sig
+ * @param cls a `struct ExchangeGroup`
+ * @param dr HTTP response code details
*/
static void
-deposit_cb (void *cls,
- const struct TALER_EXCHANGE_DepositResult *dr)
+batch_deposit_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_BatchDepositResult *dr)
{
- struct DepositConfirmation *dc = cls;
- struct PayContext *pc = dc->pc;
+ struct ExchangeGroup *eg = cls;
+ struct PayContext *pc = eg->pc;
- dc->dh = NULL;
+ 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_ce--;
switch (dr->hr.http_status)
{
case MHD_HTTP_OK:
+ handle_batch_deposit_ok (eg,
+ dr);
+ if (0 == pc->pending_at_eg)
{
- /* store result to DB */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Storing successful payment %s (%s) at instance `%s'\n",
- pc->hc->infix,
- GNUNET_h2s (&pc->h_contract_terms.hash),
- pc->hc->instance->settings.id);
- TMH_db->preflight (TMH_db->cls);
- {
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TMH_db->insert_deposit (TMH_db->cls,
- pc->hc->instance->settings.id,
- dr->details.success.deposit_timestamp,
- &pc->h_contract_terms,
- &dc->coin_pub,
- dc->exchange_url,
- &dc->amount_with_fee,
- &dc->deposit_fee,
- &dc->refund_fee,
- &dc->wire_fee,
- &pc->wm->h_wire,
- dr->details.success.exchange_sig,
- dr->details.success.exchange_pub);
- if (0 > qs)
- {
- /* Special report if retries insufficient */
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- execute_pay_transaction (pc);
- return;
- }
- /* 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;
- }
- }
-
- dc->found_in_db = true; /* well, at least NOW it'd be true ;-) */
- pc->pending--;
-
- if (0 != pc->pending_at_ce)
- return; /* still more to do with current exchange */
- check_kyc (pc,
- dc);
- find_next_exchange (pc);
- return;
+ pc->phase = PP_PAY_TRANSACTION;
+ pay_resume (pc);
}
+ return;
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Deposit operation failed with HTTP code %u/%d\n",
@@ -1005,7 +870,7 @@ deposit_cb (void *cls,
return;
}
- /* Forward error, adding the "coin_pub" for which the
+ /* Forward error, adding the "exchange_url" for which the
error was being generated */
if (TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS == dr->hr.ec)
{
@@ -1016,8 +881,8 @@ deposit_cb (void *cls,
TALER_JSON_pack_ec (
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS),
TMH_pack_exchange_reply (&dr->hr),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- &dc->coin_pub)));
+ GNUNET_JSON_pack_string ("exchange_url",
+ eg->exchange_url)));
return;
}
resume_pay_with_response (
@@ -1027,191 +892,192 @@ deposit_cb (void *cls,
TALER_JSON_pack_ec (
TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUS),
TMH_pack_exchange_reply (&dr->hr),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- &dc->coin_pub)));
+ 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 PayContext`
- * @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 cls the `struct ExchangeGroup`
+ * @param keys the keys of the exchange
+ * @param exchange representation of the exchange
*/
static void
-process_pay_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)
+process_pay_with_keys (
+ void *cls,
+ struct TALER_EXCHANGE_Keys *keys,
+ struct TMH_Exchange *exchange)
{
- struct PayContext *pc = cls;
+ struct ExchangeGroup *eg = cls;
+ struct PayContext *pc = eg->pc;
struct TMH_HandlerContext *hc = pc->hc;
- const struct TALER_EXCHANGE_Keys *keys;
- struct TALER_AgeCommitmentHash h_age_commitment = {0};
+ unsigned int group_size;
- (void) payto_uri;
- pc->fo = NULL;
+ 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",
+ eg->exchange_url);
GNUNET_assert (GNUNET_YES == pc->suspended);
- if (NULL == hr)
+ if (NULL == keys)
{
- 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))
{
- 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))
{
- 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 /deposit operation for all coins of
+ /* Initiate /batch-deposit operation for all coins of
the current exchange (!) */
- GNUNET_assert (0 == pc->pending_at_ce);
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ group_size = 0;
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
const struct TALER_EXCHANGE_DenomPublicKey *denom_details;
- enum TALER_ErrorCode ec;
- unsigned int http_status;
- bool age_verification_required = false;
+ bool is_age_restricted_denom = false;
- if (NULL != dc->dh)
- continue; /* we were here before (can happen due to
- tried_force_keys logic), don't go again */
- if (dc->found_in_db)
+ if (0 != strcmp (eg->exchange_url,
+ pc->dc[i].exchange_url))
continue;
- if (0 != strcmp (dc->exchange_url,
- pc->current_exchange))
+ if (dc->found_in_db)
continue;
+
denom_details
= TALER_EXCHANGE_get_denomination_key_by_hash (keys,
- &dc->h_denom);
+ &dc->cdd.h_denom_pub);
if (NULL == denom_details)
{
- if (! pc->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. */
- pc->tried_force_keys = true;
- pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange,
- pc->wm->wire_method,
- GNUNET_YES,
- &process_pay_with_exchange,
- pc);
- if (NULL != pc->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 */
- 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->h_denom),
- 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;
}
- if (GNUNET_OK !=
- TMH_AUDITORS_check_dk (exchange_handle,
- denom_details,
- exchange_trusted,
- &http_status,
- &ec))
+ dc->deposit_fee = denom_details->fees.deposit;
+ dc->refund_fee = denom_details->fees.refund;
+
+ if (GNUNET_TIME_absolute_is_past (
+ denom_details->expire_deposit.abs_time))
{
- if (! pc->tried_force_keys)
- {
- /* let's try *forcing* a re-download of /keys from the exchange.
- Maybe the wallet has seen auditors that we missed. */
- pc->tried_force_keys = true;
- pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange,
- pc->wm->wire_method,
- GNUNET_YES,
- &process_pay_with_exchange,
- pc);
- if (NULL != pc->fo)
- return;
- }
+ 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. */
- age_verification_required = 0 < pc->minimum_age &&
- 0 < denom_details->key.age_mask.bits;
+ is_age_restricted_denom = (0 != denom_details->key.age_mask.bits);
- if (age_verification_required)
+ if (is_age_restricted_denom &&
+ (0 < pc->minimum_age))
{
+ /* Minimum age given and restricted coin provided: We need to verify the
+ * minimum age */
unsigned int code = 0;
- if (NULL == dc->age_commitment)
+ if (dc->no_age_commitment)
{
+ GNUNET_break_op (0);
code = TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_MISSING;
goto AGE_FAIL;
}
-
- dc->age_commitment->mask = denom_details->key.age_mask;
- if ((dc->age_commitment->num + 1) !=
- __builtin_popcount (dc->age_commitment->mask.bits))
+ dc->age_commitment.mask = denom_details->key.age_mask;
+ if (((int) (dc->age_commitment.num + 1)) !=
+ __builtin_popcount (dc->age_commitment.mask.bits))
{
+ GNUNET_break_op (0);
code =
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_SIZE_MISMATCH;
goto AGE_FAIL;
}
-
if (GNUNET_OK !=
TALER_age_commitment_verify (
- dc->age_commitment,
+ &dc->age_commitment,
pc->minimum_age,
&dc->minimum_age_sig))
code = TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_VERIFICATION_FAILED;
-
AGE_FAIL:
if (0 < code)
{
+ GNUNET_break_op (0);
+ GNUNET_free (dc->age_commitment.keys);
resume_pay_with_response (
pc,
MHD_HTTP_BAD_REQUEST,
@@ -1224,35 +1090,86 @@ AGE_FAIL:
/* Age restriction successfully verified!
* Calculate the hash of the age commitment. */
- TALER_age_commitment_hash (dc->age_commitment,
- &h_age_commitment);
+ TALER_age_commitment_hash (&dc->age_commitment,
+ &dc->cdd.h_age_commitment);
+ GNUNET_free (dc->age_commitment.keys);
}
+ 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. */
+ 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_AGE_COMMITMENT_HASH_MISSING),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &denom_details->h_key)));
+ return;
+ }
+ group_size++;
+ }
- dc->deposit_fee = denom_details->fees.deposit;
- dc->refund_fee = denom_details->fees.refund;
- dc->wire_fee = *wire_fee;
- GNUNET_assert (NULL != pc->wm);
- TMH_db->preflight (TMH_db->cls);
- dc->dh = TALER_EXCHANGE_deposit (exchange_handle,
- &dc->amount_with_fee,
- pc->wire_transfer_deadline,
- pc->wm->payto_uri,
- &pc->wm->wire_salt,
- &pc->h_contract_terms,
- age_verification_required ?
- &h_age_commitment: NULL,
- NULL, /* FIXME oec: extension json blob */
- &dc->coin_pub,
- &dc->ub_sig,
- &denom_details->key,
- pc->timestamp,
- &hc->instance->merchant_pub,
- pc->refund_deadline,
- &dc->coin_sig,
- &deposit_cb,
- dc,
- &ec);
- if (NULL == dc->dh)
+ if (0 == group_size)
+ {
+ GNUNET_break (0);
+ 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)
+ {
+ pc->phase = PP_PAY_TRANSACTION;
+ pay_resume (pc);
+ return;
+ }
+ return;
+ }
+
+ {
+ struct TALER_EXCHANGE_CoinDepositDetail cdds[group_size];
+ struct TALER_EXCHANGE_DepositContractDetail dcd = {
+ .wire_deadline = pc->wire_transfer_deadline,
+ .merchant_payto_uri = pc->wm->payto_uri,
+ .wire_salt = pc->wm->wire_salt,
+ .h_contract_terms = pc->h_contract_terms,
+ .wallet_timestamp = pc->timestamp,
+ .merchant_pub = hc->instance->merchant_pub,
+ .refund_deadline = pc->refund_deadline
+ };
+ enum TALER_ErrorCode ec;
+ size_t off = 0;
+
+ for (size_t i = 0; i<pc->coins_cnt; i++)
+ {
+ struct DepositConfirmation *dc = &pc->dc[i];
+
+ if (dc->found_in_db)
+ continue;
+ if (0 != strcmp (dc->exchange_url,
+ eg->exchange_url))
+ continue;
+ 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 (
+ 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
@@ -1263,56 +1180,238 @@ AGE_FAIL:
TALER_ErrorCode_get_http_status_safe (ec),
TALER_MHD_MAKE_JSON_PACK (
TALER_JSON_pack_ec (ec),
- GNUNET_JSON_pack_uint64 ("coin_idx",
- i)));
+ GNUNET_JSON_pack_string ("exchange_url",
+ eg->exchange_url)));
return;
}
+ pc->pending_at_eg++;
if (TMH_force_audit)
- TALER_EXCHANGE_deposit_force_dc (dc->dh);
- pc->pending_at_ce++;
+ 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++;
+}
+
+
/**
- * Find the exchange we need to talk to for the next
- * pending deposit permission.
+ * Handle a timeout for the processing of the pay request.
*
- * @param pc payment context we are processing
+ * @param cls our `struct PayContext`
*/
static void
-find_next_exchange (struct PayContext *pc)
+handle_pay_timeout (void *cls)
{
- GNUNET_assert (0 == pc->pending_at_ce);
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ 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[in,out] pc payment context we are processing
+ */
+static void
+phase_batch_deposits (struct PayContext *pc)
+{
+ for (unsigned int i = 0; i<pc->num_exchanges; i++)
{
- struct DepositConfirmation *dc = &pc->dc[i];
+ struct ExchangeGroup *eg = pc->egs[i];
+ bool have_coins = false;
- if (dc->found_in_db)
- continue;
+ for (size_t j = 0; j<pc->coins_cnt; j++)
+ {
+ struct DepositConfirmation *dc = &pc->dc[j];
- pc->current_exchange = dc->exchange_url;
- pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange,
- pc->wm->wire_method,
- GNUNET_NO,
- &process_pay_with_exchange,
- pc);
- if (NULL == pc->fo)
+ if (0 != strcmp (eg->exchange_url,
+ pc->dc[j].exchange_url))
+ continue;
+ if (dc->found_in_db)
+ continue;
+ have_coins = true;
+ break;
+ }
+ if (! have_coins)
+ continue; /* no coins left to deposit at this exchange */
+ 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)
+ {
+ pc->phase = PP_PAY_TRANSACTION;
+ pay_resume (pc);
return;
}
- pc->current_exchange = NULL;
- /* We are done with all the HTTP requests, go back and try
- the 'big' database transaction! (It should work now!) */
- GNUNET_assert (0 == pc->pending);
- execute_pay_transaction (pc);
+ /* 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;
}
@@ -1325,7 +1424,6 @@ find_next_exchange (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,
@@ -1333,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];
@@ -1346,17 +1443,30 @@ check_coin_paid (void *cls,
continue; /* processed earlier, skip "expensive" memcmp() */
/* Get matching coin from results*/
if ( (0 != GNUNET_memcmp (coin_pub,
- &dc->coin_pub)) ||
+ &dc->cdd.coin_pub)) ||
(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->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,
@@ -1367,8 +1477,7 @@ check_coin_paid (void *cls,
deposit_fee));
dc->deposit_fee = *deposit_fee;
dc->refund_fee = *refund_fee;
- dc->wire_fee = *wire_fee;
- dc->amount_with_fee = *amount_with_fee;
+ dc->cdd.amount = *amount_with_fee;
dc->found_in_db = true;
pc->pending--;
}
@@ -1400,13 +1509,22 @@ 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];
+
/* Get matching coins from results. */
if (0 != GNUNET_memcmp (coin_pub,
- &dc->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,
@@ -1429,99 +1547,108 @@ 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].amount_with_fee;
-
/**
* 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,
&acc_fee)) ||
(0 >
TALER_amount_add (&acc_amount,
- &dc->amount_with_fee,
+ &dc->cdd.amount,
&acc_amount)) )
{
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 ==
TALER_amount_cmp (&dc->deposit_fee,
- &dc->amount_with_fee))
+ &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",
@@ -1533,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,
@@ -1543,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,
@@ -1599,7 +1704,7 @@ check_payment_sufficient (struct PayContext *pc)
* Sum of fees of *all* the different exchanges of all the coins are
* higher than the fixed limit that the merchant is willing to pay. The
* difference must be paid by the customer.
- *///
+ */
struct TALER_Amount excess_fee;
/* compute fee amount to be covered by customer */
@@ -1614,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;
}
}
@@ -1638,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;
}
@@ -1653,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;
@@ -1682,66 +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);
- }
-}
-
-
-/**
- * Actually perform the payment transaction.
+ * Execute the DB transaction. If required (from
+ * soft/serialization errors), the transaction can be
+ * restarted here.
*
- * @param pc payment transaction to run
+ * @param[in,out] pc payment context to transact
*/
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;
@@ -1750,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()
@@ -1770,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;
@@ -1781,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;
}
@@ -1801,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;
@@ -1829,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;
}
}
@@ -1848,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). */
- find_next_exchange (pc);
+ pc->phase = PP_BATCH_DEPOSITS;
return;
}
@@ -1880,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;
}
}
@@ -1897,7 +1972,35 @@ execute_pay_transaction (struct PayContext *pc)
TMH_OSF_CLAIMED | TMH_OSF_PAID,
pc->timestamp,
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",
+ jhook);
+ json_decref (jhook);
+ if (qs < 0)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return; /* do it again */
+ GNUNET_break (0);
+ 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;
+ }
+ }
{
enum GNUNET_DB_QueryStatus qs;
@@ -1908,494 +2011,622 @@ 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);
}
+ pc->phase = PP_PAYMENT_NOTIFICATION;
+}
+
+
+/**
+ * Function called with information about a coin that was deposited.
+ * Checks if this coin is in our list of deposits as well.
+ *
+ * @param cls closure with our `struct PayContext *`
+ * @param deposit_serial which deposit operation is this about
+ * @param exchange_url URL of the exchange that issued the coin
+ * @param h_wire hash of merchant's wire details
+ * @param deposit_timestamp when was the deposit made
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param coin_pub public key of the coin
+ */
+static void
+deposit_paid_check (
+ void *cls,
+ uint64_t deposit_serial,
+ const char *exchange_url,
+ const struct TALER_MerchantWireHashP *h_wire,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub)
+{
+ struct PayContext *pc = cls;
- /* Generate response (payment successful) */
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
- struct GNUNET_CRYPTO_EddsaSignature sig;
+ struct DepositConfirmation *dci = &pc->dc[i];
- /* Sign on our end (as the payment did go through, even if it may
- have been refunded already) */
+ if ( (0 ==
+ GNUNET_memcmp (&dci->cdd.coin_pub,
+ coin_pub)) &&
+ (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)) )
+ {
+ dci->matched_in_db = true;
+ break;
+ }
+ }
+}
+
+
+/**
+ * Handle case where contract was already paid. Either decides
+ * the payment is idempotent, or refunds the excess payment.
+ *
+ * @param[in,out] pc context we use to handle the payment
+ */
+static void
+phase_contract_paid (struct PayContext *pc)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ bool unmatched = false;
+ json_t *refunds;
+
+ qs = TMH_db->lookup_deposits_by_order (TMH_db->cls,
+ pc->order_serial,
+ &deposit_paid_check,
+ pc);
+ if (qs <= 0)
+ {
+ GNUNET_break (0);
+ 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 (size_t i = 0; i<pc->coins_cnt; i++)
+ {
+ struct DepositConfirmation *dci = &pc->dc[i];
+
+ if (! dci->matched_in_db)
+ unmatched = true;
+ }
+ if (! unmatched)
+ {
+ /* Everything fine, idempotent request */
+ struct TALER_MerchantSignatureP sig;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Idempotent pay request for order `%s', signing again\n",
+ pc->order_id);
TALER_merchant_pay_sign (&pc->h_contract_terms,
&pc->hc->instance->merchant_priv,
&sig);
+ 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,
+ "Client attempted to pay extra for already paid order `%s'\n",
+ pc->order_id);
+ refunds = json_array ();
+ GNUNET_assert (NULL != refunds);
+ for (size_t i = 0; i<pc->coins_cnt; i++)
+ {
+ struct DepositConfirmation *dci = &pc->dc[i];
+ struct TALER_MerchantSignatureP merchant_sig;
- /* Build the response */
- resume_pay_with_response (
- pc,
- MHD_HTTP_OK,
- TALER_MHD_MAKE_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("sig",
- &sig)));
+ if (dci->matched_in_db)
+ continue;
+ TALER_merchant_refund_sign (&dci->cdd.coin_pub,
+ &pc->h_contract_terms,
+ 0, /* rtransaction id */
+ &dci->cdd.amount,
+ &pc->hc->instance->merchant_priv,
+ &merchant_sig);
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ refunds,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto (
+ "coin_pub",
+ &dci->cdd.coin_pub),
+ GNUNET_JSON_pack_data_auto (
+ "merchant_sig",
+ &merchant_sig),
+ TALER_JSON_pack_amount ("amount",
+ &dci->cdd.amount),
+ GNUNET_JSON_pack_uint64 ("rtransaction_id",
+ 0))));
}
+ 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)));
}
/**
- * Try to parse the pay request into the given pay context.
+ * Check the database state for the given order.
* Schedules an error response in the connection on failure.
*
- * @param connection HTTP connection we are receiving payment on
- * @param[in,out] hc context with further information about the request
- * @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
-parse_pay (struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc,
- struct PayContext *pc)
+static void
+phase_check_contract (struct PayContext *pc)
{
- /* First, parse request */
+ /* obtain contract terms */
+ enum GNUNET_DB_QueryStatus qs;
+ bool paid = false;
+
+ if (NULL != pc->contract_terms)
+ {
+ json_decref (pc->contract_terms);
+ pc->contract_terms = 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)
{
- const char *session_id = NULL;
- json_t *coins;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("coins",
- &coins),
+ /* single, read-only SQL statements should never cause
+ serialization problems */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ /* Always report on hard error to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ 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)
+ {
+ 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);
+ 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);
+ pc->phase = PP_CONTRACT_PAID;
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling payment for order `%s' with contract hash `%s'\n",
+ pc->order_id,
+ GNUNET_h2s (&pc->h_contract_terms.hash));
+
+ /* basic sanity check on the contract */
+ if (NULL == json_object_get (pc->contract_terms,
+ "merchant"))
+ {
+ /* invalid contract */
+ 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_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_any ("amount",
+ &pc->amount),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("session_id",
- &session_id),
+ /* This one does not have to be a Web URL */
+ GNUNET_JSON_spec_string ("fulfillment_url",
+ &fulfillment_url),
+ NULL),
+ TALER_JSON_spec_amount_any ("max_fee",
+ &pc->max_fee),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &pc->timestamp),
+ GNUNET_JSON_spec_timestamp ("refund_deadline",
+ &pc->refund_deadline),
+ GNUNET_JSON_spec_timestamp ("pay_deadline",
+ &pc->pay_deadline),
+ GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
+ &pc->wire_transfer_deadline),
+ GNUNET_JSON_spec_fixed_auto ("h_wire",
+ &pc->h_wire),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("minimum_age",
+ &pc->minimum_age),
NULL),
GNUNET_JSON_spec_end ()
};
-
+ enum GNUNET_GenericReturnValue res;
+
+ pc->minimum_age = 0;
+ res = TALER_MHD_parse_internal_json_data (pc->connection,
+ pc->contract_terms,
+ espec);
+ if (NULL != fulfillment_url)
+ pc->fulfillment_url = GNUNET_strdup (fulfillment_url);
+ if (GNUNET_YES != res)
{
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- hc->request_body,
- spec);
- if (GNUNET_YES != res)
- {
- GNUNET_break_op (0);
- return res;
- }
+ GNUNET_break (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 ("");
- }
+ 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;
+ }
- if (! json_is_array (coins))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MISSING,
- "'coins' must be an array"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
+ for (size_t i = 0; i<pc->coins_cnt; i++)
+ {
+ struct DepositConfirmation *dc = &pc->dc[i];
- pc->coins_cnt = json_array_size (coins);
- if (pc->coins_cnt > MAX_COIN_ALLOWED_COINS)
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&dc->cdd.amount,
+ &pc->amount))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES == TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "'coins' array too long"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ pc->amount.currency));
+ return;
}
+ }
- /* note: 1 coin = 1 deposit confirmation expected */
- pc->dc = GNUNET_new_array (pc->coins_cnt,
- struct DepositConfirmation);
+ if (GNUNET_TIME_timestamp_cmp (pc->wire_transfer_deadline,
+ <,
+ pc->refund_deadline))
+ {
+ /* This should already have been checked when creating the order! */
+ 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_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE,
+ NULL));
+ return;
+ }
+ if (GNUNET_TIME_absolute_is_past (pc->pay_deadline.abs_time))
+ {
+ /* too late */
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_GONE,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED,
+ NULL));
+ return;
+ }
- /* 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;
- json_t *age_commitment = NULL;
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &dc->coin_sig),
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &dc->coin_pub),
- TALER_JSON_spec_denom_sig ("ub_sig",
- &dc->ub_sig),
- GNUNET_JSON_spec_fixed_auto ("h_denom",
- &dc->h_denom),
- TALER_JSON_spec_amount ("contribution",
- TMH_currency,
- &dc->amount_with_fee),
- GNUNET_JSON_spec_string ("exchange_url",
- &exchange_url),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("minimum_age_sig",
- &dc->minimum_age_sig),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("age_commitment",
- &age_commitment),
- NULL),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- coin,
- ispec);
- if (GNUNET_YES != res)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return res;
- }
+ /* Make sure wire method (still) exists for this instance */
+ {
+ struct TMH_WireMethod *wm;
- for (unsigned int j = 0; j<coins_index; j++)
- {
- if (0 ==
- GNUNET_memcmp (&dc->coin_pub,
- &pc->dc[j].coin_pub))
- {
- GNUNET_break_op (0);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "duplicate coin in list"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- }
+ wm = pc->hc->instance->wm_head;
+ while (0 != GNUNET_memcmp (&pc->h_wire,
+ &wm->h_wire))
+ wm = wm->next;
+ if (NULL == wm)
+ {
+ 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_WIRE_HASH_UNKNOWN,
+ NULL));
+ return;
+ }
+ pc->wm = wm;
+ }
+ pc->phase = PP_PAY_TRANSACTION;
+}
- dc->exchange_url = GNUNET_strdup (exchange_url);
- dc->index = coins_index;
- dc->pc = pc;
- if (0 !=
- strcasecmp (dc->amount_with_fee.currency,
- TMH_currency))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES == TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_GENERIC_CURRENCY_MISMATCH,
- TMH_currency))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
+/**
+ * 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;
- {
- bool has_commitment = (NULL != age_commitment) &&
- json_is_array (age_commitment);
- bool has_sig = ! GNUNET_is_zero_ (&dc->minimum_age_sig,
- sizeof(dc->minimum_age_sig));
- if (has_sig != has_commitment)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "inconsistency: 'mininum_age_sig' vs. 'age_commitment'")
- )
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
-
- /* Parse the AgeCommitment, i. e. the public keys */
- if (has_commitment)
- {
- json_t *pk;
- unsigned int idx;
- size_t num = json_array_size (age_commitment);
-
- /* Sanity check the amount of AgeCommitment's public keys. The
- * actual check will be performed once we now the denominations. */
- if (32 <= num)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "'age_commitment' too large"
- ))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
-
- dc->age_commitment = GNUNET_new (struct TALER_AgeCommitment);
- dc->age_commitment->num = num;
- dc->age_commitment->keys =
- GNUNET_new_array (num,
- struct TALER_AgeCommitmentPublicKeyP);
- /* Note that dc->age_commitment.mask will be set later, based on
- * the actual denomination. */
-
- json_array_foreach (age_commitment, idx, pk) {
- struct GNUNET_JSON_Specification pkspec[] = {
- GNUNET_JSON_spec_fixed_auto (NULL,
- &dc
- ->age_commitment
- ->keys[idx].pub),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (pk,
- pkspec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "age_commitment"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- }
- }
- }
- }
+ 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;
}
- GNUNET_JSON_parse_free (spec);
}
- /* obtain contract terms */
+ /* copy session ID (if set) */
+ if (NULL != session_id)
{
- enum GNUNET_DB_QueryStatus qs;
- json_t *contract_terms = NULL;
-
- qs = TMH_db->lookup_contract_terms (TMH_db->cls,
- hc->instance->settings.id,
- pc->order_id,
- &contract_terms,
- &pc->order_serial,
- 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 to enable diagnostics */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "contract terms"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- return (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
- pc->order_id))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
+ 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;
+ }
- /* hash contract (needed later) */
- if (GNUNET_OK !=
- TALER_JSON_contract_hash (contract_terms,
- &pc->h_contract_terms))
- {
- GNUNET_break (0);
- json_decref (contract_terms);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
- NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Handling payment for order `%s' with contract hash `%s'\n",
- pc->order_id,
- GNUNET_h2s (&pc->h_contract_terms.hash));
+ /* note: 1 coin = 1 deposit confirmation expected */
+ pc->dc = GNUNET_new_array (pc->coins_cnt,
+ struct DepositConfirmation);
- /* basic sanity check on the contract */
- if (NULL == json_object_get (contract_terms,
- "merchant"))
- {
- /* invalid contract */
- GNUNET_break (0);
- json_decref (contract_terms);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_MERCHANT_FIELD_MISSING,
- NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
+ /* This loop populates the array 'dc' in 'pc' */
+ {
+ unsigned int coins_index;
+ json_t *coin;
- /* Get details from contract and check fundamentals */
+ json_array_foreach (coins, coins_index, coin)
{
- const char *fulfillment_url = NULL;
- struct GNUNET_JSON_Specification espec[] = {
- TALER_JSON_spec_amount ("amount",
- TMH_currency,
- &pc->amount),
+ 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 (
- 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),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &pc->timestamp),
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &pc->refund_deadline),
- GNUNET_JSON_spec_timestamp ("pay_deadline",
- &pc->pay_deadline),
- GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
- &pc->wire_transfer_deadline),
- GNUNET_JSON_spec_fixed_auto ("h_wire",
- &pc->h_wire),
+ 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_uint32 ("minimum_age",
- &pc->minimum_age),
- NULL),
+ 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;
- pc->minimum_age = 0;
-
- res = TALER_MHD_parse_internal_json_data (connection,
- contract_terms,
- espec);
- if (NULL != fulfillment_url)
- pc->fulfillment_url = GNUNET_strdup (fulfillment_url);
- json_decref (contract_terms);
+ res = TALER_MHD_parse_json_data (pc->connection,
+ coin,
+ ispec);
if (GNUNET_YES != res)
{
- GNUNET_break (0);
- return 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;
+ }
}
- }
-
- if (GNUNET_TIME_timestamp_cmp (pc->wire_transfer_deadline,
- <,
- pc->refund_deadline))
- {
- /* This should already have been checked when creating the order! */
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE,
- NULL);
- }
- if (GNUNET_TIME_absolute_is_past (pc->pay_deadline.abs_time))
- {
- /* too late */
- return (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_GONE,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED,
- NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- }
+ dc->exchange_url = GNUNET_strdup (exchange_url);
+ dc->index = coins_index;
+ dc->pc = pc;
- /* Make sure wire method (still) exists for this instance */
- {
- struct TMH_WireMethod *wm;
+ /* 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;
+ }
- wm = hc->instance->wm_head;
- while (0 != GNUNET_memcmp (&pc->h_wire,
- &wm->h_wire))
- wm = wm->next;
- if (NULL == wm)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_HASH_UNKNOWN,
- NULL);
+ /* 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->wm = wm;
- }
-
- if (0 < pc->minimum_age)
- {
- /* TODO oec: check
- * 1. denomination are age restricted
- * 2. consume their mask
- * 3. valididate minimum age
- */
}
-
- return GNUNET_OK;
+ 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_DEBUG,
- "Resuming pay with error after timeout\n");
- if (NULL != pc->fo)
+ if (NULL != pc->timeout_task)
+ {
+ GNUNET_SCHEDULER_cancel (pc->timeout_task);
+ pc->timeout_task = NULL;
+ }
+ if (NULL != pc->contract_terms)
{
- TMH_EXCHANGES_find_exchange_cancel (pc->fo);
- pc->fo = NULL;
+ json_decref (pc->contract_terms);
+ pc->contract_terms = NULL;
}
- resume_pay_with_error (pc,
- MHD_HTTP_GATEWAY_TIMEOUT,
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
- 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);
}
@@ -2407,58 +2638,74 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh,
struct PayContext *pc = hc->ctx;
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;
+ GNUNET_CONTAINER_DLL_insert (pc_head,
+ pc_tail,
+ pc);
}
- 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)
+ while (1)
{
- /* We are *done* processing the request, just queue the response (!) */
- if (UINT_MAX == pc->response_code)
+ 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);
- }
- {
- enum GNUNET_GenericReturnValue ret;
-
- ret = parse_pay (connection,
- hc,
- 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 b6aebf71..134cd2ee 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);
}
@@ -580,7 +565,8 @@ TMH_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
enum GNUNET_GenericReturnValue res;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("h_contract", &prd->h_contract_terms),
+ GNUNET_JSON_spec_fixed_auto ("h_contract",
+ &prd->h_contract_terms),
GNUNET_JSON_spec_end ()
};
res = TALER_MHD_parse_json_data (connection,
@@ -665,7 +651,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)
@@ -681,8 +667,9 @@ TMH_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
}
{
- GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (TMH_currency,
- &prd->refund_amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TMH_currency,
+ &prd->refund_amount));
qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
hc->instance->settings.id,
&prd->h_contract_terms,
@@ -722,9 +709,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 7d3f7806..00000000
--- a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
+++ /dev/null
@@ -1,998 +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);
- 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;
- 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;
-
- 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
new file mode 100644
index 00000000..cdaf917e
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_post-using-templates.c
@@ -0,0 +1,301 @@
+/*
+ This file is part of TALER
+ (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
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received 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-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"
+#include "taler-merchant-httpd_private-post-orders.h"
+#include "taler-merchant-httpd_helper.h"
+#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;
+ const char *template_id = hc->infix;
+ const char *summary = NULL;
+ const char *fulfillment_url = NULL;
+ const char *fulfillment_message = NULL;
+ struct TALER_Amount amount;
+ bool no_amount;
+ json_t *fake_body;
+ bool no_summary;
+ 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_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;
+
+ 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;
+ }
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->lookup_template (TMH_db->cls,
+ mi->settings.id,
+ template_id,
+ &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);
+ 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);
+ 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);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* all good */
+ break;
+ } /* End of the switch */
+ }
+
+ {
+ /* 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 ()
+ };
+
+ {
+ 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);
+ }
+ }
+
+ 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 ( (! 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 (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 ( (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 ( (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))
+ ))
+ );
+ }
+
+ 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-get-reserves-ID.h b/src/backend/taler-merchant-httpd_post-using-templates.h
index d72805c4..2f788c40 100644
--- a/src/backend/taler-merchant-httpd_private-get-reserves-ID.h
+++ b/src/backend/taler-merchant-httpd_post-using-templates.h
@@ -1,6 +1,6 @@
/*
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
@@ -14,26 +14,27 @@
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_post-using-templates.h
+ * @brief headers for POST /using-templates handler
+ * @author Priscilla Huang
*/
-#ifndef TALER_MERCHANT_HTTPD_GET_RESERVES_ID_H
-#define TALER_MERCHANT_HTTPD_GET_RESERVES_ID_H
-#include <microhttpd.h>
+#ifndef TALER_MERCHANT_HTTPD_POST_USING_TEMPLATES_H
+#define TALER_MERCHANT_HTTPD_POST_USING_TEMPLATES_H
+
#include "taler-merchant-httpd.h"
/**
- * Manages a GET /reserves/$RESERVE_PUB call.
+ * Generate a template that customer can use it. Returns an MHD_RESULT.
*
- * @param rh context of the handler
+ * @param rh details about this request 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,
+TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc);
+
#endif
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 9926d0b3..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:
@@ -92,6 +104,7 @@ TMH_private_delete_instances_ID (const struct TMH_RequestHandler *rh,
{
struct TMH_MerchantInstance *mi = hc->instance;
+ (void) rh;
return delete_instances_ID (mi,
connection);
}
@@ -104,6 +117,7 @@ TMH_private_delete_instances_default_ID (const struct TMH_RequestHandler *rh,
{
struct TMH_MerchantInstance *mi;
+ (void) rh;
mi = TMH_lookup_instance (hc->infix);
if (NULL == mi)
{
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 9eaa8b0e..cd8aa10c 100644
--- a/src/backend/taler-merchant-httpd_private-delete-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_private-delete-orders-ID.c
@@ -38,11 +38,23 @@ TMH_private_delete_orders_ID (const struct TMH_RequestHandler *rh,
{
struct TMH_MerchantInstance *mi = hc->instance;
enum GNUNET_DB_QueryStatus qs;
+ const char *force_s;
+ bool force;
+
+ (void) rh;
+ force_s = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "force");
+ if (NULL == force_s)
+ force_s = "no";
+ force = (0 == strcasecmp (force_s,
+ "yes"));
GNUNET_assert (NULL != mi);
qs = TMH_db->delete_order (TMH_db->cls,
mi->settings.id,
- hc->infix);
+ hc->infix,
+ force);
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
qs = TMH_db->delete_contract_terms (TMH_db->cls,
mi->settings.id,
@@ -64,6 +76,10 @@ TMH_private_delete_orders_ID (const struct TMH_RequestHandler *rh,
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
{
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,
@@ -71,27 +87,34 @@ TMH_private_delete_orders_ID (const struct TMH_RequestHandler *rh,
NULL,
&unused,
NULL);
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- uint64_t order_serial;
-
- qs = TMH_db->lookup_contract_terms (TMH_db->cls,
- mi->settings.id,
- hc->infix,
- NULL,
- &order_serial,
- NULL);
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ 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,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
+ hc->infix);
+ if (paid)
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_DELETE_ORDERS_ALREADY_PAID,
+ hc->infix);
return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_DELETE_ORDERS_AWAITING_PAYMENT,
hc->infix);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_DELETE_ORDERS_AWAITING_PAYMENT,
- hc->infix);
+ }
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
return TALER_MHD_reply_static (connection,
MHD_HTTP_NO_CONTENT,
diff --git a/src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.c b/src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.c
new file mode 100644
index 00000000..b147b84f
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.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-delete-otp-devices-ID.c
+ * @brief implement DELETE /otp-devices/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-delete-otp-devices-ID.h"
+#include <taler/taler_json_lib.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)
+{
+ 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_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,
+ "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_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_GENERIC_TEMPLATE_UNKNOWN,
+ hc->infix);
+ 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-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-products-ID.c b/src/backend/taler-merchant-httpd_private-delete-products-ID.c
index bb7964aa..7d314785 100644
--- a/src/backend/taler-merchant-httpd_private-delete-products-ID.c
+++ b/src/backend/taler-merchant-httpd_private-delete-products-ID.c
@@ -39,6 +39,7 @@ TMH_private_delete_products_ID (const struct TMH_RequestHandler *rh,
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_product (TMH_db->cls,
diff --git a/src/backend/taler-merchant-httpd_private-delete-templates-ID.c b/src/backend/taler-merchant-httpd_private-delete-templates-ID.c
new file mode 100644
index 00000000..9602b652
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-templates-ID.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-delete-templates-ID.c
+ * @brief implement DELETE /templates/$ID
+ * @author Priscilla HUANG
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-delete-templates-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a DELETE "/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_delete_templates_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;
+
+ (void) rh;
+ GNUNET_assert (NULL != mi);
+ GNUNET_assert (NULL != hc->infix);
+ qs = TMH_db->delete_template (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_template");
+ 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_template (soft)");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
+ hc->infix);
+ 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-templates-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-delete-templates-ID.h b/src/backend/taler-merchant-httpd_private-delete-templates-ID.h
new file mode 100644
index 00000000..21fb46ac
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-templates-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-templates-ID.h
+ * @brief implement DELETE /templates/$ID/
+ * @author Priscilla HUANG
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_TEMPLATES_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_TEMPLATES_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a DELETE "/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_delete_templates_ID (const struct TMH_RequestHandler *rh,
+ 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-delete-transfers-ID.c b/src/backend/taler-merchant-httpd_private-delete-transfers-ID.c
index 93656f4a..9b4412ba 100644
--- a/src/backend/taler-merchant-httpd_private-delete-transfers-ID.c
+++ b/src/backend/taler-merchant-httpd_private-delete-transfers-ID.c
@@ -23,14 +23,6 @@
#include <taler/taler_json_lib.h>
-/**
- * Handle a DELETE "/private/transfers/$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_transfers_ID (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
@@ -41,6 +33,7 @@ TMH_private_delete_transfers_ID (const struct TMH_RequestHandler *rh,
unsigned long long serial;
char dummy;
+ (void) rh;
GNUNET_assert (NULL != mi);
if (1 !=
sscanf (hc->infix,
diff --git a/src/backend/taler-merchant-httpd_private-delete-reserves-ID.c b/src/backend/taler-merchant-httpd_private-delete-webhooks-ID.c
index c391ed0e..e8e2d283 100644
--- a/src/backend/taler-merchant-httpd_private-delete-reserves-ID.c
+++ b/src/backend/taler-merchant-httpd_private-delete-webhooks-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
- * @author Christian Grothoff
+ * @file taler-merchant-httpd_private-delete-webhooks-ID.c
+ * @brief implement DELETE /webhooks/$ID
+ * @author Priscilla HUANG
*/
#include "platform.h"
-#include "taler-merchant-httpd_private-delete-reserves-ID.h"
+#include "taler-merchant-httpd_private-delete-webhooks-ID.h"
#include <taler/taler_json_lib.h>
/**
- * Handle a DELETE "/reserves/$ID" request.
+ * Handle a DELETE "/webhooks/$ID" request.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
@@ -32,58 +32,36 @@
* @return MHD result code
*/
MHD_RESULT
-TMH_private_delete_reserves_ID (const struct TMH_RequestHandler *rh,
+TMH_private_delete_webhooks_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;
- 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_MERCHANT_GENERIC_RESERVE_PUB_MALFORMED,
- hc->infix);
- }
- purge = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "purge");
+ (void) rh;
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_webhook (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_webhook");
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_webhook (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_WEBHOOK_UNKNOWN,
hc->infix);
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
return TALER_MHD_reply_static (connection,
@@ -97,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-webhooks-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-delete-webhooks-ID.h b/src/backend/taler-merchant-httpd_private-delete-webhooks-ID.h
new file mode 100644
index 00000000..caf7caba
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-webhooks-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-webhooks-ID.h
+ * @brief implement DELETE /webhooks/$ID/
+ * @author Priscilla HUANG
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_WEBHOOKS_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_WEBHOOKS_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a DELETE "/webhooks/$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_webhooks_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-delete-webhooks-ID.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-accounts-ID.h b/src/backend/taler-merchant-httpd_private-get-accounts-ID.h
new file mode 100644
index 00000000..da5cb729
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-accounts-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-get-accounts-ID.h
+ * @brief implement GET /accounts/$ID/
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_ACCOUNTS_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_ACCOUNTS_ID_H
+
+#include "taler-merchant-httpd.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);
+
+/* 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 e0e2cd39..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
@@ -35,6 +35,18 @@
*/
#define STALE_KYC_TIMEOUT GNUNET_TIME_UNIT_MONTHS
+/**
+ * How long should clients cache a KYC failure response?
+ */
+#define EXPIRATION_KYC_FAILURE GNUNET_TIME_relative_multiply ( \
+ 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)
+
/**
* Information we keep per /kyc request.
@@ -61,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.
@@ -98,10 +110,6 @@ struct ExchangeKycRequest
*/
struct GNUNET_TIME_Timestamp last_check;
- /**
- * Last KYC status returned by the exchange.
- */
- bool kyc_ok;
};
@@ -184,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".
@@ -205,6 +213,13 @@ struct KycContext
* True if @e h_wire was given.
*/
bool have_h_wire;
+
+ /**
+ * We're still waiting on the exchange to determine
+ * the KYC status of our deposit(s).
+ */
+ bool kyc_serial_pending;
+
};
@@ -263,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);
@@ -290,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
@@ -303,8 +318,44 @@ resume_kyc_with_response (struct KycContext *kc,
unsigned int response_code,
struct MHD_Response *response)
{
+ char dat[128];
+
kc->response_code = response_code;
kc->response = response;
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ /* KYC failed, cache briefly */
+ TALER_MHD_get_date_string (GNUNET_TIME_relative_to_absolute (
+ EXPIRATION_KYC_FAILURE),
+ dat);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_EXPIRES,
+ dat));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "max-age=300"));
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ /* KYC passed, cache for a long time! */
+ TALER_MHD_get_date_string (GNUNET_TIME_relative_to_absolute (
+ EXPIRATION_KYC_SUCCESS),
+ dat);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_EXPIRES,
+ dat));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "max-age=3600"));
+ break;
+ case MHD_HTTP_BAD_GATEWAY:
+ case MHD_HTTP_GATEWAY_TIMEOUT:
+ break; /* no caching */
+ }
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Resuming /kyc handling as exchange interaction is done (%u)\n",
response_code);
@@ -344,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 (
@@ -377,6 +428,53 @@ 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.
+ *
+ * @param[in] ekr key request that is done (and will be freed)
+ */
+static void
+ekr_finished (struct ExchangeKycRequest *ekr)
+{
+ struct KycContext *kc = ekr->kc;
+
+ GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head,
+ kc->exchange_pending_tail,
+ ekr);
+ GNUNET_free (ekr->exchange_url);
+ GNUNET_free (ekr->payto_uri);
+ GNUNET_free (ekr);
+ if (NULL != kc->exchange_pending_head)
+ return; /* wait for more */
+ /* All exchange requests done, create final
+ big response from cumulated replies */
+ if ( (0 == json_array_size (kc->pending_kycs)) &&
+ (0 == json_array_size (kc->timeout_kycs)) )
+ {
+ /* special case: all KYC operations did succeed
+ after we asked at the exchanges => 204 */
+ struct MHD_Response *response;
+
+ response = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ resume_kyc_with_response (kc,
+ MHD_HTTP_NO_CONTENT,
+ response);
+ return;
+ }
+ resume_kyc_with_response (
+ kc,
+ kc->response_code, /* MHD_HTTP_OK or MHD_HTTP_BAD_GATEWAY */
+ TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_array_incref ("pending_kycs",
+ kc->pending_kycs),
+ GNUNET_JSON_pack_array_incref ("timeout_kycs",
+ kc->timeout_kycs)));
+}
+
+
+/**
* Function called with the result of a KYC check.
*
* @param cls a `struct ExchangeKycRequest *`
@@ -396,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.kyc_ok.exchange_sig,
- &ks->details.kyc_ok.exchange_pub,
- ks->details.kyc_ok.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,
@@ -413,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.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;
@@ -439,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,
@@ -464,82 +639,61 @@ exchange_check_cb (void *cls,
ks->ec),
GNUNET_JSON_pack_uint64 ("exchange_http_status",
ks->http_status))));
- break;
- }
- GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head,
- kc->exchange_pending_tail,
- ekr);
- GNUNET_free (ekr->exchange_url);
- GNUNET_free (ekr->payto_uri);
- GNUNET_free (ekr);
- if (NULL != kc->exchange_pending_head)
- return; /* wait for more */
- /* All exchange requests done, create final
- big response from cummulated replies */
- if ( (0 == json_array_size (kc->pending_kycs)) &&
- (0 == json_array_size (kc->timeout_kycs)) )
- {
- /* special case: all KYC operations did succeed
- after we asked at the exchanges => 204 */
- struct MHD_Response *response;
-
- response = MHD_create_response_from_buffer (0,
- "",
- MHD_RESPMEM_PERSISTENT);
- resume_kyc_with_response (kc,
- MHD_HTTP_NO_CONTENT,
- response);
- return;
}
- resume_kyc_with_response (
- kc,
- kc->response_code, /* MHD_HTTP_OK or MHD_HTTP_BAD_GATEWAY */
- TALER_MHD_MAKE_JSON_PACK (
- GNUNET_JSON_pack_array_incref ("pending_kycs",
- kc->pending_kycs),
- GNUNET_JSON_pack_array_incref ("timeout_kycs",
- kc->timeout_kycs)));
+ ekr_finished (ekr);
}
/**
- * 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) exchange;
ekr->fo = NULL;
+ if (NULL == keys)
+ {
+ kc->response_code = MHD_HTTP_BAD_GATEWAY;
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ kc->timeout_kycs,
+ GNUNET_JSON_PACK (
+ 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,
- ekr->exchange_kyc_serial,
- &h_payto,
- kc->timeout,
- &exchange_check_cb,
- ekr);
+ ekr->kyc = TALER_EXCHANGE_kyc_check (
+ TMH_curl_ctx,
+ ekr->exchange_url,
+ keys,
+ ekr->exchange_kyc_serial,
+ &h_payto,
+ 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
@@ -548,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,
@@ -556,17 +711,24 @@ 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),
<,
STALE_KYC_TIMEOUT)) )
return; /* KYC ok, ignore! */
+ if (0 == exchange_kyc_serial)
+ {
+ kc->kyc_serial_pending = true;
+ return;
+ }
kc->response_code = MHD_HTTP_ACCEPTED;
ekr = GNUNET_new (struct ExchangeKycRequest);
GNUNET_CONTAINER_DLL_insert (kc->exchange_pending_head,
@@ -577,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);
}
@@ -618,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,
@@ -672,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;
@@ -718,6 +830,16 @@ get_instances_ID_kyc (struct TMH_MerchantInstance *mi,
"account_kyc_get_status");
}
}
+ if (kc->kyc_serial_pending)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange legitimization UUID unknown, assuming KYC pending\n");
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ GNUNET_JSON_pack_string ("hint",
+ "awaiting legitimization UUID"));
+ }
if (NULL == kc->exchange_pending_head)
return TALER_MHD_reply_static (connection,
MHD_HTTP_NO_CONTENT,
@@ -758,6 +880,7 @@ TMH_private_get_instances_ID_kyc (const struct TMH_RequestHandler *rh,
{
struct TMH_MerchantInstance *mi = hc->instance;
+ (void) rh;
return get_instances_ID_kyc (mi,
connection,
hc);
@@ -771,6 +894,7 @@ TMH_private_get_instances_default_ID_kyc (const struct TMH_RequestHandler *rh,
{
struct TMH_MerchantInstance *mi;
+ (void) rh;
mi = TMH_lookup_instance (hc->infix);
if (NULL == mi)
{
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 3eedcd83..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
@@ -29,7 +29,7 @@
* @param value a `struct TMH_MerchantInstance *`
* @return #GNUNET_OK (continue to iterate)
*/
-static int
+static enum GNUNET_GenericReturnValue
add_instance (void *cls,
const struct GNUNET_HashCode *key,
void *value)
@@ -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)),
@@ -107,6 +110,7 @@ TMH_private_get_instances (const struct TMH_RequestHandler *rh,
{
json_t *ia;
+ (void) rh;
(void) hc;
ia = json_array ();
GNUNET_assert (NULL != ia);
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 828d1d65..98653997 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,30 @@ 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;
+
+ /**
+ * Timestamp of the last payment.
+ */
+ struct GNUNET_TIME_Timestamp last_payment;
+
+ /**
+ * 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 +340,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 +366,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 +437,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 +454,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,353 +469,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;
+}
+
+
+/**
+ * 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
+ };
- gorc->tt = NULL;
- gorc_resume (gorc,
- MHD_HTTP_REQUEST_TIMEOUT,
- TALER_EC_GENERIC_TIMEOUT);
+ 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 hr HTTP response data
- * @param dd details about the deposit (NULL on errors)
+ * @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.payment_target_uuid,
- 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);
+
}
@@ -758,30 +1001,37 @@ gorc_cleanup (void *cls)
* @param pending true if the this refund was not yet processed by the wallet/exchange
*/
static void
-process_refunds_cb (void *cls,
- uint64_t refund_serial,
- struct GNUNET_TIME_Timestamp timestamp,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const char *exchange_url,
- uint64_t rtransaction_id,
- const char *reason,
- const struct TALER_Amount *refund_amount,
- bool pending)
+process_refunds_cb (
+ void *cls,
+ uint64_t refund_serial,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const char *exchange_url,
+ uint64_t rtransaction_id,
+ const char *reason,
+ const struct TALER_Amount *refund_amount,
+ bool pending)
{
struct GetOrderRequestContext *gorc = cls;
- GNUNET_assert (0 ==
- json_array_append_new (
- gorc->refund_details,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("amount",
- refund_amount),
- GNUNET_JSON_pack_bool ("pending",
- pending),
- GNUNET_JSON_pack_timestamp ("timestamp",
- timestamp),
- GNUNET_JSON_pack_string ("reason",
- reason))));
+ 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,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("amount",
+ refund_amount),
+ GNUNET_JSON_pack_bool ("pending",
+ pending),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ timestamp),
+ GNUNET_JSON_pack_string ("reason",
+ reason))));
/* For refunded coins, we are not charged deposit fees, so subtract those
again */
for (struct TransferQuery *tq = gorc->tq_head;
@@ -792,12 +1042,30 @@ process_refunds_cb (void *cls,
GNUNET_memcmp (&tq->coin_pub,
coin_pub))
{
- 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->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,
@@ -808,6 +1076,132 @@ 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 h_wire hash of the merchant's wire account into which the deposit was made
+ * @param deposit_timestamp when was the deposit made
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @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,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
+ 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));
+ gorc->last_payment
+ = GNUNET_TIME_timestamp_max (gorc->last_payment,
+ deposit_timestamp);
+ 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.
*
@@ -821,32 +1215,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,
@@ -864,647 +1269,296 @@ 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);
- qs = TMH_db->lookup_contract_terms (TMH_db->cls,
- hc->instance->settings.id,
- hc->infix,
- &gorc->contract_terms,
- &gorc->order_serial,
- 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) )
- {
- 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)
+ /* 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))
{
- 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"));
+ 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 &&
- (! wired) &&
- gorc->transfer_status_requested)
+ if (GNUNET_TIME_absolute_is_zero (gorc->last_payment.abs_time))
{
- /* 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;
- }
+ GNUNET_break (GNUNET_YES ==
+ TALER_amount_is_zero (&gorc->contract_amount));
+ gorc->last_payment = gorc->timestamp;
}
+ 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_timestamp ("last_payment",
+ gorc->last_payment),
+ 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) &&
- (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;
- }
- if (! paid)
- {
- /* User never paid for this order */
- char *taler_pay_uri;
- char *order_status_url;
- MHD_RESULT 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));
+}
- 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;
- }
- /* 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");
- }
+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;
- /* Generate final reply, including wire details if we have them */
+ 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 e6f615ea..4c6a104e 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));
}
@@ -359,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",
@@ -381,19 +406,33 @@ add_order (void *cls,
return;
}
+ if (TALER_amount_is_zero (&order_amount) &&
+ (po->of.wired != TALER_EXCHANGE_YNA_ALL) )
+ {
+ /* If we are actually filtering by wire status,
+ and the order was over an amount of zero,
+ do not return it as wire status is not
+ exactly meaningful for orders over zero. */
+ json_decref (contract_terms);
+ GNUNET_free (order_id);
+ return;
+ }
+
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);
@@ -402,7 +441,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;
}
@@ -600,7 +647,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)
{
@@ -620,11 +666,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,
@@ -632,7 +686,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,
@@ -640,63 +694,41 @@ 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");
}
- // FIXME: use date_s, as we round to seconds anyway!
{
- const char *date_ms_str;
+ const char *date_s_str;
- date_ms_str = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "date_ms");
- if (NULL == date_ms_str)
+ date_s_str = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "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
{
@@ -704,7 +736,7 @@ TMH_private_get_orders (const struct TMH_RequestHandler *rh,
unsigned long long ll;
if (1 !=
- sscanf (date_ms_str,
+ sscanf (date_s_str,
"%llu%c",
&ll,
&dummy))
@@ -716,111 +748,68 @@ TMH_private_get_orders (const struct TMH_RequestHandler *rh,
"date_ms");
}
- of.date = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_from_ms (ll));
- if (GNUNET_TIME_absolute_is_never (of.date.abs_time))
+ po->of.date = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_from_s (ll));
+ if (GNUNET_TIME_absolute_is_never (po->of.date.abs_time))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "date_ms");
+ "date_s");
}
}
}
+ 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,
- "%lld%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)
@@ -837,7 +826,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;
@@ -848,8 +837,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-tips-ID.h b/src/backend/taler-merchant-httpd_private-get-otp-devices.h
index a99dc365..a97ca179 100644
--- a/src/backend/taler-merchant-httpd_private-get-tips-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,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-otp-devices.h
+ * @brief implement GET /otp-devices
* @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_OTP_DEVICES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_OTP_DEVICES_H
+
#include "taler-merchant-httpd.h"
/**
- * Manages a GET /tips/$ID call, returning the status of the tip.
+ * Handle a GET "/otp-devices" 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_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 6c683887..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,19 +39,13 @@ 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))));
}
-/**
- * Handle a GET "/private/products" 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_products (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
@@ -58,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 5b1481a7..00000000
--- a/src/backend/taler-merchant-httpd_private-get-reserves-ID.c
+++ /dev/null
@@ -1,227 +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;
-
- 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 be0925d3..00000000
--- a/src/backend/taler-merchant-httpd_private-get-reserves.c
+++ /dev/null
@@ -1,145 +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;
-
- 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
new file mode 100644
index 00000000..35fdd1d0
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-templates-ID.c
@@ -0,0 +1,83 @@
+/*
+ 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-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_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 };
+ 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_string ("template_description",
+ tp.template_description),
+ GNUNET_JSON_pack_allow_null (
+ 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;
+ }
+}
+
+
+/* end of taler-merchant-httpd_private-get-templates-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-templates-ID.h b/src/backend/taler-merchant-httpd_private-get-templates-ID.h
new file mode 100644
index 00000000..fcdd6a2e
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-templates-ID.h
@@ -0,0 +1,42 @@
+/*
+ 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-templates-ID.h
+ * @brief implement GET /templates/$ID/
+ * @author Priscilla HUANG
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_TEMPLATES_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_TEMPLATES_ID_H
+
+#include "taler-merchant-httpd.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);
+
+/* 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
new file mode 100644
index 00000000..d0bec884
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-templates.c
@@ -0,0 +1,79 @@
+/*
+ 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-templates.c
+ * @brief implement GET /templates
+ * @author Priscilla HUANG
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-templates.h"
+
+
+/**
+ * Add template details to our JSON array.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param template_id ID of the template
+ * @param template_description human-readable description for the template
+ */
+static void
+add_template (void *cls,
+ const char *template_id,
+ const char *template_description)
+{
+ json_t *pa = cls;
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ pa,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("template_id", template_id),
+ GNUNET_JSON_pack_string ("template_description",
+ template_description))));
+}
+
+
+MHD_RESULT
+TMH_private_get_templates (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_templates (TMH_db->cls,
+ hc->instance->settings.id,
+ &add_template,
+ 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 ("templates",
+ pa));
+}
+
+
+/* end of taler-merchant-httpd_private-get-templates.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-templates.h b/src/backend/taler-merchant-httpd_private-get-templates.h
new file mode 100644
index 00000000..d791bba7
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-templates.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-get-templates.h
+ * @brief implement GET /templates
+ * @author Priscilla HUANG
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_TEMPLATES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_TEMPLATES_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a GET "/templates" 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 (const struct TMH_RequestHandler *rh,
+ 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 9b2e0de2..00000000
--- a/src/backend/taler-merchant-httpd_private-get-tips-ID.c
+++ /dev/null
@@ -1,155 +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;
-
- GNUNET_assert (NULL != hc->infix);
- 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 4f358be2..00000000
--- a/src/backend/taler-merchant-httpd_private-get-tips.c
+++ /dev/null
@@ -1,156 +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;
-
- 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 03a7beaa..3e540297 100644
--- a/src/backend/taler-merchant-httpd_private-get-transfers.c
+++ b/src/backend/taler-merchant-httpd_private-get-transfers.c
@@ -103,6 +103,7 @@ TMH_private_get_transfers (const struct TMH_RequestHandler *rh,
uint64_t offset;
enum TALER_EXCHANGE_YesNoAll verified;
+ (void) rh;
payto_uri = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"payto_uri");
@@ -136,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
new file mode 100644
index 00000000..e1566efd
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-webhooks-ID.c
@@ -0,0 +1,92 @@
+/*
+ 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-webhooks-ID.c
+ * @brief implement GET /webhooks/$ID
+ * @author Priscilla HUANG
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-webhooks-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a GET "/webhooks/$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_webhooks_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ struct TALER_MERCHANTDB_WebhookDetails wb = { 0 };
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_assert (NULL != mi);
+ qs = TMH_db->lookup_webhook (TMH_db->cls,
+ mi->settings.id,
+ hc->infix,
+ &wb);
+ 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_webhook");
+ }
+ if (0 == qs)
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_WEBHOOK_UNKNOWN,
+ hc->infix);
+ }
+ {
+ MHD_RESULT ret;
+
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("event_type",
+ wb.event_type),
+ GNUNET_JSON_pack_string ("url",
+ wb.url),
+ GNUNET_JSON_pack_string ("http_method",
+ wb.http_method),
+ 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);
+ GNUNET_free (wb.header_template);
+ GNUNET_free (wb.body_template);
+
+ return ret;
+ }
+}
+
+
+/* end of taler-merchant-httpd_private-get-webhooks-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-webhooks-ID.h b/src/backend/taler-merchant-httpd_private-get-webhooks-ID.h
new file mode 100644
index 00000000..7b876c57
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-webhooks-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-get-webhooks-ID.h
+ * @brief implement GET /webhooks/$ID/
+ * @author Priscilla HUANG
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_WEBHOOKS_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_WEBHOOKS_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a GET "/webhooks/$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_webhooks_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-get-webhooks-ID.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-webhooks.c b/src/backend/taler-merchant-httpd_private-get-webhooks.c
new file mode 100644
index 00000000..251cd95e
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-webhooks.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-webhooks.c
+ * @brief implement GET /webhooks
+ * @author Priscilla HUANG
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-webhooks.h"
+
+
+/**
+ * Add webhook details to our JSON array.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param webhook_id ID of the webhook
+ * @param event_type what type of event is the hook for
+ */
+static void
+add_webhook (void *cls,
+ const char *webhook_id,
+ const char *event_type)
+{
+ json_t *pa = cls;
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ pa,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("webhook_id",
+ webhook_id),
+ GNUNET_JSON_pack_string ("event_type",
+ event_type))));
+}
+
+
+MHD_RESULT
+TMH_private_get_webhooks (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_webhooks (TMH_db->cls,
+ hc->instance->settings.id,
+ &add_webhook,
+ 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 ("webhooks",
+ pa));
+}
+
+
+/* end of taler-merchant-httpd_private-get-webhooks.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-webhooks.h b/src/backend/taler-merchant-httpd_private-get-webhooks.h
new file mode 100644
index 00000000..196891d5
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-webhooks.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-get-webhooks.h
+ * @brief implement GET /webhooks
+ * @author Priscilla HUANG
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_WEBHOOKS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_WEBHOOKS_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a GET "/webhooks" 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_webhooks (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-get-webhooks.h */
+#endif
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 24f7c097..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,39 +63,35 @@ 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_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),
- GNUNET_JSON_spec_mark_optional(
+ GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("email",
- (const char **) &is.email),
+ (const char **) &is.email),
NULL),
- GNUNET_JSON_spec_mark_optional(
+ GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("logo",
- (const char **) &is.logo),
+ (const char **) &is.logo),
NULL),
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",
@@ -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,12 +158,7 @@ patch_instances_ID (struct TMH_MerchantInstance *mi,
"jurisdiction");
}
- 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);
- for (unsigned int i = 0; i<MAX_RETRIES; i++)
+ for (unsigned int retry = 0; retry<MAX_RETRIES; retry++)
{
/* Cleanup after earlier loops */
{
@@ -181,34 +185,24 @@ 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,
- is.email))) &&
+ is.email))) &&
((mi->settings.website == is.website) ||
(NULL != is.website && NULL != mi->settings.website &&
0 == strcmp (mi->settings.website,
- is.website))) &&
+ is.website))) &&
((mi->settings.logo == is.logo) ||
(NULL != is.logo && NULL != mi->settings.logo &&
0 == strcmp (mi->settings.logo,
- is.logo))) &&
+ is.logo))) &&
(1 == json_equal (mi->settings.address,
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)) &&
@@ -230,159 +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_CONTAINER_DLL_insert (wm_head,
- wm_tail,
- wm);
- ad.h_wire = wm->h_wire;
- 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);
@@ -392,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);
@@ -402,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 a158db7c..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,7 +93,7 @@ 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;
@@ -136,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;
@@ -154,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;
@@ -177,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,
@@ -192,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,
@@ -205,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,
@@ -218,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,
@@ -230,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
new file mode 100644
index 00000000..e8a6c531
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-templates-ID.c
@@ -0,0 +1,243 @@
+/*
+ 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-patch-templates-ID.c
+ * @brief implementing PATCH /templates/$ID request handling
+ * @author Priscilla HUANG
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-patch-templates-ID.h"
+#include "taler-merchant-httpd_helper.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Determine the cause of the PATCH failure in more detail and report.
+ *
+ * @param connection connection to report on
+ * @param instance_id instance we are processing
+ * @param template_id ID of the product to patch
+ * @param tp template details we failed to set
+ */
+static MHD_RESULT
+determine_cause (struct MHD_Connection *connection,
+ const char *instance_id,
+ const char *template_id,
+ const struct TALER_MERCHANTDB_TemplateDetails *tp)
+{
+ struct TALER_MERCHANTDB_TemplateDetails tpx;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->lookup_template (TMH_db->cls,
+ instance_id,
+ template_id,
+ &tpx);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ 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:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "unexpected serialization problem");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ 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:
+ break; /* do below */
+ }
+
+ {
+ enum TALER_ErrorCode ec;
+
+ ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ TALER_MERCHANTDB_template_details_free (&tpx);
+ GNUNET_break (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE != ec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ ec,
+ NULL);
+ }
+}
+
+
+/**
+ * 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_templates_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ const char *template_id = hc->infix;
+ struct TALER_MERCHANTDB_TemplateDetails tp = {0};
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("template_description",
+ (const char **) &tp.template_description),
+ GNUNET_JSON_spec_mark_optional (
+ 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 ()
+ };
+
+ GNUNET_assert (NULL != mi);
+ GNUNET_assert (NULL != template_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;
+ }
+
+ if (! TMH_template_contract_valid (tp.template_contract))
+ {
+ 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,
+ "template_contract");
+ }
+ 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,
+ "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);
+ {
+ 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 = determine_cause (connection,
+ mi->settings.id,
+ template_id,
+ &tp);
+ 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-templates-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-patch-templates-ID.h b/src/backend/taler-merchant-httpd_private-patch-templates-ID.h
new file mode 100644
index 00000000..63ec50cb
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-templates-ID.h
@@ -0,0 +1,43 @@
+/*
+ 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-templates-ID.h
+ * @brief implementing PATCH /templates request handling
+ * @author Priscilla HUANG
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_TEMPLATES_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_TEMPLATES_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_templates_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-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
new file mode 100644
index 00000000..80d889fa
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-webhooks-ID.c
@@ -0,0 +1,188 @@
+/*
+ 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-webhooks-ID.c
+ * @brief implementing PATCH /webhooks/$ID request handling
+ * @author Priscilla HUANG
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-patch-webhooks-ID.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
+
+
+/**
+ * Determine the cause of the PATCH failure in more detail and report.
+ *
+ * @param connection connection to report on
+ * @param instance_id instance we are processing
+ * @param webhook_id ID of the webhook to patch
+ * @param wb webhook details we failed to set
+ */
+static MHD_RESULT
+determine_cause (struct MHD_Connection *connection,
+ const char *instance_id,
+ const char *webhook_id,
+ const struct TALER_MERCHANTDB_WebhookDetails *wb)
+{
+ struct TALER_MERCHANTDB_WebhookDetails wpx;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->lookup_webhook (TMH_db->cls,
+ instance_id,
+ webhook_id,
+ &wpx);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ 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:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "unexpected serialization problem");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_WEBHOOK_UNKNOWN,
+ webhook_id);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* do below */
+ }
+
+ {
+ enum TALER_ErrorCode ec;
+
+ ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ TALER_MERCHANTDB_webhook_details_free (&wpx);
+ GNUNET_break (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE != ec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ ec,
+ NULL);
+ }
+}
+
+
+/**
+ * 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_webhooks_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ const char *webhook_id = hc->infix;
+ struct TALER_MERCHANTDB_WebhookDetails wb = {0};
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("event_type",
+ (const char **) &wb.event_type),
+ TALER_JSON_spec_web_url ("url",
+ (const char **) &wb.url),
+ GNUNET_JSON_spec_string ("http_method",
+ (const char **) &wb.http_method),
+ 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 ()
+ };
+
+ GNUNET_assert (NULL != mi);
+ GNUNET_assert (NULL != webhook_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_webhook (TMH_db->cls,
+ mi->settings.id,
+ webhook_id,
+ &wb);
+ {
+ 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 = determine_cause (connection,
+ mi->settings.id,
+ webhook_id,
+ &wb);
+ 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-webhooks-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-patch-webhooks-ID.h b/src/backend/taler-merchant-httpd_private-patch-webhooks-ID.h
new file mode 100644
index 00000000..38404012
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-webhooks-ID.h
@@ -0,0 +1,43 @@
+/*
+ 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-webhooks-ID.h
+ * @brief implementing PATCH /webhooks request handling
+ * @author Priscilla HUANG
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_WEBHOOKS_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_WEBHOOKS_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_webhooks_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
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 b4e77d46..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
@@ -217,6 +217,7 @@ TMH_private_post_instances_default_ID_auth (const struct TMH_RequestHandler *rh,
struct TMH_HandlerContext *hc)
{
struct TMH_MerchantInstance *mi;
+ MHD_RESULT ret;
mi = TMH_lookup_instance (hc->infix);
if (NULL == mi)
@@ -226,9 +227,11 @@ TMH_private_post_instances_default_ID_auth (const struct TMH_RequestHandler *rh,
TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
hc->infix);
}
- return post_instances_ID_auth (mi,
- connection,
- hc);
+ mi->auth_override = true;
+ ret = post_instances_ID_auth (mi,
+ connection,
+ hc);
+ 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 96352a93..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,22 +121,35 @@ 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;
qs = TMH_db->lookup_contract_terms (TMH_db->cls,
hc->instance->settings.id,
@@ -165,9 +157,34 @@ TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
&contract_terms,
&order_serial,
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",
@@ -177,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,
@@ -189,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,
@@ -208,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);
@@ -237,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,
@@ -257,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);
@@ -272,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",
@@ -290,51 +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;
-
- qs = TMH_db->lookup_contract_terms (TMH_db->cls,
- hc->instance->settings.id,
- hc->infix,
- &contract_terms,
- &order_serial,
- 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 efaaf5a7..eedece55 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
@@ -22,16 +22,25 @@
* @brief the POST /orders handler
* @author Christian Grothoff
* @author Marcello Stanisci
+ * @author Christian Blättler
*/
#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
+#include <microhttpd.h>
+#include <string.h>
+#include <taler/taler_error_codes.h>
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd.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_contract.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_private-get-orders.h"
+#include "taler_merchantdb_plugin.h"
/**
@@ -40,6 +49,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,57 +67,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 *error_name;
- unsigned int error_line;
- enum GNUNET_GenericReturnValue res;
- struct GNUNET_JSON_Specification spec[] = {
- // FIXME: parse and format-validate all
- // optional fields of a product and check validity
- GNUNET_JSON_spec_string ("description",
- &description),
- 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_ERROR,
- "Product parsing failed at #%u: %s:%u\n",
- (unsigned int) index,
- error_name,
- error_line);
- 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
@@ -114,46 +77,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);
@@ -179,33 +109,607 @@ 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
+ {
+ /**
+ * Version of the contract terms.
+ */
+ enum TALER_MerchantContractVersion version;
+
+ /**
+ * Our order ID.
+ */
+ const char *order_id;
+
+ /**
+ * Summary of the contract.
+ */
+ const char *summary;
+
+ /**
+ * Internationalized summary.
+ */
+ json_t *summary_i18n;
+
+ /**
+ * URL that will show that the contract was successful
+ * after it has been paid for.
+ */
+ const char *fulfillment_url;
+
+ /**
+ * Message shown to the customer after paying for the contract.
+ * 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;
+
+ /**
+ * Array of products that are part of the purchase.
+ */
+ const json_t *products;
+
+ /**
+ * URL where the same contract could be ordered again (if available).
+ */
+ const char *public_reorder_url;
+
+ /**
+ * Array of contract choices. Is null for v0 contracts.
+ */
+ const json_t *choices;
+
+ /**
+ * 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;
+
+ /**
+ * 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_PARSE_CHOICES phase.
+ */
+ struct
+ {
+ /**
+ * Array of possible specific contracts the wallet/customer may choose
+ * from by selecting the respective index when signing the deposit
+ * confirmation.
+ */
+ struct TALER_MerchantContractChoice *choices;
+
+ /**
+ * Length of the @e choices array.
+ */
+ unsigned int choices_len;
+
+ /**
+ * Array of token types referenced in the contract.
+ */
+ struct TALER_MerchantContractTokenAuthority *authorities;
+
+ /**
+ * Length of the @e authorities array.
+ */
+ unsigned int authorities_len;
+ } parse_choices;
+
+ /**
+ * 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_PARSE_CHOICES,
+ 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;
+ }
+ // TODO: Check if this is even correct
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ {
+ GNUNET_array_grow (oc->parse_choices.choices[i].inputs,
+ oc->parse_choices.choices[i].inputs_len,
+ 0);
+ GNUNET_array_grow (oc->parse_choices.choices[i].outputs,
+ oc->parse_choices.choices[i].outputs_len,
+ 0);
+ }
+ 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;
@@ -218,14 +722,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) */
@@ -233,10 +779,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);
@@ -249,13 +795,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);
@@ -265,17 +812,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)
@@ -283,7 +830,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);
@@ -301,268 +848,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),
@@ -571,188 +973,1037 @@ 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 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 =
+ &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);
+}
+
+/**
+ * Fetch details about the token family with the given @a slug
+ * and add them to the list of token authorities. Check if the
+ * token family already has a valid key configured and if not,
+ * create a new one.
+ *
+ * @param[in,out] oc order context
+ * @param slug slug of the token family
+ * @param start_date validity start date of the token
*/
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[])
+set_token_authority (struct OrderContext *oc,
+ const char *slug,
+ struct GNUNET_TIME_Timestamp start_date)
+{
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details;
+ struct TALER_MerchantContractTokenAuthority authority;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute min_start_date = GNUNET_TIME_absolute_subtract (
+ start_date.abs_time,
+ // TODO: make this configurable. This is the granularity of token
+ // expiration dates.
+ GNUNET_TIME_UNIT_DAYS
+ );
+
+ qs = TMH_db->lookup_token_family_key (TMH_db->cls,
+ oc->hc->instance->settings.id,
+ slug,
+ GNUNET_TIME_absolute_to_timestamp (min_start_date),
+ start_date,
+ &key_details);
+
+ if (qs <= 0)
+ {
+ enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ unsigned int http_status = 0;
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Token family slug unknown\n");
+ http_status = MHD_HTTP_NOT_FOUND;
+ ec = TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* case listed to make compilers happy */
+ GNUNET_assert (0);
+ }
+ GNUNET_break (0);
+ reply_with_error (oc,
+ http_status,
+ ec,
+ "token_family_slug");
+ return MHD_NO;
+ }
+
+ if (NULL == key_details.pub)
+ {
+ /* If public key is NULL, private key must also be NULL */
+ GNUNET_assert (NULL == key_details.priv);
+
+ struct GNUNET_CRYPTO_BlindSignPrivateKey *priv;
+ struct GNUNET_CRYPTO_BlindSignPublicKey *pub;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Timestamp valid_before = GNUNET_TIME_absolute_to_timestamp(
+ GNUNET_TIME_absolute_add (now,
+ key_details.token_family.duration));
+
+ GNUNET_CRYPTO_blind_sign_keys_create (&priv,
+ &pub,
+ // TODO: Make cipher and key length configurable
+ GNUNET_CRYPTO_BSA_RSA,
+ 4096);
+
+ struct TALER_TokenFamilyPublicKey token_pub = {
+ .public_key = *pub,
+ };
+ struct TALER_TokenFamilyPrivateKey token_priv = {
+ .private_key = *priv,
+ };
+
+ qs = TMH_db->insert_token_family_key (TMH_db->cls,
+ slug,
+ &token_pub,
+ &token_priv,
+ GNUNET_TIME_absolute_to_timestamp (now),
+ valid_before);
+
+ authority.token_expiration = valid_before;
+ authority.pub = &token_pub;
+ // GNUNET_CRYPTO_blind_sign_priv_decref (&token_priv.private_key);
+ } else {
+ authority.token_expiration = key_details.valid_before;
+ authority.pub = key_details.pub;
+ }
+
+ authority.label = slug;
+ authority.description = key_details.token_family.description;
+ authority.description_i18n = key_details.token_family.description_i18n;
+
+ GNUNET_free (key_details.token_family.slug);
+ GNUNET_free (key_details.token_family.name);
+ if (NULL != key_details.priv) {
+ GNUNET_CRYPTO_blind_sign_priv_decref (&key_details.priv->private_key);
+ }
+
+ switch (key_details.token_family.kind) {
+ case TALER_MERCHANTDB_TFK_Subscription:
+ authority.kind = TALER_MCTK_SUBSCRIPTION;
+ authority.details.subscription.start_date = key_details.valid_after;
+ authority.details.subscription.end_date = key_details.valid_before;
+ authority.critical = true;
+ // TODO: Set trusted domains
+ break;
+ case TALER_MERCHANTDB_TFK_Discount:
+ authority.kind = TALER_MCTK_DISCOUNT;
+ authority.critical = false;
+ // TODO: Set expected domains
+ break;
+ }
+
+ GNUNET_array_append (oc->parse_choices.authorities,
+ oc->parse_choices.authorities_len,
+ authority);
+
+ return MHD_YES;
+}
+
+/**
+ * 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 =
- &hc->instance->settings;
- const char *order_id = NULL;
- const char *fulfillment_url = NULL;
+ &oc->hc->instance->settings;
+ json_t *merchant;
+ json_t *token_types = json_object ();
+ json_t *choices = json_array ();
+
+ 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;
+
+ /* Handle merchant address */
+ loca = settings->address;
+ if (NULL != loca)
+ {
+ loca = json_deep_copy (loca);
+ GNUNET_assert (NULL != loca);
+ GNUNET_assert (0 ==
+ json_object_set_new (merchant,
+ "address",
+ loca));
+ }
+ }
+ {
+ json_t *juri;
+
+ /* Handle merchant jurisdiction */
+ juri = settings->jurisdiction;
+ if (NULL != juri)
+ {
+ juri = json_deep_copy (juri);
+ GNUNET_assert (NULL != juri);
+ GNUNET_assert (0 ==
+ json_object_set_new (merchant,
+ "jurisdiction",
+ juri));
+ }
+ }
+
+ for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ {
+ struct TALER_MerchantContractTokenAuthority *authority = &oc->parse_choices.authorities[i];
+
+ // TODO: Finish spec to clearly define how token families are stored in
+ // ContractTerms.
+ // Here are some thoughts:
+ // - Multiple keys of the same token family can be referenced in
+ // one contract. E.g. exchanging old subscription for new.
+ // - TokenAuthority should be renamed to TokenFamily for consistency.
+ // - TokenFamilySlug can be used instead of TokenAuthorityLabel, but
+ // every token-based in- or output needs to have a valid_after date,
+ // so it's clear with key is referenced.
+ json_t *jauthority = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("description",
+ authority->description),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("description_i18n",
+ authority->description_i18n)),
+ GNUNET_JSON_pack_data_auto ("h_pub",
+ &authority->pub->public_key.pub_key_hash),
+ GNUNET_JSON_pack_data_auto ("pub",
+ &authority->pub->public_key.pub_key_hash),
+ GNUNET_JSON_pack_timestamp ("token_expiration",
+ authority->token_expiration)
+ );
+
+ GNUNET_assert (0 ==
+ json_object_set_new (token_types,
+ authority->label,
+ jauthority));
+ }
+
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ {
+ struct TALER_MerchantContractChoice *choice = &oc->parse_choices.choices[i];
+
+ json_t *inputs = json_array ();
+ json_t *outputs = json_array ();
+
+ for (unsigned int j = 0; j<choice->inputs_len; j++)
+ {
+ struct TALER_MerchantContractInput *input = &choice->inputs[j];
+
+ json_t *jinput = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("type",
+ input->type)
+ );
+
+ if (TALER_MCIT_TOKEN == input->type)
+ {
+ GNUNET_assert(0 ==
+ json_object_set_new(jinput,
+ "number",
+ json_integer (
+ input->details.token.count)));
+ GNUNET_assert(0 ==
+ json_object_set_new(jinput,
+ "token_family_slug",
+ json_string (
+ input->details.token.token_family_slug)));
+ }
+
+ GNUNET_assert (0 == json_array_append_new (inputs, jinput));
+ }
+
+ for (unsigned int j = 0; j<choice->outputs_len; j++)
+ {
+ struct TALER_MerchantContractOutput *output = &choice->outputs[j];
+
+ json_t *joutput = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("type",
+ output->type)
+ );
+
+ if (TALER_MCOT_TOKEN == output->type)
+ {
+ GNUNET_assert(0 ==
+ json_object_set_new(joutput,
+ "number",
+ json_integer (
+ output->details.token.count)));
+
+ GNUNET_assert(0 ==
+ json_object_set_new(joutput,
+ "token_family_slug",
+ json_string (
+ output->details.token.token_family_slug)));
+ }
+
+ GNUNET_assert (0 == json_array_append (outputs, joutput));
+ }
+
+ json_t *jchoice = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_incref ("inputs",
+ inputs),
+ GNUNET_JSON_pack_array_incref ("outputs",
+ outputs)
+ );
+
+ GNUNET_assert (0 == json_array_append (choices, jchoice));
+ }
+
+ oc->serialize_order.contract = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("version",
+ oc->parse_order.version),
+ 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_array_incref ("choices",
+ choices)
+ ),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("token_types",
+ token_types)
+ ),
+ 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;
+}
+
+
+/**
+ * Parse the order field of the request. Upon success, continue
+ * processing with parse_choices().
+ *
+ * @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 char *version = NULL;
+ 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[] = {
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("merchant_base_url",
- &merchant_base_url),
+ GNUNET_JSON_spec_string ("version",
+ &version),
NULL),
+ 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_json ("merchant",
- &jmerchant),
+ GNUNET_JSON_spec_array_const ("products",
+ &oc->parse_order.products),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ 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 ("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 (
+ GNUNET_JSON_spec_string ("public_reorder_url",
+ &oc->parse_order.public_reorder_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("choices",
+ &oc->parse_order.choices),
+ 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 (NULL == version || 0 == strcmp("0", version))
+ {
+ oc->parse_order.version = TALER_MCV_V0;
+
+ if (NULL != oc->parse_order.choices)
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_UNEXPECTED_REQUEST_ERROR,
+ "choices array must be null for v0 contracts");
+ return;
+ }
+ }
+ else if (0 == strcmp("1", version))
+ {
+ oc->parse_order.version = TALER_MCV_V1;
+
+ if (! json_is_array(oc->parse_order.choices))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "order.choices is not a valid array");
+ return;
+ }
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_VERSION_MALFORMED,
+ "invalid version specified in order, supported are null, '0' or '1'");
+ 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;
@@ -760,17 +2011,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)
{
- return TALER_MHD_reply_with_error (
- connection,
+ GNUNET_JSON_parse_free (spec);
+ 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,
@@ -787,25 +2039,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)
{
@@ -816,28 +2064,26 @@ patch_order (struct MHD_Connection *connection,
if (strstr (pos + strlen ("${ORDER_ID}"),
"${ORDER_ID}"))
{
- /* FIXME: free anything? */
GNUNET_break_op (0);
- 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);
}
}
@@ -848,418 +2094,507 @@ 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);
- 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);
- 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);
- 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);
- 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);
- 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;
}
- jmerchant = 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 != jmerchant);
- {
- json_t *loca;
- /* Handle merchant address */
- loca = settings->address;
- if (NULL != loca)
- {
- loca = json_deep_copy (loca);
- GNUNET_assert (0 ==
- json_object_set_new (jmerchant,
- "address",
- loca));
- }
- }
+ if ( (NULL != oc->parse_order.delivery_location) &&
+ (! TMH_location_object_valid (oc->parse_order.delivery_location)) )
{
- json_t *locj;
-
- /* Handle merchant jurisdiction */
- locj = settings->jurisdiction;
- if (NULL != locj)
- {
- locj = json_deep_copy (locj);
- GNUNET_assert (0 ==
- json_object_set_new (jmerchant,
- "jurisdiction",
- locj));
- }
+ GNUNET_break_op (0);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "delivery_location");
+ return;
}
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "merchant",
- jmerchant));
- /* 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))
- {
- 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");
- }
+ oc->phase++;
+}
- if ( (NULL != delivery_location) &&
- (! TMH_location_object_valid (delivery_location)) )
+/**
+ * Parse contract choices. Upon success, continue
+ * processing with merge_inventory().
+ *
+ * @param[in,out] oc order context
+ */
+static void
+parse_choices (struct OrderContext *oc)
+{
+ if (NULL == oc->parse_order.choices)
{
- 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");
+ oc->phase++;
+ return;
}
- /* sanity check result */
+ GNUNET_array_grow (oc->parse_choices.choices,
+ oc->parse_choices.choices_len,
+ json_array_size (oc->parse_order.choices));
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
{
- struct TALER_PrivateContractHashP h_control;
+ const char *error_name;
+ unsigned int error_line;
+ const json_t *jinputs;
+ const json_t *joutputs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("inputs",
+ &jinputs),
+ GNUNET_JSON_spec_array_const ("outputs",
+ &joutputs),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue ret;
- switch (TALER_JSON_contract_hash (order,
- &h_control))
+ ret = GNUNET_JSON_parse (json_array_get (oc->parse_order.choices, i),
+ spec,
+ &error_name,
+ &error_line);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Choice parsing failed: %s:%u\n",
+ error_name,
+ error_line);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "choice");
+ return;
+ }
+
+ if (! json_is_array (jinputs) ||
+ ! json_is_array (joutputs))
{
- case GNUNET_SYSERR:
- GNUNET_break (0);
- 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_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;
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inputs or outputs");
+ return;
+ }
+
+ {
+ // TODO: Maybe move to a separate function
+ const json_t *jinput;
+ size_t idx;
+ json_array_foreach ((json_t *) jinputs, idx, jinput)
+ {
+ // TODO: Assuming this struct would have more fields, would i use GNUNET_new then?
+ // Or should i use GNUNET_grow first and then get the element using the index?
+ // Assuming you add a field in the future, it's easier to that way, so you don't
+ // free it.
+ struct TALER_MerchantContractInput input = {.details.token.count = 1};
+ const char *kind;
+ const char *ierror_name;
+ unsigned int ierror_line;
+
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("kind",
+ &kind),
+ GNUNET_JSON_spec_string ("token_family_slug",
+ &input.details.token.token_family_slug),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &input.details.token.valid_after),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("count",
+ &input.details.token.count),
+ NULL),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jinput,
+ ispec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid input #%u for field %s\n",
+ (unsigned int) idx,
+ ierror_name);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ ierror_name);
+ return;
+ }
+
+ input.type = TMH_string_to_contract_input_type (kind);
+
+ if (TALER_MCIT_INVALID == input.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Field 'kind' invalid in input #%u\n",
+ (unsigned int) idx);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "kind");
+ return;
+ }
+
+ if (0 == input.details.token.count)
+ {
+ /* Ignore inputs with 'number' field set to 0 */
+ continue;
+ }
+
+ bool found = false;
+
+ for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ {
+ if (0 == strcmp (oc->parse_choices.authorities[i].label,
+ input.details.token.token_family_slug))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (! found)
+ {
+ MHD_RESULT res;
+ res = set_token_authority (oc,
+ input.details.token.token_family_slug,
+ input.details.token.valid_after);
+
+ if (MHD_NO == res)
+ {
+ return;
+ }
+ }
+
+ GNUNET_array_append (oc->parse_choices.choices[i].inputs,
+ oc->parse_choices.choices[i].inputs_len,
+ input);
+ }
+ }
+
+ {
+ const json_t *joutput;
+ size_t idx;
+ json_array_foreach ((json_t *) joutputs, idx, joutput)
+ {
+ struct TALER_MerchantContractOutput output = { .details.token.count = 1 };
+ const char *kind;
+ const char *ierror_name;
+ unsigned int ierror_line;
+
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("kind",
+ &kind),
+ GNUNET_JSON_spec_string ("token_family_slug",
+ &output.details.token.token_family_slug),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &output.details.token.valid_after),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("count",
+ &output.details.token.count),
+ NULL),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (joutput,
+ ispec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_JSON_parse_free (ispec);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid output #%u for field %s\n",
+ (unsigned int) idx,
+ ierror_name);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ ierror_name);
+ return;
+ }
+
+ output.type = TMH_string_to_contract_output_type (kind);
+
+ if (TALER_MCOT_INVALID == output.type)
+ {
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_JSON_parse_free (ispec);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Field 'kind' invalid in output #%u\n",
+ (unsigned int) idx);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "kind");
+ return;
+ }
+
+ if (0 == output.details.token.count)
+ {
+ /* Ignore outputs with 'number' field set to 0 */
+ continue;
+ }
+
+ bool found = false;
+
+ for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ {
+ if (0 == strcmp (oc->parse_choices.authorities[i].label,
+ output.details.token.token_family_slug))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (! found)
+ {
+ MHD_RESULT res;
+ res = set_token_authority (oc,
+ output.details.token.token_family_slug,
+ output.details.token.valid_after);
+
+ if (MHD_NO == res)
+ {
+ return;
+ }
+ }
+
+ GNUNET_array_append (oc->parse_choices.choices[i].outputs,
+ oc->parse_choices.choices[i].outputs_len,
+ output);
+ }
}
}
- return execute_order (connection,
- hc,
- h_post_data,
- order,
- claim_token,
- inventory_products_length,
- inventory_products,
- uuids_length,
- uuids);
-}
+ 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)
{
@@ -1279,6 +2614,9 @@ merge_inventory (struct MHD_Connection *connection,
ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
break;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Product %s from order unknown\n",
+ ip->product_id);
http_status = MHD_HTTP_NOT_FOUND;
ec = TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN;
break;
@@ -1286,12 +2624,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;
@@ -1308,12 +2650,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);
@@ -1321,160 +2663,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_json ("inventory_products",
- &ip),
+ GNUNET_JSON_spec_array_const ("inventory_products",
+ &ip),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("lock_uuids",
- &uuid),
+ GNUNET_JSON_spec_string ("session_id",
+ &oc->parse_request.session_id),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ 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 ()
};
@@ -1486,19 +2849,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;
}
}
}
@@ -1506,21 +2866,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);
@@ -1528,49 +2877,89 @@ 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_PARSE_CHOICES:
+ parse_choices (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-otp-devices.h b/src/backend/taler-merchant-httpd_private-post-otp-devices.h
new file mode 100644
index 00000000..96564d08
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-otp-devices.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-post-otp-devices.h
+ * @brief implementing POST /otp-devices request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_OTP_DEVICES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_OTP_DEVICES_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Generate an OTP device.
+ *
+ * @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_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 0072007d..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_MERCHANT_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 0351ab10..00000000
--- a/src/backend/taler-merchant-httpd_private-post-reserves.c
+++ /dev/null
@@ -1,396 +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 (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
new file mode 100644
index 00000000..7aa72992
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-templates.c
@@ -0,0 +1,284 @@
+/*
+ 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-post-templates.c
+ * @brief implementing POST /templates request handling
+ * @author Priscilla HUANG
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-post-templates.h"
+#include "taler-merchant-httpd_helper.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Check if the two templates are identical.
+ *
+ * @param t1 template to compare
+ * @param t2 other template to compare
+ * @return true if they are 'equal', false if not or of payto_uris is not an array
+ */
+static bool
+templates_equal (const struct TALER_MERCHANTDB_TemplateDetails *t1,
+ const struct TALER_MERCHANTDB_TemplateDetails *t2)
+{
+ return ( (0 == strcmp (t1->template_description,
+ t2->template_description)) &&
+ ( ( (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)) );
+}
+
+
+MHD_RESULT
+TMH_private_post_templates (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 };
+ const char *template_id;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("template_id",
+ &template_id),
+ GNUNET_JSON_spec_string ("template_description",
+ (const char **) &tp.template_description),
+ GNUNET_JSON_spec_mark_optional (
+ 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);
+ {
+ 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 (! TMH_template_contract_valid (tp.template_contract))
+ {
+ GNUNET_break_op (0);
+ json_dumpf (tp.template_contract,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "template_contract");
+ }
+
+ 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,
+ "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;
+ }
+ }
+ }
+
+ 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_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,
+ &etp);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_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);
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ 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:
+ break;
+ }
+ /* 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 of taler-merchant-httpd_private-post-templates.c */
diff --git a/src/backend/taler-merchant-httpd_private-post-templates.h b/src/backend/taler-merchant-httpd_private-post-templates.h
new file mode 100644
index 00000000..35e37625
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-templates.h
@@ -0,0 +1,43 @@
+/*
+ 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-templates.h
+ * @brief implementing POST /templates request handling
+ * @author Priscilla HUANG
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_TEMPLATES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_TEMPLATES_H
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Generate a template entry.
+ *
+ * @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_templates (const struct TMH_RequestHandler *rh,
+ 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 15efeb3c..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,966 +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),
- TALER_JSON_pack_amount ("expected_wad_fee",
- &fees.wad),
- 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,
@@ -994,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:
@@ -1011,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
new file mode 100644
index 00000000..391c7ea9
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-webhooks.c
@@ -0,0 +1,215 @@
+/*
+ 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-webhooks.c
+ * @brief implementing POST /webhooks request handling
+ * @author Priscilla HUANG
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-post-webhooks.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 webhooks are identical.
+ *
+ * @param w1 webhook to compare
+ * @param w2 other webhook to compare
+ * @return true if they are 'equal', false if not or of payto_uris is not an array
+ */
+static bool
+webhooks_equal (const struct TALER_MERCHANTDB_WebhookDetails *w1,
+ const struct TALER_MERCHANTDB_WebhookDetails *w2)
+{
+ return ( (0 == strcmp (w1->event_type,
+ w2->event_type)) &&
+ (0 == strcmp (w1->url,
+ w2->url)) &&
+ (0 == strcmp (w1->http_method,
+ w2->http_method)) &&
+ ( ( (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)) ) ) );
+}
+
+
+MHD_RESULT
+TMH_private_post_webhooks (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ struct TALER_MERCHANTDB_WebhookDetails wb = { 0 };
+ const char *webhook_id;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("webhook_id",
+ &webhook_id),
+ GNUNET_JSON_spec_string ("event_type",
+ (const char **) &wb.event_type),
+ TALER_JSON_spec_web_url ("url",
+ (const char **) &wb.url),
+ GNUNET_JSON_spec_string ("http_method",
+ (const char **) &wb.http_method),
+ 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 ()
+ };
+
+ 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 webhook of this id is known */
+ struct TALER_MERCHANTDB_WebhookDetails ewb;
+
+ if (GNUNET_OK !=
+ TMH_db->start (TMH_db->cls,
+ "/post webhooks"))
+ {
+ 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_webhook (TMH_db->cls,
+ mi->settings.id,
+ webhook_id,
+ &ewb);
+ 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 ewb == wb? */
+ {
+ bool eq;
+
+ eq = webhooks_equal (&wb,
+ &ewb);
+ TALER_MERCHANTDB_webhook_details_free (&ewb);
+ 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_WEBHOOKS_CONFLICT_WEBHOOK_EXISTS,
+ webhook_id);
+ }
+ } /* end switch (qs) */
+
+ qs = TMH_db->insert_webhook (TMH_db->cls,
+ mi->settings.id,
+ webhook_id,
+ &wb);
+ 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-webhooks.c */
diff --git a/src/backend/taler-merchant-httpd_private-post-reserves.h b/src/backend/taler-merchant-httpd_private-post-webhooks.h
index ca06fe2f..fd73c9e7 100644
--- a/src/backend/taler-merchant-httpd_private-post-reserves.h
+++ b/src/backend/taler-merchant-httpd_private-post-webhooks.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,17 @@
*/
/**
- * @file taler-merchant-httpd_private-post-reserves.h
- * @brief implementing POST /reserves request handling
- * @author Christian Grothoff
+ * @file taler-merchant-httpd_private-post-webhooks.h
+ * @brief implementing POST /webhooks request handling
+ * @author Priscilla HUANG
*/
-#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_RESERVES_H
-#define TALER_MERCHANT_HTTPD_PRIVATE_POST_RESERVES_H
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_WEBHOOKS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_WEBHOOKS_H
#include "taler-merchant-httpd.h"
-/**
- * Force all post reserve contexts to be resumed as we are about
- * to shut down MHD.
- */
-void
-TMH_force_rc_resume ();
-
/**
- * Generate a reserve entry in our inventory.
+ * Generate a webhook entry.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
@@ -43,7 +36,7 @@ TMH_force_rc_resume ();
* @return MHD result code
*/
MHD_RESULT
-TMH_private_post_reserves (const struct TMH_RequestHandler *rh,
+TMH_private_post_webhooks (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc);
diff --git a/src/backend/taler-merchant-httpd_qr.c b/src/backend/taler-merchant-httpd_qr.c
index 4539beed..59e80bf2 100644
--- a/src/backend/taler-merchant-httpd_qr.c
+++ b/src/backend/taler-merchant-httpd_qr.c
@@ -78,11 +78,11 @@ TMH_create_qrcode (const char *uri)
"style='shape-rendering: crispedges;'>\n",
qrc->width,
qrc->width);
- for (unsigned int y = 0; y<qrc->width; y++)
+ for (unsigned int y = 0; y<(unsigned int) qrc->width; y++)
{
- for (unsigned int x = 0; x<qrc->width; x++)
+ for (unsigned int x = 0; x<(unsigned int) qrc->width; x++)
{
- unsigned int off = x + y * qrc->width;
+ unsigned int off = x + y * (unsigned int) qrc->width;
if (0 == (qrc->data[off] & 1))
continue;
GNUNET_buffer_write_fstr (&buf,
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 0258f883..b12200b8 100644
--- a/src/backend/taler-merchant-httpd_spa.c
+++ b/src/backend/taler-merchant-httpd_spa.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 published by the Free Software
@@ -27,14 +27,47 @@
/**
- * SPA, compressed.
+ * Resource from the WebUi.
*/
-static struct MHD_Response *zspa;
+struct WebuiFile
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct WebuiFile *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WebuiFile *prev;
+
+ /**
+ * Path this resource matches.
+ */
+ char *path;
+
+ /**
+ * SPA resource, compressed.
+ */
+ struct MHD_Response *zspa;
+
+ /**
+ * SPA resource, vanilla.
+ */
+ struct MHD_Response *spa;
+
+};
+
/**
- * SPA, vanilla.
+ * Resources of the WebuUI, kept in a DLL.
*/
-static struct MHD_Response *spa;
+static struct WebuiFile *webui_head;
+
+/**
+ * Resources of the WebuUI, kept in a DLL.
+ */
+static struct WebuiFile *webui_tail;
MHD_RESULT
@@ -42,40 +75,95 @@ TMH_return_spa (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
+ struct WebuiFile *w = NULL;
+ const char *infix = hc->infix;
+
+ if ( (NULL == infix) ||
+ (0 == strcmp (infix,
+ "")) )
+ infix = "index.html";
+ for (struct WebuiFile *pos = webui_head;
+ NULL != pos;
+ pos = pos->next)
+ if (0 == strcmp (infix,
+ pos->path))
+ {
+ w = pos;
+ break;
+ }
+ if (NULL == w)
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ hc->url);
if ( (MHD_YES ==
TALER_MHD_can_compress (connection)) &&
- (NULL != zspa) )
+ (NULL != w->zspa) )
return MHD_queue_response (connection,
MHD_HTTP_OK,
- zspa);
+ w->zspa);
return MHD_queue_response (connection,
MHD_HTTP_OK,
- spa);
+ w->spa);
}
-int
-TMH_spa_init ()
+/**
+ * Function called on each file to load for the WebUI.
+ *
+ * @param cls NULL
+ * @param dn name of the file to load
+ */
+static enum GNUNET_GenericReturnValue
+build_webui (void *cls,
+ const char *dn)
{
- char *dn;
- int fd;
- struct stat sb;
-
+ static struct
{
- char *path;
-
- path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
- if (NULL == path)
+ const char *ext;
+ const char *mime;
+ } mime_map[] = {
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- GNUNET_asprintf (&dn,
- "%s/merchant/spa/spa.html",
- path);
- GNUNET_free (path);
- }
+ .ext = "css",
+ .mime = "text/css"
+ },
+ {
+ .ext = "html",
+ .mime = "text/html"
+ },
+ {
+ .ext = "js",
+ .mime = "text/javascript"
+ },
+ {
+ .ext = "jpg",
+ .mime = "image/jpeg"
+ },
+ {
+ .ext = "jpeg",
+ .mime = "image/jpeg"
+ },
+ {
+ .ext = "png",
+ .mime = "image/png"
+ },
+ {
+ .ext = "svg",
+ .mime = "image/svg+xml"
+ },
+ {
+ .ext = NULL,
+ .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 */
fd = open (dn,
O_RDONLY);
@@ -84,7 +172,6 @@ TMH_spa_init ()
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
"open",
dn);
- GNUNET_free (dn);
return GNUNET_SYSERR;
}
if (0 !=
@@ -95,10 +182,25 @@ TMH_spa_init ()
"open",
dn);
GNUNET_break (0 == close (fd));
- GNUNET_free (dn);
return GNUNET_SYSERR;
}
+ mime = NULL;
+ ext = strrchr (dn, '.');
+ 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,
+ mime_map[i].ext))
+ {
+ mime = mime_map[i].mime;
+ break;
+ }
+
{
void *in;
ssize_t r;
@@ -110,7 +212,6 @@ TMH_spa_init ()
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"malloc");
GNUNET_break (0 == close (fd));
- GNUNET_free (dn);
return GNUNET_SYSERR;
}
r = read (fd,
@@ -124,7 +225,6 @@ TMH_spa_init ()
dn);
GNUNET_free (in);
GNUNET_break (0 == close (fd));
- GNUNET_free (dn);
return GNUNET_SYSERR;
}
csize = (size_t) r;
@@ -146,10 +246,11 @@ TMH_spa_init ()
MHD_destroy_response (zspa);
zspa = NULL;
}
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (zspa,
- MHD_HTTP_HEADER_CONTENT_TYPE,
- "text/html"));
+ if (NULL != mime)
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (zspa,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ mime));
}
}
else
@@ -166,7 +267,6 @@ TMH_spa_init ()
"open",
dn);
GNUNET_break (0 == close (fd));
- GNUNET_free (dn);
if (NULL != zspa)
{
MHD_destroy_response (zspa);
@@ -174,11 +274,62 @@ TMH_spa_init ()
}
return GNUNET_SYSERR;
}
+ if (NULL != mime)
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (spa,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ mime));
+
+ {
+ struct WebuiFile *w;
+ const char *fn;
+
+ fn = strrchr (dn, '/');
+ GNUNET_assert (NULL != fn);
+ w = GNUNET_new (struct WebuiFile);
+ w->path = GNUNET_strdup (fn + 1);
+ w->spa = spa;
+ w->zspa = zspa;
+ GNUNET_CONTAINER_DLL_insert (webui_head,
+ webui_tail,
+ w);
+ }
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TMH_spa_init ()
+{
+ char *dn;
+
+ {
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_asprintf (&dn,
+ "%smerchant/spa/",
+ path);
+ GNUNET_free (path);
+ }
+
+ if (-1 ==
+ GNUNET_DISK_directory_scan (dn,
+ &build_webui,
+ NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load WebUI from `%s'\n",
+ dn);
+ GNUNET_free (dn);
+ return GNUNET_SYSERR;
+ }
GNUNET_free (dn);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (spa,
- MHD_HTTP_HEADER_CONTENT_TYPE,
- "text/html"));
return GNUNET_OK;
}
@@ -189,14 +340,24 @@ TMH_spa_init ()
void __attribute__ ((destructor))
get_spa_fini ()
{
- if (NULL != spa)
- {
- MHD_destroy_response (spa);
- spa = NULL;
- }
- if (NULL != zspa)
+ struct WebuiFile *w;
+
+ while (NULL != (w = webui_head))
{
- MHD_destroy_response (zspa);
- zspa = NULL;
+ GNUNET_CONTAINER_DLL_remove (webui_head,
+ webui_tail,
+ w);
+ if (NULL != w->spa)
+ {
+ MHD_destroy_response (w->spa);
+ w->spa = NULL;
+ }
+ if (NULL != w->zspa)
+ {
+ MHD_destroy_response (w->zspa);
+ w->zspa = NULL;
+ }
+ GNUNET_free (w->path);
+ GNUNET_free (w);
}
}
diff --git a/src/backend/taler-merchant-httpd_spa.h b/src/backend/taler-merchant-httpd_spa.h
index 8a998c38..9450bdbd 100644
--- a/src/backend/taler-merchant-httpd_spa.h
+++ b/src/backend/taler-merchant-httpd_spa.h
@@ -24,8 +24,9 @@
#include <microhttpd.h>
#include "taler-merchant-httpd.h"
+
/**
- * Return our single-page-app user interface (see contrib/spa.html).
+ * Return our single-page-app user interface (see contrib/spa/).
*
* @param rh request handler
* @param connection the connection we act upon
@@ -37,10 +38,13 @@ TMH_return_spa (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc);
+
/**
- * Preload and compress SPA file.
+ * Preload and compress SPA files.
+ *
+ * @return #GNUNET_OK on success
*/
-int
+enum GNUNET_GenericReturnValue
TMH_spa_init (void);
diff --git a/src/backend/taler-merchant-httpd_statics.c b/src/backend/taler-merchant-httpd_statics.c
index 69331087..72f81d85 100644
--- a/src/backend/taler-merchant-httpd_statics.c
+++ b/src/backend/taler-merchant-httpd_statics.c
@@ -15,15 +15,15 @@
*/
/**
* @file taler-merchant-httpd_statics.c
- * @brief logic to load and complete HTML templates
+ * @brief logic to load and return static resource files by client language preference
* @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <taler/taler_util.h>
#include <taler/taler_mhd_lib.h>
+#include <taler/taler_templating_lib.h>
#include "taler-merchant-httpd_statics.h"
-#include "../mustach/mustach.h"
#include <gnunet/gnunet_mhd_compat.h>
@@ -144,7 +144,7 @@ TMH_return_static (const struct TMH_RequestHandler *rh,
* #GNUNET_NO to stop iteration with no error,
* #GNUNET_SYSERR to abort iteration with error!
*/
-static int
+static enum GNUNET_GenericReturnValue
load_static_file (void *cls,
const char *filename)
{
@@ -275,7 +275,7 @@ load_static_file (void *cls,
/**
* Preload static files.
*/
-int
+enum GNUNET_GenericReturnValue
TMH_statics_init ()
{
char *dn;
@@ -291,19 +291,23 @@ TMH_statics_init ()
return GNUNET_SYSERR;
}
GNUNET_asprintf (&dn,
- "%s/merchant/static/",
+ "%smerchant/static/",
path);
GNUNET_free (path);
}
ret = GNUNET_DISK_directory_scan (dn,
&load_static_file,
NULL);
- GNUNET_free (dn);
if (-1 == ret)
{
- GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Could not load static resources from `%s': %s\n",
+ dn,
+ strerror (errno));
+ GNUNET_free (dn);
return GNUNET_SYSERR;
}
+ GNUNET_free (dn);
return GNUNET_OK;
}
diff --git a/src/backend/taler-merchant-httpd_statics.h b/src/backend/taler-merchant-httpd_statics.h
index 977a488d..ac3e2ca1 100644
--- a/src/backend/taler-merchant-httpd_statics.h
+++ b/src/backend/taler-merchant-httpd_statics.h
@@ -40,8 +40,10 @@ TMH_return_static (const struct TMH_RequestHandler *rh,
/**
* Preload static files.
+ *
+ * @return #GNUNET_OK on success
*/
-int
+enum GNUNET_GenericReturnValue
TMH_statics_init (void);
diff --git a/src/backend/taler-merchant-httpd_templating.c b/src/backend/taler-merchant-httpd_templating.c
deleted file mode 100644
index 9d7766b9..00000000
--- a/src/backend/taler-merchant-httpd_templating.c
+++ /dev/null
@@ -1,452 +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 taler-merchant-httpd_templating.c
- * @brief logic to load and complete HTML templates
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <taler/taler_util.h>
-#include <taler/taler_mhd_lib.h>
-#include "taler-merchant-httpd_templating.h"
-#include "../mustach/mustach.h"
-#include "../mustach/mustach-jansson.h"
-#include <gnunet/gnunet_mhd_compat.h>
-
-
-/**
- * Entry in a key-value array we use to cache templates.
- */
-struct TVE
-{
- /**
- * A name, used as the key. NULL for the last entry.
- */
- char *name;
-
- /**
- * Language the template is in.
- */
- char *lang;
-
- /**
- * 0-terminated (!) file data to return for @e name and @e lang.
- */
- char *value;
-
-};
-
-
-/**
- * Array of templates loaded into RAM.
- */
-static struct TVE *loaded;
-
-/**
- * Length of the #loaded array.
- */
-static unsigned int loaded_length;
-
-
-/**
- * Load Mustach template into memory. Note that we intentionally cache
- * failures, that is if we ever failed to load a template, we will never try
- * again.
- *
- * @param connection the connection we act upon
- * @param name name of the template file to load
- * (MUST be a 'static' string in memory!)
- * @return NULL on error, otherwise the template
- */
-static const char *
-lookup_template (struct MHD_Connection *connection,
- const char *name)
-{
- struct TVE *best = NULL;
- const char *lang;
-
- lang = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
- if (NULL == lang)
- lang = "en";
- /* find best match by language */
- for (unsigned int i = 0; i<loaded_length; i++)
- {
- if (0 != strcmp (loaded[i].name,
- name))
- continue; /* does not match by name */
- if ( (NULL == best) ||
- (TALER_language_matches (lang,
- loaded[i].lang) >
- TALER_language_matches (lang,
- best->lang) ) )
- best = &loaded[i];
- }
- if (NULL == best)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "No templates found in `%s'\n",
- name);
- return NULL;
- }
- return best->value;
-}
-
-/**
- * Get the base URL for static resources.
- *
- * @param con the MHD connection
- * @param instance_id the instance ID
- * @returns the static files base URL, guaranteed
- * to have a trailing slash.
- */
-static char *
-make_static_url (struct MHD_Connection *con,
- 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);
-
- 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,
- "static/");
- return GNUNET_buffer_reap_str (&buf);
-}
-
-
-
-
-/**
- * Load a @a template and substitute using @a root, returning
- * the result to the @a connection with the given
- * @a http_status code.
- *
- * @param connection the connection we act upon
- * @param http_status code to use on success
- * @param template basename of the template to load
- * @param instance_id instance ID, used to compute static files URL
- * @param taler_uri value for "Taler:" header to set, or NULL
- * @param root JSON object to pass as the root context
- * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was queued,
- * #GNUNET_SYSERR on failure (to queue an error)
- */
-enum GNUNET_GenericReturnValue
-TMH_return_from_template (struct MHD_Connection *connection,
- unsigned int http_status,
- const char *template,
- const char *instance_id,
- const char *taler_uri,
- json_t *root)
-{
- struct MHD_Response *reply;
- char *body;
- size_t body_size;
-
- {
- const char *tmpl;
- int eno;
-
- tmpl = lookup_template (connection,
- template);
- if (NULL == tmpl)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to load template `%s'\n",
- template);
- if (MHD_YES !=
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_ACCEPTABLE,
- TALER_EC_MERCHANT_GENERIC_FAILED_TO_LOAD_TEMPLATE,
- template))
- return GNUNET_SYSERR;
- return GNUNET_NO;
- }
- /* Add default values to the context */
- {
- char *static_url = make_static_url (connection,
- instance_id);
- json_object_set (root,
- "static_url",
- json_string (static_url));
- GNUNET_free (static_url);
- }
- if (0 !=
- (eno = mustach_jansson (tmpl,
- root,
- &body,
- &body_size)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "mustach failed on template `%s' with error %d\n",
- template,
- eno);
- if (MHD_YES !=
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
- template))
- return GNUNET_SYSERR;
- return GNUNET_NO;
- }
- }
-
- /* try to compress reply if client allows it */
- {
- bool compressed = false;
-
- if (MHD_YES ==
- TALER_MHD_can_compress (connection))
- {
- compressed = TALER_MHD_body_compress ((void **) &body,
- &body_size);
- }
- reply = MHD_create_response_from_buffer (body_size,
- body,
- MHD_RESPMEM_MUST_FREE);
- if (NULL == reply)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (compressed)
- {
- if (MHD_NO ==
- MHD_add_response_header (reply,
- MHD_HTTP_HEADER_CONTENT_ENCODING,
- "deflate"))
- {
- GNUNET_break (0);
- MHD_destroy_response (reply);
- return GNUNET_SYSERR;
- }
- }
- }
-
- /* Add standard headers */
- if (NULL != taler_uri)
- GNUNET_break (MHD_NO !=
- MHD_add_response_header (reply,
- "Taler",
- taler_uri));
- GNUNET_break (MHD_NO !=
- MHD_add_response_header (reply,
- MHD_HTTP_HEADER_CONTENT_TYPE,
- "text/html"));
-
- /* Actually return reply */
- {
- MHD_RESULT ret;
-
- ret = MHD_queue_response (connection,
- http_status,
- reply);
- MHD_destroy_response (reply);
- if (MHD_NO == ret)
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function called with a template's filename.
- *
- * @param cls closure
- * @param filename complete filename (absolute path)
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-static int
-load_template (void *cls,
- const char *filename)
-{
- char *lang;
- char *end;
- int fd;
- struct stat sb;
- char *map;
- const char *name;
-
- if ('.' == filename[0])
- return GNUNET_OK;
-
- name = strrchr (filename,
- '/');
- if (NULL == name)
- name = filename;
- else
- name++;
- lang = strchr (name,
- '.');
- if (NULL == lang)
- return GNUNET_OK; /* name must include .$LANG */
- lang++;
- end = strchr (lang,
- '.');
- if ( (NULL == end) ||
- (0 != strcmp (end,
- ".must")) )
- return GNUNET_OK; /* name must end with '.must' */
-
- /* finally open template */
- fd = open (filename,
- O_RDONLY);
- if (-1 == fd)
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "open",
- filename);
-
- return GNUNET_SYSERR;
- }
- if (0 !=
- fstat (fd,
- &sb))
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "open",
- filename);
- GNUNET_break (0 == close (fd));
- return GNUNET_OK;
- }
- map = GNUNET_malloc_large (sb.st_size + 1);
- if (NULL == map)
- {
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "malloc");
- GNUNET_break (0 == close (fd));
- return GNUNET_SYSERR;
- }
- if (sb.st_size !=
- read (fd,
- map,
- sb.st_size))
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "read",
- filename);
- GNUNET_break (0 == close (fd));
- return GNUNET_OK;
- }
- GNUNET_break (0 == close (fd));
- GNUNET_array_grow (loaded,
- loaded_length,
- loaded_length + 1);
- loaded[loaded_length - 1].name = GNUNET_strndup (name,
- (lang - 1) - name);
- loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
- end - lang);
- loaded[loaded_length - 1].value = map;
- return GNUNET_OK;
-}
-
-
-/**
- * Preload templates.
- */
-int
-TMH_templating_init ()
-{
- char *dn;
- int ret;
-
- {
- char *path;
-
- path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
- if (NULL == path)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- GNUNET_asprintf (&dn,
- "%s/merchant/templates/",
- path);
- GNUNET_free (path);
- }
- ret = GNUNET_DISK_directory_scan (dn,
- &load_template,
- NULL);
- GNUNET_free (dn);
- if (-1 == ret)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Nicely shut down.
- */
-void __attribute__ ((destructor))
-templating_fini ()
-{
- for (unsigned int i = 0; i<loaded_length; i++)
- {
- GNUNET_free (loaded[i].name);
- GNUNET_free (loaded[i].lang);
- GNUNET_free (loaded[i].value);
- }
- GNUNET_array_grow (loaded,
- loaded_length,
- 0);
-}
diff --git a/src/backend/taler-merchant-httpd_templating.h b/src/backend/taler-merchant-httpd_templating.h
deleted file mode 100644
index a1c2e05a..00000000
--- a/src/backend/taler-merchant-httpd_templating.h
+++ /dev/null
@@ -1,56 +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 taler-merchant-httpd_templating.h
- * @brief logic to load and complete HTML templates
- * @author Christian Grothoff
- */
-#ifndef TALER_MERCHANT_HTTPD_TEMPLATING_H
-#define TALER_MERCHANT_HTTPD_TEMPLATING_H
-
-#include <microhttpd.h>
-
-
-/**
- * Load a @a template and substitute using @a root, returning
- * the result to the @a connection with the given
- * @a http_status code.
- *
- * @param connection the connection we act upon
- * @param http_status code to use on success
- * @param template basename of the template to load
- * @param instance_id instance ID, used to compute static files URL
- * @param taler_uri value for "Taler:" header to set, or NULL
- * @param root JSON object to pass as the root context
- * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was queued,
- * #GNUNET_SYSERR on failure (to queue an error)
- */
-enum GNUNET_GenericReturnValue
-TMH_return_from_template (struct MHD_Connection *connection,
- unsigned int http_status,
- const char *template,
- const char *instance_id,
- const char *taler_uri,
- json_t *root);
-
-/**
- * Preload templates.
- */
-int
-TMH_templating_init (void);
-
-
-#endif
diff --git a/src/backend/taler-merchant-webhook.c b/src/backend/taler-merchant-webhook.c
new file mode 100644
index 00000000..80db78fd
--- /dev/null
+++ b/src/backend/taler-merchant-webhook.c
@@ -0,0 +1,591 @@
+/*
+ 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-webhook.c
+ * @brief Process that runs webhooks triggered by the merchant backend
+ * @author Priscilla HUANG
+ */
+#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>
+
+
+struct WorkResponse
+{
+ struct WorkResponse *next;
+ struct WorkResponse *prev;
+ struct GNUNET_CURL_Job *job;
+ uint64_t webhook_pending_serial;
+ char *body;
+ struct curl_slist *job_headers;
+};
+
+
+static struct WorkResponse *w_head;
+
+static struct WorkResponse *w_tail;
+
+static struct GNUNET_DB_EventHandler *eh;
+
+/**
+ * The merchant's configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Our database plugin.
+ */
+static struct TALER_MERCHANTDB_Plugin *db_plugin;
+
+/**
+ * Next task to run, if any.
+ */
+static struct GNUNET_SCHEDULER_Task *task;
+
+/**
+ * Handle to the context for interacting with the bank / wire gateway.
+ */
+static struct GNUNET_CURL_Context *ctx;
+
+/**
+ * Scheduler context for running the @e ctx.
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+/**
+ * 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 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);
+ task = NULL;
+ }
+ while (NULL != (w = w_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (w_head,
+ w_tail,
+ w);
+ GNUNET_CURL_job_cancel (w->job);
+ curl_slist_free_all (w->job_headers);
+ GNUNET_free (w->body);
+ GNUNET_free (w);
+ }
+ 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;
+ }
+}
+
+
+/**
+ * Select webhook to process.
+ *
+ * @param cls NULL
+ */
+static void
+select_work (void *cls);
+
+
+/**
+ * This function is used by the function `pending_webhooks_cb`. According to the response code,
+ * we delete or update the webhook.
+ *
+ * @param cls closure
+ * @param response_code HTTP response code from server, 0 on hard error
+ * @param body http body of the response
+ * @param body_size number of bytes in @a body
+ */
+static void
+handle_webhook_response (void *cls,
+ long response_code,
+ const void *body,
+ size_t body_size)
+{
+ struct WorkResponse *w = cls;
+
+ (void) body;
+ (void) body_size;
+ w->job = NULL;
+ GNUNET_CONTAINER_DLL_remove (w_head,
+ w_tail,
+ w);
+ GNUNET_free (w->body);
+ curl_slist_free_all (w->job_headers);
+ 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);
+ }
+}
+
+
+/**
+ * Typically called by `select_work`.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @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
+ * @param http_method use for the webhook
+ * @param header of the webhook
+ * @param body of the webhook
+ */
+static void
+pending_webhooks_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 WorkResponse *w = GNUNET_new (struct WorkResponse);
+ CURL *eh;
+ (void) retries;
+ (void) next_attempt;
+ (void) cls;
+ struct curl_slist *job_headers = NULL;
+
+ GNUNET_CONTAINER_DLL_insert (w_head,
+ w_tail,
+ w);
+ w->webhook_pending_serial = webhook_pending_serial;
+ eh = curl_easy_init ();
+ GNUNET_assert (NULL != eh);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_CUSTOMREQUEST,
+ http_method));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ url));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ 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 */
+ 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_MAXREDIRS,
+ 5));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_FOLLOWLOCATION,
+ 1));
+
+ w->job = GNUNET_CURL_job_add_raw (ctx,
+ eh,
+ job_headers,
+ &handle_webhook_response,
+ w);
+ if (NULL == w->job)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "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,
+ w_tail,
+ w);
+ GNUNET_free (w);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+}
+
+
+/**
+ * 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_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
+ * @param http_method use for the webhook
+ * @param header of the webhook
+ * @param body of the webhook
+ */
+static void
+future_webhook_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)
+{
+ (void) webhook_pending_serial;
+ (void) retries;
+ (void) url;
+ (void) http_method;
+ (void) header;
+ (void) body;
+
+ task = GNUNET_SCHEDULER_add_at (next_attempt,
+ &select_work,
+ NULL);
+}
+
+
+static void
+select_work (void *cls)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Relative rel;
+
+ (void) cls;
+ task = NULL;
+ db_plugin->preflight (db_plugin->cls);
+ qs = db_plugin->lookup_pending_webhooks (db_plugin->cls,
+ &pending_webhooks_cb,
+ NULL);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to lookup pending webhooks!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ if (test_mode)
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ qs = db_plugin->lookup_future_webhook (db_plugin->cls,
+ &future_webhook_cb,
+ NULL);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to lookup future webhook!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ 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,
+ NULL);
+ return;
+ }
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ default:
+ return; // wait for completion, then select more work.
+ }
+}
+
+
+/**
+ * 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_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);
+}
+
+
+/**
+ * The main function of the taler-merchant-webhook
+ * @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 ('t',
+ "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
+ };
+ 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-webhook",
+ gettext_noop (
+ "background process that executes webhooks"),
+ 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-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
new file mode 100644
index 00000000..5f6528e6
--- /dev/null
+++ b/src/backend/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 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/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/.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 8adc1d76..defc3cf9 100644
--- a/src/backenddb/Makefile.am
+++ b/src/backenddb/Makefile.am
@@ -10,12 +10,28 @@ pkgcfg_DATA = \
sqldir = $(prefix)/share/taler/sql/merchant/
+sqlinputs = \
+ pg_*.sql \
+ procedures.sql.in
+
sql_DATA = \
- merchant-0000.sql \
+ versioning.sql \
+ procedures.sql \
merchant-0001.sql \
merchant-0002.sql \
merchant-0003.sql \
- drop0001.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
@@ -46,15 +62,126 @@ 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 \
+ pg_insert_token_family_key.h pg_insert_token_family_key.c \
+ pg_lookup_token_family_key.h pg_lookup_token_family_key.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 \
@@ -92,4 +219,5 @@ TESTS = \
EXTRA_DIST = \
test-merchantdb-postgres.conf \
merchantdb-postgres.conf \
+ $(sqlinputs) \
$(sql_DATA)
diff --git a/src/backenddb/drop0002.sql b/src/backenddb/drop.sql
index cfb6773c..9005b6ed 100644
--- a/src/backenddb/drop0002.sql
+++ b/src/backenddb/drop.sql
@@ -17,15 +17,18 @@
-- Everything in one big transaction
BEGIN;
-SELECT _v.unregister_patch('merchant-0002');
+-- This script DROPs all of the tables we create.
+WITH xpatches AS (
+ SELECT patch_name
+ FROM _v.patches
+ WHERE starts_with(patch_name,'merchant-')
+)
+ SELECT _v.unregister_patch(xpatches.patch_name)
+ FROM xpatches;
--- Drops for 0002.sql
-ALTER TABLE merchant_instances
- DROP COLUMN website,
- DROP COLUMN email,
- DROP COLUMN logo;
+DROP SCHEMA merchant CASCADE;
-- And we're out of here...
COMMIT;
diff --git a/src/backenddb/drop0001.sql b/src/backenddb/drop0001.sql
deleted file mode 100644
index cc24f059..00000000
--- a/src/backenddb/drop0001.sql
+++ /dev/null
@@ -1,68 +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/>
---
-
--- 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.
-
--- Unregister patch (0003.sql)
---SELECT _v.unregister_patch('merchant-0003');
-
--- Unregister patch (0002.sql)
---SELECT _v.unregister_patch('merchant-0002');
-
--- Unregister patch (0001.sql)
-SELECT _v.unregister_patch('merchant-0001');
-
-
-DROP TABLE IF EXISTS merchant_kyc CASCADE;
-
--- Drops for 0001.sql
-
-DROP TABLE IF EXISTS merchant_exchange_wire_fees CASCADE;
-DROP TABLE IF EXISTS merchant_exchange_signing_keys CASCADE;
-DROP TABLE IF EXISTS merchant_instances CASCADE;
-DROP TABLE IF EXISTS merchant_keys CASCADE;
-DROP TABLE IF EXISTS merchant_accounts CASCADE;
-DROP TABLE IF EXISTS merchant_inventory CASCADE;
-DROP TABLE IF EXISTS merchant_inventory_locks CASCADE;
-DROP TABLE IF EXISTS merchant_accounts CASCADE;
-DROP TABLE IF EXISTS merchant_orders CASCADE;
-DROP TABLE IF EXISTS merchant_order_locks CASCADE;
-DROP TABLE IF EXISTS merchant_contract_terms CASCADE;
-DROP TABLE IF EXISTS merchant_deposits CASCADE;
-DROP TABLE IF EXISTS merchant_refunds CASCADE;
-DROP TABLE IF EXISTS merchant_refund_proofs CASCADE;
-DROP TABLE IF EXISTS merchant_credits CASCADE;
-DROP TABLE IF EXISTS merchant_transfers CASCADE;
-DROP TABLE IF EXISTS merchant_transfer_signatures CASCADE;
-DROP TABLE IF EXISTS merchant_transfer_by_coin CASCADE;
-DROP TABLE IF EXISTS merchant_transfer_to_coin CASCADE;
-DROP TABLE IF EXISTS merchant_deposit_to_transfer CASCADE;
-DROP TABLE IF EXISTS merchant_tip_reserves CASCADE;
-DROP TABLE IF EXISTS merchant_tip_reserve_keys CASCADE;
-DROP TABLE IF EXISTS merchant_tips CASCADE;
-DROP TABLE IF EXISTS merchant_tip_pickups CASCADE;
-DROP TABLE IF EXISTS merchant_tip_pickup_signatures CASCADE;
-
-
--- And we're out of here...
-COMMIT;
diff --git a/src/backenddb/merchant-0001.sql b/src/backenddb/merchant-0001.sql
index 96e29e5b..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,12 +14,31 @@
-- 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;
-- Check patch versioning is in place.
SELECT _v.register_patch('merchant-0001', NULL, NULL);
+CREATE SCHEMA merchant;
+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
@@ -28,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)
);
@@ -65,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';
@@ -84,14 +99,49 @@ 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
@@ -108,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)
@@ -123,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 ---------------------------
@@ -131,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
@@ -159,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';
@@ -170,7 +228,7 @@ COMMENT ON COLUMN merchant_inventory.total_lost
COMMENT ON COLUMN merchant_inventory.address
IS 'JSON formatted Location of where the product is stocked';
COMMENT ON COLUMN merchant_inventory.next_restock
- IS 'GNUnet absolute time indicating when the next restock is expected. 0 for unknown.';
+ IS 'GNUnet absolute time i ndicating when the next restock is expected. 0 for unknown.';
COMMENT ON COLUMN merchant_inventory.minimum_age
IS 'Minimum age of the customer in years, to be used if an exchange supports the age restriction extension.';
@@ -202,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
@@ -222,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);
@@ -248,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)
@@ -270,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
@@ -284,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';
@@ -307,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
@@ -347,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
@@ -375,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)
@@ -392,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)
);
@@ -411,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';
@@ -421,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
@@ -455,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';
@@ -568,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
@@ -588,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 5eacaa0b..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,23 +14,158 @@
-- 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;
-- Check patch versioning is in place.
SELECT _v.register_patch('merchant-0002', NULL, NULL);
-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';
+SET search_path TO merchant;
+
+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 f57112f8..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
@@ -18,7 +18,33 @@
BEGIN;
-- Check patch versioning is in place.
--- SELECT _v.register_patch('merchant-0003', NULL, NULL);
+SELECT _v.register_patch('merchant-0003', NULL, NULL);
+
+SET search_path TO merchant;
+
+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
new file mode 100644
index 00000000..711026a2
--- /dev/null
+++ b/src/backenddb/merchant-0004.sql
@@ -0,0 +1,30 @@
+--
+-- 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;
+-- Check patch versioning is in place.
+SELECT _v.register_patch('merchant-0004', NULL, NULL);
+
+SET search_path TO merchant;
+
+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 5a121287..5894525c 100644
--- a/src/backenddb/merchantdb_helper.c
+++ b/src/backenddb/merchantdb_helper.c
@@ -17,6 +17,7 @@
* @file merchantdb_helper.c
* @brief Helper functions for the merchant database logic
* @author Christian Grothoff
+ * @author Priscilla Huang
*/
#include "platform.h"
#include <taler/taler_util.h>
@@ -36,4 +37,50 @@ TALER_MERCHANTDB_product_details_free (
}
+void
+TALER_MERCHANTDB_template_details_free (
+ struct TALER_MERCHANTDB_TemplateDetails *tp)
+{
+ GNUNET_free (tp->template_description);
+ GNUNET_free (tp->otp_id);
+ GNUNET_free (tp->required_currency);
+ json_decref (tp->editable_defaults);
+ json_decref (tp->template_contract);
+}
+
+
+void
+TALER_MERCHANTDB_webhook_details_free (
+ struct TALER_MERCHANTDB_WebhookDetails *wb)
+{
+ GNUNET_free (wb->event_type);
+ GNUNET_free (wb->url);
+ GNUNET_free (wb->http_method);
+ GNUNET_free (wb->header_template);
+ GNUNET_free (wb->body_template);
+}
+
+
+void
+TALER_MERCHANTDB_pending_webhook_details_free (
+ struct TALER_MERCHANTDB_PendingWebhookDetails *pwb)
+{
+ GNUNET_free (pwb->url);
+ GNUNET_free (pwb->http_method);
+ GNUNET_free (pwb->header);
+ GNUNET_free (pwb->body);
+}
+
+
+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..d584f5e7
--- /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
+ * @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_token_family_key.c b/src/backenddb/pg_insert_token_family_key.c
new file mode 100644
index 00000000..b13c8079
--- /dev/null
+++ b/src/backenddb/pg_insert_token_family_key.c
@@ -0,0 +1,97 @@
+/*
+ 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_insert_token_family_key.c
+ * @brief Implementation of the insert_token_family_key function for Postgres
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_pq_lib.h>
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_token_family_key.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_token_family_key (void *cls,
+ const char *token_family_slug,
+ const struct TALER_TokenFamilyPublicKey *pub,
+ const struct TALER_TokenFamilyPrivateKey *priv,
+ const struct GNUNET_TIME_Timestamp valid_after,
+ const struct GNUNET_TIME_Timestamp valid_before)
+{
+ struct PostgresClosure *pg = cls;
+ const char *cipher = NULL;
+ struct GNUNET_HashCode pub_hash;
+
+ switch (pub->public_key.cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ cipher = "rsa";
+ GNUNET_CRYPTO_rsa_public_key_hash (pub->public_key.details.rsa_public_key,
+ &pub_hash);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ cipher = "cs";
+ GNUNET_CRYPTO_hash (&pub->public_key.details.cs_public_key,
+ sizeof (pub->public_key.details.cs_public_key),
+ &pub_hash);
+ break;
+ case GNUNET_CRYPTO_BSA_INVALID:
+ /* case listed to make compilers happy */
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (token_family_slug),
+ GNUNET_PQ_query_param_blind_sign_pub (&pub->public_key),
+ GNUNET_PQ_query_param_auto_from_type (&pub->public_key.pub_key_hash),
+ GNUNET_PQ_query_param_blind_sign_priv (&priv->private_key),
+ GNUNET_PQ_query_param_timestamp (&valid_after),
+ GNUNET_PQ_query_param_timestamp (&valid_before),
+ GNUNET_PQ_query_param_string (cipher),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_assert (pub->public_key.cipher == priv->private_key.cipher);
+
+ GNUNET_assert (0 ==
+ GNUNET_memcmp (&pub_hash,
+ &pub->public_key.pub_key_hash));
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ valid_after.abs_time));
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ valid_before.abs_time));
+
+ PREPARE (pg,
+ "token_family_key_insert",
+ "INSERT INTO merchant_token_family_keys "
+ "(token_family_serial"
+ ",pub"
+ ",h_pub"
+ ",priv"
+ ",valid_after"
+ ",valid_before"
+ ",cipher)"
+ " SELECT token_family_serial, $2, $3, $4, $5, $6, $7"
+ " FROM merchant_token_families"
+ " WHERE slug = $1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "token_family_key_insert",
+ params);
+} \ No newline at end of file
diff --git a/src/backenddb/pg_insert_token_family_key.h b/src/backenddb/pg_insert_token_family_key.h
new file mode 100644
index 00000000..c4fc8d85
--- /dev/null
+++ b/src/backenddb/pg_insert_token_family_key.h
@@ -0,0 +1,46 @@
+/*
+ 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_insert_token_family_key.h
+ * @brief implementation of the insert_token_family_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_TOKEN_FAMILY_KEY_H
+#define PG_INSERT_TOKEN_FAMILY_KEY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * @param cls closure
+ * @param token_family_slug slug of the token family to insert the key for
+ * @param pub public key to insert
+ * @param priv private key to insert
+ * @param valid_after start of validity period for this key
+ * @param valid_before end of validity period for this key
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_token_family_key (void *cls,
+ const char *token_family_slug,
+ const struct TALER_TokenFamilyPublicKey *pub,
+ const struct TALER_TokenFamilyPrivateKey *priv,
+ const struct GNUNET_TIME_Timestamp valid_after,
+ const struct GNUNET_TIME_Timestamp valid_before);
+
+#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..0dd11b1b
--- /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_total_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..089543ea
--- /dev/null
+++ b/src/backenddb/pg_lookup_deposits_by_contract_and_coin.c
@@ -0,0 +1,328 @@
+/*
+ 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;
+
+ /**
+ * Total amount refunded on this coin and contract.
+ */
+ struct TALER_Amount refund_total;
+
+ /**
+ * 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_refunds_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupDepositsByCnCContext *ldcc = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_Amount refund_amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ 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);
+ ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Coin had refund of %s\n",
+ TALER_amount2s (&refund_amount));
+ if (0 == i)
+ ldcc->refund_total = refund_amount;
+ else
+ GNUNET_assert (0 <=
+ TALER_amount_add (&ldcc->refund_total,
+ &ldcc->refund_total,
+ &refund_amount));
+ GNUNET_PQ_cleanup_result (rs); /* technically useless here */
+ }
+}
+
+
+/**
+ * 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;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Coin original deposit value is %s\n",
+ TALER_amount2s (&amount_with_fee));
+ if (TALER_amount_is_valid (&ldcc->refund_total))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Coin had total refunds of %s\n",
+ TALER_amount2s (&ldcc->refund_total));
+ if (1 ==
+ TALER_amount_cmp (&ldcc->refund_total,
+ &amount_with_fee))
+ {
+ /* Refunds exceeded total deposit? not OK! */
+ GNUNET_break (0);
+ ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ if (0 ==
+ TALER_amount_cmp (&ldcc->refund_total,
+ &amount_with_fee))
+ {
+ /* refund_total == amount_with_fee;
+ in this case, the total contributed to the
+ wire transfer is zero (as are fees) */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (ldcc->refund_total.currency,
+ &amount_with_fee));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (ldcc->refund_total.currency,
+ &deposit_fee));
+
+ }
+ else
+ {
+ /* Compute deposit value by subtracting refunds */
+ GNUNET_assert (0 <
+ TALER_amount_subtract (&amount_with_fee,
+ &amount_with_fee,
+ &ldcc->refund_total));
+ if (-1 ==
+ TALER_amount_cmp (&amount_with_fee,
+ &deposit_fee))
+ {
+ /* amount_with_fee < deposit_fee, so after refunds less than
+ the deposit fee remains; reduce deposit fee to
+ the remaining value of the coin */
+ deposit_fee = amount_with_fee;
+ }
+ }
+ }
+ 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);
+ /* 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_by_coin_and_contract",
+ "SELECT"
+ " refund_amount"
+ " FROM merchant_refunds"
+ /* Join to filter by refunds that actually
+ did work, not only those we approved */
+ " JOIN merchant_refund_proofs"
+ " USING (refund_serial)"
+ " WHERE coin_pub=$3"
+ " 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))");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_refunds_by_coin_and_contract",
+ params,
+ &lookup_refunds_cb,
+ &ldcc);
+ if (0 > qs)
+ return qs;
+
+ 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..fb7637f0
--- /dev/null
+++ b/src/backenddb/pg_lookup_deposits_by_order.c
@@ -0,0 +1,166 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_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 GNUNET_TIME_Timestamp deposit_timestamp;
+ 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_timestamp ("deposit_timestamp",
+ &deposit_timestamp),
+ 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,
+ deposit_timestamp,
+ &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"
+ ",mcon.deposit_timestamp"
+ ",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..20f44e9d
--- /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..665f06cf
--- /dev/null
+++ b/src/backenddb/pg_lookup_refunds_detailed.h
@@ -0,0 +1,46 @@
+/*
+ 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..d2c651c9
--- /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 (0 == strcmp(kind, "discount"))
+ details->kind = TALER_MERCHANTDB_TFK_Discount;
+ else if (0 == strcmp(kind, "subscription"))
+ 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_token_family_key.c b/src/backenddb/pg_lookup_token_family_key.c
new file mode 100644
index 00000000..f1fa75f3
--- /dev/null
+++ b/src/backenddb/pg_lookup_token_family_key.c
@@ -0,0 +1,161 @@
+/*
+ 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_lookup_token_family_key.c
+ * @brief Implementation of the lookup_token_family_key function for Postgres
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <gnunet/gnunet_pq_lib.h>
+#include <gnunet/gnunet_time_lib.h>
+#include <string.h>
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_token_family_key.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_token_family_key (void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ struct GNUNET_TIME_Timestamp min_valid_after,
+ struct GNUNET_TIME_Timestamp max_valid_after,
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails *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_timestamp (&min_valid_after),
+ GNUNET_PQ_query_param_timestamp (&max_valid_after),
+ 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_key",
+ params,
+ rs_null);
+ }
+ else
+ {
+ char *kind;
+ details->pub = NULL;
+ details->priv = NULL;
+ details->valid_after = GNUNET_TIME_UNIT_ZERO_TS;
+ details->valid_before = GNUNET_TIME_UNIT_ZERO_TS;
+
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ // GNUNET_PQ_result_spec_allow_null (
+ // GNUNET_PQ_result_spec_blind_sign_pub ("pub",
+ // &details->pub->public_key),
+ // NULL),
+ // GNUNET_PQ_result_spec_allow_null (
+ // GNUNET_PQ_result_spec_blind_sign_priv ("priv",
+ // &details->priv->private_key),
+ // NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("key_valid_after",
+ &details->valid_after),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("key_valid_before",
+ &details->valid_before),
+ NULL),
+ GNUNET_PQ_result_spec_string ("slug",
+ &details->token_family.slug),
+ GNUNET_PQ_result_spec_string ("name",
+ &details->token_family.name),
+ GNUNET_PQ_result_spec_string ("description",
+ &details->token_family.description),
+ TALER_PQ_result_spec_json ("description_i18n",
+ &details->token_family.description_i18n),
+ GNUNET_PQ_result_spec_timestamp ("valid_after",
+ &details->token_family.valid_after),
+ GNUNET_PQ_result_spec_timestamp ("valid_before",
+ &details->token_family.valid_before),
+ GNUNET_PQ_result_spec_relative_time ("duration",
+ &details->token_family.duration),
+ GNUNET_PQ_result_spec_string ("kind",
+ &kind),
+ GNUNET_PQ_result_spec_uint64 ("issued",
+ &details->token_family.issued),
+ GNUNET_PQ_result_spec_uint64 ("redeemed",
+ &details->token_family.redeemed),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_token_family_key",
+ "SELECT"
+ " h_pub"
+ ",pub"
+ ",priv"
+ ",cipher"
+ ",merchant_token_family_keys.valid_after as key_valid_after"
+ ",merchant_token_family_keys.valid_before as key_valid_before"
+ ",slug"
+ ",name"
+ ",description"
+ ",description_i18n"
+ ",merchant_token_families.valid_after"
+ ",merchant_token_families.valid_before"
+ ",duration"
+ ",kind"
+ ",issued"
+ ",redeemed"
+ " FROM merchant_token_families"
+ " LEFT JOIN merchant_token_family_keys"
+ " ON merchant_token_families.token_family_serial = merchant_token_family_keys.token_family_serial"
+ " AND merchant_token_family_keys.valid_after >= $3"
+ " AND merchant_token_family_keys.valid_after <= $4"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"
+ " AND slug=$2"
+ " LIMIT 1");
+ enum GNUNET_DB_QueryStatus qs;
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_token_family_key",
+ params,
+ rs);
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ if (0 == strcmp(kind, "discount"))
+ details->token_family.kind = TALER_MERCHANTDB_TFK_Discount;
+ else if (0 == strcmp(kind, "subscription"))
+ details->token_family.kind = TALER_MERCHANTDB_TFK_Subscription;
+ else
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+
+ /* TODO: How to handle multiple results? */
+
+ return qs;
+ }
+} \ No newline at end of file
diff --git a/src/backenddb/pg_lookup_token_family_key.h b/src/backenddb/pg_lookup_token_family_key.h
new file mode 100644
index 00000000..aa7335cf
--- /dev/null
+++ b/src/backenddb/pg_lookup_token_family_key.h
@@ -0,0 +1,50 @@
+/*
+ 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_lookup_token_family_key.h
+ * @brief implementation of the lookup_token_family_key function for Postgres
+ * @author Christian Blättler
+ */
+#ifndef PG_LOOKUP_TOKEN_FAMILY_KEY_H
+#define PG_LOOKUP_TOKEN_FAMILY_KEY_H
+
+#include <gnunet/gnunet_common.h>
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup details about a particular token family key.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup token family key for
+ * @param token_family_slug slug of token family to lookup
+ * @param min_valid_after lower bound of the start of the key validation period
+ * @param max_valid_after upper bound of the start of the key validation period
+ * @param[out] details set to the token family key details on success, can be NULL
+ * (in that case we only want to check if the token family key exists)
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_token_family_key (void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ struct GNUNET_TIME_Timestamp min_valid_after,
+ struct GNUNET_TIME_Timestamp max_valid_after,
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails *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 87049d9e..e5f3f2a1 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--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
@@ -19,6 +19,8 @@
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
* @author Christian Grothoff
* @author Marcello Stanisci
+ * @author Priscilla Huang
+ * @author Iván Ãvalos
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
@@ -28,6 +30,117 @@
#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"
+#include "pg_insert_token_family_key.h"
+#include "pg_lookup_token_family_key.h"
+
/**
* How often do we re-try if we run into a DB serialization error?
@@ -36,76 +149,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
@@ -116,16 +159,19 @@ postgres_drop_tables (void *cls)
{
struct PostgresClosure *pc = cls;
struct GNUNET_PQ_Context *conn;
+ enum GNUNET_GenericReturnValue ret;
conn = GNUNET_PQ_connect_with_cfg (pc->cfg,
"merchantdb-postgres",
- "drop",
+ NULL,
NULL,
NULL);
if (NULL == conn)
return GNUNET_SYSERR;
+ ret = GNUNET_PQ_exec_sql (conn,
+ "drop");
GNUNET_PQ_disconnect (conn);
- return GNUNET_OK;
+ return ret;
}
@@ -140,16 +186,23 @@ postgres_create_tables (void *cls)
{
struct PostgresClosure *pc = cls;
struct GNUNET_PQ_Context *conn;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ 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",
"merchant-",
- NULL,
+ es,
NULL);
if (NULL == conn)
return GNUNET_SYSERR;
+ ret = GNUNET_PQ_exec_sql (conn,
+ "procedures");
GNUNET_PQ_disconnect (conn);
- return GNUNET_OK;
+ return ret;
}
@@ -216,14 +269,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;
@@ -237,14 +283,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)
@@ -254,9114 +293,30 @@ 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;
- uint8_t active = account_details->active;
- 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_auto_from_type (&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
- * @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)
-{
- 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_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_order",
- params);
-}
-
-
-/**
- * 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] 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,
- 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[4] = {
- /* 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);
- 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
- * @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)
-{
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_contract_terms",
- params);
- }
-}
-
-
-/**
- * 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)
- {
- /* FIXME: distinguish hard error */
- 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)
- {
- /* FIXME: distinguish hard error */
- 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)
- {
- /* FIXME: distinguish hard error */
- 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),
- TALER_PQ_RESULT_SPEC_AMOUNT ("wad_fee",
- &fees->wad),
- 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),
- TALER_PQ_query_param_amount (&fees->wad),
- 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);
-}
-
-
-/**
* 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",
- 0),
- /* 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",
- 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",
- 1),
- /* for postgres_lookup_instances() */
- GNUNET_PQ_make_prepare ("lookup_instance_auth",
- "SELECT"
- " auth_hash"
- ",auth_salt"
- " FROM merchant_instances"
- " WHERE merchant_id=$1",
- 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",
- 0),
- /* 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",
- 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)",
- 14),
- /* 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",
- 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",
- 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",
- 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",
- 5),
- /* 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",
- 1),
- /* for postgres_purge_instance() */
- GNUNET_PQ_make_prepare ("purge_instance",
- "DELETE FROM merchant_instances"
- " WHERE merchant_instances.merchant_id = $1",
- 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",
- 11),
- /* 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",
- 3),
- /* 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)",
- 2),
- /* 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)",
- 2),
- /* 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",
- 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",
- 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)",
- 2),
- /* 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",
- 13),
- /* 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",
- 14),
-
- /* 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)",
- 5),
-
- /* for postgres_expire_locks() */
- GNUNET_PQ_make_prepare ("unlock_products",
- "DELETE FROM merchant_inventory_locks"
- " WHERE expiration < $1",
- 1),
- /* for postgres_expire_locks() */
- GNUNET_PQ_make_prepare ("unlock_orders",
- "DELETE FROM merchant_orders"
- " WHERE pay_deadline < $1",
- 1),
- /* for postgres_expire_locks() */
- GNUNET_PQ_make_prepare ("unlock_contracts",
- "DELETE FROM merchant_contract_terms"
- " WHERE NOT paid"
- " AND pay_deadline < $1",
- 1),
-
- /* for postgres_delete_order() */
- GNUNET_PQ_make_prepare ("delete_order",
- "DELETE"
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_orders.order_id=$2"
- " AND"
- " ((NOT EXISTS"
- " (SELECT order_id"
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.order_id=$2))"
- " OR pay_deadline < $3)",
- 3),
- /* 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",
- 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)",
- 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",
- 7),
- 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",
- 7),
- 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",
- 7),
- 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",
- 7),
- 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",
- 7),
- 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",
- 7),
- 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",
- 7),
- 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",
- 7),
- 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",
- 7),
- 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",
- 7),
- 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",
- 7),
- 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",
- 7),
- 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",
- 7),
- 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",
- 7),
- 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",
- 7),
- 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",
- 7),
- /* 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",
- 7),
- /* for postgres_unlock_inventory() */
- GNUNET_PQ_make_prepare ("unlock_inventory",
- "DELETE"
- " FROM merchant_inventory_locks"
- " WHERE lock_uuid=$1",
- 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)",
- 4),
- /* for postgres_lookup_contract_terms() */
- GNUNET_PQ_make_prepare ("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)",
- 2),
- /* 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)",
- 7),
- /* 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)",
- 7),
- /* 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) )",
- 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))",
- 2),
- /* 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)",
- 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",
- 16),
- /* 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))",
- 2),
- /* 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)",
- 3),
- /* 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",
- 2),
- /* 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)",
- 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))",
- 5),
-
- /* 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",
- 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",
- 2),
-
- /* for postgres_lookup_payment_status() */
- GNUNET_PQ_make_prepare ("lookup_payment_status",
- "SELECT"
- " wired"
- ",paid"
- " FROM merchant_contract_terms"
- " WHERE order_serial=$1",
- 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",
- 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",
- 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)",
- 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",
- 7),
- /* for postgres_mark_order_wired() */
- GNUNET_PQ_make_prepare ("mark_order_wired",
- "UPDATE merchant_contract_terms SET"
- " wired=true"
- " WHERE order_serial=$1",
- 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",
- 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)",
- 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))",
- 2),
- /* 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))",
- 2),
- /* 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",
- 5),
- /* 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",
- 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)",
- 3),
- /* 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)",
- 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))",
- 2),
- /* 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)",
- 2),
- /* 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)",
- 2),
- /* 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))",
- 4),
- /* 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",
- 8),
- /* 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)",
- 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)))",
- 1),
- /* 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"
- ",wad_fee_val"
- ",wad_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",
- 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)",
- 3),
- /* 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;",
- 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",
- 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",
- 2),
- /* 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",
- 2),
- /* 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",
- 6),
- /* 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",
- 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",
- 4),
- /* 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",
- 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",
- 6),
- /* 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",
- 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",
- 4),
- /* 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",
- 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"
- ",wad_fee_val"
- ",wad_fee_frac"
- ",start_date"
- ",end_date"
- ",master_sig)"
- " VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
- 11),
- /* 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",
- 6),
- /* 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)",
- 4),
- /* 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)",
- 5),
- /* 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)",
- 2),
- /* 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",
- 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)",
- 2),
- /* 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))",
- 2),
- /* 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))",
- 2),
- /* 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)",
- 2),
- /* 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)",
- 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)",
- 2),
- /* 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)",
- 4),
- /* 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)",
- 8),
- /* 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)",
- 2),
- /* 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",
- 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)",
- 2),
- /* 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",
- 5),
- 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",
- 5),
- 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",
- 5),
- 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",
- 5),
- /* 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)",
- 2),
- /* 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",
- 1),
- /* 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)",
- 5),
- /* 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",
- 3),
- /* 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)",
- 2),
- /* 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",
- 3),
- /* 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",
- 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
};
pg->conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
"merchantdb-postgres",
NULL,
- NULL,
- ps);
+ es,
+ NULL);
+ pg->prep_gen++;
if (NULL == pg->conn)
return GNUNET_SYSERR;
return GNUNET_OK;
-}
+};
/**
@@ -9391,14 +346,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;
@@ -9408,93 +355,236 @@ 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;
+ = &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->insert_token_family_key
+ = &TMH_PG_insert_token_family_key;
+ plugin->lookup_token_family_key
+ = &TMH_PG_lookup_token_family_key;
+ plugin->update_deposit_confirmation_status
+ = &TMH_PG_update_deposit_confirmation_status;
+
+
return plugin;
}
@@ -9517,7 +607,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 f7c2dde7..fbb662f8 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
@@ -18,6 +18,7 @@
* @brief testcase for merchant's postgres db plugin
* @author Marcello Stanisci
* @author Christian Grothoff
+ * @author Pricilla Huang
*/
#include "platform.h"
#include <taler/taler_util.h>
@@ -41,28 +42,28 @@ static struct TALER_MERCHANTDB_Plugin *plugin;
* @param test 0 on success, non-zero on failure
*/
#define TEST_WITH_FAIL_CLAUSE(test, on_fail) \
- if ((test)) \
- { \
- GNUNET_break (0); \
- on_fail \
- }
+ if ((test)) \
+ { \
+ GNUNET_break (0); \
+ on_fail \
+ }
#define TEST_COND_RET_ON_FAIL(cond, msg) \
- if (! (cond)) \
- { \
- GNUNET_break (0); \
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
- msg); \
- return 1; \
- }
+ if (! (cond)) \
+ { \
+ GNUNET_break (0); \
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
+ msg); \
+ return 1; \
+ }
/**
* @param __test 0 on success, non-zero on failure
*/
#define TEST_RET_ON_FAIL(__test) \
- TEST_WITH_FAIL_CLAUSE (__test, \
- return 1; \
- )
+ TEST_WITH_FAIL_CLAUSE (__test, \
+ return 1; \
+ )
/* ********** Instances ********** */
@@ -100,6 +101,9 @@ static void
make_instance (char *instance_id,
struct InstanceData *instance)
{
+ memset (instance,
+ 0,
+ sizeof (*instance));
GNUNET_CRYPTO_eddsa_key_create (&instance->merchant_priv.eddsa_priv);
GNUNET_CRYPTO_eddsa_key_get_public (&instance->merchant_priv.eddsa_priv,
&instance->merchant_pub.eddsa_pub);
@@ -113,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_ ();
@@ -149,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,
@@ -228,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))
@@ -268,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'.
*
@@ -276,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;
}
}
@@ -381,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,
@@ -429,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;
}
@@ -592,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;
@@ -814,6 +786,7 @@ check_products_equal (const struct TALER_MERCHANTDB_ProductDetails *a,
(GNUNET_TIME_timestamp_cmp (a->next_restock,
!=,
b->next_restock)))
+
return 1;
return 0;
}
@@ -933,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;
@@ -965,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,
@@ -975,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))
{
@@ -1157,7 +1136,8 @@ run_test_products (struct TestProducts_Closure *cls)
stock_dec.product.total_stock = 40;
TEST_RET_ON_FAIL (test_update_product (&cls->instance,
&stock_dec,
- GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS))
+ ;
}
{
struct ProductData lost_dec = cls->products[0];
@@ -1165,7 +1145,8 @@ run_test_products (struct TestProducts_Closure *cls)
lost_dec.product.total_lost = 1;
TEST_RET_ON_FAIL (test_update_product (&cls->instance,
&lost_dec,
- GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS))
+ ;
}
TEST_RET_ON_FAIL (test_lookup_product (&cls->instance,
&cls->products[0]));
@@ -1355,10 +1336,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;
}
@@ -1488,7 +1472,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,
@@ -1620,7 +1604,8 @@ test_delete_order (const struct InstanceData *instance,
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->delete_order (plugin->cls,
instance->instance.id,
- order->id),
+ order->id,
+ false),
"Delete order failed\n");
return 0;
}
@@ -1639,11 +1624,14 @@ test_insert_contract_terms (const struct InstanceData *instance,
const struct OrderData *order,
enum GNUNET_DB_QueryStatus expected_result)
{
+ uint64_t os;
+
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_contract_terms (plugin->cls,
instance->instance.id,
order->id,
- order->contract),
+ order->contract,
+ &os),
"Insert contract terms failed\n");
return 0;
}
@@ -1831,6 +1819,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,
@@ -1861,25 +1850,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\n");
+ "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: paid status is wrong\n");
return 1;
}
return 0;
@@ -2069,6 +2077,7 @@ run_test_orders (struct TestOrders_Closure *cls)
{
json_t *lookup_contract = NULL;
uint64_t lookup_order_serial;
+
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->lookup_contract_terms (plugin->cls,
cls->instance.instance.id,
@@ -2113,21 +2122,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;
@@ -2149,25 +2148,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],
@@ -2179,6 +2174,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,
@@ -2201,13 +2197,16 @@ run_test_orders (struct TestOrders_Closure *cls)
cls->orders));
/* 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,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT))
+ ;
+ TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
+ cls->orders[0].id,
NULL,
true,
true));
TEST_RET_ON_FAIL (test_mark_order_wired (1007,
- GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS))
+ ;
/* If an order has been claimed and we aren't past
the pay deadline, we can't delete it. */
TEST_RET_ON_FAIL (test_delete_order (&cls->instance,
@@ -2372,6 +2371,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;
@@ -2410,39 +2414,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.
@@ -2461,7 +2432,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;
@@ -2471,7 +2441,7 @@ make_deposit (const struct InstanceData *instance,
GNUNET_CRYPTO_eddsa_key_create (&coin_priv.eddsa_priv);
GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
&deposit->coin_pub.eddsa_pub);
- deposit->exchange_url = "test-exchange";
+ deposit->exchange_url = "https://test-exchange/";
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:50.00",
&deposit->amount_with_fee));
@@ -2489,22 +2459,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));
}
@@ -2522,7 +2482,8 @@ test_insert_exchange_signkey (const struct ExchangeSignkeyData *signkey,
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_exchange_signkey (plugin->cls,
&signkey->master_pub,
- &signkey->exchange_pub,
+ &signkey->exchange_pub
+ ,
signkey->start_date,
signkey->expire_date,
signkey->end_date,
@@ -2547,21 +2508,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;
}
@@ -2601,7 +2580,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,
@@ -2609,8 +2587,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)
@@ -2638,14 +2615,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;
}
@@ -2669,7 +2639,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,
@@ -2841,8 +2811,8 @@ test_lookup_deposits_contract_and_coin (
* @param cls pointer to the test lookup closure.
* @param deposit_serial row number of the deposit in the database.
* @param exchange_url URL to the exchange
- * @param amount_with_fee amount of the deposit with fees.
* @param h_wire hash of the wire transfer details.
+ * @param deposit_timestamp when was the deposit made
* @param amount_with_fee amount of the deposit with fees.
* @param deposit_fee fee charged for the deposit.
* @param coin_pub public key of the coin deposited.
@@ -2852,11 +2822,13 @@ lookup_deposits_order_cb (void *cls,
uint64_t deposit_serial,
const char *exchange_url,
const struct TALER_MerchantWireHashP *h_wire,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
struct TestLookupDeposits_Closure *cmp = cls;
+
if (NULL == cmp)
return;
cmp->results_length += 1;
@@ -2906,11 +2878,11 @@ test_lookup_deposits_by_order (uint64_t order_serial,
memset (results_matching,
0,
sizeof (unsigned int) * deposits_length);
- if (deposits_length != plugin->lookup_deposits_by_order (plugin->cls,
- order_serial,
- &
- lookup_deposits_order_cb,
- &cmp))
+ if (deposits_length !=
+ plugin->lookup_deposits_by_order (plugin->cls,
+ order_serial,
+ &lookup_deposits_order_cb,
+ &cmp))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup deposits by order failed\n");
@@ -2958,8 +2930,8 @@ struct LookupDepositSerial_Closure
* @param cls pointer to the test lookup closure.
* @param deposit_serial row number of the deposit in the database.
* @param exchange_url URL to the exchange
- * @param amount_with_fee amount of the deposit with fees.
* @param h_wire hash of the wire transfer details.
+ * @param deposit_timestamp when was the deposit made.
* @param amount_with_fee amount of the deposit with fees.
* @param deposit_fee fee charged for the deposit.
* @param coin_pub public key of the coin deposited.
@@ -2969,11 +2941,14 @@ get_deposit_serial_cb (void *cls,
uint64_t deposit_serial,
const char *exchange_url,
const struct TALER_MerchantWireHashP *h_wire,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
struct LookupDepositSerial_Closure *lookup_cls = cls;
+
+ (void) deposit_timestamp;
if (NULL == lookup_cls)
return;
if ((0 == strcmp (lookup_cls->deposit->exchange_url,
@@ -3290,9 +3265,6 @@ make_wire_fee (const struct ExchangeSignkeyData *signkey,
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:0.49",
&wire_fee->fees.closing));
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:0.49",
- &wire_fee->fees.wad));
wire_fee->wire_fee_start = GNUNET_TIME_timestamp_get ();
wire_fee->wire_fee_end = GNUNET_TIME_relative_to_timestamp (
GNUNET_TIME_UNIT_MONTHS);
@@ -3353,9 +3325,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,
@@ -3363,9 +3337,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,
@@ -3793,11 +3764,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");
@@ -4128,7 +4100,8 @@ test_insert_transfer_details (
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_transfer_details (plugin->cls,
instance->instance.id,
- transfer->exchange_url,
+ transfer->exchange_url
+ ,
account->payto_uri,
&transfer->wtid,
&transfer->data),
@@ -4235,6 +4208,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);
}
@@ -4330,21 +4306,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,
@@ -4390,1269 +4368,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;
- }
- /* FIXME: What about ‘exchange_url’ and ‘payto_uri’? --ttn */
- memset (tip_cmp_results, 0, sizeof (unsigned int) * tips_length);
- 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 = "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.
*/
@@ -5754,7 +4469,8 @@ test_lookup_refunds (const struct InstanceData *instance,
if (refunds_length != cmp.results_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup refunds failed: incorrect number of results returned\n");
+ "Lookup refunds failed: incorrect number of results returned\n")
+ ;
return 1;
}
for (unsigned int i = 0; refunds_length > i; ++i)
@@ -5962,7 +4678,8 @@ test_lookup_refunds_detailed (
if (refunds_length != cmp.results_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup refunds detailed failed: incorrect number of results\n");
+ "Lookup refunds detailed failed: incorrect number of results\n")
+ ;
return 1;
}
for (unsigned int i = 0; refunds_length > i; ++i)
@@ -6276,7 +4993,8 @@ run_test_refunds (struct TestRefunds_Closure *cls)
TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
plugin->refund_coin (plugin->cls,
cls->instance.instance.id,
- &cls->deposits[0].h_contract_terms,
+ &cls->deposits[0].h_contract_terms
+ ,
cls->refunds[0].timestamp,
cls->refunds[0].coin_pub,
cls->refunds[0].reason),
@@ -6288,7 +5006,8 @@ run_test_refunds (struct TestRefunds_Closure *cls)
TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
plugin->refund_coin (plugin->cls,
cls->instance.instance.id,
- &cls->deposits[0].h_contract_terms,
+ &cls->deposits[0].h_contract_terms
+ ,
cls->refunds[0].timestamp,
cls->refunds[0].coin_pub,
cls->refunds[0].reason),
@@ -6312,14 +5031,16 @@ run_test_refunds (struct TestRefunds_Closure *cls)
refund_serial,
&cls->refund_proof.
exchange_sig,
- &cls->signkey.exchange_pub),
+ &cls->signkey.exchange_pub
+ ),
"Insert refund proof failed\n");
TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
plugin->insert_refund_proof (plugin->cls,
refund_serial,
&cls->refund_proof.
exchange_sig,
- &cls->signkey.exchange_pub),
+ &cls->signkey.exchange_pub
+ ),
"Insert refund proof failed\n");
/* Test that we can't give too much in refunds */
GNUNET_assert (GNUNET_OK ==
@@ -6485,9 +5206,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],
@@ -6691,7 +5413,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;
@@ -6707,7 +5430,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;
@@ -6747,7 +5471,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,
@@ -6757,7 +5482,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,
@@ -6767,7 +5493,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,
@@ -6804,11 +5531,1556 @@ test_kyc (void)
&kyc_status_ok,
&fail));
TEST_RET_ON_FAIL (fail);
+ json_decref (instance.instance.address);
+ json_decref (instance.instance.jurisdiction);
+ return 0;
+}
+
+
+/* *********** Templates ********** */
+
+/**
+ * A container for data relevant to a template.
+ */
+struct TemplateData
+{
+ /**
+ * The identifier of the template.
+ */
+ const char *id;
+
+ /**
+ * The details of the template.
+ */
+ struct TALER_MERCHANTDB_TemplateDetails template;
+};
+
+
+/**
+ * Creates a template for testing with.
+ *
+ * @param id the id of the template.
+ * @param template the template data to fill.
+ */
+static void
+make_template (const char *id,
+ struct TemplateData *template)
+{
+ template->id = id;
+ template->template.template_description = "This is a test template";
+ template->template.otp_id = NULL;
+ template->template.template_contract = json_array ();
+ GNUNET_assert (NULL != template->template.template_contract);
+}
+
+
+/**
+ * Frees memory associated with @e TemplateData.
+ *
+ * @param template the container to free.
+ */
+static void
+free_template_data (struct TemplateData *template)
+{
+ GNUNET_free (template->template.otp_id);
+ json_decref (template->template.template_contract);
+}
+
+
+/**
+ * Compare two templates for equality.
+ *
+ * @param a the first template.
+ * @param b the second template.
+ * @return 0 on equality, 1 otherwise.
+ */
+static int
+check_templates_equal (const struct TALER_MERCHANTDB_TemplateDetails *a,
+ const struct TALER_MERCHANTDB_TemplateDetails *b)
+{
+ if ((0 != strcmp (a->template_description,
+ b->template_description)) ||
+ ( (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;
+ return 0;
+}
+
+
+/**
+ * Tests inserting template data into the database.
+ *
+ * @param instance the instance to insert the template for.
+ * @param template the template data to insert.
+ * @param expected_result the result we expect the db to return.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_insert_template (const struct InstanceData *instance,
+ const struct TemplateData *template,
+ enum GNUNET_DB_QueryStatus expected_result)
+{
+ TEST_COND_RET_ON_FAIL (expected_result ==
+ plugin->insert_template (plugin->cls,
+ instance->instance.id,
+ template->id,
+ 0,
+ &template->template),
+ "Insert template failed\n");
+ return 0;
+}
+
+
+/**
+ * Tests updating template data in the database.
+ *
+ * @param instance the instance to update the template for.
+ * @param template the template data to update.
+ * @param expected_result the result we expect the db to return.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_update_template (const struct InstanceData *instance,
+ const struct TemplateData *template,
+ enum GNUNET_DB_QueryStatus expected_result)
+{
+ TEST_COND_RET_ON_FAIL (expected_result ==
+ plugin->update_template (plugin->cls,
+ instance->instance.id,
+ template->id,
+ &template->template),
+ "Update template failed\n");
+ return 0;
+}
+
+
+/**
+ * Tests looking up a template from the db.
+ *
+ * @param instance the instance to query from.
+ * @param template the template to query and compare to.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+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);
+ return 1;
+ }
+ const struct TALER_MERCHANTDB_TemplateDetails *to_cmp = &template->template;
+ if (0 != check_templates_equal (&lookup_result,
+ to_cmp))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup template failed: incorrect template returned\n");
+ TALER_MERCHANTDB_template_details_free (&lookup_result);
+ return 1;
+ }
+ TALER_MERCHANTDB_template_details_free (&lookup_result);
+ return 0;
+}
+
+
+/**
+ * Closure for testing template lookup
+ */
+struct TestLookupTemplates_Closure
+{
+ /**
+ * Number of template ids to compare to
+ */
+ unsigned int templates_to_cmp_length;
+
+ /**
+ * Pointer to array of template ids
+ */
+ const struct TemplateData *templates_to_cmp;
+
+ /**
+ * Pointer to array of number of matches for each template
+ */
+ unsigned int *results_matching;
+
+ /**
+ * Total number of results returned
+ */
+ unsigned int results_length;
+};
+
+
+/**
+ * Function called after calling @e test_lookup_templates
+ *
+ * @param cls a pointer to the lookup closure.
+ * @param template_id the identifier of the template found.
+ */
+static void
+lookup_templates_cb (void *cls,
+ const char *template_id,
+ 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)) )
+ cmp->results_matching[i] += 1;
+ }
+}
+
+
+/**
+ * Tests looking up all templates for an instance.
+ *
+ * @param instance the instance to query from.
+ * @param templates_length the number of templates we are expecting.
+ * @param templates the list of templates that we expect to be found.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_lookup_templates (const struct InstanceData *instance,
+ unsigned int templates_length,
+ const struct TemplateData *templates)
+{
+ unsigned int results_matching[templates_length];
+ struct TestLookupTemplates_Closure cls = {
+ .templates_to_cmp_length = templates_length,
+ .templates_to_cmp = templates,
+ .results_matching = results_matching,
+ .results_length = 0
+ };
+
+ memset (results_matching,
+ 0,
+ sizeof (unsigned int) * templates_length);
+ if (0 > plugin->lookup_templates (plugin->cls,
+ instance->instance.id,
+ &lookup_templates_cb,
+ &cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup templates failed\n");
+ return 1;
+ }
+ if (templates_length != cls.results_length)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup templates failed: incorrect number of results\n");
+ return 1;
+ }
+ for (unsigned int i = 0; templates_length > i; ++i)
+ {
+ if (1 != cls.results_matching[i])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup templates failed: mismatched data\n");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Tests deleting a template.
+ *
+ * @param instance the instance to delete the template from.
+ * @param template the template that should be deleted.
+ * @param expected_result the result that we expect the plugin to return.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_delete_template (const struct InstanceData *instance,
+ const struct TemplateData *template,
+ enum GNUNET_DB_QueryStatus expected_result)
+{
+ TEST_COND_RET_ON_FAIL (expected_result ==
+ plugin->delete_template (plugin->cls,
+ instance->instance.id,
+ template->id),
+ "Delete template failed\n");
+ return 0;
+}
+
+
+/**
+ * Closure for template tests.
+ */
+struct TestTemplates_Closure
+{
+ /**
+ * The instance to use for this test.
+ */
+ struct InstanceData instance;
+
+ /**
+ * The array of templates.
+ */
+ struct TemplateData templates[2];
+};
+
+
+/**
+ * Sets up the data structures used in the template tests.
+ *
+ * @param cls the closure to fill with test data.
+ */
+static void
+pre_test_templates (struct TestTemplates_Closure *cls)
+{
+ /* Instance */
+ make_instance ("test_inst_templates",
+ &cls->instance);
+
+ /* Templates */
+ make_template ("test_templates_pd_0",
+ &cls->templates[0]);
+
+ make_template ("test_templates_pd_1",
+ &cls->templates[1]);
+ cls->templates[1].template.template_description =
+ "This is a another test template";
+}
+
+
+/**
+ * Handles all teardown after testing.
+ *
+ * @param cls the closure containing memory to be freed.
+ */
+static void
+post_test_templates (struct TestTemplates_Closure *cls)
+{
+ free_instance_data (&cls->instance);
+ free_template_data (&cls->templates[0]);
+ free_template_data (&cls->templates[1]);
+}
+
+
+/**
+ * Runs the tests for templates.
+ *
+ * @param cls the container of the test data.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+run_test_templates (struct TestTemplates_Closure *cls)
+{
+
+ /* Test that insert without an instance fails */
+ TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
+ &cls->templates[0],
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+ /* Insert the instance */
+ TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ /* Test inserting a template */
+ TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
+ &cls->templates[0],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ /* Test that double insert fails */
+ TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
+ &cls->templates[0],
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+ /* Test lookup of individual templates */
+ TEST_RET_ON_FAIL (test_lookup_template (&cls->instance,
+ &cls->templates[0]));
+ /* Make sure it fails correctly for templates that don't exist */
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->lookup_template (plugin->cls,
+ cls->instance.instance.id,
+ "nonexistent_template",
+ NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup template failed\n");
+ return 1;
+ }
+ /* Test template update */
+ cls->templates[0].template.template_description =
+ "This is a test template that has been updated!";
+ 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,
+ json_string ("This is a test. 3CH.")));
+ 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,
+ &cls->templates[1],
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+ /* Test collective template lookup */
+ TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
+ &cls->templates[1],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ TEST_RET_ON_FAIL (test_lookup_templates (&cls->instance,
+ 2,
+ cls->templates));
+
+ /* Test template deletion */
+ TEST_RET_ON_FAIL (test_delete_template (&cls->instance,
+ &cls->templates[1],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ /* Test double deletion fails */
+ TEST_RET_ON_FAIL (test_delete_template (&cls->instance,
+ &cls->templates[1],
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+ TEST_RET_ON_FAIL (test_lookup_templates (&cls->instance,
+ 1,
+ cls->templates));
+ return 0;
+}
+
+
+/**
+ * Takes care of template testing.
+ *
+ * @return 0 on success, 1 otherwise.
+ */
+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);
+ test_result = run_test_templates (&test_cls);
+ post_test_templates (&test_cls);
+ return test_result;
+}
+
+
+/* *********** Webhooks ********** */
+
+/**
+ * A container for data relevant to a webhook.
+ */
+struct WebhookData
+{
+
+ /**
+ * The identifier of the webhook.
+ */
+ const char *id;
+
+ /**
+ * The details of the webhook.
+ */
+ struct TALER_MERCHANTDB_WebhookDetails webhook;
+};
+
+
+/**
+ * Creates a webhook for testing with.
+ *
+ * @param id the id of the webhook.
+ * @param webhook the webhook data to fill.
+ */
+static void
+make_webhook (const char *id,
+ struct WebhookData *webhook)
+{
+ webhook->id = id;
+ webhook->webhook.event_type = "Paid";
+ webhook->webhook.url = "https://exampletest.com";
+ webhook->webhook.http_method = "POST";
+ webhook->webhook.header_template = "Authorization:XYJAORKJEO";
+ webhook->webhook.body_template = "$Amount";
+}
+
+
+/**
+ * Compare two webhooks for equality.
+ *
+ * @param a the first webhook.
+ * @param b the second webhook.
+ * @return 0 on equality, 1 otherwise.
+ */
+static int
+check_webhooks_equal (const struct TALER_MERCHANTDB_WebhookDetails *a,
+ const struct TALER_MERCHANTDB_WebhookDetails *b)
+{
+ if ((0 != strcmp (a->event_type,
+ b->event_type)) ||
+ (0 != strcmp (a->url,
+ b->url)) ||
+ (0 != strcmp (a->http_method,
+ b->http_method)) ||
+ (0 != strcmp (a->header_template,
+ b->header_template)) ||
+ (0 != strcmp (a->body_template,
+ b->body_template)))
+ return 1;
+ return 0;
+}
+
+
+/**
+ * Tests inserting webhook data into the database.
+ *
+ * @param instance the instance to insert the webhook for.
+ * @param webhook the webhook data to insert.
+ * @param expected_result the result we expect the db to return.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_insert_webhook (const struct InstanceData *instance,
+ const struct WebhookData *webhook,
+ enum GNUNET_DB_QueryStatus expected_result)
+{
+ TEST_COND_RET_ON_FAIL (expected_result ==
+ plugin->insert_webhook (plugin->cls,
+ instance->instance.id,
+ webhook->id,
+ &webhook->webhook),
+ "Insert webhook failed\n");
+ return 0;
+}
+
+
+/**
+ * Tests updating webhook data in the database.
+ *
+ * @param instance the instance to update the webhook for.
+ * @param webhook the webhook data to update.
+ * @param expected_result the result we expect the db to return.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_update_webhook (const struct InstanceData *instance,
+ const struct WebhookData *webhook,
+ enum GNUNET_DB_QueryStatus expected_result)
+{
+ TEST_COND_RET_ON_FAIL (expected_result ==
+ plugin->update_webhook (plugin->cls,
+ instance->instance.id,
+ webhook->id,
+ &webhook->webhook),
+ "Update webhook failed\n");
+ return 0;
+}
+
+
+/**
+ * Tests looking up a webhook from the db.
+ *
+ * @param instance the instance to query from.
+ * @param webhook the webhook to query and compare to.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_lookup_webhook (const struct InstanceData *instance,
+ const struct WebhookData *webhook)
+{
+ struct TALER_MERCHANTDB_WebhookDetails lookup_result;
+ if (0 > plugin->lookup_webhook (plugin->cls,
+ instance->instance.id,
+ webhook->id,
+ &lookup_result))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup webhook failed\n");
+ TALER_MERCHANTDB_webhook_details_free (&lookup_result);
+ return 1;
+ }
+ const struct TALER_MERCHANTDB_WebhookDetails *to_cmp = &webhook->webhook;
+ if (0 != check_webhooks_equal (&lookup_result,
+ to_cmp))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup webhook failed: incorrect webhook returned\n");
+ TALER_MERCHANTDB_webhook_details_free (&lookup_result);
+ return 1;
+ }
+ TALER_MERCHANTDB_webhook_details_free (&lookup_result);
+ return 0;
+}
+
+
+/**
+ * Closure for testing webhook lookup
+ */
+struct TestLookupWebhooks_Closure
+{
+ /**
+ * Number of webhook ids to compare to
+ */
+ unsigned int webhooks_to_cmp_length;
+
+ /**
+ * Pointer to array of webhook ids
+ */
+ const struct WebhookData *webhooks_to_cmp;
+
+ /**
+ * Pointer to array of number of matches for each webhook
+ */
+ unsigned int *results_matching;
+
+ /**
+ * Total number of results returned
+ */
+ unsigned int results_length;
+};
+
+
+/**
+ * Function called after calling @e test_lookup_webhooks
+ *
+ * @param cls a pointer to the lookup closure.
+ * @param webhook_id the identifier of the webhook found.
+ */
+static void
+lookup_webhooks_cb (void *cls,
+ const char *webhook_id,
+ const char *event_type)
+{
+ struct TestLookupWebhooks_Closure *cmp = cls;
+ if (NULL == cmp)
+ return;
+ cmp->results_length += 1;
+ for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
+ {
+ if ((0 == strcmp (cmp->webhooks_to_cmp[i].id,
+ webhook_id)) &&
+ (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.event_type,
+ event_type)) )
+ cmp->results_matching[i] += 1;
+ }
+}
+
+
+/**
+ * Tests looking up all webhooks for an instance.
+ *
+ * @param instance the instance to query from.
+ * @param webhooks_length the number of webhooks we are expecting.
+ * @param webhooks the list of webhooks that we expect to be found.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_lookup_webhooks (const struct InstanceData *instance,
+ unsigned int webhooks_length,
+ const struct WebhookData *webhooks)
+{
+ unsigned int results_matching[webhooks_length];
+ struct TestLookupWebhooks_Closure cls = {
+ .webhooks_to_cmp_length = webhooks_length,
+ .webhooks_to_cmp = webhooks,
+ .results_matching = results_matching,
+ .results_length = 0
+ };
+ memset (results_matching, 0, sizeof (unsigned int) * webhooks_length);
+ if (0 > plugin->lookup_webhooks (plugin->cls,
+ instance->instance.id,
+ &lookup_webhooks_cb,
+ &cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup webhooks failed\n");
+ return 1;
+ }
+ if (webhooks_length != cls.results_length)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup webhooks failed: incorrect number of results\n");
+ return 1;
+ }
+ for (unsigned int i = 0; webhooks_length > i; ++i)
+ {
+ if (1 != cls.results_matching[i])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup webhooks failed: mismatched data\n");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Function called after calling @e test_lookup_webhooks
+ *
+ * @param cls a pointer to the lookup closure.
+ * @param webhook_id the identifier of the webhook found.
+ */
+static void
+lookup_webhook_by_event_cb (void *cls,
+ uint64_t webhook_serial,
+ const char *event_type,
+ const char *url,
+ const char *http_method,
+ const char *header_template,
+ const char *body_template)
+{
+ struct TestLookupWebhooks_Closure *cmp = cls;
+ if (NULL == cmp)
+ return;
+ cmp->results_length += 1;
+ for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
+ {
+ if ((0 == strcmp (cmp->webhooks_to_cmp[i].webhook.event_type,
+ event_type)) &&
+ (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.url,
+ url)) &&
+ (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.http_method,
+ http_method)) &&
+ (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.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.
+ *
+ * @param instance the instance to query from.
+ * @param webhooks_length the number of webhooks we are expecting.
+ * @param webhooks the list of webhooks that we expect to be found.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_lookup_webhook_by_event (const struct InstanceData *instance,
+ unsigned int webhooks_length,
+ const struct WebhookData *webhooks)
+{
+ unsigned int results_matching[webhooks_length];
+ struct TestLookupWebhooks_Closure cls = {
+ .webhooks_to_cmp_length = webhooks_length,
+ .webhooks_to_cmp = webhooks,
+ .results_matching = results_matching,
+ .results_length = 0
+ };
+ memset (results_matching, 0, sizeof (unsigned int) * webhooks_length);
+ if (0 > plugin->lookup_webhook_by_event (plugin->cls,
+ instance->instance.id,
+ webhooks->webhook.event_type,
+ &lookup_webhook_by_event_cb,
+ &cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup webhooks by event failed\n");
+ return 1;
+ }
+
+ if (webhooks_length != cls.results_length)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup webhooks by event failed: incorrect number of results\n");
+ return 1;
+ }
+ for (unsigned int i = 0; webhooks_length > i; ++i)
+ {
+ if (1 != cls.results_matching[i])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup webhooks by event failed: mismatched data\n");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Tests deleting a webhook.
+ *
+ * @param instance the instance to delete the webhook from.
+ * @param webhook the webhook that should be deleted.
+ * @param expected_result the result that we expect the plugin to return.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_delete_webhook (const struct InstanceData *instance,
+ const struct WebhookData *webhook,
+ enum GNUNET_DB_QueryStatus expected_result)
+{
+ TEST_COND_RET_ON_FAIL (expected_result ==
+ plugin->delete_webhook (plugin->cls,
+ instance->instance.id,
+ webhook->id),
+ "Delete webhook failed\n");
+ return 0;
+}
+
+
+/**
+ * Closure for webhook tests.
+ */
+struct TestWebhooks_Closure
+{
+ /**
+ * The instance to use for this test.
+ */
+ struct InstanceData instance;
+
+ /**
+ * The array of webhooks.
+ */
+ struct WebhookData webhooks[3];
+};
+
+
+/**
+ * Sets up the data structures used in the webhook tests.
+ *
+ * @param cls the closure to fill with test data.
+ */
+static void
+pre_test_webhooks (struct TestWebhooks_Closure *cls)
+{
+ /* Instance */
+ make_instance ("test_inst_webhooks",
+ &cls->instance);
+
+ /* Webhooks */
+ make_webhook ("test_webhooks_wb_0",
+ &cls->webhooks[0]);
+
+ make_webhook ("test_webhooks_wb_1",
+ &cls->webhooks[1]);
+ cls->webhooks[1].webhook.event_type = "Test paid";
+ cls->webhooks[1].webhook.url = "https://example.com";
+ cls->webhooks[1].webhook.http_method = "POST";
+ cls->webhooks[1].webhook.header_template = "Authorization:1XYJAOR493O";
+ cls->webhooks[1].webhook.body_template = "$Amount";
+
+ make_webhook ("test_webhooks_wb_2",
+ &cls->webhooks[2]);
+ cls->webhooks[2].webhook.event_type = "Test paid";
+ cls->webhooks[2].webhook.url = "https://examplerefund.com";
+ cls->webhooks[2].webhook.http_method = "POST";
+ cls->webhooks[2].webhook.header_template = "Authorization:XY6ORK52JEO";
+ cls->webhooks[2].webhook.body_template = "$Amount";
+}
+
+
+/**
+ * Handles all teardown after testing.
+ *
+ * @param cls the closure containing memory to be freed.
+ */
+static void
+post_test_webhooks (struct TestWebhooks_Closure *cls)
+{
+ free_instance_data (&cls->instance);
+}
+
+
+/**
+ * Runs the tests for webhooks.
+ *
+ * @param cls the container of the test data.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+run_test_webhooks (struct TestWebhooks_Closure *cls)
+{
+
+ /* Test that insert without an instance fails */
+ TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
+ &cls->webhooks[0],
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+ /* Insert the instance */
+ TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ /* Test inserting a webhook */
+ TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
+ &cls->webhooks[0],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ /* Test that double insert fails */
+ TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
+ &cls->webhooks[0],
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+ /* Test lookup of individual webhooks */
+ TEST_RET_ON_FAIL (test_lookup_webhook (&cls->instance,
+ &cls->webhooks[0]));
+ /* Make sure it fails correctly for webhooks that don't exist */
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->lookup_webhook (plugin->cls,
+ cls->instance.instance.id,
+ "nonexistent_webhook",
+ NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup webhook failed\n");
+ return 1;
+ }
+ /* Test webhook update */
+ cls->webhooks[0].webhook.event_type =
+ "Test paid";
+ cls->webhooks[0].webhook.url =
+ "example.com";
+ cls->webhooks[0].webhook.http_method =
+ "POST";
+ cls->webhooks[0].webhook.header_template =
+ "Authorization:WEKFOEKEXZ";
+ cls->webhooks[0].webhook.body_template =
+ "$Amount";
+ TEST_RET_ON_FAIL (test_update_webhook (&cls->instance,
+ &cls->webhooks[0],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+
+ TEST_RET_ON_FAIL (test_lookup_webhook (&cls->instance,
+ &cls->webhooks[0]));
+ TEST_RET_ON_FAIL (test_update_webhook (&cls->instance,
+ &cls->webhooks[1],
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+ /* Test collective webhook lookup */
+ TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
+ &cls->webhooks[1],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ TEST_RET_ON_FAIL (test_lookup_webhooks (&cls->instance,
+ 2,
+ cls->webhooks));
+ TEST_RET_ON_FAIL (test_lookup_webhook_by_event (&cls->instance,
+ 2,
+ cls->webhooks));
+ TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
+ &cls->webhooks[2],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+
+ /* Test webhook deletion */
+ TEST_RET_ON_FAIL (test_delete_webhook (&cls->instance,
+ &cls->webhooks[1],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ /* Test double deletion fails */
+ TEST_RET_ON_FAIL (test_delete_webhook (&cls->instance,
+ &cls->webhooks[1],
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+ cls->webhooks[1] = cls->webhooks[2];
+ TEST_RET_ON_FAIL (test_lookup_webhooks (&cls->instance,
+ 2,
+ cls->webhooks));
+ TEST_RET_ON_FAIL (test_lookup_webhook_by_event (&cls->instance,
+ 2,
+ cls->webhooks));
+ return 0;
+}
+
+
+/**
+ * Takes care of webhook testing.
+ *
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_webhooks (void)
+{
+ struct TestWebhooks_Closure test_cls;
+ pre_test_webhooks (&test_cls);
+ int test_result = run_test_webhooks (&test_cls);
+ post_test_webhooks (&test_cls);
+ return test_result;
+}
+
+
+/* *********** Pending Webhooks ********** */
+
+/**
+ * A container for data relevant to a pending webhook.
+ */
+struct PendingWebhookData
+{
+ /**
+ * Reference to the configured webhook template.
+ */
+ uint64_t webhook_serial;
+
+ /**
+ * The details of the pending webhook.
+ */
+ struct TALER_MERCHANTDB_PendingWebhookDetails pwebhook;
+};
+
+
+/**
+ * Creates a pending webhook for testing with.
+ *
+ * @param serial reference to the configured webhook template.
+ * @param pwebhook the pending webhook data to fill.
+ */
+static void
+make_pending_webhook (uint64_t webhook_serial,
+ struct PendingWebhookData *pwebhook)
+{
+ pwebhook->webhook_serial = webhook_serial;
+ pwebhook->pwebhook.next_attempt = GNUNET_TIME_UNIT_ZERO_ABS;
+ pwebhook->pwebhook.retries = 0;
+ pwebhook->pwebhook.url = "https://exampletest.com";
+ pwebhook->pwebhook.http_method = "POST";
+ pwebhook->pwebhook.header = "Authorization:XYJAORKJEO";
+ pwebhook->pwebhook.body = "$Amount";
+}
+
+
+/**
+ * Tests inserting pending webhook data into the database.
+ *
+ * @param instance the instance to insert the pending webhook for.
+ * @param pending webhook the pending webhook data to insert.
+ * @param expected_result the result we expect the db to return.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_insert_pending_webhook (const struct InstanceData *instance,
+ struct PendingWebhookData *pwebhook,
+ enum GNUNET_DB_QueryStatus expected_result)
+{
+
+ TEST_COND_RET_ON_FAIL (expected_result ==
+ plugin->insert_pending_webhook (plugin->cls,
+ instance->instance.id,
+ pwebhook->
+ webhook_serial,
+ pwebhook->pwebhook.url,
+ pwebhook->pwebhook.
+ http_method,
+ pwebhook->pwebhook.
+ header,
+ pwebhook->pwebhook.body
+ ),
+ "Insert pending webhook failed\n");
+ return 0;
+}
+
+
+/**
+ * Tests updating pending webhook data in the database.
+ *
+ * @param instance the instance to update the pending webhook for.
+ * @param pending webhook the pending webhook data to update.
+ * @param expected_result the result we expect the db to return.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+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),
+ "Update pending webhook failed\n");
+ return 0;
+}
+
+
+/**
+ * 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 cls a pointer to the lookup closure.
+ * @param webhook_serial reference to the configured webhook template.
+ */
+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
+ {
+ 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
+ */
+struct TestLookupPendingWebhooks_Closure
+{
+ /**
+ * Number of webhook serial to compare to
+ */
+ unsigned int webhooks_to_cmp_length;
+
+ /**
+ * Pointer to array of webhook serials
+ */
+ const struct PendingWebhookData *webhooks_to_cmp;
+
+ /**
+ * Pointer to array of number of matches for each pending webhook
+ */
+ unsigned int *results_matching;
+
+ /**
+ * Total number of results returned
+ */
+ unsigned int results_length;
+};
+
+
+/**
+ * Function called after calling @e test_lookup_all_webhook,
+ * test_lookup_future_webhook and test_lookup_pending_webhook
+ *
+ * @param cls a pointer to the lookup closure.
+ * @param webhook_serial reference to the configured webhook template.
+ */
+static void
+lookup_pending_webhooks_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 TestLookupPendingWebhooks_Closure *cmp = cls;
+
+ cmp->results_length++;
+ for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
+ {
+ 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]++;
+ }
+ }
+}
+
+
+/**
+ * Tests looking up the pending webhook for an instance.
+ *
+ * @param instance the instance to query from.
+ * @param pwebhooks_length the number of pending webhook we are expecting.
+ * @param pwebhooks the list of pending webhooks that we expect to be found.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_lookup_pending_webhooks (const struct InstanceData *instance,
+ unsigned int pwebhooks_length,
+ const struct PendingWebhookData *pwebhooks)
+{
+ unsigned int results_matching[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 (results_matching));
+ if (0 > plugin->lookup_pending_webhooks (plugin->cls,
+ &lookup_pending_webhooks_cb,
+ &cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup pending webhook failed\n");
+ return 1;
+ }
+ if (pwebhooks_length != cls.results_length)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup pending webhook failed: incorrect number of results\n");
+ return 1;
+ }
+ for (unsigned int i = 0; i < pwebhooks_length; i++)
+ {
+ if (1 != cls.results_matching[i])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup pending webhook failed: mismatched data\n");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Tests looking up the future webhook to send for an instance.
+ *
+ * @param instance the instance to query from.
+ * @param pwebhooks_length the number of pending webhook we are expecting.
+ * @param pwebhooks the list of pending webhooks that we expect to be found.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_lookup_future_webhook (const struct InstanceData *instance,
+ unsigned int pwebhooks_length,
+ const struct PendingWebhookData *pwebhooks)
+{
+ unsigned int results_matching[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 (results_matching));
+ if (0 > plugin->lookup_future_webhook (plugin->cls,
+ &lookup_pending_webhooks_cb,
+ &cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup future webhook failed\n");
+ return 1;
+ }
+ if (pwebhooks_length != cls.results_length)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup future webhook failed: incorrect number of results\n");
+ return 1;
+ }
+ for (unsigned int i = 0; pwebhooks_length > i; ++i)
+ {
+ if (1 != cls.results_matching[i])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup future webhook failed: mismatched data\n");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Tests looking up all the pending webhook for an instance.
+ *
+ * @param instance the instance to query from.
+ * @param pwebhooks_length the number of pending webhook we are expecting.
+ * @param pwebhooks the list of pending webhooks that we expect to be found.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+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[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 (results_matching));
+ if (0 > plugin->lookup_all_webhooks (plugin->cls,
+ instance->instance.id,
+ min_row,
+ max_results,
+ &lookup_pending_webhooks_cb,
+ &cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup all webhooks failed\n");
+ return 1;
+ }
+ if (pwebhooks_length != cls.results_length)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup all webhooks failed: incorrect number of results\n");
+ return 1;
+ }
+ for (unsigned int i = 0; pwebhooks_length > i; ++i)
+ {
+ if (1 != cls.results_matching[i])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup all webhooks failed: mismatched data\n");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Tests deleting a pending webhook.
+ *
+ * @param instance the instance to delete the pending webhook from.
+ * @param pwebhook the pending webhook that should be deleted.
+ * @param expected_result the result that we expect the plugin to return.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+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,
+ webhooks_pending_serial),
+ "Delete webhook failed\n");
+ return 0;
+}
+
+
+/**
+ * Closure for pending webhook tests.
+ */
+struct TestPendingWebhooks_Closure
+{
+ /**
+ * The instance to use for this test.
+ */
+ struct InstanceData instance;
+
+ /**
+ * The array of pending webhooks.
+ */
+ struct PendingWebhookData pwebhooks[2];
+};
+
+
+/**
+ * Sets up the data structures used in the pending webhook tests.
+ *
+ * @param cls the closure to fill with test data.
+ */
+static void
+pre_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
+{
+ /* Instance */
+ make_instance ("test_inst_pending_webhooks",
+ &cls->instance);
+
+ /* Webhooks */
+ make_pending_webhook (1,
+ &cls->pwebhooks[0]);
+
+ make_pending_webhook (4,
+ &cls->pwebhooks[1]);
+ cls->pwebhooks[1].pwebhook.url = "https://test.com";
+ cls->pwebhooks[1].pwebhook.http_method = "POST";
+ cls->pwebhooks[1].pwebhook.header = "Authorization:XYJAO5R06EO";
+ cls->pwebhooks[1].pwebhook.body = "$Amount";
+}
+
+
+/**
+ * Handles all teardown after testing.
+ *
+ * @param cls the closure containing memory to be freed.
+ */
+static void
+post_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
+{
+ free_instance_data (&cls->instance);
+}
+
+
+/**
+ * Runs the tests for pending webhooks.
+ *
+ * @param cls the container of the test data.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+run_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
+{
+ /* Test that insert without an instance fails */
+ TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
+ &cls->pwebhooks[0],
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+
+ /* Insert the instance */
+ TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+
+ /* Test inserting a pending webhook */
+ TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
+ &cls->pwebhooks[0],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
+ &cls->pwebhooks[1],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ /* 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 (webhook_pending_serial1,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ /* Test double deletion fails */
+ 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 (webhook_pending_serial0,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance,
+ 0,
+ NULL));
return 0;
}
/**
+ * Takes care of pending webhook testing.
+ *
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_pending_webhooks (void)
+{
+ struct TestPendingWebhooks_Closure test_cls;
+ pre_test_pending_webhooks (&test_cls);
+ int test_result = run_test_pending_webhooks (&test_cls);
+ post_test_pending_webhooks (&test_cls);
+ return test_result;
+}
+
+
+/**
* Function that runs all tests.
*
* @return 0 on success, 1 otherwise.
@@ -6821,10 +7093,12 @@ 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 ());
+ TEST_RET_ON_FAIL (test_templates ());
+ TEST_RET_ON_FAIL (test_webhooks ());
+ TEST_RET_ON_FAIL (test_pending_webhooks ());
return 0;
}
@@ -6865,6 +7139,8 @@ run (void *cls)
result = run_tests ();
if (0 == result)
+ /** result = run_test_templates ();
+ if (0 == result)*/
{
/* Test dropping tables */
if (GNUNET_OK != plugin->drop_tables (plugin->cls))
@@ -6908,6 +7184,7 @@ main (int argc,
(void) GNUNET_asprintf (&config_filename,
"%s.conf",
testname);
+ fprintf (stdout, "Using %s\n", config_filename);
cfg = GNUNET_CONFIGURATION_create ();
if (GNUNET_OK !=
GNUNET_CONFIGURATION_parse (cfg,
diff --git a/src/backenddb/merchant-0000.sql b/src/backenddb/versioning.sql
index 98e7f661..444cf95e 100644
--- a/src/backenddb/merchant-0000.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 8c8775f6..7f1b9292 100644
--- a/src/include/Makefile.am
+++ b/src/include/Makefile.am
@@ -1,10 +1,11 @@
# This Makefile.am is in the public domain
EXTRA_DIST = \
- platform.h
+ platform.h gettext.h
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/gettext.h b/src/include/gettext.h
new file mode 100644
index 00000000..45851265
--- /dev/null
+++ b/src/include/gettext.h
@@ -0,0 +1,71 @@
+/* Convenience header for conditional use of GNU <libintl.h>.
+ Copyright Copyright (C) 1995-1998, 2000-2002 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published
+ by the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA. */
+
+#ifndef _LIBGETTEXT_H
+#define _LIBGETTEXT_H 1
+
+/* NLS can be disabled through the configure --disable-nls option. */
+#if ENABLE_NLS
+
+/* Get declarations of GNU message catalog functions. */
+#include <libintl.h>
+
+#else
+
+/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which
+ chokes if dcgettext is defined as a macro. So include it now, to make
+ later inclusions of <locale.h> a NOP. We don't include <libintl.h>
+ as well because people using "gettext.h" will not include <libintl.h>,
+ and also including <libintl.h> would fail on SunOS 4, whereas <locale.h>
+ is GNUNET_OK. */
+#if defined(__sun)
+#include <locale.h>
+#endif
+
+/* Disabled NLS.
+ The casts to 'const char *' serve the purpose of producing warnings
+ for invalid uses of the value returned from these functions.
+ On pre-ANSI systems without 'const', the config.h file is supposed to
+ contain "#define const". */
+#define gettext(Msgid) ((const char *) (Msgid))
+#define dgettext(Domainname, Msgid) ((const char *) (Msgid))
+#define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid))
+#define ngettext(Msgid1, Msgid2, N) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+#define dngettext(Domainname, Msgid1, Msgid2, N) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+#define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+/* slight modification here to avoid warnings: generate GNUNET_NO code,
+ not even the cast... */
+#define textdomain(Domainname)
+#define bindtextdomain(Domainname, Dirname)
+#define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset))
+
+#endif
+
+/* A pseudo function call that serves as a marker for the automated
+ extraction of messages, but does not call gettext(). The run-time
+ translation is done at a different place in the code.
+ The argument, String, should be a literal string. Concatenated strings
+ and other string expressions won't work.
+ The macro's expansion is not parenthesized, so that it is suitable as
+ initializer for static 'char[]' or 'const char[]' variables. */
+#define gettext_noop(String) String
+
+#endif /* _LIBGETTEXT_H */
diff --git a/src/include/platform.h b/src/include/platform.h
index 39ffa046..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>
@@ -26,10 +26,10 @@
/* Include our configuration header */
#ifndef HAVE_USED_CONFIG_H
-# define HAVE_USED_CONFIG_H
-# ifdef HAVE_CONFIG_H
-# include "taler_merchant_config.h"
-# endif
+#define HAVE_USED_CONFIG_H
+#ifdef HAVE_CONFIG_H
+#include "taler_merchant_config.h"
+#endif
#endif
@@ -42,8 +42,219 @@
/* Include the features available for GNU source */
#define _GNU_SOURCE
-/* Include GNUnet's platform file */
-#include <gnunet/platform.h>
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#ifdef __clang__
+#undef HAVE_STATIC_ASSERT
+#endif
+
+/**
+ * These may be expensive, but good for debugging...
+ */
+#define ALLOW_EXTRA_CHECKS GNUNET_YES
+
+/**
+ * For strptime (glibc2 needs this).
+ */
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE 499
+#endif
+
+#ifndef _REENTRANT
+#define _REENTRANT
+#endif
+
+/* configuration options */
+
+#define VERBOSE_STATS 0
+
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#if HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#if HAVE_NETINET_IN_SYSTM_H
+#include <netinet/in_systm.h>
+#endif
+#if HAVE_NETINET_IP_H
+#include <netinet/ip.h> /* superset of previous */
+#endif
+#include <arpa/inet.h>
+#include <netinet/tcp.h>
+#include <pwd.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <grp.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <signal.h>
+#include <libgen.h>
+#ifdef HAVE_MALLOC_H
+#include <malloc.h> /* for mallinfo on GNU */
+#endif
+#include <unistd.h> /* KLB_FIX */
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h> /* KLB_FIX */
+#include <fcntl.h>
+#include <math.h>
+#if HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#if HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <time.h>
+#ifdef BSD
+#include <net/if.h>
+#endif
+#if defined(BSD) && defined(__FreeBSD__) && defined(__FreeBSD_kernel__)
+#include <semaphore.h>
+#endif
+#ifdef DARWIN
+#include <dlfcn.h>
+#include <semaphore.h>
+#include <net/if.h>
+#endif
+#if defined(__linux__) || defined(GNU)
+#include <net/if.h>
+#endif
+#ifdef SOLARIS
+#include <sys/sockio.h>
+#include <sys/filio.h>
+#include <sys/loadavg.h>
+#include <semaphore.h>
+#endif
+#if HAVE_UCRED_H
+#include <ucred.h>
+#endif
+#if HAVE_SYS_UCRED_H
+#include <sys/ucred.h>
+#endif
+#if HAVE_IFADDRS_H
+#include <ifaddrs.h>
+#endif
+#include <errno.h>
+#include <limits.h>
+
+#if HAVE_VFORK_H
+#include <vfork.h>
+#endif
+
+#include <ctype.h>
+#if HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+#if HAVE_ENDIAN_H
+#include <endian.h>
+#endif
+#if HAVE_SYS_ENDIAN_H
+#include <sys/endian.h>
+#endif
+
+#define DIR_SEPARATOR '/'
+#define DIR_SEPARATOR_STR "/"
+#define PATH_SEPARATOR ':'
+#define PATH_SEPARATOR_STR ":"
+#define NEWLINE "\n"
+
+#include <locale.h>
+#include "gettext.h"
+/**
+ * GNU gettext support macro.
+ */
+#define _(String) dgettext (PACKAGE, String)
+#define LIBEXTRACTOR_GETTEXT_DOMAIN "libextractor"
+
+#include <sys/mman.h>
+
+/* FreeBSD_kernel is not defined on the now discontinued kFreeBSD */
+#if defined(BSD) && defined(__FreeBSD__) && defined(__FreeBSD_kernel__)
+#define __BYTE_ORDER BYTE_ORDER
+#define __BIG_ENDIAN BIG_ENDIAN
+#endif
+
+#ifdef DARWIN
+#define __BYTE_ORDER BYTE_ORDER
+#define __BIG_ENDIAN BIG_ENDIAN
+/* not available on darwin, override configure */
+#undef HAVE_STAT64
+#undef HAVE_MREMAP
+#endif
+
+#if ! HAVE_ATOLL
+long long
+atoll (const char *nptr);
+
+#endif
+
+#if ENABLE_NLS
+#include "langinfo.h"
+#endif
+
+#ifndef SIZE_MAX
+#define SIZE_MAX ((size_t) (-1))
+#endif
+
+#ifndef O_LARGEFILE
+#define O_LARGEFILE 0
+#endif
+
+/**
+ * AI_NUMERICSERV not defined in windows. Then we just do without.
+ */
+#ifndef AI_NUMERICSERV
+#define AI_NUMERICSERV 0
+#endif
+
+
+#if defined(__sparc__)
+#define MAKE_UNALIGNED(val) ({ __typeof__((val)) __tmp; memmove (&__tmp, &(val), \
+ sizeof((val))); \
+ __tmp; })
+#else
+#define MAKE_UNALIGNED(val) val
+#endif
+
+/**
+ * The termination signal
+ */
+#define GNUNET_TERM_SIG SIGTERM
+
+
+#ifndef PATH_MAX
+/**
+ * Assumed maximum path length.
+ */
+#define PATH_MAX 4096
+#endif
+
+#if HAVE_THREAD_LOCAL_GCC
+#define TALER_THREAD_LOCAL __thread
+#else
+#define TALER_THREAD_LOCAL
+#endif
+
+
+/**
+ * clang et al do not have such an attribute
+ */
+#if __has_attribute (__nonstring__)
+# define __nonstring __attribute__((__nonstring__))
+#else
+# define __nonstring
+#endif
/* Do not use shortcuts for gcrypt mpi */
#define GCRYPT_NO_MPI_MACROS 1
@@ -66,22 +277,42 @@
/* 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
@@ -89,6 +320,14 @@
#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 d632df67..057c9eff 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-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 Affero General Public License as published by the Free Software
@@ -16,8 +16,11 @@
/**
* @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
*/
#ifndef _TALER_MERCHANT_SERVICE_H
#define _TALER_MERCHANT_SERVICE_H
@@ -28,6 +31,11 @@
#include <gnunet/gnunet_curl_lib.h>
#include <jansson.h>
+/**
+ * Library version (in hex) for compatibility tests.
+ */
+#define TALER_MERCHANT_SERVICE_VERSION 0x00100000
+
/**
* General information about the HTTP response we obtained
@@ -106,37 +114,6 @@ struct TALER_MERCHANT_HttpResponse
/**
- * Take a @a response from the merchant API that (presumably) contains
- * error details and setup the corresponding @a hr structure. Internally
- * used to convert merchant's responses in to @a hr.
- *
- * @param response if NULL we will report #TALER_EC_GENERIC_INVALID_RESPONSE in `ec`
- * @param http_status http status to use
- * @param[out] hr response object to initialize, fields will
- * only be valid as long as @a response is valid as well
- */
-void
-TALER_MERCHANT_parse_error_details_ (const json_t *response,
- unsigned int http_status,
- struct TALER_MERCHANT_HttpResponse *hr);
-
-
-/**
- * Construct a new base URL using the existing @a base_url
- * and the given @a instance_id. The result WILL end with
- * '/'.
- *
- * @param base_url a merchant base URL without "/instances/" in it,
- * must not be the empty string; MAY end with '/'.
- * @param instance_id ID of an instance
- * @return "${base_url}/instances/${instance_id}/"
- */
-char *
-TALER_MERCHANT_baseurl_add_instance (const char *base_url,
- const char *instance_id);
-
-
-/**
* Contains information gathered from parsing a taler://pay URI.
*/
struct TALER_MERCHANT_PayUriData
@@ -186,7 +163,7 @@ struct TALER_MERCHANT_PayUriData
* @param[out] parse_data data extracted from the URI. Must be free'd.
* @return #GNUNET_SYSERR if @e pay_uri is malformed, #GNUNET_OK otherwise.
*/
-int
+enum GNUNET_GenericReturnValue
TALER_MERCHANT_parse_pay_uri (const char *pay_uri,
struct TALER_MERCHANT_PayUriData *parse_data);
@@ -241,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);
@@ -318,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;
@@ -334,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);
/**
@@ -368,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);
@@ -412,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;
};
@@ -424,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);
/**
@@ -452,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);
/**
@@ -476,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
@@ -493,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
@@ -514,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,
@@ -566,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
@@ -586,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,
@@ -672,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
@@ -712,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
@@ -725,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;
};
@@ -755,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);
/**
@@ -783,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);
/**
@@ -882,7 +949,422 @@ TALER_MERCHANT_instance_delete_cancel (
* @param arg request to cancel.
*/
#define TALER_MERCHANT_instance_purge_cancel(arg) \
- TALER_MERCHANT_instance_delete_cancel (arg)
+ 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 *********************** */
@@ -904,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;
};
@@ -911,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);
/**
@@ -960,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);
/**
@@ -1091,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
@@ -1279,6 +1895,172 @@ TALER_MERCHANT_product_delete_cancel (
struct TALER_MERCHANT_ProductDeleteHandle *pdh);
+/* ********************* /tokenfamilies ************************** */
+
+/**
+ * Handle for a GET /tokenfamilies/$SLUG operation.
+ */
+struct TALER_MERCHANT_TokenFamilyGetHandle;
+
+
+/**
+ * Response to GET /tokenfamilies/$SLUG operation.
+ */
+struct TALER_MERCHANT_TokenFamilyGetResponse
+{
+ /**
+ * HTTP response details
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Details depending on HTTP status.
+ */
+ union
+ {
+ /**
+ * Details for #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * Identifier for the token family consisting of unreserved characters
+ * according to RFC 3986.
+ */
+ const char *slug;
+
+ /**
+ * Human-readable name for the token family.
+ */
+ const char *name;
+
+ /**
+ * description of the token family
+ */
+ const char *description;
+
+ /**
+ * Optional map from IETF BCP 47 language tags to localized descriptions.
+ */
+ const json_t *description_i18n;
+
+ /**
+ * Start time of the token family's validity period.
+ */
+ struct GNUNET_TIME_Timestamp valid_after;
+
+ /**
+ * End time of the token family's validity period.
+ */
+ struct GNUNET_TIME_Timestamp valid_before;
+
+ /**
+ * Validity duration of an issued token.
+ */
+ struct GNUNET_TIME_Relative duration;
+
+ /**
+ * Kind of token family, "subscription" or "discount".
+ */
+ const char *kind;
+
+ /**
+ * How many tokens have been issued for this family.
+ */
+ uint64_t issued;
+
+ /**
+ * How many tokens have been redeemed for this family.
+ */
+ uint64_t redeemed;
+ } ok;
+
+ } details;
+
+};
+
+/**
+ * Cancel GET /tokenfamilies/$SLUG operation.
+ *
+ * @param handle operation to cancel
+ */
+void
+TALER_MERCHANT_token_family_get_cancel (
+ struct TALER_MERCHANT_TokenFamilyGetHandle *handle);
+
+
+/**
+ * Function called with the result of the GET /tokenfamilies/$SLUG operation.
+ *
+ * @param cls closure
+ * @param pgr response details
+ */
+typedef void
+(*TALER_MERCHANT_TokenFamilyGetCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_TokenFamilyGetResponse *pgr);
+
+/**
+ * Handle for a POST /tokenfamilies operation.
+ */
+struct TALER_MERCHANT_TokenFamiliesPostHandle;
+
+
+/**
+ * Function called with the result of the POST /tokenfamilies operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ */
+typedef void
+(*TALER_MERCHANT_TokenFamiliesPostCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Make a POST /tokenfamilies request to add a token family to the
+ * merchant instance.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param slug short, url-safe identifier for the token family
+ * @param name human-readable name for the token family
+ * @param description description of the token family
+ * @param description_i18n Map from IETF BCP 47 language tags to localized descriptions
+ * @param valid_after when the token family becomes valid
+ * @param valid_before when the token family expires
+ * @param duration how long tokens issued by this token family are valid for
+ * @param kind kind of token family, "subscription" or "discount"
+ * @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_TokenFamiliesPostHandle *
+TALER_MERCHANT_token_families_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *slug,
+ const char *name,
+ const char *description,
+ const json_t *description_i18n,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct GNUNET_TIME_Timestamp valid_before,
+ struct GNUNET_TIME_Relative duration,
+ const char *kind,
+ TALER_MERCHANT_TokenFamiliesPostCallback cb,
+ void *cb_cls);
+
+/**
+ * Cancel POST /tokenfamilies operation.
+ *
+ * @param handle operation to cancel
+ */
+void
+TALER_MERCHANT_token_families_post_cancel (
+ struct TALER_MERCHANT_TokenFamiliesPostHandle *handle);
+
/* ********************* /orders ************************** */
@@ -1364,7 +2146,7 @@ struct TALER_MERCHANT_PostOrdersReply
/**
* Callbacks of this type are used to serve the result of submitting a
- * POST /orders request to a merchant.
+ * POST /using-templates and POST /orders request to a merchant.
*
* @param cls closure
* @param por response details
@@ -1389,12 +2171,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
@@ -1444,7 +2227,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);
@@ -1454,7 +2274,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 (
@@ -1512,19 +2332,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);
/**
@@ -1578,9 +2430,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 (
@@ -1595,35 +2484,78 @@ struct TALER_MERCHANT_OrderWalletGetHandle;
/**
+ * Response to a GET /orders/$ID request.
+ */
+struct TALER_MERCHANT_OrderWalletGetResponse
+{
+ /**
+ * Full HTTP response details.
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Response details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Response details if the response code is #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * True if there is at least on refund on this payment.
+ */
+ bool refunded;
+
+ /**
+ * True if there are refunds waiting to be
+ * obtained.
+ */
+ bool refund_pending;
+
+ /**
+ * Amount that was refunded, only set if
+ * @e refunded is #GNUNET_YES.
+ */
+ struct TALER_Amount refund_amount;
+
+ } ok;
+
+ /**
+ * Response if a payment is required from the client.
+ */
+ struct
+ {
+
+ /**
+ * The URI that instructs the wallet to process
+ * the payment.
+ */
+ const char *taler_pay_uri;
+
+ /**
+ * Equivalent order that this customer paid already, or NULL for none.
+ */
+ const char *already_paid_order_id;
+
+ } payment_required;
+
+ } details;
+};
+
+
+/**
* Callback to process a GET /orders/$ID response
*
* @param cls closure
- * @param hr HTTP response details
- * @param paid #GNUNET_YES if the payment is settled, #GNUNET_NO if not
- * settled, #GNUNET_SYSERR on error
- * (note that refunded payments are returned as paid!)
- * @param refunded #GNUNET_YES if there is at least on refund on this payment,
- * #GNUNET_NO if refunded, #GNUNET_SYSERR or error
- * @param refund_pending #GNUNET_YES if there are refunds waiting to be
- * obtained, #GNUNET_NO if all refunds have been obtained, #GNUNET_SYSERR
- * on error.
- * @param refund_amount amount that was refunded, NULL if there
- * was no refund
- * @param taler_pay_uri the URI that instructs the wallets to process
- * the payment
- * @param already_paid_order_id equivalent order that this customer
- * paid already, or NULL for none
+ * @param owgs HTTP response details
*/
typedef void
(*TALER_MERCHANT_OrderWalletGetCallback) (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- enum GNUNET_GenericReturnValue paid,
- enum GNUNET_GenericReturnValue refunded,
- enum GNUNET_GenericReturnValue refund_pending,
- struct TALER_Amount *refund_amount,
- const char *taler_pay_uri,
- const char *already_paid_order_id);
+ const struct TALER_MERCHANT_OrderWalletGetResponse *owgs);
/**
@@ -1666,7 +2598,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 (
@@ -1798,151 +2730,165 @@ enum TALER_MERCHANT_OrderStatusCode
*/
struct TALER_MERCHANT_OrderStatusResponse
{
-
/**
- * Status of the order.
+ * HTTP response details.
*/
- enum TALER_MERCHANT_OrderStatusCode status;
+ struct TALER_MERCHANT_HttpResponse hr;
/**
- * Details depending on the payment status given in @e status.
+ * Details provided depending on the HTTP status code.
*/
union
{
-
/**
- * Details provided if @e status is #TALER_MERCHANT_OSC_PAID.
+ * Details provided if the HTTP status is #MHD_HTTP_OK.
*/
struct
{
- /**
- * Amount that was refunded.
- */
- struct TALER_Amount refund_amount;
-
- /**
- * Amount that was deposited into our bank account,
- * excluding fees.
- */
- struct TALER_Amount deposit_total;
-
- /**
- * The full contract terms of the order.
- */
- const json_t *contract_terms;
-
- /**
- * Array of wire transfers made for this payment to the
- * merchant by the exchange. Of length @e wts_len.
- */
- struct TALER_MERCHANT_WireTransfer *wts;
-
- /**
- * Length of the @e wts array.
- */
- 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.
- */
- struct TALER_MERCHANT_RefundOrderDetail *refunds;
-
- /**
- * Length of the @e refunds array.
- */
- unsigned int refunds_len;
-
- /**
- * Error code encountered trying to contact the exchange
- * about the wire tracking, 0 for none.
- */
- enum TALER_ErrorCode exchange_ec;
/**
- * HTTP status code encountered trying to contact the exchange
- * about the wire tracking, 0 for no error.
+ * Status of the order.
*/
- unsigned int exchange_hc;
+ enum TALER_MERCHANT_OrderStatusCode status;
/**
- * true if there is at least on refund on this payment,
- * false if there are no refunds.
+ * Details depending on the payment status given in @e status.
*/
- bool refunded;
-
- /**
- * true if refunds were approved that have not yet been obtained
- * by the wallet.
- */
- bool refund_pending;
-
- /**
- * true if the exchange paid the merchant for this order,
- * false if not.
- */
- bool wired;
-
- } paid;
+ union
+ {
+
+ /**
+ * Details provided if @e status is #TALER_MERCHANT_OSC_PAID.
+ */
+ struct
+ {
+ /**
+ * Amount that was refunded.
+ */
+ struct TALER_Amount refund_amount;
+
+ /**
+ * Amount that was deposited into our bank account,
+ * excluding fees.
+ */
+ struct TALER_Amount deposit_total;
+
+ /**
+ * The full contract terms of the order.
+ */
+ const json_t *contract_terms;
+
+ /**
+ * Array of wire transfers made for this payment to the
+ * merchant by the exchange. Of length @e wts_len.
+ */
+ struct TALER_MERCHANT_WireTransfer *wts;
+
+ /**
+ * Length of the @e wts array.
+ */
+ unsigned int wts_len;
+
+ /**
+ * Details returned by the merchant backend about refunds.
+ * Of length @e refunds_len.
+ */
+ struct TALER_MERCHANT_RefundOrderDetail *refunds;
+
+ /**
+ * Length of the @e refunds array.
+ */
+ unsigned int refunds_len;
+
+ /**
+ * Error code encountered trying to contact the exchange
+ * about the wire tracking, 0 for none.
+ */
+ enum TALER_ErrorCode exchange_ec;
+
+ /**
+ * HTTP status code encountered trying to contact the exchange
+ * about the wire tracking, 0 for no error.
+ */
+ unsigned int exchange_hc;
+
+ /**
+ * true if there is at least on refund on this payment,
+ * false if there are no refunds.
+ */
+ bool refunded;
+
+ /**
+ * true if refunds were approved that have not yet been obtained
+ * by the wallet.
+ */
+ bool refund_pending;
+
+ /**
+ * true if the exchange paid the merchant for this order,
+ * false if not.
+ */
+ bool wired;
+
+ /**
+ * Time of the last payment made on this order.
+ * Only available if the server supports protocol
+ * **v14** or higher, otherwise zero.
+ */
+ struct GNUNET_TIME_Timestamp last_payment;
+ } paid;
+
+ /**
+ * Details provided if @e status is #TALER_MERCHANT_OSC_CLAIMED.
+ */
+ struct
+ {
+
+ /**
+ * The full contract terms of the claimed order (including client nonce from claiming).
+ */
+ const json_t *contract_terms;
+
+ } claimed;
+
+ /**
+ * Details provided if @e status is #TALER_MERCHANT_OSC_UNPAID.
+ */
+ struct
+ {
+
+ /**
+ * URI that should be shown to the wallet to trigger a payment.
+ */
+ const char *taler_pay_uri;
+
+ /**
+ * Alternative order ID which was paid for already in the same session.
+ * Only given if the same product was purchased before in the same session.
+ * Otherwise NULL.
+ */
+ const char *already_paid_order_id;
+
+ /**
+ * Order summary.
+ */
+ const char *summary;
+
+ /**
+ * Time when the order was created.
+ */
+ struct GNUNET_TIME_Timestamp creation_time;
+
+ /**
+ * Total amount the order is about (amount to be paid by customer).
+ */
+ struct TALER_Amount contract_amount;
+
+ } unpaid;
+
+ } details;
- /**
- * Details provided if @e status is #TALER_MERCHANT_OSC_CLAIMED.
- */
- struct
- {
-
- /**
- * The full contract terms of the claimed order (including client nonce from claiming).
- */
- const json_t *contract_terms;
-
- } claimed;
-
- /**
- * Details provided if @e status is #TALER_MERCHANT_OSC_UNPAID.
- */
- struct
- {
-
- /**
- * URI that should be shown to the wallet to trigger a payment.
- */
- const char *taler_pay_uri;
-
- /**
- * Alternative order ID which was paid for already in the same session.
- * Only given if the same product was purchased before in the same session.
- * Otherwise NULL.
- */
- const char *already_paid_order_id;
-
- /**
- * Order summary.
- */
- const char *summary;
-
- /**
- * Time when the order was created.
- */
- struct GNUNET_TIME_Timestamp creation_time;
-
- /**
- * Total amount the order is about (amount to be paid by customer).
- */
- struct TALER_Amount contract_amount;
-
- } unpaid;
+ } ok;
} details;
};
@@ -1952,13 +2898,11 @@ struct TALER_MERCHANT_OrderStatusResponse
* Callback to process a GET /orders/$ID request
*
* @param cls closure
- * @param hr HTTP response details
- * @param osr order status response details (on success)
+ * @param osr order status response details
*/
typedef void
(*TALER_MERCHANT_OrderMerchantGetCallback) (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
const struct TALER_MERCHANT_OrderStatusResponse *osr);
@@ -1969,10 +2913,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 exhcange.
- * 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
@@ -1986,7 +2928,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);
@@ -1995,7 +2936,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 (
@@ -2027,6 +2968,7 @@ typedef void
* @param ctx the context
* @param backend_url HTTP base URL for the backend
* @param order_id identifier of the order
+ * @param force force deletion of claimed (but unpaid) orders
* @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
@@ -2036,6 +2978,7 @@ TALER_MERCHANT_order_delete (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
const char *order_id,
+ bool force,
TALER_MERCHANT_OrderDeleteCallback cb,
void *cb_cls);
@@ -2043,7 +2986,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 (
@@ -2057,22 +3000,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);
/**
@@ -2091,19 +3066,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);
@@ -2120,19 +3096,59 @@ struct TALER_MERCHANT_OrderPayHandle;
/**
+ * Information returned in response to a payment.
+ */
+struct TALER_MERCHANT_PayResponse
+{
+
+ /**
+ * General HTTP response details.
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Details returned depending on @e hr.
+ */
+ union
+ {
+
+ /**
+ * Details returned on success.
+ */
+ struct
+ {
+
+ /**
+ * Signature affirming that the order was paid.
+ */
+ struct TALER_MerchantSignatureP merchant_sig;
+
+ /**
+ * 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.
+
+ } details;
+
+};
+
+
+/**
* Callbacks of this type are used to serve the result of submitting a
* POST /orders/$ID/pay request to a merchant.
*
* @param cls closure
- * @param hr HTTP response details
- * @param merchant_sig signature from the merchant
- * affirming payment, or NULL on errors
+ * @param pr HTTP response details
*/
typedef void
(*TALER_MERCHANT_OrderPayCallback) (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_MerchantSignatureP *merchant_sig);
+ const struct TALER_MERCHANT_PayResponse *pr);
/**
@@ -2196,6 +3212,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
@@ -2208,8 +3225,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);
@@ -2272,6 +3290,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)
@@ -2288,23 +3307,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);
/**
@@ -2326,18 +3347,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);
/**
@@ -2351,6 +3400,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
@@ -2364,6 +3414,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);
@@ -2403,22 +3454,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);
/**
@@ -2463,15 +3549,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);
/**
@@ -2518,13 +3605,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);
/**
@@ -2532,11 +3620,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);
/**
@@ -2546,20 +3634,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);
/**
@@ -2574,13 +3693,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.
@@ -2626,17 +3746,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;
};
@@ -2644,20 +3821,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);
/**
@@ -2696,56 +3865,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);
/**
@@ -2897,21 +4066,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);
/**
@@ -2966,678 +4168,784 @@ TALER_MERCHANT_transfers_get_cancel (
struct TALER_MERCHANT_GetTransfersHandle *gth);
-/* ******************* /reserves *************** */
+/* ********************* /kyc ************************** */
+
+/**
+ * Handle for getting the KYC status of instance(s).
+ */
+struct TALER_MERCHANT_KycGetHandle;
/**
- * @brief Handle to a POST /reserves operation at a merchant's backend.
+ * Information about KYC actions the merchant still must perform.
*/
-struct TALER_MERCHANT_PostReservesHandle;
+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). Can
+ * be NULL, specifically if KYC is satisfied but
+ * the transactions are hanging in AML.
+ */
+ 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;
+
+ /**
+ * AML state for our account.
+ */
+ enum TALER_AmlDecisionState aml_status;
+};
/**
- * Callbacks of this type are used to work the result of submitting a
- * POST /reserves request to a merchant
+ * Information about KYC status failures at the exchange.
+ */
+struct TALER_MERCHANT_ExchangeKycFailureDetail
+{
+ /**
+ * 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.
+ */
+ enum TALER_ErrorCode exchange_code;
+
+ /**
+ * 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.
+ */
+struct TALER_MERCHANT_KycResponse
+{
+ /**
+ * HTTP response details.
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Response details depending on the HTTP status.
+ */
+ 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;
+
+ /**
+ * 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;
+
+ /**
+ * Length of the @e timeout_kycs array.
+ */
+ unsigned int timeout_kycs_length;
+ } kyc_status;
+
+ } details;
+
+};
+
+
+/**
+ * Callback to with a response from a GET [/private]/kyc request
*
* @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 kr response details
*/
typedef void
-(*TALER_MERCHANT_PostReservesCallback) (
+(*TALER_MERCHANT_KycGetCallback) (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const char *payto_uri);
+ const struct TALER_MERCHANT_KycResponse *kr);
/**
- * Request backend to create a reserve.
+ * 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 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 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 a handle for this request
+ * @return handle for this operation, NULL upon errors
*/
-struct TALER_MERCHANT_PostReservesHandle *
-TALER_MERCHANT_reserves_post (
+struct TALER_MERCHANT_KycGetHandle *
+TALER_MERCHANT_kyc_get (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- const struct TALER_Amount *initial_balance,
+ const struct TALER_MerchantWireHashP *h_wire,
const char *exchange_url,
- const char *wire_method,
- TALER_MERCHANT_PostReservesCallback cb,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_MERCHANT_KycGetCallback 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.
+ * 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 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_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 GET [/private]/kyc/$KYC_ID request.
*
- * @param prh the operation to cancel
+ * @param kyc handle to the request to be canceled
*/
void
-TALER_MERCHANT_reserves_post_cancel (
- struct TALER_MERCHANT_PostReservesHandle *prh);
+TALER_MERCHANT_kyc_get_cancel (
+ struct TALER_MERCHANT_KycGetHandle *kyc);
+
+
+/* ********************* /otp-devices *********************** */
/**
- * Handle for a GET /reserves operation.
+ * Handle for a GET /otp-devices operation.
*/
-struct TALER_MERCHANT_ReservesGetHandle;
-
+struct TALER_MERCHANT_OtpDevicesGetHandle;
/**
- * Information about a reserve.
+ * Individual OTP device (minimal information
+ * returned via GET /otp-devices).
*/
-struct TALER_MERCHANT_ReserveSummary
+struct TALER_MERCHANT_OtpDeviceEntry
{
/**
- * Public key of the reserve
+ * OTP device identifier.
*/
- struct TALER_ReservePublicKeyP reserve_pub;
+ const char *otp_device_id;
/**
- * Timestamp when it was established
+ * OTP device description.
*/
- struct GNUNET_TIME_Timestamp creation_time;
+ const char *device_description;
- /**
- * Timestamp when it expires
- */
- struct GNUNET_TIME_Timestamp expiration_time;
+};
- /**
- * Initial amount as per reserve creation call
- */
- struct TALER_Amount merchant_initial_amount;
-
- /**
- * Initial amount as per exchange, 0 if exchange did
- * not confirm reserve creation yet.
- */
- struct TALER_Amount exchange_initial_amount;
+/**
+ * Response to a GET /otp-devices operation.
+ */
+struct TALER_MERCHANT_OtpDevicesGetResponse
+{
/**
- * Amount picked up so far.
+ * HTTP response details
*/
- struct TALER_Amount pickup_amount;
+ struct TALER_MERCHANT_HttpResponse hr;
/**
- * Amount approved for tips that exceeds the pickup_amount.
+ * Details depending on status.
*/
- struct TALER_Amount committed_amount;
+ union
+ {
+ /**
+ * Details if status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * length of the @e otp_devices array
+ */
+ unsigned int otp_devices_length;
- /**
- * Is this reserve active (false if it was deleted but not purged)
- */
- bool active;
+ /**
+ * array of otp_devices the requested instance offers
+ */
+ const struct TALER_MERCHANT_OtpDeviceEntry *otp_devices;
+ } ok;
+ } details;
};
/**
- * Callback to process a GET /reserves request
+ * Function called with the result of the GET /otp-devices operation.
*
* @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
+ * @param tgr response details
*/
typedef void
-(*TALER_MERCHANT_ReservesGetCallback) (
+(*TALER_MERCHANT_OtpDevicesGetCallback)(
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- unsigned int reserves_length,
- const struct TALER_MERCHANT_ReserveSummary reserves[]);
+ const struct TALER_MERCHANT_OtpDevicesGetResponse *tgr);
/**
- * Issue a GET /reserves request to the backend. Informs the backend
- * that a customer wants to pick up a reserves.
+ * Make a GET /otp-devices request.
*
- * @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 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_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_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 /reserves request.
+ * Cancel GET /otp-devices operation.
*
- * @param rgh handle to the request to be canceled
+ * @param tgh operation to cancel
*/
void
-TALER_MERCHANT_reserves_get_cancel (
- struct TALER_MERCHANT_ReservesGetHandle *rgh);
+TALER_MERCHANT_otp_devices_get_cancel (
+ struct TALER_MERCHANT_OtpDevicesGetHandle *tgh);
/**
- * Handle for a request to obtain details on a specific
- * (tipping) reserve.
+ * 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_ReserveGetHandle;
+struct TALER_MERCHANT_OtpDeviceGetHandle;
/**
- * Details about a tip granted by the merchant.
+ * Details in a response to a GET /otp-devices request.
*/
-struct TALER_MERCHANT_TipDetails
+struct TALER_MERCHANT_OtpDeviceGetResponse
{
/**
- * Identifier for the tip.
+ * HTTP response details.
*/
- struct TALER_TipIdentifierP tip_id;
+ struct TALER_MERCHANT_HttpResponse hr;
/**
- * Total value of the tip (including fees).
+ * Response details depending on the HTTP status.
*/
- struct TALER_Amount amount;
+ union
+ {
+ /**
+ * Information returned if the status was #MHD_HTTP_OK.
+ */
+ struct
+ {
- /**
- * Human-readable reason for why the tip was granted.
- */
- const char *reason;
+ /**
+ * description of the otp_device
+ */
+ const char *otp_device_description;
+
+ /**
+ * 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 *otp_code;
+
+ /**
+ * current counter.
+ */
+ uint64_t otp_ctr;
+
+ /**
+ * OTP algorithm used.
+ */
+ enum TALER_MerchantConfirmationAlgorithm otp_alg;
+
+ /**
+ * Timestamp in second used to compute
+ * @e otp_code.
+ */
+ uint64_t otp_timestamp_s;
+
+ } ok;
+
+ } details;
};
/**
- * Callback to process a GET /reserve/$RESERVE_PUB request
+ * Function called with the result of the GET /otp-device/$ID operation.
*
* @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
+ * @param tgr HTTP response details
*/
typedef void
-(*TALER_MERCHANT_ReserveGetCallback) (
+(*TALER_MERCHANT_OtpDeviceGetCallback)(
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[]);
+ const struct TALER_MERCHANT_OtpDeviceGetResponse *tgr);
/**
- * Issue a GET /reserve/$RESERVE_ID request to the backend. Queries the backend
- * about the status of a reserve.
+ * 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 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 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_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_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 /reserve/$RESERVE_ID request.
+ * Cancel GET /otp-devices/$ID operation.
*
- * @param rgh handle to the request to be canceled
+ * @param tgh operation to cancel
*/
void
-TALER_MERCHANT_reserve_get_cancel (
- struct TALER_MERCHANT_ReserveGetHandle *rgh);
+TALER_MERCHANT_otp_device_get_cancel (
+ struct TALER_MERCHANT_OtpDeviceGetHandle *tgh);
/**
- * Handle for a /tip-authorize operation.
+ * Handle for a POST /otp-devices operation.
*/
-struct TALER_MERCHANT_TipAuthorizeHandle;
+struct TALER_MERCHANT_OtpDevicesPostHandle;
/**
- * Callback for a /reserves/$RESERVE_PUB/tip-authorize request. Returns the result of
- * the operation.
+ * Function called with the result of the POST /otp-devices operation.
*
* @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
*/
typedef void
-(*TALER_MERCHANT_TipAuthorizeCallback) (
+(*TALER_MERCHANT_OtpDevicesPostCallback)(
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_HttpResponse *hr);
/**
- * Issue a /tip-authorize request to the backend. Informs the backend
- * that a tip should be created.
+ * 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 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
- * @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
+ * @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_TipAuthorizeHandle *
-TALER_MERCHANT_tip_authorize2 (
+struct TALER_MERCHANT_OtpDevicesPostHandle *
+TALER_MERCHANT_otp_devices_post (
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);
-
-
-/**
- * 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.
- *
- * @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
- * @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);
+ 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 pending /tip-authorize request
+ * Cancel POST /otp-devices operation.
*
- * @param ta handle from the operation to cancel
+ * @param[in] tph operation to cancel
*/
void
-TALER_MERCHANT_tip_authorize_cancel (
- struct TALER_MERCHANT_TipAuthorizeHandle *ta);
+TALER_MERCHANT_otp_devices_post_cancel (
+ struct TALER_MERCHANT_OtpDevicesPostHandle *tph);
/**
- * Handle for a request to delete or purge a specific reserve.
+ * Handle for a PATCH /otp-device operation.
*/
-struct TALER_MERCHANT_ReserveDeleteHandle;
+struct TALER_MERCHANT_OtpDevicePatchHandle;
/**
- * Callback to process a DELETE /reserve/$RESERVE_PUB request
+ * Function called with the result of the PATCH /otp-device operation.
*
* @param cls closure
* @param hr HTTP response details
*/
typedef void
-(*TALER_MERCHANT_ReserveDeleteCallback) (
+(*TALER_MERCHANT_OtpDevicePatchCallback)(
void *cls,
const struct TALER_MERCHANT_HttpResponse *hr);
/**
- * Issue a DELETE /reserve/$RESERVE_ID request to the backend. Only
- * deletes the private key of the reserve, preserves tipping data.
+ * Make a PATCH /otp-device request to update OTP device details
*
- * @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 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 handle for this operation, NULL upon errors
+ * @return the request handle; NULL upon error
*/
-struct TALER_MERCHANT_ReserveDeleteHandle *
-TALER_MERCHANT_reserve_delete (
+struct TALER_MERCHANT_OtpDevicePatchHandle *
+TALER_MERCHANT_otp_device_patch (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- TALER_MERCHANT_ReserveDeleteCallback cb,
+ 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);
/**
- * 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.
+ * Cancel PATCH /otp-device operation.
*
- * @param rdh handle to the request to be canceled
+ * @param[in] tph operation to cancel
*/
void
-TALER_MERCHANT_reserve_delete_cancel (
- struct TALER_MERCHANT_ReserveDeleteHandle *rdh);
-
-
-/* ********************* /tips ************************** */
+TALER_MERCHANT_otp_device_patch_cancel (
+ struct TALER_MERCHANT_OtpDevicePatchHandle *tph);
/**
- * Handle for a GET /tips/$TIP_ID (public variant) operation.
+ * Handle for a DELETE /otp-device/$ID operation.
*/
-struct TALER_MERCHANT_TipWalletGetHandle;
+struct TALER_MERCHANT_OtpDeviceDeleteHandle;
/**
- * Callback to process a GET /tips/$TIP_ID request
+ * Function called with the result of the DELETE /otp-device/$ID 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
*/
typedef void
-(*TALER_MERCHANT_TipWalletGetCallback) (
+(*TALER_MERCHANT_OtpDeviceDeleteCallback)(
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_HttpResponse *hr);
/**
- * Issue a GET /tips/$TIP_ID (public variant) request to the backend. Returns
- * information needed 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 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 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 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_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 GET /tips/$TIP_ID request.
+ * Cancel DELETE /otp-device/$ID operation.
*
- * @param tgh handle to the request to be canceled
+ * @param[in] tdh operation to cancel
*/
void
-TALER_MERCHANT_wallet_tip_get_cancel (
- struct TALER_MERCHANT_TipWalletGetHandle *tgh);
+TALER_MERCHANT_otp_device_delete_cancel (
+ struct TALER_MERCHANT_OtpDeviceDeleteHandle *tdh);
+
+
+/* ********************* /templates *********************** */
/**
- * Handle for a GET /private/tips/$TIP_ID (private variant) operation.
+ * Handle for a GET /templates operation.
*/
-struct TALER_MERCHANT_TipMerchantGetHandle;
-
+struct TALER_MERCHANT_TemplatesGetHandle;
/**
- * Summary information for a tip pickup.
+ * Individual template (minimal information
+ * returned via GET /templates).
*/
-struct TALER_MERCHANT_PickupDetail
+struct TALER_MERCHANT_TemplateEntry
{
/**
- * Identifier of the pickup.
+ * template identifier.
*/
- struct TALER_PickupIdentifierP pickup_id;
+ const char *template_id;
+
+};
+
+/**
+ * Response to a GET /templates operation.
+ */
+struct TALER_MERCHANT_TemplatesGetResponse
+{
/**
- * Number of planchets involved.
+ * HTTP response details
*/
- uint64_t num_planchets;
+ struct TALER_MERCHANT_HttpResponse hr;
/**
- * Total amount requested for this pickup.
+ * Details depending on status.
*/
- struct TALER_Amount requested_amount;
+ union
+ {
+ /**
+ * Details if status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * length of the @e templates array
+ */
+ unsigned int templates_length;
+
+ /**
+ * array of templates the requested instance offers
+ */
+ const struct TALER_MERCHANT_TemplateEntry *templates;
+ } ok;
+ } details;
};
/**
- * Callback to process a GET /private/tips/$TIP_ID request
+ * Function called with the result of the GET /templates operation.
*
* @param cls closure
- * @param hr HTTP response details
- * @param total_authorized how many tips were authorized under this tip
- * @param total_picked_up how many tips have been picked up
- * @param reason what was the reason given for the tip
- * @param expiration when the tip will expire
- * @param reserve_pub which reserve is funding this tip
- * @param pickups_length length of the @a pickups array
- * @param pickups array of pickup operations performed for this tip
+ * @param tgr response details
*/
typedef void
-(*TALER_MERCHANT_TipMerchantGetCallback) (
+(*TALER_MERCHANT_TemplatesGetCallback)(
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_Amount *total_authorized,
- const struct TALER_Amount *total_picked_up,
- const char *reason,
- struct GNUNET_TIME_Timestamp expiration,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- unsigned int pickups_length,
- const struct TALER_MERCHANT_PickupDetail pickups[]);
+ const struct TALER_MERCHANT_TemplatesGetResponse *tgr);
/**
- * Issue a GET /private/tips/$TIP_ID (private variant) request to the backend.
- * Returns information needed to pick up a tip.
+ * Make a GET /templates request.
*
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param tip_id which tip should we query
- * @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 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_TipMerchantGetHandle *
-TALER_MERCHANT_merchant_tip_get (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const struct TALER_TipIdentifierP *tip_id,
- bool pickups,
- TALER_MERCHANT_TipMerchantGetCallback 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/tips/$TIP_ID request.
+ * Cancel GET /templates 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_templates_get_cancel (
+ struct TALER_MERCHANT_TemplatesGetHandle *tgh);
/**
- * Handle for a GET /private/tips request.
+ * 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_TipsGetHandle;
+struct TALER_MERCHANT_TemplateGetHandle;
/**
- * Database entry information of a tip.
+ * Details in a response to a GET /private/templates/$ID request.
*/
-struct TALER_MERCHANT_TipEntry
+struct TALER_MERCHANT_TemplateGetResponse
{
/**
- * Row number of the tip in the database.
+ * HTTP response details.
*/
- uint64_t row_id;
+ struct TALER_MERCHANT_HttpResponse hr;
/**
- * Identifier for the tip.
+ * Response details depending on the HTTP status.
*/
- struct TALER_TipIdentifierP tip_id;
+ union
+ {
+ /**
+ * Information returned if the status was #MHD_HTTP_OK.
+ */
+ struct
+ {
- /**
- * Total value of the tip (including fees).
- */
- struct TALER_Amount tip_amount;
+ /**
+ * 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;
};
/**
- * Callback to process a GET /private/tips request.
+ * Function called with the result of the GET /private/template/$ID 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
+ * @param tgr HTTP response details
*/
typedef void
-(*TALER_MERCHANT_TipsGetCallback) (
+(*TALER_MERCHANT_TemplateGetCallback)(
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_TemplateGetResponse *tgr);
/**
- * Issue a GET /private/tips request with filters to the backend.
+ * Make a GET /private/template/$ID request to get details about an
+ * individual template.
*
- * @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 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 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_TemplateGetHandle *
+TALER_MERCHANT_template_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *template_id,
+ TALER_MERCHANT_TemplateGetCallback cb,
+ void *cb_cls);
/**
- * Cancel a GET /private/tips request.
+ * Cancel GET /private/templates/$ID operation.
*
- * @param tgh the operation to cancel
+ * @param tgh operation to cancel
*/
void
-TALER_MERCHANT_tips_get_cancel (struct TALER_MERCHANT_TipsGetHandle *tgh);
+TALER_MERCHANT_template_get_cancel (
+ struct TALER_MERCHANT_TemplateGetHandle *tgh);
/**
- * Handle for a POST /tips/$TIP_ID/pickup operation.
+ * 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_TipPickupHandle;
+struct TALER_MERCHANT_WalletTemplateGetHandle;
/**
- * Details about a pickup operation, as returned to the application.
+ * Details in a response to a GET /templates request.
*/
-struct TALER_MERCHANT_PickupDetails
+struct TALER_MERCHANT_WalletTemplateGetResponse
{
/**
- * HTTP response data.
+ * HTTP response details.
*/
struct TALER_MERCHANT_HttpResponse hr;
/**
- * Details about the response.
+ * Response details depending on the HTTP status.
*/
union
{
/**
- * Details if the status is #MHD_HTTP_OK.
+ * Information returned if the status was #MHD_HTTP_OK.
*/
struct
{
/**
- * Array of length @e num_sigs with details about each of the coins that
- * were picked up.
+ * Template for the contract.
*/
- struct TALER_EXCHANGE_PrivateCoinDetails *pcds;
+ const json_t *template_contract;
- /**
- * Length of the @e pcds array.
- */
- unsigned int num_sigs;
- } success;
+ } ok;
} details;
@@ -3645,299 +4953,554 @@ struct TALER_MERCHANT_PickupDetails
/**
- * Callback for a POST /tips/$TIP_ID/pickup request. Returns the result of
- * the operation.
+ * Function called with the result of the GET /template/$ID operation.
*
* @param cls closure
- * @param hr HTTP response details
- * @param num_sigs length of the @a reserve_sigs array, 0 on error
- * @param sigs array of signatures over the coins, NULL on error
+ * @param tgr HTTP response details
*/
typedef void
-(*TALER_MERCHANT_TipPickupCallback) (
+(*TALER_MERCHANT_WalletTemplateGetCallback)(
void *cls,
- const struct TALER_MERCHANT_PickupDetails *pd);
+ const struct TALER_MERCHANT_WalletTemplateGetResponse *tgr);
/**
- * Information per planchet.
+ * Make a GET /template/$ID request to get details about an
+ * individual template.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @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_PlanchetData
-{
- /**
- * Planchet secrets.
- */
- struct TALER_PlanchetMasterSecretP ps;
+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);
- /**
- * Denomination key desired.
- */
- const struct TALER_EXCHANGE_DenomPublicKey *pk;
-};
+/**
+ * Cancel GET /templates/$ID operation.
+ *
+ * @param tgh operation to cancel
+ */
+void
+TALER_MERCHANT_wallet_template_get_cancel (
+ struct TALER_MERCHANT_WalletTemplateGetHandle *tgh);
+
+
+/**
+ * Handle for a POST /templates operation.
+ */
+struct TALER_MERCHANT_TemplatesPostHandle;
+
/**
- * Issue a POST /tips/$TIP_ID/pickup request to the backend. Informs the
- * backend that a customer wants to pick up a tip.
+ * Function called with the result of the POST /templates operation.
*
- * @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 cls closure
+ * @param hr HTTP response details
+ */
+typedef void
+(*TALER_MERCHANT_TemplatesPostCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Make a POST /templates request to add a template
+ *
+ * @param ctx the context
+ * @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 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
+ * @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_TemplatesPostHandle *
+TALER_MERCHANT_templates_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *template_id,
+ const char *template_description,
+ const char *otp_id,
+ const json_t *template_contract,
+ TALER_MERCHANT_TemplatesPostCallback cb,
+ void *cb_cls);
/**
- * Cancel a pending /tips/$TIP_ID/pickup request
+ * Cancel POST /templates 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_templates_post_cancel (
+ struct TALER_MERCHANT_TemplatesPostHandle *tph);
/**
- * Handle for a low-level /tip-pickup operation (without unblinding).
+ * Handle for a PATCH /template operation.
*/
-struct TALER_MERCHANT_TipPickup2Handle;
+struct TALER_MERCHANT_TemplatePatchHandle;
/**
- * 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 PATCH /template 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_TemplatePatchCallback)(
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 PATCH /template request to update template details
*
- * @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 template_id identifier to use for the template; the template must exist,
+ * or the transaction will fail with a #MHD_HTTP_NOT_FOUND
+ * HTTP status code
+ * @param template_description description of the template
+ * @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
+ * @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_TemplatePatchHandle *
+TALER_MERCHANT_template_patch (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *template_id,
+ const char *template_description,
+ const char *otp_id,
+ json_t *template_contract,
+ TALER_MERCHANT_TemplatePatchCallback cb,
+ void *cb_cls);
/**
- * Cancel a pending /tip-pickup request.
+ * Cancel PATCH /template operation.
*
- * @param tp handle from the operation to cancel
+ * @param[in] tph operation to cancel
*/
void
-TALER_MERCHANT_tip_pickup2_cancel (
- struct TALER_MERCHANT_TipPickup2Handle *tp);
+TALER_MERCHANT_template_patch_cancel (
+ struct TALER_MERCHANT_TemplatePatchHandle *tph);
-/* ********************* /kyc ************************** */
+/**
+ * Handle for a DELETE /template/$ID operation.
+ */
+struct TALER_MERCHANT_TemplateDeleteHandle;
+
/**
- * Handle for GETing the KYC status of instance(s).
+ * Function called with the result of the DELETE /template/$ID operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
*/
-struct TALER_MERCHANT_KycGetHandle;
+typedef void
+(*TALER_MERCHANT_TemplateDeleteCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr);
/**
- * Information about KYC actions the merchant still must perform.
+ * Make a DELETE /template/$ID request to delete a template.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param template_id identifier of the template
+ * @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_AccountKycRedirectDetail
-{
+struct TALER_MERCHANT_TemplateDeleteHandle *
+TALER_MERCHANT_template_delete (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *template_id,
+ TALER_MERCHANT_TemplateDeleteCallback cb,
+ void *cb_cls);
- /**
- * 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;
+/**
+ * Cancel DELETE /template/$ID operation.
+ *
+ * @param[in] tdh operation to cancel
+ */
+void
+TALER_MERCHANT_template_delete_cancel (
+ struct TALER_MERCHANT_TemplateDeleteHandle *tdh);
- /**
- * Our bank wire account this is about.
- */
- const char *payto_uri;
-};
+
+/**
+ * Make a POST /using-templates request to add an using template
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param template_id which template should be used
+ * @param summary summary of the using template
+ * @param amount to pay given by the customer
+ * @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_UsingTemplatesPostHandle *
+TALER_MERCHANT_using_templates_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *template_id,
+ const char *summary,
+ const struct TALER_Amount *amount,
+ TALER_MERCHANT_PostOrdersCallback cb,
+ void *cb_cls);
/**
- * Information about KYC status failures at the exchange.
+ * Cancel POST /using-templates operation.
+ *
+ * @param[in] utph operation to cancel
*/
-struct TALER_MERCHANT_ExchangeKycFailureDetail
-{
- /**
- * Base URL of the exchange this is about.
- */
- const char *exchange_url;
+void
+TALER_MERCHANT_using_templates_post_cancel (
+ struct TALER_MERCHANT_UsingTemplatesPostHandle *utph);
- /**
- * Error code indicating errors the exchange
- * returned, or #TALER_EC_INVALID for none.
- */
- enum TALER_ErrorCode exchange_code;
+/* ********************* /webhooks *********************** */
+
+
+/**
+ * Handle for a GET /webhooks operation.
+ */
+struct TALER_MERCHANT_WebhooksGetHandle;
+
+/**
+ * Individual webhook (minimal information
+ * returned via GET /webhooks).
+ */
+struct TALER_MERCHANT_WebhookEntry
+{
/**
- * HTTP status code returned by the exchange when we asked for
- * information about the KYC status.
- * 0 if there was no response at all.
+ * webhook identifier.
*/
- unsigned int exchange_http_status;
+ const char *webhook_id;
+
};
/**
- * Details in a response to a GET /kyc request.
+ * Response to a GET /webhooks operation.
*/
-struct TALER_MERCHANT_KycResponse
+struct TALER_MERCHANT_WebhooksGetResponse
{
+ /**
+ * HTTP response details
+ */
struct TALER_MERCHANT_HttpResponse hr;
/**
- * Response details.
+ * 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.
- */
- struct TALER_MERCHANT_AccountKycRedirectDetail *pending_kycs;
-
/**
- * Array with information about KYC failures at the exchange.
+ * length of the @e webhooks array
*/
- struct TALER_MERCHANT_ExchangeKycFailureDetail *timeout_kycs;
+ unsigned int webhooks_length;
/**
- * Length of the @e pending_kycs array.
+ * array of templates the requested instance offers
*/
- unsigned int pending_kycs_length;
+ const struct TALER_MERCHANT_WebhookEntry *webhooks;
- /**
- * Length of the @e timeout_kycs array.
- */
- unsigned int timeout_kycs_length;
- } kyc_status;
+ } ok;
} details;
-
};
/**
- * Callback to with a response from a GET [/private]/kyc request
+ * Function called with the result of the GET /webhooks operation.
*
* @param cls closure
- * @param kr response details
+ * @param wgr response details
*/
typedef void
-(*TALER_MERCHANT_KycGetCallback) (
+(*TALER_MERCHANT_WebhooksGetCallback)(
void *cls,
- const struct TALER_MERCHANT_KycResponse *kr);
+ const struct TALER_MERCHANT_WebhooksGetResponse *wgr);
/**
- * Issue a GET /private/kycs/$KYC_ID (private variant) request to the backend.
- * Returns KYC status of bank accounts.
+ * Make a GET /webhooks request.
*
- * @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 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_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);
+struct TALER_MERCHANT_WebhooksGetHandle *
+TALER_MERCHANT_webhooks_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ TALER_MERCHANT_WebhooksGetCallback cb,
+ void *cb_cls);
/**
- * Issue a GET /management/instances/$INSTANCE/kyc request to the backend.
- * Returns KYC status of bank accounts.
+ * Cancel GET /webhooks operation.
*
- * @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[in] tgh operation to cancel
+ */
+void
+TALER_MERCHANT_webhooks_get_cancel (
+ struct TALER_MERCHANT_WebhooksGetHandle *tgh);
+
+
+/**
+ * Handle for a GET /webhook/$ID operation. Gets details
+ * about a single webhook. Do not confused with a
+ * `struct TALER_MERCHANT_WebhooksGetHandle`, which
+ * obtains a list of all webhooks.
+ */
+struct TALER_MERCHANT_WebhookGetHandle;
+
+
+/**
+ * Function called with the result of the GET /webhooks operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param event_type event of the webhook
+ * @param url url to trigger webhook at
+ * @param http_method use for the webhook
+ * @param header_template header of the webhook
+ * @param body_template body of the webhook
+ */
+typedef void
+(*TALER_MERCHANT_WebhookGetCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const char *event_type,
+ const char *url,
+ const char *http_method,
+ const char *header_template,
+ const char *body_template);
+
+
+/**
+ * Make a GET /webhook/$ID request to get details about an
+ * individual webhook.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param webhook_id identifier of the webhook to inquire about
+ * @param cb function to call with the backend's webhook 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_WebhookGetHandle *
+TALER_MERCHANT_webhook_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *webhook_id,
+ TALER_MERCHANT_WebhookGetCallback cb,
+ void *cb_cls);
/**
- * Cancel a GET [/private]/kyc/$KYC_ID request.
+ * Cancel GET /webhooks/$ID operation.
*
- * @param kyc handle to the request to be canceled
+ * @param[in] tgh operation to cancel
*/
void
-TALER_MERCHANT_kyc_get_cancel (
- struct TALER_MERCHANT_KycGetHandle *kyc);
+TALER_MERCHANT_webhook_get_cancel (
+ struct TALER_MERCHANT_WebhookGetHandle *tgh);
+
+
+/**
+ * Handle for a POST /webhooks operation.
+ */
+struct TALER_MERCHANT_WebhooksPostHandle;
+
+
+/**
+ * Function called with the result of the POST /webhooks operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ */
+typedef void
+(*TALER_MERCHANT_WebhooksPostCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Make a POST /webhooks request to add a webhook
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param webhook_id identifier to use for the webhook
+ * @param event_type event of the webhook
+ * @param url url use by the customer
+ * @param http_method http method use by the merchant
+ * @param header_template header of the webhook
+ * @param body_template body of the webhook
+ * @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_WebhooksPostHandle *
+TALER_MERCHANT_webhooks_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *webhook_id,
+ const char *event_type,
+ const char *url,
+ const char *http_method,
+ const char *header_template,
+ const char *body_template,
+ TALER_MERCHANT_WebhooksPostCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel POST /webhooks operation.
+ *
+ * @param[in] wph operation to cancel
+ */
+void
+TALER_MERCHANT_webhooks_post_cancel (
+ struct TALER_MERCHANT_WebhooksPostHandle *wph);
+
+
+/**
+ * Handle for a PATCH /webhook operation.
+ */
+struct TALER_MERCHANT_WebhookPatchHandle;
+
+
+/**
+ * Function called with the result of the PATCH /webhook operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ */
+typedef void
+(*TALER_MERCHANT_WebhookPatchCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Make a PATCH /webhook request to update webhook details
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param webhook_id identifier to use for the webhook; the webhook must exist,
+ * or the transaction will fail with a #MHD_HTTP_NOT_FOUND
+ * HTTP status code
+ * @param event_type event of the webhook
+ * @param url url use by the customer
+ * @param http_method http method use by the merchant
+ * @param header_template header of the webhook
+ * @param body_template body of the webhook
+ * @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_WebhookPatchHandle *
+TALER_MERCHANT_webhook_patch (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *webhook_id,
+ const char *event_type,
+ const char *url,
+ const char *http_method,
+ const char *header_template,
+ const char *body_template,
+ TALER_MERCHANT_WebhookPatchCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel PATCH /webhook operation.
+ *
+ * @param[in] wph operation to cancel
+ */
+void
+TALER_MERCHANT_webhook_patch_cancel (
+ struct TALER_MERCHANT_WebhookPatchHandle *wph);
+
+
+/**
+ * Handle for a DELETE /webhook$ID operation.
+ */
+struct TALER_MERCHANT_WebhookDeleteHandle;
+
+
+/**
+ * Function called with the result of the DELETE /webhook/$ID operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ */
+typedef void
+(*TALER_MERCHANT_WebhookDeleteCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Make a DELETE /webhook/$ID request to delete a webhook.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param webhook_id identifier of the webhook
+ * @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_WebhookDeleteHandle *
+TALER_MERCHANT_webhook_delete (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *webhook_id,
+ TALER_MERCHANT_WebhookDeleteCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel DELETE /webhook/$ID operation.
+ *
+ * @param[in] wdh operation to cancel
+ */
+void
+TALER_MERCHANT_webhook_delete_cancel (
+ struct TALER_MERCHANT_WebhookDeleteHandle *wdh);
#endif /* _TALER_MERCHANT_SERVICE_H */
diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h
index 64e37c72..47d081fc 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-2021 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
@@ -21,11 +21,13 @@
* @file taler_merchant_testing_lib.h
* @brief API for writing an interpreter to test Taler components
* @author Christian Grothoff <christian@grothoff.org>
- * @author Marcello Stanisci
+ * @author Marcello Shtanisci
+ * @author Priscilla HUANG
*/
#ifndef TALER_MERCHANT_TESTING_LIB_H
#define TALER_MERCHANT_TESTING_LIB_H
+#include <gnunet/gnunet_time_lib.h>
#include <taler/taler_testing_lib.h>
#include "taler_merchant_service.h"
@@ -35,33 +37,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 ************ */
@@ -106,8 +92,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.
*/
@@ -115,8 +99,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);
@@ -146,14 +128,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
@@ -165,14 +143,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,
@@ -180,20 +154,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.
@@ -204,14 +233,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);
@@ -238,35 +263,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.
@@ -320,9 +316,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.
*/
@@ -338,6 +335,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);
@@ -492,6 +490,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.
@@ -504,6 +503,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,
@@ -511,6 +511,7 @@ TALER_TESTING_cmd_merchant_post_orders (
struct GNUNET_TIME_Timestamp pay_deadline,
const char *amount);
+
/**
* Make the "proposal" command AVOIDING claiming the order.
*
@@ -534,10 +535,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.
@@ -559,6 +562,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,
@@ -573,6 +577,64 @@ 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);
+
+
+/**
+ * Create an order with a choices array with input and output tokens.
+ *
+ * @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 token_family_reference label of the POST /tokenfamilies cmd.
+ * @param order_id the name of the order to add.
+ * @param refund_deadline the deadline for refunds on this order.
+ * @param pay_deadline the deadline for payment on this order.
+ * @param amount the amount this order is for.
+ * @return the command
+ */
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_orders_choices (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *token_family_reference,
+ const char *order_id,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ const char *amount);
+
+
+/**
* Define a "GET /orders" CMD.
*
* @param label command label.
@@ -580,7 +642,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.
*/
@@ -615,9 +677,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);
/**
@@ -633,13 +696,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);
/**
@@ -736,13 +827,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,
+ ...);
/**
@@ -769,16 +861,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);
/**
@@ -835,14 +972,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);
/**
@@ -856,11 +994,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);
/**
@@ -873,10 +1012,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);
/**
@@ -915,12 +1055,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);
/**
@@ -938,11 +1079,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,
+ ...);
/**
@@ -956,10 +1098,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 *************** */
@@ -1070,454 +1213,572 @@ TALER_TESTING_cmd_merchant_delete_transfer (const char *label,
unsigned int http_status);
-/* ******************* /reserves *************** */
+/**
+ * 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
+ * @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_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);
+
+
+/* ****** OTP devices ******* */
/**
- * Define a "POST /reserves" CMD
+ * Define a "POST /otp-devices" CMD.
*
* @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 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.
* @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_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);
/**
- * This commands does not query the backend at all,
- * but just makes up a fake reserve.
+ * Define a "PATCH /otp-devices/$ID" CMD.
*
* @param label command label.
+ * @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.
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_post_reserves_fake (const char *label);
+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 reserve" CMD.
+ * Define a "GET /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
+ * GET /otp-devices request.
* @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
+ * 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_reserve (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- const char *reserve_reference);
+TALER_TESTING_cmd_merchant_get_otp_devices (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ ...);
/**
- * Define a "GET reserve" CMD.
+ * Define a "GET otp device" 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
+ * GET /otp-devices/$ID request.
+ * @param otp_id the ID of the otp to query
* @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)
+ * @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_get_reserve_with_tips (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- const char *reserve_reference,
- ...);
+TALER_TESTING_cmd_merchant_get_otp_device (const char *label,
+ const char *merchant_url,
+ const char *otp_id,
+ unsigned int http_status,
+ const char *otp_reference);
/**
- * Define a "GET /reserves" CMD
+ * Define a "DELETE otp device" CMD.
*
* @param label command label.
- * @param merchant_url url to the merchant.
+ * @param merchant_url base URL of the merchant serving the
+ * DELETE /otp-devices/$ID request.
+ * @param otp_id the ID of the otp to query
* @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
- * (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_delete_otp_device (const char *label,
+ const char *merchant_url,
+ const char *otp_id,
+ unsigned int http_status);
+
+
+/* ****** Templates ******* */
+
+/**
+ * Define a "POST /templates" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * POST /templates request.
+ * @param template_id the ID of the template to query
+ * @param template_description description of the template
+ * @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.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_templates2 (
+ const char *label,
+ const char *merchant_url,
+ const char *template_id,
+ const char *template_description,
+ const char *otp_id,
+ json_t *template_contract,
+ unsigned int http_status);
/**
- * Define a "DELETE reserve" CMD.
+ * Define a "POST /templates" CMD, simple version
*
* @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
+ * POST /templates request.
+ * @param template_id the ID of the template to create
+ * @param template_description description of the template
* @param http_status expected HTTP response code.
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_delete_reserve (const char *label,
+TALER_TESTING_cmd_merchant_post_templates (const char *label,
const char *merchant_url,
- const char *reserve_reference,
+ const char *template_id,
+ const char *template_description,
unsigned int http_status);
/**
- * Define a "PURGE reserve" CMD.
+ * Define a "PATCH /templates/$ID" 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
+ * PATCH /template request.
+ * @param template_id the ID of the template to query
+ * @param template_description description of the template
+ * @param otp_id OTP device to use
+ * @param template_contract contract of the company
* @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);
+TALER_TESTING_cmd_merchant_patch_template (
+ const char *label,
+ const char *merchant_url,
+ const char *template_id,
+ const char *template_description,
+ const char *otp_id,
+ json_t *template_contract,
+ unsigned int http_status);
/**
- * Define a get tips CMD.
+ * Define a "GET /templates" 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 label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * GET /templates request.
+ * @param http_status expected HTTP response code.
* @param ... NULL-terminated list of labels (const char *) of
- * tip (commands) we expect to be returned in the list
+ * template (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_get_tips (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- ...);
+TALER_TESTING_cmd_merchant_get_templates (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ ...);
/**
- * Define a get tips CMD.
+ * Define a "GET template" 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)
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * GET /templates/$ID request.
+ * @param template_id the ID of the template to query
+ * @param http_status expected HTTP response code.
+ * @param template_reference reference to a "POST /templates" or "PATCH /templates/$ID" CMD
+ * that will provide what we expect the backend to return to us
+ * @return the command.
*/
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,
- ...);
+TALER_TESTING_cmd_merchant_get_template (const char *label,
+ const char *merchant_url,
+ const char *template_id,
+ unsigned int http_status,
+ const char *template_reference);
/**
- * Define a GET /private/tips/$TIP_ID CMD.
+ * Define a "DELETE template" 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 label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * DELETE /templates/$ID request.
+ * @param template_id the ID of the template to query
+ * @param http_status expected HTTP response code.
+ * @return the command.
*/
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);
+TALER_TESTING_cmd_merchant_delete_template (const char *label,
+ const char *merchant_url,
+ const char *template_id,
+ unsigned int http_status);
/**
- * Define a GET /private/tips/$TIP_ID CMD.
+ * Define a "POST /using-templates" CMD, simple version
*
- * @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)
+ * @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_get_tip_with_pickups (const char *label,
- const char *merchant_url,
- const char *tip_reference,
- 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);
+
+
+/* ****** Token Families ******* */
+
/**
- * Define a GET /tips/$TIP_ID CMD.
+ * Define a "POST /tokenfamilies" 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 label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * POST /tokenfamilies request.
+ * @param http_status expected HTTP response code.
+ * @param slug slug of the token family.
+ * @param name name of the token family.
+ * @param description description of the token family.
+ * @param description_i18n internationalized description of the token family.
+ * @param valid_after start of the validity time of the token family.
+ * @param valid_before end of the validity time of the token family.
+ * @param duration validity duration of an issued token of the token family.
+ * @param kind kind of the token family. either "subscription" or "discount".
+ * @return the command.
*/
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);
+TALER_TESTING_cmd_merchant_post_tokenfamilies (
+ const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *slug,
+ const char *name,
+ const char *description,
+ json_t *description_i18n,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct GNUNET_TIME_Timestamp valid_before,
+ struct GNUNET_TIME_Relative duration,
+ const char *kind);
+
+/* ****** Webhooks ******* */
/**
- * Define a GET /tips/$TIP_ID CMD.
+ * Define a "POST /webhooks" 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.
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * POST /webhooks request.
+ * @param webhook_id the ID of the webhook to query
+ * @param event_type event of the webhook
+ * @param url use by the customer
+ * @param http_method use by the merchant
+ * @param header_template of the webhook
+ * @param body_template of the webhook
+ * @param http_status expected HTTP response code.
+ * @return the command.
*/
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);
+TALER_TESTING_cmd_merchant_post_webhooks2 (
+ const char *label,
+ const char *merchant_url,
+ const char *webhook_id,
+ const char *event_type,
+ const char *url,
+ const char *http_method,
+ const char *header_template,
+ const char *body_template,
+ unsigned int http_status);
/**
- * Create a /tip-authorize CMD.
+ * Define a "POST /webhooks" CMD, simple version
*
- * @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 label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * POST /webhooks request.
+ * @param webhook_id the ID of the webhook to query
+ * @param event_type event of the webhook
+ * @param http_status expected HTTP response code.
+ * @return the command.
*/
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);
+TALER_TESTING_cmd_merchant_post_webhooks (const char *label,
+ const char *merchant_url,
+ const char *webhook_id,
+ const char *event_type,
+ unsigned int http_status);
/**
- * Create a /tip-authorize CMD.
+ * Define a "PATCH /webhooks/$ID" 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.
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * PATCH /webhook request.
+ * @param webhook_id the ID of the webhook to query
+ * @param event_type event of the webhook
+ * @param url use by the customer
+ * @param http_method use by the merchant
+ * @param header_template of the webhook
+ * @param body_template of the webhook
+ * @param http_status expected HTTP response code.
+ * @return the command.
*/
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);
+TALER_TESTING_cmd_merchant_patch_webhook (
+ const char *label,
+ const char *merchant_url,
+ const char *webhook_id,
+ const char *event_type,
+ const char *url,
+ const char *http_method,
+ const char *header_template,
+ const char *body_template,
+ unsigned int http_status);
/**
- * Create a /tip-authorize CMD, specifying the Taler error code
- * that is expected to be returned by the backend.
+ * Define a "GET /webhooks" 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.
- * @param ec expected Taler-defined error code.
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * GET /webhooks request.
+ * @param http_status expected HTTP response code.
+ * @param ... NULL-terminated list of labels (const char *) of
+ * webhook (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_tip_authorize_with_ec (const char *label,
+TALER_TESTING_cmd_merchant_get_webhooks (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.
+ * Define a "GET webhook" 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.
- * @param ec expected Taler-defined error code.
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * GET /templates/$ID request.
+ * @param webhook_id the ID of the webhook to query
+ * @param http_status expected HTTP response code.
+ * @param webhook_reference reference to a "POST /webhooks" or "PATCH /webhooks/$ID" CMD
+ * that will provide what we expect the backend to return to us
+ * @return the command.
*/
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);
+TALER_TESTING_cmd_merchant_get_webhook (const char *label,
+ const char *merchant_url,
+ const char *webhook_id,
+ unsigned int http_status,
+ const char *webhook_reference);
/**
- * 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.
+ * Define a "DELETE webhook" CMD.
*
* @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * DELETE /webhooks/$ID request.
+ * @param webhook_id the ID of the webhook to query
+ * @param http_status expected HTTP response code.
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_authorize_fake (const char *label);
+TALER_TESTING_cmd_merchant_delete_webhook (const char *label,
+ const char *merchant_url,
+ 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);
/**
- * Define a /tip-pickup CMD, equipped with the expected error
- * code.
+ * Command to run the 'taler-merchant-depositcheck' program.
*
- * @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
+ * @param label command label.
+ * @param config_filename configuration file used by the deposit check helper.
*/
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);
+TALER_TESTING_cmd_depositcheck (const char *label,
+ const char *config_filename);
+
/**
- * Define a /tip-pickup CMD.
+ * Command to run the 'taler-merchant-exchange' program.
*
- * @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
+ * @param label command label.
+ * @param config_filename configuration file used by the webhook.
*/
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);
+TALER_TESTING_cmd_run_tme (const char *label,
+ const char *config_filename);
/**
- * Run a command to fetch the KYC status of a merchant.
+ * This function is used to start the web server.
*
- * @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
+ * @param label command label
+ * @param port is the port of the web server
*/
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_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 ******* */
/**
- * Call #op on all simple traits.
+ * Call @a op on all simple traits.
+ *
+ * @param op macro to call
*/
// 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) \
@@ -1525,17 +1786,43 @@ TALER_TESTING_cmd_merchant_kyc_get (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 *)
-
-
-/**
- * Call #op on all indexed traits.
+ 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 (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) \
+ op (token_family_slug, const char) \
+ op (token_family_duration, const struct GNUNET_TIME_Relative) \
+ op (token_family_kind, const char)
+
+
+/**
+ * Call @a op on all indexed traits.
+ *
+ * @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 9cce9aac..3a641a54 100644
--- a/src/include/taler_merchantdb_lib.h
+++ b/src/include/taler_merchantdb_lib.h
@@ -59,6 +59,44 @@ TALER_MERCHANTDB_product_details_free (
struct TALER_MERCHANTDB_ProductDetails *pd);
+/**
+ * Free members of @a tp, but not @a tp itself.
+ *
+ * @param[in] tp template details to clean up
+ */
+void
+TALER_MERCHANTDB_template_details_free (
+ struct TALER_MERCHANTDB_TemplateDetails *tp);
+
+
+/**
+ * Free members of @a wb, but not @a wb itself.
+ *
+ * @param[in] wb webhook details to clean up
+ */
+void
+TALER_MERCHANTDB_webhook_details_free (
+ struct TALER_MERCHANTDB_WebhookDetails *wb);
+
+/**
+ * Free members of @a pwb, but not @a pwb itself.
+ *
+ * @param[in] pwb pending webhook details to clean up
+ */
+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 41fb0b59..44fdc0ab 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.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 Lesser General Public License as published by the Free Software
@@ -18,10 +18,13 @@
* @brief database access for the merchant
* @author Florian Dold
* @author Christian Grothoff
+ * @author Priscilla Huang
*/
#ifndef TALER_MERCHANTDB_PLUGIN_H
#define TALER_MERCHANTDB_PLUGIN_H
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_time_lib.h>
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_db_lib.h>
#include <taler/taler_exchange_service.h>
@@ -95,7 +98,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?
@@ -104,6 +120,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.
*/
@@ -150,6 +179,7 @@ struct TALER_MERCHANTDB_InstanceSettings
* merchant's logo data uri
*/
char *logo;
+
/**
* Address of the business
*/
@@ -161,22 +191,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.
- */
- 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.
+ * Use STEFAN curves to determine acceptable
+ * fees by default (otherwise: accept no fees by default).
*/
- uint32_t default_wire_fee_amortization;
+ bool use_stefan;
/**
* If the frontend does NOT specify an execution date, how long should
@@ -192,6 +210,10 @@ struct TALER_MERCHANTDB_InstanceSettings
*/
struct GNUNET_TIME_Relative default_pay_delay;
+ /**
+ * Type of user this merchant represents.
+ */
+ enum TALER_KYCLOGIC_KycUserType ut;
};
@@ -203,8 +225,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)(
@@ -212,19 +232,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);
@@ -303,11 +335,243 @@ struct TALER_MERCHANTDB_ProductDetails
/**
+ * Typically called by `lookup_templates`.
+ *
+ * @param cls closure
+ * @param template_id ID of the template
+ * @param template_description description of the template
+ */
+typedef void
+(*TALER_MERCHANTDB_TemplatesCallback)(void *cls,
+ const char *template_id,
+ const char *template_description);
+
+
+/**
+ * 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
+{
+ /**
+ * Description of the template.
+ */
+ char *template_description;
+
+ /**
+ * In this template contract, we can have additional information.
+ */
+ json_t *template_contract;
+
+ /**
+ * ID of the OTP device linked to the template, or NULL.
+ */
+ 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;
+};
+
+
+/**
+ * Typically called by `lookup_webhooks`.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param webhook_id ID of the webhook
+ * @param event_type event of the webhook
+ */
+typedef void
+(*TALER_MERCHANTDB_WebhooksCallback)(void *cls,
+ const char *webhook_id,
+ const char *event_type);
+
+
+/**
+ * Details about a webhook.
+ */
+struct TALER_MERCHANTDB_WebhookDetails
+{
+
+ /**
+ * event of the webhook.
+ */
+ char *event_type;
+
+ /**
+ * URL of the webhook. The customer will be redirected on this url.
+ */
+ char *url;
+
+ /**
+ * Http method used by the webhook.
+ */
+ char *http_method;
+
+ /**
+ * Header template of the webhook.
+ */
+ char *header_template;
+
+ /**
+ * Body template of the webhook.
+ */
+ char *body_template;
+
+};
+
+
+/**
+ * Typically called by `lookup_webhook_by_event`.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param webhook_serial reference to the configured webhook template.
+ * @param event_type which type of event triggers this type of webhook
+ * @param url the HTTP URL to make the webhook request to
+ * @param http_method HTTP method use for the webhook
+ * @param header_template template for the header of the webhook
+ * @param body_template template for the body of the webhook
+ */
+typedef void
+(*TALER_MERCHANTDB_WebhookDetailCallback)(void *cls,
+ uint64_t webhook_serial,
+ const char *event_type,
+ const char *url,
+ const char *http_method,
+ const char *header_template,
+ const char *body_template);
+
+
+/**
+ * Typically called by `lookup_pending_webhooks`.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @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
+ * @param http_method use for the webhook
+ * @param header of the webhook
+ * @param body of the webhook
+ */
+typedef void
+(*TALER_MERCHANTDB_PendingWebhooksCallback)(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);
+
+
+/**
+ * Details about the pending webhook.
+ */
+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.
+ */
+ struct GNUNET_TIME_Absolute next_attempt;
+
+ /**
+ * How often have we tried this request so far.
+ */
+ uint32_t retries;
+
+ /**
+ * URL of the webhook. The customer will be redirected on this url.
+ */
+ char *url;
+
+ /**
+ * Http method used for the webhook.
+ */
+ char *http_method;
+
+ /**
+ * Header of the webhook.
+ */
+ char *header;
+
+ /**
+ * Body of the webhook.
+ */
+ char *body;
+
+};
+
+
+/**
* Filter preferences.
*/
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;
@@ -370,7 +634,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)(
@@ -379,8 +642,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);
/**
@@ -457,6 +719,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)(
@@ -466,7 +729,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);
/**
@@ -476,6 +740,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,
@@ -520,6 +789,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.
*
@@ -535,6 +858,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
@@ -564,71 +908,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);
/**
@@ -637,9 +934,10 @@ struct TALER_MERCHANTDB_TipDetails
* @param cls closure
* @param deposit_serial which deposit operation is this about
* @param exchange_url URL of the exchange that issued the coin
+ * @param h_wire hash of merchant's wire details
+ * @param deposit_timestamp when was the deposit made
* @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 merchant's wire details
* @param coin_pub public key of the coin
*/
typedef void
@@ -648,61 +946,13 @@ typedef void
uint64_t deposit_serial,
const char *exchange_url,
const struct TALER_MerchantWireHashP *h_wire,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
const struct TALER_CoinSpendPublicKeyP *coin_pub);
/**
- * 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
@@ -733,25 +983,151 @@ typedef void
/**
- * Details about a pickup operation executed by the merchant.
+ * Possible token family kinds.
*/
-struct TALER_MERCHANTDB_PickupDetails
+enum TALER_MERCHANTDB_TokenFamilyKind
{
+
/**
- * Identifier for the pickup operation.
+ * Token family representing a discount token
*/
- struct TALER_PickupIdentifierP pickup_id;
+ TALER_MERCHANTDB_TFK_Discount = 0,
/**
- * Total amount requested for this @e pickup_id.
+ * Token family representing a subscription token
*/
- struct TALER_Amount requested_amount;
+ 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
+{
/**
- * Number of planchets involved in the request.
+ * Token family slug used for identification.
*/
- unsigned int num_planchets;
+ 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_TokenFamilyKeyDetails
+{
+ /**
+ * 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 GNUNET_TIME_Timestamp valid_before;
+
+ /**
+ * Token family public key.
+ */
+ struct TALER_TokenFamilyPublicKey *pub;
+
+ /**
+ * Token family private key.
+ */
+ struct TALER_TokenFamilyPrivateKey *priv;
+
+ /**
+ * Details about the token family this key belongs to.
+ */
+ struct TALER_MERCHANTDB_TokenFamilyDetails token_family;
+};
+
+/**
+ * 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;
};
@@ -884,7 +1260,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);
@@ -893,7 +1268,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);
@@ -971,6 +1346,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.
*
@@ -1082,6 +1583,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
@@ -1093,7 +1595,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);
/**
@@ -1101,6 +1604,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
@@ -1108,6 +1614,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);
@@ -1210,8 +1718,9 @@ struct TALER_MERCHANTDB_Plugin
* instances.
*
* @param cls closure
+ * @return database result code
*/
- void
+ enum GNUNET_DB_QueryStatus
(*expire_locks)(void *cls);
@@ -1222,13 +1731,15 @@ struct TALER_MERCHANTDB_Plugin
* @param cls closure
* @param instance_id instance to delete order of
* @param order_id order to delete
+ * @param force force deletion of claimed but unpaid orders
* @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
* if locks prevent deletion OR order unknown
*/
enum GNUNET_DB_QueryStatus
(*delete_order)(void *cls,
const char *instance_id,
- const char *order_id);
+ const char *order_id,
+ bool force);
/**
@@ -1295,20 +1806,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);
/**
@@ -1354,16 +1871,73 @@ struct TALER_MERCHANTDB_Plugin
* @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 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,
- 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);
/**
@@ -1378,14 +1952,17 @@ struct TALER_MERCHANTDB_Plugin
* @param instance_id instance's identifier
* @param order_id order_id used to store
* @param claim_token the token belonging to the order
+ * @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
- (*insert_contract_terms)(void *cls,
- const char *instance_id,
- const char *order_id,
- json_t *contract_terms);
+ (*insert_contract_terms)(
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ json_t *contract_terms,
+ uint64_t *order_serial);
/**
@@ -1472,36 +2049,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);
/**
@@ -1604,25 +2207,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
@@ -1657,6 +2241,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
@@ -1767,6 +2392,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
@@ -1776,9 +2402,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
@@ -1953,20 +2612,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);
/**
@@ -2045,340 +2706,638 @@ 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);
+ const struct TALER_MasterSignatureP *master_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.
+ * 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_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);
+ * @param cls closure
+ * @param master_pub public key of the exchange
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*delete_exchange_accounts)(
+ void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub);
/**
- * 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.
+ * Return information about wire accounts of an exchange.
*
- * @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
+ * @param cb function to call on each account
+ * @param cb_cls closure for @a cb
+ * @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);
+ (*select_accounts_by_exchange)(
+ void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ TALER_MERCHANTDB_ExchangeAccountCallback cb,
+ void *cb_cls);
/**
- * Lookup reserves.
+ * Insert information about a wire account 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 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
+ (*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 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 transaction status
+ * @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_reserves)(void *cls,
+ (*lookup_templates)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_TemplatesCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * 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
+ (*lookup_template)(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);
+ 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);
+
+
+ /**
+ * 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
+ (*insert_template)(void *cls,
+ const char *instance_id,
+ const char *template_id,
+ uint64_t otp_serial_id,
+ const struct TALER_MERCHANTDB_TemplateDetails *td);
+
+
+ /**
+ * 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
+ (*delete_otp)(void *cls,
+ const char *instance_id,
+ const char *otp_id);
+
+ /**
+ * 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
+ (*insert_otp)(void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ const struct TALER_MERCHANTDB_OtpDeviceDetails *td);
/**
- * Lookup reserves pending activation across all instances.
+ * Update details about a particular OTP device.
*
* @param cls closure
- * @param cb function to call with reserve data
+ * @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
+ (*update_otp)(void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ const struct TALER_MERCHANTDB_OtpDeviceDetails *td);
+
+ /**
+ * 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 transaction status
+ * @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_pending_reserves)(void *cls,
- TALER_MERCHANTDB_PendingReservesCallback cb,
- void *cb_cls);
+ (*lookup_otp_devices)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_OtpDeviceCallback cb,
+ void *cb_cls);
/**
- * Lookup reserve details.
+ * Lookup details about an OTP device.
*
* @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 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
+ (*select_otp)(void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ struct TALER_MERCHANTDB_OtpDeviceDetails *td);
+
+
+ /**
+ * 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
+ (*select_otp_serial)(void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ uint64_t *serial);
+
+
+ /**
+ * 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
+ (*update_template)(void *cls,
+ const char *instance_id,
+ const char *template_id,
+ const struct TALER_MERCHANTDB_TemplateDetails *td);
+
+
+ /**
+ * 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 transaction status
+ * @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_reserve)(void *cls,
+ (*lookup_webhooks)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_WebhooksCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * 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
+ (*lookup_webhook)(void *cls,
+ const char *instance_id,
+ const char *webhook_id,
+ struct TALER_MERCHANTDB_WebhookDetails *wb);
+
+ /**
+ * 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
+ (*delete_webhook)(void *cls,
const char *instance_id,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- bool fetch_tips,
- TALER_MERCHANTDB_ReserveDetailsCallback cb,
- void *cb_cls);
+ const char *webhook_id);
/**
- * Delete private key of a reserve.
+ * Insert details about a particular webhook.
*
* @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 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
- (*delete_reserve)(void *cls,
+ (*insert_webhook)(void *cls,
const char *instance_id,
- const struct TALER_ReservePublicKeyP *reserve_pub);
+ const char *webhook_id,
+ const struct TALER_MERCHANTDB_WebhookDetails *wb);
+
/**
- * Purge all information about a reserve (including tips from it).
+ * Update details about a particular webhook.
*
* @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 update webhook 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
- (*purge_reserve)(void *cls,
- const char *instance_id,
- const struct TALER_ReservePublicKeyP *reserve_pub);
+ (*update_webhook)(void *cls,
+ const char *instance_id,
+ const char *webhook_id,
+ const struct TALER_MERCHANTDB_WebhookDetails *wb);
+ /**
+ * 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
+ (*lookup_webhook_by_event)(void *cls,
+ const char *instance_id,
+ const char *event_type,
+ TALER_MERCHANTDB_WebhookDetailCallback cb,
+ void *cb_cls);
/**
- * 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.
+ * Insert webhook in the pending webhook.
*
- * @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);
+ * @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
+ (*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);
+ /**
+ * Lookup the webhook that need to be send in priority. These webhooks are not successfully
+ * 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
+ (*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
+ (*lookup_future_webhook)(void *cls,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls);
/**
- * Lookup pickup details for pickup @a pickup_id.
+ * Lookup all the webhooks in the pending webhook.
+ * Use by the administrator
*
- * @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
+ * @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
+ (*lookup_all_webhooks)(void *cls,
+ const char *instance_id,
+ uint64_t min_row,
+ uint32_t max_results,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * 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 we should make the next request to the webhook
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_pending_webhook)(void *cls,
+ uint64_t webhook_pending_serial,
+ struct GNUNET_TIME_Absolute next_attempt);
+ // maybe add: http status of failure?
+
+
+ /**
+ * 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
+ */
+ enum GNUNET_DB_QueryStatus
+ (*delete_pending_webhook)(void *cls,
+ 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
- (*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[]);
+ (*select_exchange_keys)(void *cls,
+ const char *exchange_url,
+ struct TALER_EXCHANGE_Keys **keys);
/**
- * Lookup tip details for tip @a tip_id.
+ * Insert or update @a keys into the database.
*
- * @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
+ * @param cls plugin closure
+ * @param keys data to store
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*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);
+ (*insert_exchange_keys)(void *cls,
+ const struct TALER_EXCHANGE_Keys *keys);
/**
- * Lookup tips
+ * Lookup all of the token families the given instance has configured.
*
- * @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 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 transaction status
+ * @return database result code
*/
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_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);
/**
- * Lookup tip details for tip @a tip_id.
+ * Delete information about a token family.
*
- * @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
+ * @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
- (*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);
+ (*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 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 token family.
*
- * @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 token family 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_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_token_family)(
+ void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ const struct TALER_MERCHANTDB_TokenFamilyDetails *details);
/**
- * Insert blind signature obtained from the exchange during a
- * tip pickup operation.
+ * Lookup details about a particular token family key.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup token family key for
+ * @param token_family_slug slug of token family to lookup
+ * @param min_valid_after lower bound of the start of the key validation period
+ * @param max_valid_after upper bound of the start of the key validation period
+ * @param[out] details set to the token family key details on success, can be NULL
+ * (in that case we only want to check if the token family key exists)
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_token_family_key) (
+ void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ struct GNUNET_TIME_Timestamp min_valid_after,
+ struct GNUNET_TIME_Timestamp max_valid_after,
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details);
+
+
+ /**
+ * Insert details a key pair for a token family.
*
- * @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 token_family_slug slug of token family to insert the key pair for
+ * @param pub token family public key
+ * @param priv token family private key
+ * @param valid_after start of the key validation period
+ * @param valid_before end of the key validation period
+ * @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_pickup_blind_signature)(
+ (*insert_token_family_key)(
void *cls,
- const struct TALER_PickupIdentifierP *pickup_id,
- uint32_t offset,
- const struct TALER_BlindedDenominationSignature *blind_sig);
+ const char *token_family_slug,
+ const struct TALER_TokenFamilyPublicKey *pub,
+ const struct TALER_TokenFamilyPrivateKey *priv,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct GNUNET_TIME_Timestamp valid_before);
+
+ /**
+ * 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 95e431e5..1e7430d4 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -10,34 +10,46 @@ 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.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_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 \
+ merchant_api_get_webhook.c \
+ 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 \
@@ -46,35 +58,30 @@ 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_tip_authorize.c \
- merchant_api_tip_pickup.c \
- merchant_api_tip_pickup2.c \
- merchant_api_wallet_get_tip.c \
+ merchant_api_post_templates.c \
+ merchant_api_post_tokenfamilies.c \
+ merchant_api_post_using_templates.c \
+ merchant_api_post_webhooks.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 \
-lgnunetutil \
-ljansson \
+ -lcurl \
$(XLIB)
-if HAVE_LIBCURL
-libtalermerchant_la_LIBADD += -lcurl
-else
-if HAVE_LIBGNURL
-libtalermerchant_la_LIBADD += -lgnurl
-endif
-endif
-
check_PROGRAMS = \
test_merchant_api_common
diff --git a/src/lib/merchant_api_common.c b/src/lib/merchant_api_common.c
index 7e22f645..f5569ce3 100644
--- a/src/lib/merchant_api_common.c
+++ b/src/lib/merchant_api_common.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
@@ -18,10 +18,12 @@
* @file merchant_api_common.c
* @brief Implementation of common logic for libtalermerchant
* @author Christian Grothoff
+ * @author Priscilla Huang
*/
#include "platform.h"
#include <curl/curl.h>
#include "taler_merchant_service.h"
+#include "merchant_api_common.h"
#include <gnunet/gnunet_uri_lib.h>
#include <taler/taler_json_lib.h>
@@ -105,31 +107,7 @@ TALER_MERCHANT_parse_error_details_ (const json_t *response,
}
-char *
-TALER_MERCHANT_baseurl_add_instance (const char *base_url,
- const char *instance_id)
-{
- char *ret;
- bool end_sl;
-
- if ('\0' == *base_url)
- {
- GNUNET_break (0);
- return NULL;
- }
- end_sl = '/' == base_url[strlen (base_url) - 1];
-
- GNUNET_asprintf (&ret,
- (end_sl)
- ? "%sinstances/%s/"
- : "%s/instances/%s/",
- base_url,
- instance_id);
- return ret;
-}
-
-
-int
+enum GNUNET_GenericReturnValue
TALER_MERCHANT_parse_pay_uri (const char *pay_uri,
struct TALER_MERCHANT_PayUriData *parse_data)
{
@@ -267,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)
@@ -371,3 +349,138 @@ TALER_MERCHANT_parse_refund_uri_free (
GNUNET_free (parse_data->order_id);
GNUNET_free (parse_data->ssid);
}
+
+
+void
+TALER_MERCHANT_handle_order_creation_response_ (
+ TALER_MERCHANT_PostOrdersCallback cb,
+ void *cb_cls,
+ long response_code,
+ const json_t *json)
+{
+ struct TALER_MERCHANT_PostOrdersReply por = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+ struct TALER_ClaimTokenP token;
+
+ switch (response_code)
+ {
+ case 0:
+ por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ bool no_token;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("order_id",
+ &por.details.ok.order_id),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("token",
+ &token),
+ &no_token),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ por.hr.http_status = 0;
+ por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ else
+ {
+ if (! no_token)
+ por.details.ok.token = &token;
+ }
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ json_dumpf (json,
+ stderr,
+ JSON_INDENT (2));
+ por.hr.ec = TALER_JSON_get_error_code (json);
+ por.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:
+ por.hr.ec = TALER_JSON_get_error_code (json);
+ por.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we need to authenticate. */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* 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
+ reply to the application */
+ por.hr.ec = TALER_JSON_get_error_code (json);
+ por.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ por.hr.ec = TALER_JSON_get_error_code (json);
+ por.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_CONFLICT:
+ por.hr.ec = TALER_JSON_get_error_code (json);
+ por.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_GONE:
+ /* The quantity of some product requested was not available. */
+ {
+
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string (
+ "product_id",
+ &por.details.gone.product_id),
+ GNUNET_JSON_spec_uint64 (
+ "requested_quantity",
+ &por.details.gone.requested_quantity),
+ GNUNET_JSON_spec_uint64 (
+ "available_quantity",
+ &por.details.gone.available_quantity),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp (
+ "restock_expected",
+ &por.details.gone.restock_expected),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ por.hr.http_status = 0;
+ por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ break;
+ }
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry,
+ but this API leaves this to the application */
+ por.hr.ec = TALER_JSON_get_error_code (json);
+ por.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ por.hr.ec = TALER_JSON_get_error_code (json);
+ por.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) por.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ } // end of switch
+ cb (cb_cls,
+ &por);
+}
diff --git a/src/lib/merchant_api_common.h b/src/lib/merchant_api_common.h
new file mode 100644
index 00000000..19a92149
--- /dev/null
+++ b/src/lib/merchant_api_common.h
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ 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
+ 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_common.h
+ * @brief Implementation of common logic for libtalermerchant
+ * @author Christian Grothoff
+ * @author Priscilla Huang
+ */
+#ifndef MERCHANT_API_COMMON_H
+#define MERCHANT_API_COMMON_H
+#include "taler_merchant_service.h"
+
+
+/**
+ * Function called when we're done processing a
+ * HTTP POST request to create an order.
+ *
+ * @param cb callback
+ * @param cb_cls closure for @a cb
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not JSON
+ */
+void
+TALER_MERCHANT_handle_order_creation_response_ (
+ TALER_MERCHANT_PostOrdersCallback cb,
+ void *cb_cls,
+ long response_code,
+ const json_t *json);
+
+
+/**
+ * Take a @a response from the merchant API that (presumably) contains
+ * error details and setup the corresponding @a hr structure. Internally
+ * used to convert merchant's responses in to @a hr.
+ *
+ * @param response if NULL we will report #TALER_EC_GENERIC_INVALID_RESPONSE in `ec`
+ * @param http_status http status to use
+ * @param[out] hr response object to initialize, fields will
+ * only be valid as long as @a response is valid as well
+ */
+void
+TALER_MERCHANT_parse_error_details_ (const json_t *response,
+ unsigned int http_status,
+ struct TALER_MERCHANT_HttpResponse *hr);
+
+
+#endif
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_order.c b/src/lib/merchant_api_delete_order.c
index 33a12294..a0cf941f 100644
--- a/src/lib/merchant_api_delete_order.c
+++ b/src/lib/merchant_api_delete_order.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020-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
@@ -116,6 +116,7 @@ TALER_MERCHANT_order_delete (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
const char *order_id,
+ bool force,
TALER_MERCHANT_OrderDeleteCallback cb,
void *cb_cls)
{
@@ -129,8 +130,11 @@ TALER_MERCHANT_order_delete (
char *path;
GNUNET_asprintf (&path,
- "private/orders/%s",
- order_id);
+ "private/orders/%s%s",
+ order_id,
+ force
+ ? "?force=yes"
+ : "");
odh->url = TALER_url_join (backend_url,
path,
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
new file mode 100644
index 00000000..b2083cc9
--- /dev/null
+++ b/src/lib/merchant_api_delete_template.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_template.c
+ * @brief Implementation of the DELETE /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 DELETE /templates/$ID operation.
+ */
+struct TALER_MERCHANT_TemplateDeleteHandle
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_TemplateDeleteCallback 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_TemplateDeleteHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_delete_template_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_TemplateDeleteHandle *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 /templates/$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_template_delete_cancel (tdh);
+}
+
+
+struct TALER_MERCHANT_TemplateDeleteHandle *
+TALER_MERCHANT_template_delete (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *template_id,
+ TALER_MERCHANT_TemplateDeleteCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_TemplateDeleteHandle *tdh;
+
+ tdh = GNUNET_new (struct TALER_MERCHANT_TemplateDeleteHandle);
+ tdh->ctx = ctx;
+ tdh->cb = cb;
+ tdh->cb_cls = cb_cls;
+ {
+ char *path;
+
+ GNUNET_asprintf (&path,
+ "private/templates/%s",
+ template_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_template_finished,
+ tdh);
+ }
+ return tdh;
+}
+
+
+void
+TALER_MERCHANT_template_delete_cancel (
+ struct TALER_MERCHANT_TemplateDeleteHandle *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_webhook.c b/src/lib/merchant_api_delete_webhook.c
new file mode 100644
index 00000000..14ecdcd1
--- /dev/null
+++ b/src/lib/merchant_api_delete_webhook.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_template.c
+ * @brief Implementation of the DELETE /webhooks/$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 DELETE /webhooks/$ID operation.
+ */
+struct TALER_MERCHANT_WebhookDeleteHandle
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_WebhookDeleteCallback 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 /webhooks/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_WebhookDeleteHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_delete_webhook_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_WebhookDeleteHandle *wdh = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_HttpResponse hr = {
+ .http_status = (unsigned int) response_code,
+ .reply = json
+ };
+
+ wdh->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got /webhooks/$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;
+ }
+ wdh->cb (wdh->cb_cls,
+ &hr);
+ TALER_MERCHANT_webhook_delete_cancel (wdh);
+}
+
+
+struct TALER_MERCHANT_WebhookDeleteHandle *
+TALER_MERCHANT_webhook_delete (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *webhook_id,
+ TALER_MERCHANT_WebhookDeleteCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_WebhookDeleteHandle *wdh;
+
+ wdh = GNUNET_new (struct TALER_MERCHANT_WebhookDeleteHandle);
+ wdh->ctx = ctx;
+ wdh->cb = cb;
+ wdh->cb_cls = cb_cls;
+ {
+ char *path;
+
+ GNUNET_asprintf (&path,
+ "private/webhooks/%s",
+ webhook_id);
+ wdh->url = TALER_url_join (backend_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == wdh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wdh);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ wdh->url);
+ {
+ CURL *eh;
+
+ eh = TALER_MERCHANT_curl_easy_get_ (wdh->url);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_CUSTOMREQUEST,
+ MHD_HTTP_METHOD_DELETE));
+ wdh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_delete_webhook_finished,
+ wdh);
+ }
+ return wdh;
+}
+
+
+void
+TALER_MERCHANT_webhook_delete_cancel (
+ struct TALER_MERCHANT_WebhookDeleteHandle *wdh)
+{
+ if (NULL != wdh->job)
+ GNUNET_CURL_job_cancel (wdh->job);
+ GNUNET_free (wdh->url);
+ GNUNET_free (wdh);
+}
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..b4b700bd 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 14
/**
- * How many configs are we backwards compatible with?
+ * How many configs are we backwards-compatible with?
*/
-#define MERCHANT_PROTOCOL_AGE 1
+#define MERCHANT_PROTOCOL_AGE 2
+/**
+ * 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 2a1725cf..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++)
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
+ 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;
+ }
}
- timeout_kycs[i].exchange_http_status = (unsigned int) hs;
- timeout_kycs[i].exchange_code = (enum TALER_ErrorCode) ec;
+ for (unsigned int i = 0; i<num_touts; i++)
+ {
+ 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;
+ }
+ 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;
}
@@ -217,6 +240,8 @@ handle_get_kyc_finished (void *cls,
kr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, merchant says we need to authenticate. */
break;
+ case MHD_HTTP_SERVICE_UNAVAILABLE:
+ break;
default:
/* unexpected response code */
kr.hr.ec = TALER_JSON_get_error_code (json);
@@ -258,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",
@@ -296,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,
@@ -319,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,
@@ -345,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 817c9240..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);
@@ -273,7 +306,9 @@ TALER_MERCHANT_orders_get2 (
/* build ogh->url with the various optional arguments */
{
- char dstr[30];
+ 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,13 +327,15 @@ TALER_MERCHANT_orders_get2 (
sizeof (cbuf),
"%llu",
(unsigned long long) start_row);
- // FIXME: use date_s, no need for milliseconds!
- GNUNET_snprintf (dstr,
- sizeof (dstr),
- "%llu",
- (unsigned long long) (date.abs_time.abs_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.
- rel_value_us));
+ 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)
{
have_date = ! GNUNET_TIME_absolute_is_zero (date.abs_time);
@@ -323,7 +360,7 @@ TALER_MERCHANT_orders_get2 (
(TALER_EXCHANGE_YNA_ALL != wired)
? TALER_yna_to_string (wired)
: NULL,
- "date_ms",
+ "date_s",
(have_date)
? dstr
: NULL,
@@ -336,10 +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)
{
@@ -352,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 cc6bc562..00000000
--- a/src/lib/merchant_api_get_reserve.c
+++ /dev/null
@@ -1,317 +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_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 24a527d6..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_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);
- // FIXME: use different format?
- 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
new file mode 100644
index 00000000..9bbcc93a
--- /dev/null
+++ b/src/lib/merchant_api_get_template.c
@@ -0,0 +1,201 @@
+/*
+ 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_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_TemplateGetHandle
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_TemplateGetCallback 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_TemplateGetHandle *tgh = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_TemplateGetResponse 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_string ("template_description",
+ &tgr.details.ok.template_description),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("otp_id",
+ &tgr.details.ok.otp_id),
+ NULL),
+ 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_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_template_get_cancel (tgh);
+}
+
+
+struct TALER_MERCHANT_TemplateGetHandle *
+TALER_MERCHANT_template_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *template_id,
+ TALER_MERCHANT_TemplateGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_TemplateGetHandle *tgh;
+ CURL *eh;
+
+ tgh = GNUNET_new (struct TALER_MERCHANT_TemplateGetHandle);
+ tgh->ctx = ctx;
+ tgh->cb = cb;
+ tgh->cb_cls = cb_cls;
+ {
+ char *path;
+
+ GNUNET_asprintf (&path,
+ "private/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_template_get_cancel (
+ struct TALER_MERCHANT_TemplateGetHandle *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_templates.c b/src/lib/merchant_api_get_templates.c
new file mode 100644
index 00000000..f1f973b5
--- /dev/null
+++ b/src/lib/merchant_api_get_templates.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_templates.c
+ * @brief Implementation of the GET /templates 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>
+
+
+/**
+ * Maximum number of templates we return.
+ */
+#define MAX_TEMPLATES 1024
+
+
+/**
+ * Handle for a GET /templates operation.
+ */
+struct TALER_MERCHANT_TemplatesGetHandle
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_TemplatesGetCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * 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 enum GNUNET_GenericReturnValue
+parse_templates (const json_t *ia,
+ struct TALER_MERCHANT_TemplatesGetResponse *tgr,
+ struct TALER_MERCHANT_TemplatesGetHandle *tgh)
+{
+ 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;
+ }
+ {
+ 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,
+ tgr);
+ tgh->cb = NULL; /* just to be sure */
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /templates request.
+ *
+ * @param cls the `struct TALER_MERCHANT_TemplatesGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_get_templates_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_TemplatesGetHandle *tgh = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_TemplatesGetResponse tgr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ tgh->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got /templates response with status code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ const json_t *templates;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("templates",
+ &templates),
+ 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_templates (templates,
+ &tgr,
+ tgh))
+ {
+ TALER_MERCHANT_templates_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_templates_get_cancel (tgh);
+}
+
+
+struct TALER_MERCHANT_TemplatesGetHandle *
+TALER_MERCHANT_templates_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ TALER_MERCHANT_TemplatesGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_TemplatesGetHandle *tgh;
+ CURL *eh;
+
+ tgh = GNUNET_new (struct TALER_MERCHANT_TemplatesGetHandle);
+ tgh->ctx = ctx;
+ tgh->cb = cb;
+ tgh->cb_cls = cb_cls;
+ tgh->url = TALER_url_join (backend_url,
+ "private/templates",
+ 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_templates_finished,
+ tgh);
+ return tgh;
+}
+
+
+void
+TALER_MERCHANT_templates_get_cancel (
+ struct TALER_MERCHANT_TemplatesGetHandle *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_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_tokenfamily.c b/src/lib/merchant_api_get_tokenfamily.c
new file mode 100644
index 00000000..d7e6b06e
--- /dev/null
+++ b/src/lib/merchant_api_get_tokenfamily.c
@@ -0,0 +1,210 @@
+/*
+ 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 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_tokenfamily.c
+ * @brief Implementation of the GET /tokenfamily/$ID request of the merchant's HTTP API
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_json_lib.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 /tokenfamilies/$SLUG operation.
+ */
+struct TALER_MERCHANT_TokenFamilyGetHandle
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_TokenFamilyGetCallback 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 /tokenfamilies/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_TokenFamilyGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_get_token_family_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_TokenFamilyGetHandle *handle = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_TokenFamilyGetResponse res = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ handle->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got /tokenfamilies/$ID response with status code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ // Parse token family response
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("slug",
+ &res.details.ok.slug),
+ GNUNET_JSON_spec_string ("name",
+ &res.details.ok.name),
+ GNUNET_JSON_spec_string ("description",
+ &res.details.ok.description),
+ GNUNET_JSON_spec_object_const ("description_i18n",
+ &res.details.ok.description_i18n),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &res.details.ok.valid_after),
+ GNUNET_JSON_spec_timestamp ("valid_before",
+ &res.details.ok.valid_before),
+ GNUNET_JSON_spec_relative_time ("duation",
+ &res.details.ok.duration),
+ GNUNET_JSON_spec_string ("kind",
+ &res.details.ok.kind),
+ GNUNET_JSON_spec_uint64 ("issued",
+ &res.details.ok.issued),
+ GNUNET_JSON_spec_uint64 ("redeemed",
+ &res.details.ok.redeemed),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK ==
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ handle->cb (handle->cb_cls,
+ &res);
+ GNUNET_JSON_parse_free (spec);
+ TALER_MERCHANT_token_family_get_cancel (handle);
+ return;
+ }
+ res.hr.http_status = 0;
+ res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ case MHD_HTTP_UNAUTHORIZED:
+ res.hr.ec = TALER_JSON_get_error_code (json);
+ res.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we need to authenticate. */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ res.hr.ec = TALER_JSON_get_error_code (json);
+ res.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ res.hr.ec = TALER_JSON_get_error_code (json);
+ res.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) res.hr.ec);
+ break;
+ }
+}
+
+struct TALER_MERCHANT_TokenFamilyGetHandle *
+TALER_MERCHANT_token_family_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *token_family_slug,
+ TALER_MERCHANT_TokenFamilyGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_TokenFamilyGetHandle *handle;
+ CURL *eh;
+
+ handle = GNUNET_new (struct TALER_MERCHANT_TokenFamilyGetHandle);
+ handle->ctx = ctx;
+ handle->cb = cb;
+ handle->cb_cls = cb_cls;
+ {
+ char *path;
+
+ GNUNET_asprintf (&path,
+ "private/tokenfamilies/%s",
+ token_family_slug);
+ handle->url = TALER_url_join (backend_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == handle->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (handle);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ handle->url);
+ eh = TALER_MERCHANT_curl_easy_get_ (handle->url);
+ handle->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_get_token_family_finished,
+ handle);
+ return handle;
+}
+
+void
+TALER_MERCHANT_token_family_get_cancel (
+ struct TALER_MERCHANT_TokenFamilyGetHandle *handle)
+{
+ if (NULL != handle->job)
+ GNUNET_CURL_job_cancel (handle->job);
+ GNUNET_free (handle->url);
+ GNUNET_free (handle);
+} \ No newline at end of file
diff --git a/src/lib/merchant_api_get_transfers.c b/src/lib/merchant_api_get_transfers.c
index 8938c3d7..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
@@ -27,6 +27,7 @@
#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>
@@ -80,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 ()
};
@@ -106,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);
@@ -137,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),
@@ -173,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);
}
@@ -260,7 +249,6 @@ TALER_MERCHANT_transfers_get (
sizeof (offset_s),
"%lld",
(unsigned long long) offset);
- // FIXME: use other format?
before_s = GNUNET_strdup (GNUNET_TIME_timestamp2s (before));
after_s = GNUNET_strdup (GNUNET_TIME_timestamp2s (after));
gth->url = TALER_url_join (backend_url,
diff --git a/src/lib/merchant_api_get_webhook.c b/src/lib/merchant_api_get_webhook.c
new file mode 100644
index 00000000..551aa915
--- /dev/null
+++ b/src/lib/merchant_api_get_webhook.c
@@ -0,0 +1,221 @@
+/*
+ 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_webhook.c
+ * @brief Implementation of the GET /webhooks/$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 /webhooks/$ID operation.
+ */
+struct TALER_MERCHANT_WebhookGetHandle
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_WebhookGetCallback 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 /webhooks/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_WebhookGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_get_webhook_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_WebhookGetHandle *wgh = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_HttpResponse hr = {
+ .http_status = (unsigned int) response_code,
+ .reply = json
+ };
+
+ wgh->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got /webhooks/$ID response with status code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ const char *event_type;
+ const char *url;
+ const char *http_method;
+ const char *header_template;
+ const char *body_template;
+ bool rst_ok = true;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("event_type",
+ &event_type),
+ TALER_JSON_spec_web_url ("url",
+ &url),
+ GNUNET_JSON_spec_string ("http_method",
+ &http_method),
+ GNUNET_JSON_spec_string ("header_template",
+ &header_template),
+ GNUNET_JSON_spec_string ("body_template",
+ &body_template),
+ GNUNET_JSON_spec_end ()
+ };
+
+
+ if ( (rst_ok) &&
+ (GNUNET_OK ==
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL)) )
+ {
+ wgh->cb (wgh->cb_cls,
+ &hr,
+ event_type,
+ url,
+ http_method,
+ header_template,
+ body_template);
+ GNUNET_JSON_parse_free (spec);
+ TALER_MERCHANT_webhook_get_cancel (wgh);
+ return;
+ }
+ 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;
+ 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;
+ }
+ wgh->cb (wgh->cb_cls,
+ &hr,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ TALER_MERCHANT_webhook_get_cancel (wgh);
+}
+
+
+struct TALER_MERCHANT_WebhookGetHandle *
+TALER_MERCHANT_webhook_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *webhook_id,
+ TALER_MERCHANT_WebhookGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_WebhookGetHandle *wgh;
+ CURL *eh;
+
+ wgh = GNUNET_new (struct TALER_MERCHANT_WebhookGetHandle);
+ wgh->ctx = ctx;
+ wgh->cb = cb;
+ wgh->cb_cls = cb_cls;
+ {
+ char *path;
+
+ GNUNET_asprintf (&path,
+ "private/webhooks/%s",
+ webhook_id);
+ wgh->url = TALER_url_join (backend_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == wgh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wgh);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ wgh->url);
+ eh = TALER_MERCHANT_curl_easy_get_ (wgh->url);
+ wgh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_get_webhook_finished,
+ wgh);
+ return wgh;
+}
+
+
+void
+TALER_MERCHANT_webhook_get_cancel (
+ struct TALER_MERCHANT_WebhookGetHandle *wgh)
+{
+ if (NULL != wgh->job)
+ GNUNET_CURL_job_cancel (wgh->job);
+ GNUNET_free (wgh->url);
+ GNUNET_free (wgh);
+}
diff --git a/src/lib/merchant_api_get_webhooks.c b/src/lib/merchant_api_get_webhooks.c
new file mode 100644
index 00000000..e702baac
--- /dev/null
+++ b/src/lib/merchant_api_get_webhooks.c
@@ -0,0 +1,246 @@
+/*
+ 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_webhooks.c
+ * @brief Implementation of the GET /webhooks 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>
+
+
+/**
+ * Maximum number of webhooks we return.
+ */
+#define MAX_WEBHOOKS 1024
+
+/**
+ * Handle for a GET /webhooks operation.
+ */
+struct TALER_MERCHANT_WebhooksGetHandle
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_WebhooksGetCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * 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 enum GNUNET_GenericReturnValue
+parse_webhooks (const json_t *ia,
+ struct TALER_MERCHANT_WebhooksGetResponse *wgr,
+ struct TALER_MERCHANT_WebhooksGetHandle *wgh)
+{
+ 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;
+ }
+ {
+ 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,
+ wgr);
+ wgh->cb = NULL; /* just to be sure */
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /webhooks request.
+ *
+ * @param cls the `struct TALER_MERCHANT_WebhooksGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_get_webhooks_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_WebhooksGetHandle *wgh = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_WebhooksGetResponse wgr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ wgh->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got /webhooks response with status code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ const json_t *webhooks;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("webhooks",
+ &webhooks),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ wgr.hr.http_status = 0;
+ wgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ if (GNUNET_OK ==
+ parse_webhooks (webhooks,
+ &wgr,
+ wgh))
+ {
+ TALER_MERCHANT_webhooks_get_cancel (wgh);
+ return;
+ }
+ wgr.hr.http_status = 0;
+ wgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ case MHD_HTTP_UNAUTHORIZED:
+ 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 */
+ 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) wgr.hr.ec);
+ break;
+ }
+ wgh->cb (wgh->cb_cls,
+ &wgr);
+ TALER_MERCHANT_webhooks_get_cancel (wgh);
+}
+
+
+struct TALER_MERCHANT_WebhooksGetHandle *
+TALER_MERCHANT_webhooks_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ TALER_MERCHANT_WebhooksGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_WebhooksGetHandle *wgh;
+ CURL *eh;
+
+ wgh = GNUNET_new (struct TALER_MERCHANT_WebhooksGetHandle);
+ wgh->ctx = ctx;
+ wgh->cb = cb;
+ wgh->cb_cls = cb_cls;
+ wgh->url = TALER_url_join (backend_url,
+ "private/webhooks",
+ NULL);
+ if (NULL == wgh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wgh);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ wgh->url);
+ eh = TALER_MERCHANT_curl_easy_get_ (wgh->url);
+ wgh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_get_webhooks_finished,
+ wgh);
+ return wgh;
+}
+
+
+void
+TALER_MERCHANT_webhooks_get_cancel (
+ struct TALER_MERCHANT_WebhooksGetHandle *wgh)
+{
+ if (NULL != wgh->job)
+ GNUNET_CURL_job_cancel (wgh->job);
+ GNUNET_free (wgh->url);
+ GNUNET_free (wgh);
+}
diff --git a/src/lib/merchant_api_lock_product.c b/src/lib/merchant_api_lock_product.c
index de4da906..4f7c8be4 100644
--- a/src/lib/merchant_api_lock_product.c
+++ b/src/lib/merchant_api_lock_product.c
@@ -28,6 +28,7 @@
#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>
diff --git a/src/lib/merchant_api_merchant_get_order.c b/src/lib/merchant_api_merchant_get_order.c
index 167e46be..3bd4003b 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
@@ -72,47 +83,48 @@ struct TALER_MERCHANT_OrderMerchantGetHandle
* the response and call the callback.
*
* @param omgh handle for the request
- * @param[in,out] hr HTTP response we got
+ * @param[in,out] osr HTTP response we got
*/
static void
handle_unpaid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
- struct TALER_MERCHANT_HttpResponse *hr)
+ struct TALER_MERCHANT_OrderStatusResponse *osr)
{
- struct TALER_MERCHANT_OrderStatusResponse osr = {
- .status = TALER_MERCHANT_OSC_UNPAID
- };
struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("total_amount",
- &osr.details.unpaid.contract_amount),
+ TALER_JSON_spec_amount_any (
+ "total_amount",
+ &osr->details.ok.details.unpaid.contract_amount),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("already_paid_order_id",
- &osr.details.unpaid.already_paid_order_id),
+ GNUNET_JSON_spec_string (
+ "already_paid_order_id",
+ &osr->details.ok.details.unpaid.already_paid_order_id),
NULL),
- GNUNET_JSON_spec_string ("taler_pay_uri",
- &osr.details.unpaid.taler_pay_uri),
- GNUNET_JSON_spec_string ("summary",
- &osr.details.unpaid.summary),
- GNUNET_JSON_spec_timestamp ("creation_time",
- &osr.details.unpaid.creation_time),
+ GNUNET_JSON_spec_string (
+ "taler_pay_uri",
+ &osr->details.ok.details.unpaid.taler_pay_uri),
+ GNUNET_JSON_spec_string (
+ "summary",
+ &osr->details.ok.details.unpaid.summary),
+ GNUNET_JSON_spec_timestamp (
+ "creation_time",
+ &osr->details.ok.details.unpaid.creation_time),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
- GNUNET_JSON_parse (hr->reply,
+ GNUNET_JSON_parse (osr->hr.reply,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
- hr->http_status = 0;
- hr->ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ osr->hr.http_status = 0;
+ osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
omgh->cb (omgh->cb_cls,
- hr,
- NULL);
+ osr);
return;
}
+ osr->details.ok.status = TALER_MERCHANT_OSC_UNPAID;
omgh->cb (omgh->cb_cls,
- hr,
- &osr);
+ osr);
}
@@ -122,38 +134,34 @@ handle_unpaid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
* paid. Parse the response and call the callback.
*
* @param omgh handle for the request
- * @param[in,out] hr HTTP response we got
+ * @param[in,out] osr HTTP response we got
*/
static void
handle_claimed (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
- struct TALER_MERCHANT_HttpResponse *hr)
+ struct TALER_MERCHANT_OrderStatusResponse *osr)
{
- struct TALER_MERCHANT_OrderStatusResponse osr = {
- .status = TALER_MERCHANT_OSC_CLAIMED
- };
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("contract_terms",
- (json_t **) &osr.details.claimed.contract_terms),
+ GNUNET_JSON_spec_object_const (
+ "contract_terms",
+ &osr->details.ok.details.claimed.contract_terms),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
- GNUNET_JSON_parse (hr->reply,
+ GNUNET_JSON_parse (osr->hr.reply,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
- hr->http_status = 0;
- hr->ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ osr->hr.http_status = 0;
+ osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
omgh->cb (omgh->cb_cls,
- hr,
- NULL);
+ osr);
return;
}
+ osr->details.ok.status = TALER_MERCHANT_OSC_CLAIMED;
omgh->cb (omgh->cb_cls,
- hr,
- &osr);
- GNUNET_JSON_parse_free (spec);
+ osr);
}
@@ -163,200 +171,158 @@ handle_claimed (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
* the response and call the callback.
*
* @param omgh handle for the request
- * @param[in,out] hr HTTP response we got
+ * @param[in,out] osr HTTP response we got
*/
static void
handle_paid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
- struct TALER_MERCHANT_HttpResponse *hr)
+ struct TALER_MERCHANT_OrderStatusResponse *osr)
{
- uint32_t ec32;
uint32_t hc32;
- json_t *wire_details;
- json_t *wire_reports;
- json_t *refund_details;
- struct TALER_MERCHANT_OrderStatusResponse osr = {
- .status = TALER_MERCHANT_OSC_PAID
- };
+ const json_t *wire_details;
+ const json_t *refund_details;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_bool ("refunded",
- &osr.details.paid.refunded),
+ &osr->details.ok.details.paid.refunded),
GNUNET_JSON_spec_bool ("refund_pending",
- &osr.details.paid.refund_pending),
+ &osr->details.ok.details.paid.refund_pending),
GNUNET_JSON_spec_bool ("wired",
- &osr.details.paid.wired),
+ &osr->details.ok.details.paid.wired),
TALER_JSON_spec_amount_any ("deposit_total",
- &osr.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.paid.refund_amount),
- GNUNET_JSON_spec_json ("contract_terms",
- (json_t **) &osr.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.refund_amount),
+ GNUNET_JSON_spec_object_const (
+ "contract_terms",
+ &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),
+ /* Only available since **v14** */
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("last_payment",
+ &osr->details.ok.details.paid.last_payment),
+ NULL),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
- GNUNET_JSON_parse (hr->reply,
+ GNUNET_JSON_parse (osr->hr.reply,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
- hr->http_status = 0;
- hr->ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ osr->hr.http_status = 0;
+ osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
omgh->cb (omgh->cb_cls,
- hr,
- NULL);
+ osr);
return;
}
- if (! (json_is_array (wire_details) &&
- json_is_array (wire_reports) &&
- json_is_array (refund_details) &&
- json_is_object (osr.details.paid.contract_terms)) )
- {
- GNUNET_break_op (0);
- hr->http_status = 0;
- hr->ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- omgh->cb (omgh->cb_cls,
- hr,
- NULL);
- GNUNET_JSON_parse_free (spec);
- return;
- }
- osr.details.paid.exchange_ec = (enum TALER_ErrorCode) ec32;
- osr.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);
- hr->http_status = 0;
- hr->ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- omgh->cb (omgh->cb_cls,
- hr,
- NULL);
- 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;
+ }
+ if ( (json_array_size (refund_details) != (size_t) ref_len) ||
+ (ref_len > MAX_REFUND_DETAILS) )
+ {
+ 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++)
{
- 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))
+ 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);
- hr->http_status = 0;
- hr->ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- omgh->cb (omgh->cb_cls,
- hr,
- NULL);
- 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);
- hr->http_status = 0;
- hr->ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- omgh->cb (omgh->cb_cls,
- hr,
- NULL);
- 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.paid.wts = wts;
- osr.details.paid.wts_len = wts_len;
- osr.details.paid.wrs = wrs;
- osr.details.paid.wrs_len = wrs_len;
- osr.details.paid.refunds = ref;
- osr.details.paid.refunds_len = ref_len;
- omgh->cb (omgh->cb_cls,
- hr,
- &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);
}
@@ -376,9 +342,9 @@ handle_merchant_order_get_finished (void *cls,
struct TALER_MERCHANT_OrderMerchantGetHandle *omgh = cls;
const json_t *json = response;
const char *order_status;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_OrderStatusResponse osr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
omgh->job = NULL;
@@ -387,106 +353,106 @@ handle_merchant_order_get_finished (void *cls,
case MHD_HTTP_OK:
/* see below */
break;
+ case MHD_HTTP_ACCEPTED:
+ /* see below */
+ omgh->cb (omgh->cb_cls,
+ &osr);
+ TALER_MERCHANT_merchant_order_get_cancel (omgh);
+ 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;
+ osr.hr.ec = TALER_JSON_get_error_code (json);
+ osr.hr.hint = TALER_JSON_get_error_hint (json);
+ omgh->cb (omgh->cb_cls,
+ &osr);
+ TALER_MERCHANT_merchant_order_get_cancel (omgh);
+ return;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ osr.hr.ec = TALER_JSON_get_error_code (json);
+ osr.hr.hint = TALER_JSON_get_error_hint (json);
omgh->cb (omgh->cb_cls,
- &hr,
- NULL);
+ &osr);
TALER_MERCHANT_merchant_order_get_cancel (omgh);
return;
case MHD_HTTP_GATEWAY_TIMEOUT:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ osr.hr.ec = TALER_JSON_get_error_code (json);
+ osr.hr.hint = TALER_JSON_get_error_hint (json);
omgh->cb (omgh->cb_cls,
- &hr,
- NULL);
+ &osr);
TALER_MERCHANT_merchant_order_get_cancel (omgh);
return;
default:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ osr.hr.ec = TALER_JSON_get_error_code (json);
+ osr.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Polling payment failed with HTTP status code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) osr.hr.ec);
GNUNET_break_op (0);
omgh->cb (omgh->cb_cls,
- &hr,
- NULL);
+ &osr);
TALER_MERCHANT_merchant_order_get_cancel (omgh);
return;
}
- order_status = json_string_value (json_object_get (json, "order_status"));
+ order_status = json_string_value (json_object_get (json,
+ "order_status"));
if (NULL == order_status)
{
GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ osr.hr.http_status = 0;
+ osr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
omgh->cb (omgh->cb_cls,
- &hr,
- NULL);
+ &osr);
TALER_MERCHANT_merchant_order_get_cancel (omgh);
return;
}
- if (0 == strcmp ("paid", order_status))
+ if (0 == strcmp ("paid",
+ order_status))
{
handle_paid (omgh,
- &hr);
+ &osr);
}
- else if (0 == strcmp ("claimed", order_status))
+ else if (0 == strcmp ("claimed",
+ order_status))
{
handle_claimed (omgh,
- &hr);
+ &osr);
}
- else if (0 == strcmp ("unpaid", order_status))
+ else if (0 == strcmp ("unpaid",
+ order_status))
{
handle_unpaid (omgh,
- &hr);
+ &osr);
}
else
{
GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ osr.hr.http_status = 0;
+ osr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
omgh->cb (omgh->cb_cls,
- &hr,
- NULL);
+ &osr);
}
TALER_MERCHANT_merchant_order_get_cancel (omgh);
}
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;
@@ -497,7 +463,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",
@@ -505,7 +471,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);
@@ -529,16 +494,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 020167c4..00000000
--- a/src/lib/merchant_api_merchant_get_tip.c
+++ /dev/null
@@ -1,324 +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_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 int
-parse_pickups (const json_t *pa,
- const struct TALER_Amount *total_authorized,
- const struct TALER_Amount *total_picked_up,
- const char *reason,
- struct GNUNET_TIME_Timestamp expiration,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- 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;
- int ret = GNUNET_OK;
-
- 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);
- ret = GNUNET_SYSERR;
- break;
- }
- }
- if (GNUNET_OK == ret)
- {
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = MHD_HTTP_OK
- };
-
- tgh->cb (tgh->cb_cls,
- &hr,
- total_authorized,
- total_picked_up,
- reason,
- expiration,
- reserve_pub,
- pa_len,
- pickups);
- }
- return ret;
-}
-
-
-/**
- * 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_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .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 TALER_Amount total_authorized;
- struct TALER_Amount total_picked_up;
- const char *reason;
- struct GNUNET_TIME_Timestamp expiration;
- struct TALER_ReservePublicKeyP reserve_pub;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("total_authorized",
- &total_authorized),
- TALER_JSON_spec_amount_any ("total_picked_up",
- &total_picked_up),
- GNUNET_JSON_spec_string ("reason",
- &reason),
- GNUNET_JSON_spec_timestamp ("expiration",
- &expiration),
- GNUNET_JSON_spec_fixed_auto ("reserve_pub",
- &reserve_pub),
- 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
- {
- json_t *pickups = json_object_get (json,
- "pickups");
- if (! json_is_array (pickups))
- {
- tgh->cb (tgh->cb_cls,
- &hr,
- &total_authorized,
- &total_picked_up,
- reason,
- expiration,
- &reserve_pub,
- 0,
- NULL);
- TALER_MERCHANT_merchant_tip_get_cancel (tgh);
- return;
- }
- else if (GNUNET_OK == parse_pickups (pickups,
- &total_authorized,
- &total_picked_up,
- reason,
- expiration,
- &reserve_pub,
- tgh))
- {
- GNUNET_JSON_parse_free (spec);
- TALER_MERCHANT_merchant_tip_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;
- 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;
- 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;
- }
- tgh->cb (tgh->cb_cls,
- &hr,
- NULL,
- NULL,
- NULL,
- GNUNET_TIME_UNIT_ZERO_TS,
- NULL,
- 0,
- NULL);
- 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,
- 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 *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);
- tgh->url = TALER_url_join (backend_url,
- arg_str,
- "pickups", pickups ? "yes" : NULL,
- 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 7a6c390a..420cd549 100644
--- a/src/lib/merchant_api_patch_instance.c
+++ b/src/lib/merchant_api_patch_instance.c
@@ -29,7 +29,9 @@
#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_kyclogic_lib.h>
#include <taler/taler_curl_lib.h>
@@ -156,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 565a66f3..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
@@ -28,6 +28,7 @@
#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>
@@ -149,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;
@@ -235,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_product.c b/src/lib/merchant_api_patch_product.c
index 7f931655..48db078f 100644
--- a/src/lib/merchant_api_patch_product.c
+++ b/src/lib/merchant_api_patch_product.c
@@ -28,6 +28,7 @@
#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>
diff --git a/src/lib/merchant_api_patch_template.c b/src/lib/merchant_api_patch_template.c
new file mode 100644
index 00000000..7dfebf9c
--- /dev/null
+++ b/src/lib/merchant_api_patch_template.c
@@ -0,0 +1,249 @@
+/*
+ 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_template.c
+ * @brief Implementation of the PATCH /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 "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 /templates/$ID operation.
+ */
+struct TALER_MERCHANT_TemplatePatchHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_TemplatePatchCallback 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 /templates/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_TemplatePatchHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_patch_template_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_TemplatePatchHandle *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 /templates/$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_template_patch_cancel (tph);
+}
+
+
+struct TALER_MERCHANT_TemplatePatchHandle *
+TALER_MERCHANT_template_patch (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *template_id,
+ const char *template_description,
+ const char *otp_id,
+ json_t *template_contract,
+ TALER_MERCHANT_TemplatePatchCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_TemplatePatchHandle *tph;
+ json_t *req_obj;
+
+ req_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("template_description",
+ template_description),
+ GNUNET_JSON_pack_allow_null (
+ 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);
+ tph->ctx = ctx;
+ tph->cb = cb;
+ tph->cb_cls = cb_cls;
+ {
+ char *path;
+
+ GNUNET_asprintf (&path,
+ "private/templates/%s",
+ template_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_template_finished,
+ tph);
+ }
+ return tph;
+}
+
+
+void
+TALER_MERCHANT_template_patch_cancel (
+ struct TALER_MERCHANT_TemplatePatchHandle *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_template.c */
diff --git a/src/lib/merchant_api_patch_webhook.c b/src/lib/merchant_api_patch_webhook.c
new file mode 100644
index 00000000..6d8b6340
--- /dev/null
+++ b/src/lib/merchant_api_patch_webhook.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_webhook.c
+ * @brief Implementation of the PATCH /webhooks/$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_curl_defaults.h"
+#include "merchant_api_common.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_curl_lib.h>
+
+
+/**
+ * Handle for a PATCH /webhooks/$ID operation.
+ */
+struct TALER_MERCHANT_WebhookPatchHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_WebhookPatchCallback 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 /webhooks/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_WebhookPatchHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_patch_webhook_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_WebhookPatchHandle *wph = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_HttpResponse hr = {
+ .http_status = (unsigned int) response_code,
+ .reply = json
+ };
+
+ wph->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "PATCH /webhooks/$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;
+ }
+ wph->cb (wph->cb_cls,
+ &hr);
+ TALER_MERCHANT_webhook_patch_cancel (wph);
+}
+
+
+struct TALER_MERCHANT_WebhookPatchHandle *
+TALER_MERCHANT_webhook_patch (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *webhook_id,
+ const char *event_type,
+ const char *url,
+ const char *http_method,
+ const char *header_template,
+ const char *body_template,
+ TALER_MERCHANT_WebhookPatchCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_WebhookPatchHandle *wph;
+ json_t *req_obj;
+
+ req_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("event_type",
+ event_type),
+ GNUNET_JSON_pack_string ("url",
+ url),
+ GNUNET_JSON_pack_string ("http_method",
+ http_method),
+ GNUNET_JSON_pack_string ("header_template",
+ header_template),
+ GNUNET_JSON_pack_string ("body_template",
+ body_template));
+ wph = GNUNET_new (struct TALER_MERCHANT_WebhookPatchHandle);
+ wph->ctx = ctx;
+ wph->cb = cb;
+ wph->cb_cls = cb_cls;
+ {
+ char *path;
+
+ GNUNET_asprintf (&path,
+ "private/webhooks/%s",
+ webhook_id);
+ wph->url = TALER_url_join (backend_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == wph->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ json_decref (req_obj);
+ GNUNET_free (wph);
+ return NULL;
+ }
+ {
+ CURL *eh;
+
+ eh = TALER_MERCHANT_curl_easy_get_ (wph->url);
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&wph->post_ctx,
+ eh,
+ req_obj))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ json_decref (req_obj);
+ GNUNET_free (wph);
+ return NULL;
+ }
+ json_decref (req_obj);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_CUSTOMREQUEST,
+ MHD_HTTP_METHOD_PATCH));
+ wph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ wph->post_ctx.headers,
+ &handle_patch_webhook_finished,
+ wph);
+ }
+ return wph;
+}
+
+
+void
+TALER_MERCHANT_webhook_patch_cancel (
+ struct TALER_MERCHANT_WebhookPatchHandle *wph)
+{
+ if (NULL != wph->job)
+ {
+ GNUNET_CURL_job_cancel (wph->job);
+ wph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&wph->post_ctx);
+ GNUNET_free (wph->url);
+ GNUNET_free (wph);
+}
+
+
+/* end of merchant_api_patch_webhook.c */
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 89b7d2fb..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
@@ -29,8 +29,10 @@
#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>
+#include <taler/taler_kyclogic_lib.h>
/**
@@ -162,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,
@@ -177,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,
@@ -209,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 406a6015..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
@@ -31,6 +31,7 @@
#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_exchange_service.h>
@@ -38,6 +39,12 @@
/**
+ * Maximum number of refunds we return.
+ */
+#define MAX_REFUNDS 1024
+
+
+/**
* @brief An abort Handle
*/
struct TALER_MERCHANT_OrderAbortHandle
@@ -101,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 ()
};
@@ -123,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)];
@@ -149,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)
@@ -168,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;
}
@@ -183,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;
}
@@ -222,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;
@@ -234,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
@@ -275,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;
@@ -295,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;
@@ -388,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 0a70c1b0..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,36 +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_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;
}
@@ -128,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 2c398f90..785d956f 100644
--- a/src/lib/merchant_api_post_order_paid.c
+++ b/src/lib/merchant_api_post_order_paid.c
@@ -30,6 +30,7 @@
#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_exchange_service.h>
@@ -89,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;
@@ -101,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);
}
@@ -167,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)
@@ -179,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 c246a1d4..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
@@ -30,6 +30,7 @@
#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>
@@ -91,6 +92,23 @@ struct TALER_MERCHANT_OrderPayHandle
struct TALER_MerchantPublicKeyP merchant_pub;
/**
+ * JSON with the full reply, used during async
+ * processing.
+ */
+ json_t *full_reply;
+
+ /**
+ * Pointer into @e coins array for the coin that
+ * created a conflict (that we are checking).
+ */
+ const struct TALER_MERCHANT_PaidCoin *error_pc;
+
+ /**
+ * Coin history that proves a conflict.
+ */
+ json_t *error_history;
+
+ /**
* Number of @e coins we are paying with.
*/
unsigned int num_coins;
@@ -105,132 +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 cryptograophic proof that the
- * coin was actually already spent!
- *
- * @param pc handle of the original coin we paid with
- * @param json cryptograophic proof of coin's transaction
- * history as was returned by the exchange/merchant
- * @return #GNUNET_OK if proof checks out
- */
-static int
-check_coin_history (const struct TALER_MERCHANT_PaidCoin *pc,
- json_t *json)
-{
- struct TALER_Amount spent;
- struct TALER_Amount spent_plus_contrib;
- struct TALER_DenominationHashP h_denom_pub;
- struct TALER_DenominationHashP h_denom_pub_pc;
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_verify_coin_history (NULL, /* do not verify fees */
- pc->amount_with_fee.currency,
- &pc->coin_pub,
- json,
- &h_denom_pub,
- &spent))
- {
- /* Exchange's history fails to verify */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (0 >
- TALER_amount_add (&spent_plus_contrib,
- &spent,
- &pc->amount_with_fee))
- {
- /* We got an integer overflow? Bad application! */
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- TALER_denom_pub_hash (&pc->denom_pub,
- &h_denom_pub_pc);
- if ( (-1 != TALER_amount_cmp (&pc->denom_value,
- &spent_plus_contrib)) &&
- (0 != GNUNET_memcmp (&h_denom_pub,
- &h_denom_pub_pc)) )
- {
- /* 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 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 oph handle of the original pay operation
- * @param json cryptograophic proof returned by the
- * exchange/merchant
- * @return #GNUNET_OK if proof checks out
- */
-static enum GNUNET_GenericReturnValue
-check_conflict (struct TALER_MERCHANT_OrderPayHandle *oph,
- const json_t *json)
-{
- json_t *history;
- json_t *ereply;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("exchange_reply", &ereply),
- GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub),
- GNUNET_JSON_spec_end ()
- };
- struct GNUNET_JSON_Specification hspec[] = {
- GNUNET_JSON_spec_json ("history", &history),
- GNUNET_JSON_spec_end ()
- };
-
- 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 == memcmp (&oph->coins[i].coin_pub,
- &coin_pub,
- sizeof (struct TALER_CoinSpendPublicKeyP)))
- {
- int ret;
-
- ret = check_coin_history (&oph->coins[i],
- history);
- GNUNET_JSON_parse_free (hspec);
- return ret;
- }
- }
- 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.
*
@@ -245,12 +137,10 @@ handle_pay_finished (void *cls,
{
struct TALER_MERCHANT_OrderPayHandle *oph = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_PayResponse pr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
- struct TALER_MerchantSignatureP *merchant_sigp = NULL;
- struct TALER_MerchantSignatureP merchant_sig;
oph->job = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -259,15 +149,21 @@ handle_pay_finished (void *cls,
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (oph->am_wallet)
{
/* Here we can (and should) verify the merchant's signature */
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("sig",
- &merchant_sig),
+ GNUNET_JSON_spec_fixed_auto (
+ "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 ()
};
@@ -277,94 +173,90 @@ handle_pay_finished (void *cls,
NULL, NULL))
{
GNUNET_break_op (0);
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.http_status = 0;
- hr.hint = "sig field missing in response";
+ pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ pr.hr.http_status = 0;
+ pr.hr.hint = "sig field missing in response";
break;
}
if (GNUNET_OK !=
TALER_merchant_pay_verify (&oph->h_contract_terms,
&oph->merchant_pub,
- &merchant_sig))
+ &pr.details.ok.merchant_sig))
{
GNUNET_break_op (0);
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.http_status = 0;
- hr.hint = "signature invalid";
+ pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ pr.hr.http_status = 0;
+ pr.hr.hint = "signature invalid";
}
- merchant_sigp = &merchant_sig;
}
break;
/* Tolerating Not Acceptable because sometimes
* - especially in tests - we might want to POST
* coins one at a time. */
case MHD_HTTP_NOT_ACCEPTABLE:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ pr.hr.ec = TALER_JSON_get_error_code (json);
+ pr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_BAD_REQUEST:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ pr.hr.ec = TALER_JSON_get_error_code (json);
+ pr.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_PAYMENT_REQUIRED:
/* was originally paid, but then refunded */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ pr.hr.ec = TALER_JSON_get_error_code (json);
+ pr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ pr.hr.ec = TALER_JSON_get_error_code (json);
+ pr.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);
+ pr.hr.ec = TALER_JSON_get_error_code (json);
+ pr.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_PRECONDITION_FAILED:
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &hr);
- /* Nothing really to verify, the merchant is blaming us for failing to
- satisfy some constraint (likely it does not like our exchange because
- of some disagreement on the PKI). 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);
+ pr.hr.ec = TALER_JSON_get_error_code (json);
+ pr.hr.hint = TALER_JSON_get_error_hint (json);
/* The merchant couldn't generate a timely response, likely because
it itself waited too long on the exchange.
Pass on to application. */
break;
case MHD_HTTP_CONFLICT:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- if (GNUNET_OK != check_conflict (oph,
- json))
- {
- GNUNET_break_op (0);
- response_code = 0;
- }
+ TALER_MERCHANT_parse_error_details_ (json,
+ MHD_HTTP_CONFLICT,
+ &pr.hr);
break;
case MHD_HTTP_GONE:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &pr.hr);
/* The merchant says we are too late, the offer has expired or some
denomination key of a coin involved has expired.
Might be a disagreement in timestamps? Still, pass on to application. */
break;
+ case MHD_HTTP_PRECONDITION_FAILED:
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &pr.hr);
+ /* Nothing really to verify, the merchant is blaming us for failing to
+ satisfy some constraint (likely it does not like our exchange because
+ of some disagreement on the PKI). We should pass the JSON reply to the
+ application */
+ break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &pr.hr);
/* Server had an internal issue; we should retry,
but this API leaves this to the application */
break;
@@ -373,37 +265,36 @@ handle_pay_finished (void *cls,
We should pass the JSON reply to the application */
TALER_MERCHANT_parse_error_details_ (json,
response_code,
- &hr);
+ &pr.hr);
break;
case MHD_HTTP_SERVICE_UNAVAILABLE:
TALER_MERCHANT_parse_error_details_ (json,
response_code,
- &hr);
+ &pr.hr);
/* Exchange couldn't respond properly; the retry is
left to the application */
break;
case MHD_HTTP_GATEWAY_TIMEOUT:
TALER_MERCHANT_parse_error_details_ (json,
response_code,
- &hr);
+ &pr.hr);
/* Exchange couldn't respond in a timely fashion;
the retry is left to the application */
break;
default:
TALER_MERCHANT_parse_error_details_ (json,
response_code,
- &hr);
+ &pr.hr);
/* unexpected response code */
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) pr.hr.ec);
GNUNET_break_op (0);
break;
}
oph->pay_cb (oph->pay_cb_cls,
- &hr,
- merchant_sigp);
+ &pr);
TALER_MERCHANT_order_pay_cancel (oph);
}
@@ -414,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)
{
@@ -432,6 +324,7 @@ TALER_MERCHANT_order_pay_frontend (
return NULL;
}
j_coins = json_array ();
+ GNUNET_assert (NULL != j_coins);
for (unsigned int i = 0; i<num_coins; i++)
{
json_t *j_coin;
@@ -503,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)));
@@ -532,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 !=
@@ -565,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,
@@ -575,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))
@@ -586,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];
@@ -613,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,
@@ -637,6 +541,7 @@ TALER_MERCHANT_order_pay (
merchant_url,
order_id,
session_id,
+ wallet_data,
num_coins,
pc,
pay_cb,
@@ -661,6 +566,8 @@ TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *oph)
oph->job = NULL;
}
TALER_curl_easy_post_finished (&oph->post_ctx);
+ json_decref (oph->error_history);
+ json_decref (oph->full_reply);
GNUNET_free (oph->coins);
GNUNET_free (oph->url);
GNUNET_free (oph);
diff --git a/src/lib/merchant_api_post_order_refund.c b/src/lib/merchant_api_post_order_refund.c
index c59bd762..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
@@ -28,6 +28,7 @@
#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>
@@ -84,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 ()
};
@@ -117,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 c4ccea36..56881133 100644
--- a/src/lib/merchant_api_post_orders.c
+++ b/src/lib/merchant_api_post_orders.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
@@ -30,6 +30,7 @@
#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>
@@ -88,132 +89,12 @@ handle_post_order_finished (void *cls,
{
struct TALER_MERCHANT_PostOrdersHandle *po = cls;
const json_t *json = response;
- struct TALER_MERCHANT_PostOrdersReply por = {
- .hr.http_status = (unsigned int) response_code,
- .hr.reply = json
- };
- struct TALER_ClaimTokenP token;
po->job = NULL;
- switch (response_code)
- {
- case 0:
- por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- bool no_token;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("order_id",
- &por.details.ok.order_id),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("token",
- &token),
- &no_token),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- por.hr.http_status = 0;
- por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- }
- else
- {
- if (! no_token)
- por.details.ok.token = &token;
- }
- }
- break;
- case MHD_HTTP_BAD_REQUEST:
- json_dumpf (json,
- stderr,
- JSON_INDENT (2));
- por.hr.ec = TALER_JSON_get_error_code (json);
- por.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:
- por.hr.ec = TALER_JSON_get_error_code (json);
- por.hr.hint = TALER_JSON_get_error_hint (json);
- /* Nothing really to verify, merchant says we need to authenticate. */
- break;
- case MHD_HTTP_FORBIDDEN:
- /* 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
- reply to the application */
- por.hr.ec = TALER_JSON_get_error_code (json);
- por.hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- por.hr.ec = TALER_JSON_get_error_code (json);
- por.hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_CONFLICT:
- por.hr.ec = TALER_JSON_get_error_code (json);
- por.hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_GONE:
- /* The quantity of some product requested was not available. */
- {
-
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string (
- "product_id",
- &por.details.gone.product_id),
- GNUNET_JSON_spec_uint64 (
- "requested_quantity",
- &por.details.gone.requested_quantity),
- GNUNET_JSON_spec_uint64 (
- "available_quantity",
- &por.details.gone.available_quantity),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp (
- "restock_expected",
- &por.details.gone.restock_expected),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- por.hr.http_status = 0;
- por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- }
- break;
- }
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry,
- but this API leaves this to the application */
- por.hr.ec = TALER_JSON_get_error_code (json);
- por.hr.hint = TALER_JSON_get_error_hint (json);
- break;
- default:
- /* unexpected response code */
- por.hr.ec = TALER_JSON_get_error_code (json);
- por.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) por.hr.ec);
- GNUNET_break_op (0);
- break;
- }
- po->cb (po->cb_cls,
- &por);
+ TALER_MERCHANT_handle_order_creation_response_ (po->cb,
+ po->cb_cls,
+ response_code,
+ json);
TALER_MERCHANT_orders_post_cancel (po);
}
@@ -226,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,
@@ -234,7 +117,7 @@ TALER_MERCHANT_orders_post (struct GNUNET_CURL_Context *ctx,
0,
NULL,
0,
- NULL,
+ no_uuids,
true,
cb,
cb_cls);
@@ -251,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)
@@ -271,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 43799032..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
@@ -29,6 +29,7 @@
#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>
@@ -158,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,
@@ -171,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)
{
@@ -182,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)));
@@ -234,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 7942e3e0..00000000
--- a/src/lib/merchant_api_post_reserves.c
+++ /dev/null
@@ -1,246 +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 <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
new file mode 100644
index 00000000..3ab4320c
--- /dev/null
+++ b/src/lib/merchant_api_post_templates.c
@@ -0,0 +1,279 @@
+/*
+ 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_templates.c
+ * @brief Implementation of the POST /templates 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_curl_defaults.h"
+#include "merchant_api_common.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_curl_lib.h>
+
+
+/**
+ * Handle for a POST /templates/$ID operation.
+ */
+struct TALER_MERCHANT_TemplatesPostHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_TemplatesPostCallback 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 /templates request.
+ *
+ * @param cls the `struct TALER_MERCHANT_TemplatesPostHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_post_templates_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_TemplatesPostHandle *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 /templates 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_templates_post_cancel (tph);
+}
+
+
+static bool
+test_template_contract_valid (const json_t *template_contract)
+{
+ const char *summary;
+ struct TALER_Amount amount = { .value = 0};
+ uint32_t minimum_age = 0;
+ struct GNUNET_TIME_Relative pay_duration = { 0 };
+ 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_any ("amount",
+ &amount),
+ NULL),
+ GNUNET_JSON_spec_uint32 ("minimum_age",
+ &minimum_age),
+ GNUNET_JSON_spec_relative_time ("pay_duration",
+ &pay_duration),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *ename;
+ unsigned int eline;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (template_contract,
+ spec,
+ &ename,
+ &eline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid template_contract for field %s\n",
+ ename);
+ return false;
+ }
+ return true;
+}
+
+
+struct TALER_MERCHANT_TemplatesPostHandle *
+TALER_MERCHANT_templates_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *template_id,
+ const char *template_description,
+ const char *otp_id,
+ const json_t *template_contract,
+ TALER_MERCHANT_TemplatesPostCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_TemplatesPostHandle *tph;
+ json_t *req_obj;
+
+ if (! test_template_contract_valid (template_contract))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ req_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("template_id",
+ template_id),
+ GNUNET_JSON_pack_string ("template_description",
+ template_description),
+ GNUNET_JSON_pack_allow_null (
+ 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);
+ tph->ctx = ctx;
+ tph->cb = cb;
+ tph->cb_cls = cb_cls;
+ tph->url = TALER_url_join (backend_url,
+ "private/templates",
+ 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_templates_finished,
+ tph);
+ GNUNET_assert (NULL != tph->job);
+ }
+ return tph;
+}
+
+
+void
+TALER_MERCHANT_templates_post_cancel (
+ struct TALER_MERCHANT_TemplatesPostHandle *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_templates.c */
diff --git a/src/lib/merchant_api_post_tokenfamilies.c b/src/lib/merchant_api_post_tokenfamilies.c
new file mode 100644
index 00000000..0c5e18c2
--- /dev/null
+++ b/src/lib/merchant_api_post_tokenfamilies.c
@@ -0,0 +1,246 @@
+/*
+ This file is part of TALER
+ 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
+ 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_tokenfamilies.c
+ * @brief Implementation of the POST /tokenfamilies request
+ * of the merchant's HTTP API
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_time_lib.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 /tokenfamilies operation.
+ */
+struct TALER_MERCHANT_TokenFamiliesPostHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_TokenFamiliesPostCallback 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 /tokenfamilies request.
+ *
+ * @param cls the `struct TALER_MERCHANT_TokenFamiliesPostHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_post_token_families_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_TokenFamiliesPostHandle *handle = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_HttpResponse hr = {
+ .http_status = (unsigned int) response_code,
+ .reply = json
+ };
+
+ handle->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "POST /tokenfamilies 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;
+ }
+ handle->cb (handle->cb_cls,
+ &hr);
+ TALER_MERCHANT_token_families_post_cancel (handle);
+}
+
+struct TALER_MERCHANT_TokenFamiliesPostHandle *
+TALER_MERCHANT_token_families_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *slug,
+ const char *name,
+ const char *description,
+ const json_t *description_i18n,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct GNUNET_TIME_Timestamp valid_before,
+ struct GNUNET_TIME_Relative duration,
+ const char *kind,
+ TALER_MERCHANT_TokenFamiliesPostCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_TokenFamiliesPostHandle *handle;
+ json_t *req_obj;
+
+ req_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("slug",
+ slug),
+ GNUNET_JSON_pack_string ("name",
+ name),
+ GNUNET_JSON_pack_string ("description",
+ description),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("description_i18n",
+ (json_t *) description_i18n)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("valid_after",
+ valid_after)),
+ GNUNET_JSON_pack_timestamp ("valid_before",
+ valid_before),
+ GNUNET_JSON_pack_time_rel ("duration",
+ duration),
+ GNUNET_JSON_pack_string ("kind",
+ kind));
+ handle = GNUNET_new (struct TALER_MERCHANT_TokenFamiliesPostHandle);
+ handle->ctx = ctx;
+ handle->cb = cb;
+ handle->cb_cls = cb_cls;
+ handle->url = TALER_url_join (backend_url,
+ "private/tokenfamilies",
+ NULL);
+ if (NULL == handle->url)
+ {
+
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ json_decref (req_obj);
+ GNUNET_free (handle);
+ return NULL;
+ }
+ {
+ CURL *eh;
+
+ eh = TALER_MERCHANT_curl_easy_get_ (handle->url);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_curl_easy_post (&handle->post_ctx,
+ eh,
+ req_obj));
+ json_decref (req_obj);
+ handle->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ handle->post_ctx.headers,
+ &handle_post_token_families_finished,
+ handle);
+ GNUNET_assert (NULL != handle->job);
+ }
+ return handle;
+}
+
+
+void
+TALER_MERCHANT_token_families_post_cancel (
+ struct TALER_MERCHANT_TokenFamiliesPostHandle *pph)
+{
+ if (NULL != pph->job)
+ {
+ GNUNET_CURL_job_cancel (pph->job);
+ pph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&pph->post_ctx);
+ GNUNET_free (pph->url);
+ GNUNET_free (pph);
+}
diff --git a/src/lib/merchant_api_post_transfers.c b/src/lib/merchant_api_post_transfers.c
index 01144833..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
@@ -27,6 +27,7 @@
#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>
@@ -84,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:
@@ -201,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);
}
}
@@ -246,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
new file mode 100644
index 00000000..f09c34cb
--- /dev/null
+++ b/src/lib/merchant_api_post_using_templates.c
@@ -0,0 +1,177 @@
+/*
+ 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 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_using_templates.c
+ * @brief Implementation of the POST /using_templates 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 POST /templates/$ID operation.
+ */
+struct TALER_MERCHANT_UsingTemplatesPostHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_PostOrdersCallback 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 /using-templates request.
+ *
+ * @param cls the `struct TALER_MERCHANT_UsingTemplatesPostHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_post_using_templates_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_UsingTemplatesPostHandle *utph = cls;
+ const json_t *json = response;
+
+ utph->job = NULL;
+ TALER_MERCHANT_handle_order_creation_response_ (utph->cb,
+ utph->cb_cls,
+ response_code,
+ json);
+ TALER_MERCHANT_using_templates_post_cancel (utph);
+}
+
+
+struct TALER_MERCHANT_UsingTemplatesPostHandle *
+TALER_MERCHANT_using_templates_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *template_id,
+ const char *summary,
+ const struct TALER_Amount *amount,
+ TALER_MERCHANT_PostOrdersCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_UsingTemplatesPostHandle *utph;
+ json_t *req_obj;
+
+ req_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("summary",
+ summary)),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("amount",
+ amount)));
+ utph = GNUNET_new (struct TALER_MERCHANT_UsingTemplatesPostHandle);
+ utph->ctx = ctx;
+ utph->cb = cb;
+ utph->cb_cls = cb_cls;
+ {
+ char *path;
+
+ GNUNET_asprintf (&path,
+ "templates/%s",
+ template_id);
+ utph->url = TALER_url_join (backend_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == utph->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ json_decref (req_obj);
+ GNUNET_free (utph);
+ return NULL;
+ }
+ {
+ CURL *eh;
+
+ eh = TALER_MERCHANT_curl_easy_get_ (utph->url);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_curl_easy_post (&utph->post_ctx,
+ eh,
+ req_obj));
+ json_decref (req_obj);
+ utph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ utph->post_ctx.headers,
+ &handle_post_using_templates_finished,
+ utph);
+ GNUNET_assert (NULL != utph->job);
+ }
+ return utph;
+}
+
+
+void
+TALER_MERCHANT_using_templates_post_cancel (
+ struct TALER_MERCHANT_UsingTemplatesPostHandle *utph)
+{
+ if (NULL != utph->job)
+ {
+ GNUNET_CURL_job_cancel (utph->job);
+ utph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&utph->post_ctx);
+ GNUNET_free (utph->url);
+ GNUNET_free (utph);
+}
+
+
+/* end of merchant_api_post_using_templates.c */
diff --git a/src/lib/merchant_api_post_webhooks.c b/src/lib/merchant_api_post_webhooks.c
new file mode 100644
index 00000000..a23ce2c3
--- /dev/null
+++ b/src/lib/merchant_api_post_webhooks.c
@@ -0,0 +1,240 @@
+/*
+ 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_webhooks.c
+ * @brief Implementation of the POST /webhooks 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_curl_defaults.h"
+#include "merchant_api_common.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_curl_lib.h>
+
+
+/**
+ * Handle for a POST /webhooks/$ID operation.
+ */
+struct TALER_MERCHANT_WebhooksPostHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_WebhooksPostCallback 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 /webhooks request.
+ *
+ * @param cls the `struct TALER_MERCHANT_WebhooksPostHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_post_webhooks_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_WebhooksPostHandle *wph = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_HttpResponse hr = {
+ .http_status = (unsigned int) response_code,
+ .reply = json
+ };
+
+ wph->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "POST /webhooks 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;
+ }
+ wph->cb (wph->cb_cls,
+ &hr);
+ TALER_MERCHANT_webhooks_post_cancel (wph);
+}
+
+
+struct TALER_MERCHANT_WebhooksPostHandle *
+TALER_MERCHANT_webhooks_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *webhook_id,
+ const char *event_type,
+ const char *url,
+ const char *http_method,
+ const char *header_template,
+ const char *body_template,
+ TALER_MERCHANT_WebhooksPostCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_WebhooksPostHandle *wph;
+ json_t *req_obj;
+
+ req_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("webhook_id",
+ webhook_id),
+ GNUNET_JSON_pack_string ("event_type",
+ event_type),
+ GNUNET_JSON_pack_string ("url",
+ url),
+ GNUNET_JSON_pack_string ("http_method",
+ http_method),
+ GNUNET_JSON_pack_string ("header_template",
+ header_template),
+ GNUNET_JSON_pack_string ("body_template",
+ body_template));
+ wph = GNUNET_new (struct TALER_MERCHANT_WebhooksPostHandle);
+ wph->ctx = ctx;
+ wph->cb = cb;
+ wph->cb_cls = cb_cls;
+ wph->url = TALER_url_join (backend_url,
+ "private/webhooks",
+ NULL);
+ if (NULL == wph->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ json_decref (req_obj);
+ GNUNET_free (wph);
+ return NULL;
+ }
+ {
+ CURL *eh;
+
+ eh = TALER_MERCHANT_curl_easy_get_ (wph->url);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_curl_easy_post (&wph->post_ctx,
+ eh,
+ req_obj));
+ json_decref (req_obj);
+ wph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ wph->post_ctx.headers,
+ &handle_post_webhooks_finished,
+ wph);
+ GNUNET_assert (NULL != wph->job);
+ }
+ return wph;
+}
+
+
+void
+TALER_MERCHANT_webhooks_post_cancel (
+ struct TALER_MERCHANT_WebhooksPostHandle *wph)
+{
+ if (NULL != wph->job)
+ {
+ GNUNET_CURL_job_cancel (wph->job);
+ wph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&wph->post_ctx);
+ GNUNET_free (wph->url);
+ GNUNET_free (wph);
+}
+
+
+/* end of merchant_api_post_webhooks.c */
diff --git a/src/lib/merchant_api_tip_authorize.c b/src/lib/merchant_api_tip_authorize.c
deleted file mode 100644
index f8990b4c..00000000
--- a/src/lib/merchant_api_tip_authorize.c
+++ /dev/null
@@ -1,375 +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 <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 cd1a0c83..00000000
--- a/src/lib/merchant_api_tip_pickup2.c
+++ /dev/null
@@ -1,350 +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 <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);
- 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 4037ea57..763b2c83 100644
--- a/src/lib/merchant_api_wallet_get_order.c
+++ b/src/lib/merchant_api_wallet_get_order.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018, 2020 Taler Systems SA
+ Copyright (C) 2018, 2020, 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
@@ -29,6 +29,7 @@
#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>
@@ -79,19 +80,13 @@ cb_failure (struct TALER_MERCHANT_OrderWalletGetHandle *owgh,
enum TALER_ErrorCode ec,
const json_t *reply)
{
- struct TALER_MERCHANT_HttpResponse hr = {
- .ec = ec,
- .reply = reply
+ struct TALER_MERCHANT_OrderWalletGetResponse owgr = {
+ .hr.ec = ec,
+ .hr.reply = reply
};
owgh->cb (owgh->cb_cls,
- &hr,
- GNUNET_SYSERR,
- GNUNET_SYSERR,
- GNUNET_SYSERR,
- NULL,
- NULL,
- NULL);
+ &owgr);
}
@@ -115,22 +110,19 @@ handle_wallet_get_order_finished (void *cls,
{
case MHD_HTTP_OK:
{
- struct TALER_Amount refund_amount;
- bool refunded;
- bool refund_pending;
+ struct TALER_MERCHANT_OrderWalletGetResponse owgr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_bool ("refunded",
- &refunded),
+ &owgr.details.ok.refunded),
GNUNET_JSON_spec_bool ("refund_pending",
- &refund_pending),
+ &owgr.details.ok.refund_pending),
TALER_JSON_spec_amount_any ("refund_amount",
- &refund_amount),
+ &owgr.details.ok.refund_amount),
GNUNET_JSON_spec_end ()
};
- struct TALER_MERCHANT_HttpResponse hr = {
- .reply = json,
- .http_status = MHD_HTTP_OK
- };
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
@@ -144,70 +136,54 @@ handle_wallet_get_order_finished (void *cls,
TALER_MERCHANT_wallet_order_get_cancel (owgh);
return;
}
-
owgh->cb (owgh->cb_cls,
- &hr,
- GNUNET_YES,
- refunded ? GNUNET_YES : GNUNET_NO,
- refund_pending ? GNUNET_YES : GNUNET_NO,
- refunded ? &refund_amount : NULL,
- NULL, /* paid! */
- NULL);/* paid! */
+ &owgr);
GNUNET_JSON_parse_free (spec);
break;
}
case MHD_HTTP_PAYMENT_REQUIRED:
{
+ struct TALER_MERCHANT_OrderWalletGetResponse owgr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_PAYMENT_REQUIRED
+ };
+
/* Status is: unpaid */
- const char *taler_pay_uri = json_string_value (json_object_get (json,
- "taler_pay_uri"));
- const char *already_paid = json_string_value (json_object_get (json,
- "already_paid_order_id"));
- if (NULL == taler_pay_uri)
+ owgr.details.payment_required.taler_pay_uri
+ = json_string_value (json_object_get (json,
+ "taler_pay_uri"));
+ owgr.details.payment_required.already_paid_order_id
+ = json_string_value (json_object_get (json,
+ "already_paid_order_id"));
+ if (NULL == owgr.details.payment_required.taler_pay_uri)
{
GNUNET_break_op (0);
cb_failure (owgh,
TALER_EC_GENERIC_REPLY_MALFORMED,
json);
+ break;
}
- else
- {
- struct TALER_MERCHANT_HttpResponse hr = {
- .reply = json,
- .http_status = MHD_HTTP_OK
- };
-
- owgh->cb (owgh->cb_cls,
- &hr,
- GNUNET_NO,
- GNUNET_NO,
- GNUNET_NO,
- NULL,
- taler_pay_uri,
- already_paid);
- }
+ owgh->cb (owgh->cb_cls,
+ &owgr);
break;
}
default:
{
- struct TALER_MERCHANT_HttpResponse hr;
+ struct TALER_MERCHANT_OrderWalletGetResponse owgr = {
+ .hr.reply = json,
+ .hr.http_status = response_code
+ };
TALER_MERCHANT_parse_error_details_ (response,
response_code,
- &hr);
+ &owgr.hr);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Checking order status failed with HTTP status code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) owgr.hr.ec);
GNUNET_break_op (0);
owgh->cb (owgh->cb_cls,
- &hr,
- GNUNET_SYSERR,
- GNUNET_SYSERR,
- GNUNET_SYSERR,
- NULL,
- NULL,
- NULL);
+ &owgr);
break;
}
}
@@ -229,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);
@@ -238,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;
@@ -255,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",
@@ -295,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 07a46ab7..00000000
--- a/src/lib/merchant_api_wallet_get_tip.c
+++ /dev/null
@@ -1,226 +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 <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 1bc28e2d..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
@@ -26,11 +26,16 @@
#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>
#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.
@@ -70,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`
@@ -110,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 ()
};
@@ -150,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,
@@ -182,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 ()
};
@@ -195,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..bcb856e5
--- /dev/null
+++ b/src/merchant-tools/taler-merchant-passwd.c
@@ -0,0 +1,197 @@
+/*
+ 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 (0 != strncmp (pw,
+ RFC_8959_PREFIX,
+ strlen (RFC_8959_PREFIX)))
+ {
+ fprintf (stderr,
+ "Invalid password specified, does not begin with `%s'\n",
+ RFC_8959_PREFIX);
+ 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 cb43eaf5..46888171 100644
--- a/src/merchant-tools/taler-merchant-setup-reserve.c
+++ b/src/merchant-tools/taler-merchant-setup-reserve.c
@@ -24,6 +24,10 @@
#include <gnunet/gnunet_util_lib.h>
#include "taler_merchant_service.h"
+/**
+ * How often do we try before giving up?
+ */
+#define MAX_TRIES 30
/**
* Return value from main().
@@ -97,6 +101,17 @@ static char *apikey;
static char *keypass;
/**
+ * How often have we tried?
+ */
+static unsigned int tries;
+
+/**
+ * Task to do the main work.
+ */
+static struct GNUNET_SCHEDULER_Task *task;
+
+
+/**
* Shutdown task (invoked when the process is being terminated)
*
* @param cls NULL
@@ -104,6 +119,11 @@ static char *keypass;
static void
do_shutdown (void *cls)
{
+ if (NULL != task)
+ {
+ GNUNET_SCHEDULER_cancel (task);
+ task = NULL;
+ }
if (NULL != ctx)
{
GNUNET_CURL_fini (ctx);
@@ -123,50 +143,120 @@ do_shutdown (void *cls)
/**
+ * Function that makes the call to setup the reserve.
+ *
+ * @param cls NULL
+ */
+static void
+do_request (void *cls);
+
+
+/**
* 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
+ * @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",
+ prr->hr.http_status,
+ (int) prr->hr.ec,
+ prr->hr.hint);
+ global_ret = 1;
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ case MHD_HTTP_BAD_GATEWAY:
+ tries++;
+ if (tries < MAX_TRIES)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Merchant failed, will try again.\n");
+ task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
+ &do_request,
+ NULL);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Merchant failed too often (%u/%d), giving up\n",
+ 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;
}
@@ -219,10 +309,19 @@ run (void *cls,
}
GNUNET_free (auth_header);
}
-
/* setup termination logic */
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
NULL);
+ task = GNUNET_SCHEDULER_add_now (&do_request,
+ NULL);
+}
+
+
+static void
+do_request (void *cls)
+{
+ (void) cls;
+ task = NULL;
/* run actual (async) operation */
prh = TALER_MERCHANT_reserves_post (ctx,
merchant_base_url,
diff --git a/src/mustach/.gitignore b/src/mustach/.gitignore
deleted file mode 100644
index b2bf6ef9..00000000
--- a/src/mustach/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-test_mustach_jansson
diff --git a/src/mustach/AUTHORS b/src/mustach/AUTHORS
deleted file mode 100644
index 2fcc6043..00000000
--- a/src/mustach/AUTHORS
+++ /dev/null
@@ -1,23 +0,0 @@
-Main author:
- José Bollo <jobol@nonadev.net>
-
-Contributors:
- Abhishek Mishra
- Atlas
- Harold L Marzan
- Lailton Fernando Mariano
- Sami Kerola
- Sijmen J. Mulder
- Tomasz Sieprawski
-
-Packagers:
- pkgsrc: Sijmen J. Mulder
- alpine linux: Lucas Ramage
-
-Thanks to issue submitters:
- Dante Torres
- @fabbe
- Johann Oskarsson
- Mark Bucciarelli
- Paul Wisehart
- Thierry Fournier
diff --git a/src/mustach/LICENSE-2.0.txt b/src/mustach/LICENSE-2.0.txt
deleted file mode 100644
index d6456956..00000000
--- a/src/mustach/LICENSE-2.0.txt
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/src/mustach/Makefile.am b/src/mustach/Makefile.am
deleted file mode 100644
index d4cbb2d9..00000000
--- a/src/mustach/Makefile.am
+++ /dev/null
@@ -1,33 +0,0 @@
-# This Makefile.am is in the public domain
-AM_CPPFLAGS = -I$(top_srcdir)/src/include $(LIBGCRYPT_CFLAGS)
-
-if USE_COVERAGE
- AM_CFLAGS = --coverage -O0
- XLIB = -lgcov
-endif
-
-
-lib_LIBRARIES = \
- libmustach.a
-
-libmustach_a_SOURCES = \
- mustach.c mustach.h \
- mustach-jansson.c mustach-jansson.h
-
-test_mustach_jansson_SOURCES = \
- test_mustach_jansson.c
-test_mustach_jansson_LDADD = \
- -lgnunetutil \
- libmustach.a \
- $(XLIB)
-
-check_PROGRAMS = \
- test_mustach_jansson
-
-check_SCRIPTS = \
- run-original-tests.sh
-
-TESTS = $(check_SCRIPTS) $(check_PROGRAMS)
-
-EXTRA_DIST = \
- $(check_SCRIPTS)
diff --git a/src/mustach/ORIGIN b/src/mustach/ORIGIN
deleted file mode 100644
index fafb0ae7..00000000
--- a/src/mustach/ORIGIN
+++ /dev/null
@@ -1,9 +0,0 @@
-Cloned originally from https://gitlab.com/jobol/mustach/
-
-Changes:
-========
-
-Renamed original Makefile to Makefile.orig and wrote Makefile.am for us.
-
-Added run-original-tests.sh shell script as a wrapper around Makefile.org
-to us the original build process for the test suite.
diff --git a/src/mustach/README.md b/src/mustach/README.md
deleted file mode 100644
index a6df19f6..00000000
--- a/src/mustach/README.md
+++ /dev/null
@@ -1,214 +0,0 @@
-# Introduction to Mustach 0.99
-
-`mustach` is a C implementation of the [mustache](http://mustache.github.io "main site for mustache")
-template specification.
-
-The main site for `mustach` is on [gitlab](https://gitlab.com/jobol/mustach).
-
-The best way to use mustach is to copy the files **mustach.h** and **mustach.c**
-directly into your project and use it.
-
-Alternatively, make and meson files are provided for building `mustach` and
-`libmustach.so` shared library.
-
-## Distributions offering mustach package
-
-### Alpine Linux
-
-```sh
-apk add mustach
-apk add mustach-lib
-apk add mustach-dev
-```
-
-### NetBSD
-
-```sh
-cd devel/mustach
-make
-```
-
-See http://pkgsrc.se/devel/mustach
-
-## Using Mustach from sources
-
-The file **mustach.h** is the main documentation. Look at it.
-
-The current source files are:
-
-- **mustach.c** core implementation of mustache in C
-- **mustach.h** header file for core definitions
-- **mustach-json-c.c** tiny json wrapper of mustach using [json-c](https://github.com/json-c/json-c)
-- **mustach-json-c.h** header file for using the tiny JSON wrapper
-- **mustach-tool.c** simple tool for applying template files to a JSON file
-
-The file **mustach-json-c.c** is the main example of use of **mustach** core
-and it is also a practical implementation that can be used. It uses the library
-json-c. (NOTE for Mac OS: available through homebrew).
-
-HELP REQUESTED TO GIVE EXAMPLE BASED ON OTHER LIBRARIES (ex: janson, ...).
-
-The tool **mustach** is build using `make`, its usage is:
-
- mustach json template [template]...
-
-It then outputs the result of applying the templates files to the JSON file.
-
-### Portability
-
-Some system does not provide *open_memstream*. In that case, tell your
-preferred compiler to declare the preprocessor symbol **NO_OPEN_MEMSTREAM**.
-Example:
-
- gcc -DNO_OPEN_MEMSTREAM
-
-### Integration
-
-The file **mustach.h** is the main documentation. Look at it.
-
-The file **mustach-json-c.c** provides a good example of integration.
-
-If you intend to use basic HTML/XML escaping and standard C FILE, the callbacks
-of the interface **mustach_itf** that you have to implement are:
-`enter`, `next`, `leave`, `get`.
-
-If you intend to use specific escaping and/or specific output, the callbacks
-of the interface **mustach_itf** that you have to implement are:
-`enter`, `next`, `leave`, `get` and `emit`.
-
-### Extensions
-
-By default, the current implementation provides the following extensions:
-
-#### Explicit Substitution
-
-This is a core extension implemented in file **mustach.c**.
-
-In somecases the name of the key used for substitution begins with a
-character reserved for mustach: one of `#`, `^`, `/`, `&`, `{`, `>` and `=`.
-This extension introduces the special character `:` to explicitly
-tell mustach to just substitute the value. So `:` becomes a new special
-character.
-
-#### Value Testing and Comparing
-
-This are a tool extension implemented in file **mustach-json-c.c**.
-
-These extensions allows you to test the value of the selected key.
-They allow to write `key=value` (matching test) or `key=!value`
-(not matching test) in any query.
-
-The specific comparison extension also allows to compare if greater,
-lesser, etc.. than a value. It allows to write `key>value`.
-
-It the comparator sign appears in the first column it is ignored
-as if it was escaped.
-
-#### Access to current value
-
-The value of the current field can be accessed using single dot like
-in `{{#key}}{{.}}{{/key}}` that applied to `{"key":3.14}` produces `3.14`
-and `{{#array}} {{.}}{{/array}}` applied to `{"array":[1,2]}` produces
-` 1 2`.
-
-#### Iteration on objects
-
-Using the pattern `{{#X.*}}...{{/X.*}}` it is possible to iterate on
-fields of `X`. Example: `{{s.*}} {{*}}:{{.}}{{/s.*}}` applied on
-`{"s":{"a":1,"b":true}}` produces ` a:1 b:true`. Here the single star
-`{{*}}` is replaced by the iterated key and the single dot `{{.}}` is
-replaced by its value.
-
-### Removing Extensions
-
-When compiling mustach.c or mustach-json-c.c,
-extensions can be removed by defining macros
-using option -D.
-
-The possible macros are of 3 categories, the global,
-the mustach core specific and the mustach-json-c example
-of implementation specific.
-
-#### Global macros
-
-- `NO_EXTENSION_FOR_MUSTACH`
-
- This macro disables any current or future
- extensions for the core or the example.
-
-#### Macros for the core mustach engine (mustach.c)
-
-- `NO_COLON_EXTENSION_FOR_MUSTACH`
-
- This macro remove the ability to use colon (:)
- as explicit command for variable substitution.
- This extension allows to have name starting
- with one of the mustach character `:#^/&{=>`
-
-- `NO_ALLOW_EMPTY_TAG`
-
- Generate the error MUSTACH_ERROR_EMPTY_TAG automatically
- when an empty tag is encountered.
-
-#### Macros for the implementation example (mustach-json-c.c)
-
-- `NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH`
-
- This macro allows the program to check whether
- the actual value is equal to an expected value.
- This is useful in `{{#key=val}}` or `{{^key=val}}`
- with the corresponding `{{/key=val}}`.
- It can also be used in `{{key=val}}` but this
- doesn't seem to be useful.
-
-- `NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH`
-
- This macro allows the program to compare the actual
- value with an expected value. The comparison operators
- are `=`, `>`, `<`, `>=`, `<=`. The meaning of the
- comparison numeric or alphabetic depends on the type
- of the inspected value. Also the result of the comparison
- can be inverted if the value starts with `!`.
- Example of use: `{{key>=val}}`, or `{{#key>=val}}` and
- `{{^key>=val}}` with their matching `{{/key>=val}}`.
-
-- `NO_USE_VALUE_ESCAPE_FIRST_EXTENSION_FOR_MUSTACH`
-
- This macro fordids automatic escaping of coparison
- sign appearing at first column.
-
-- `NO_JSON_POINTER_EXTENSION_FOR_MUSTACH`
-
- This macro removes the possible use of JSON pointers.
- JSON pointers are defined in IETF RFC 6901.
- If not set, any key starting with "/" is a JSON pointer.
- This implies to use the colon to introduce keys.
- So `NO_COLON_EXTENSION_FOR_MUSTACH` implies
- `NO_JSON_POINTER_EXTENSION_FOR_MUSTACH`.
- A special escaping is used for `=`, `<`, `>` signs when
- values comparisons are enabled: `~=` gives `=` in the key.
-
-- `NO_OBJECT_ITERATION_FOR_MUSTACH`
-
- Disable the object iteration extension. That extension allows
- to iterate over the keys of an object. The iteration on object
- is selected by using the selector `{{#key.*}}`. In the context
- of iterating over object keys, the single key `{{*}}` returns the
- key and `{{.}}` returns the value.
-
-- `NO_SINGLE_DOT_EXTENSION_FOR_MUSTACH`
-
- Disable access to current object value using single dot
- like in `{{.}}`.
-
-- `NO_INCLUDE_PARTIAL_FALLBACK`
-
- Disable include of file by partial pattern like `{{> name}}`.
- By default if a such pattern is found, **mustach** search
- for `name` in the current json context. This what is done
- historically and when `NO_INCLUDE_PARTIAL_FALLBACK` is defined.
- When `NO_INCLUDE_PARTIAL_FALLBACK` is defined, if the value is
- found in the json context, the files `name` and `name.mustache`
- are searched in that order and the first file found is used
- as partial content. The macro `INCLUDE_PARTIAL_EXTENSION` can
- be use for changing the extension added.
diff --git a/src/mustach/meson.build b/src/mustach/meson.build
deleted file mode 100644
index c7ecc8df..00000000
--- a/src/mustach/meson.build
+++ /dev/null
@@ -1,12 +0,0 @@
-project('mustach', 'c',
- version: '1.0.0'
-)
-
-mustach_inc = include_directories('.')
-mustach_lib = shared_library('mustach',
- 'mustach.c',
- include_directories: mustach_inc
-)
-
-mustach_dep = declare_dependency(link_with: mustach_lib,
- include_directories: mustach_inc)
diff --git a/src/mustach/mustach-jansson.c b/src/mustach/mustach-jansson.c
deleted file mode 100644
index 2aed5829..00000000
--- a/src/mustach/mustach-jansson.c
+++ /dev/null
@@ -1,417 +0,0 @@
-/*
- Copyright (C) 2020 Taler Systems SA
-
- Original license:
- Author: José Bollo <jobol@nonadev.net>
- Author: José Bollo <jose.bollo@iot.bzh>
-
- https://gitlab.com/jobol/mustach
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-#include "platform.h"
-#include "mustach-jansson.h"
-
-struct Context
-{
- /**
- * Context object.
- */
- json_t *cont;
-
- /**
- * Current object.
- */
- json_t *obj;
-
- /**
- * Opaque object iterator.
- */
- void *iter;
-
- /**
- * Current index when iterating over an array.
- */
- unsigned int index;
-
- /**
- * Count when iterating over an array.
- */
- unsigned int count;
-
- bool is_objiter;
-};
-
-enum Bang
-{
- BANG_NONE,
- BANG_I18N,
- BANG_STRINGIFY,
- BANG_AMOUNT_CURRENCY,
- BANG_AMOUNT_DECIMAL,
-};
-
-struct JanssonClosure
-{
- json_t *root;
- mustach_jansson_write_cb writecb;
- int depth;
-
- /**
- * Did the last find(..) call result in an iterable?
- */
- struct Context stack[MUSTACH_MAX_DEPTH];
-
- /**
- * The last object we found should be iterated over.
- */
- bool found_iter;
-
- /**
- * Last bang we found.
- */
- enum Bang found_bang;
-
- /**
- * Language for i18n lookups.
- */
- const char *lang;
-};
-
-
-static json_t *
-walk (json_t *obj, const char *path)
-{
- char *saveptr = NULL;
- char *sp = GNUNET_strdup (path);
- char *p = sp;
- while (true)
- {
- char *tok = strtok_r (p, ".", &saveptr);
- if (tok == NULL)
- break;
- obj = json_object_get (obj, tok);
- if (obj == NULL)
- break;
- p = NULL;
- }
- GNUNET_free (sp);
- return obj;
-}
-
-
-static json_t *
-find (struct JanssonClosure *e, const char *name)
-{
- json_t *obj = NULL;
- char *path = GNUNET_strdup (name);
- char *bang;
-
- bang = strchr (path, '!');
-
- e->found_bang = BANG_NONE;
-
- if (NULL != bang)
- {
- *bang = 0;
- bang++;
-
- if (0 == strcmp (bang, "i18n"))
- e->found_bang = BANG_I18N;
- else if (0 == strcmp(bang, "stringify"))
- e->found_bang = BANG_STRINGIFY;
- else if (0 == strcmp(bang, "amount_decimal"))
- e->found_bang = BANG_AMOUNT_CURRENCY;
- else if (0 == strcmp(bang, "amount_currency"))
- e->found_bang = BANG_AMOUNT_DECIMAL;
- }
-
- if (BANG_I18N == e->found_bang && NULL != e->lang)
- {
- char *aug_path;
- GNUNET_asprintf (&aug_path, "%s_i18n.%s", path, e->lang);
- obj = walk (e->stack[e->depth].obj, aug_path);
- GNUNET_free (aug_path);
- }
-
- if (NULL == obj)
- {
- obj = walk (e->stack[e->depth].obj, path);
- }
-
- GNUNET_free (path);
-
- return obj;
-}
-
-
-static int
-start(void *closure)
-{
- struct JanssonClosure *e = closure;
- e->depth = 0;
- e->stack[0].cont = NULL;
- e->stack[0].obj = e->root;
- e->stack[0].index = 0;
- e->stack[0].count = 1;
- e->lang = json_string_value (json_object_get (e->root, "$language"));
- return MUSTACH_OK;
-}
-
-
-static int
-emituw (void *closure, const char *buffer, size_t size, int escape, FILE *file)
-{
- struct JanssonClosure *e = closure;
- if (!escape)
- e->writecb (file, buffer, size);
- else
- do
- {
- switch (*buffer)
- {
- case '<':
- e->writecb (file, "&lt;", 4);
- break;
- case '>':
- e->writecb (file, "&gt;", 4);
- break;
- case '&':
- e->writecb (file, "&amp;", 5);
- break;
- default:
- e->writecb (file, buffer, 1);
- break;
- }
- buffer++;
- }
- while(--size);
- return MUSTACH_OK;
-}
-
-
-static int
-enter(void *closure, const char *name)
-{
- struct JanssonClosure *e = closure;
- json_t *o = find(e, name);
- if (++e->depth >= MUSTACH_MAX_DEPTH)
- return MUSTACH_ERROR_TOO_DEEP;
-
- if (json_is_object (o))
- {
- if (e->found_iter)
- {
- void *iter = json_object_iter (o);
- if (NULL == iter)
- {
- e->depth--;
- return 0;
- }
- e->stack[e->depth].is_objiter = 1;
- e->stack[e->depth].iter = iter;
- e->stack[e->depth].obj = json_object_iter_value (iter);
- e->stack[e->depth].cont = o;
- }
- else
- {
- e->stack[e->depth].is_objiter = 0;
- e->stack[e->depth].obj = o;
- e->stack[e->depth].cont = o;
- }
- return 1;
- }
-
- if (json_is_array (o))
- {
- unsigned int size = json_array_size (o);
- if (size == 0)
- {
- e->depth--;
- return 0;
- }
- e->stack[e->depth].count = size;
- e->stack[e->depth].cont = o;
- e->stack[e->depth].obj = json_array_get (o, 0);
- e->stack[e->depth].index = 0;
- e->stack[e->depth].is_objiter = 0;
- return 1;
- }
-
- e->depth--;
- return 0;
-}
-
-
-static int
-next (void *closure)
-{
- struct JanssonClosure *e = closure;
- struct Context *ctx;
- if (e->depth <= 0)
- return MUSTACH_ERROR_CLOSING;
- ctx = &e->stack[e->depth];
- if (ctx->is_objiter)
- {
- ctx->iter = json_object_iter_next (ctx->obj, ctx->iter);
- if (NULL == ctx->iter)
- return 0;
- ctx->obj = json_object_iter_value (ctx->iter);
- return 1;
- }
- ctx->index++;
- if (ctx->index >= ctx->count)
- return 0;
- ctx->obj = json_array_get (ctx->cont, ctx->index);
- return 1;
-}
-
-static int
-leave (void *closure)
-{
- struct JanssonClosure *e = closure;
- if (e->depth <= 0)
- return MUSTACH_ERROR_CLOSING;
- e->depth--;
- return 0;
-}
-
-static void
-freecb (void *v)
-{
- free (v);
-}
-
-static int
-get (void *closure, const char *name, struct mustach_sbuf *sbuf)
-{
- struct JanssonClosure *e = closure;
- json_t *obj;
-
- if ( (0 == strcmp (name, "*") ) &&
- (e->stack[e->depth].is_objiter ) )
- {
- sbuf->value = json_object_iter_key (e->stack[e->depth].iter);
- return MUSTACH_OK;
- }
- obj = find (e, name);
- if (NULL != obj)
- {
- switch (e->found_bang)
- {
- case BANG_I18N:
- case BANG_NONE:
- {
- const char *s = json_string_value (obj);
- if (NULL != s)
- {
- sbuf->value = s;
- return MUSTACH_OK;
- }
- }
- break;
- case BANG_STRINGIFY:
- sbuf->value = json_dumps (obj, JSON_INDENT (2));
- sbuf->freecb = freecb;
- return MUSTACH_OK;
- case BANG_AMOUNT_DECIMAL:
- {
- char *s;
- char *c;
- if (!json_is_string (obj))
- break;
- s = strdup (json_string_value (obj));
- c = strchr (s, ':');
- if (NULL != c)
- *c = 0;
- sbuf->value = s;
- sbuf->freecb = freecb;
- return MUSTACH_OK;
- }
- break;
- case BANG_AMOUNT_CURRENCY:
- {
- const char *s;
- if (!json_is_string (obj))
- break;
- s = json_string_value (obj);
- s = strchr (s, ':');
- if (NULL == s)
- break;
- sbuf->value = s + 1;
- return MUSTACH_OK;
- }
- break;
- default:
- break;
- }
- }
- sbuf->value = "";
- return MUSTACH_OK;
-}
-
-static struct mustach_itf itf = {
- .start = start,
- .put = NULL,
- .enter = enter,
- .next = next,
- .leave = leave,
- .partial =NULL,
- .get = get,
- .emit = NULL,
- .stop = NULL
-};
-
-static struct mustach_itf itfuw = {
- .start = start,
- .put = NULL,
- .enter = enter,
- .next = next,
- .leave = leave,
- .partial = NULL,
- .get = get,
- .emit = emituw,
- .stop = NULL
-};
-
-int fmustach_jansson (const char *template, json_t *root, FILE *file)
-{
- struct JanssonClosure e = { 0 };
- e.root = root;
- return fmustach(template, &itf, &e, file);
-}
-
-int fdmustach_jansson (const char *template, json_t *root, int fd)
-{
- struct JanssonClosure e = { 0 };
- e.root = root;
- return fdmustach(template, &itf, &e, fd);
-}
-
-int mustach_jansson (const char *template, json_t *root, char **result, size_t *size)
-{
- struct JanssonClosure e = { 0 };
- e.root = root;
- e.writecb = NULL;
- return mustach(template, &itf, &e, result, size);
-}
-
-int umustach_jansson (const char *template, json_t *root, mustach_jansson_write_cb writecb, void *closure)
-{
- struct JanssonClosure e = { 0 };
- e.root = root;
- e.writecb = writecb;
- return fmustach(template, &itfuw, &e, closure);
-}
-
diff --git a/src/mustach/mustach-jansson.h b/src/mustach/mustach-jansson.h
deleted file mode 100644
index 27dcdd64..00000000
--- a/src/mustach/mustach-jansson.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- Copyright (C) 2020 Taler Systems SA
-
- Original license:
- Author: José Bollo <jose.bollo@iot.bzh>
- Author: José Bollo <jobol@nonadev.net>
-
- https://gitlab.com/jobol/mustach
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-#ifndef _mustach_jansson_h_included_
-#define _mustach_jansson_h_included_
-
-#include <taler/taler_json_lib.h>
-#include "mustach.h"
-
-/**
- * fmustach_jansson - Renders the mustache 'template' in 'file' for 'root'.
- *
- * @template: the template string to instantiate
- * @root: the root json object to render
- * \@file: the file where to write the result
- *
- * Returns 0 in case of success, -1 with errno set in case of system error
- * a other negative value in case of error.
- */
-extern int fmustach_jansson(const char *template, json_t *root, FILE *file);
-
-/**
- * fmustach_jansson - Renders the mustache 'template' in 'fd' for 'root'.
- *
- * @template: the template string to instantiate
- * @root: the root json object to render
- * @fd: the file descriptor number where to write the result
- *
- * Returns 0 in case of success, -1 with errno set in case of system error
- * a other negative value in case of error.
- */
-extern int fdmustach_jansson(const char *template, json_t *root, int fd);
-
-
-/**
- * fmustach_jansson - Renders the mustache 'template' in 'result' for 'root'.
- *
- * @template: the template string to instantiate
- * @root: the root json object to render
- * @result: the pointer receiving the result when 0 is returned
- * @size: the size of the returned result
- *
- * Returns 0 in case of success, -1 with errno set in case of system error
- * a other negative value in case of error.
- */
-extern int mustach_jansson(const char *template, json_t *root, char **result, size_t *size);
-
-/**
- * umustach_jansson - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'.
- *
- * @template: the template string to instantiate
- * @root: the root json object to render
- * @writecb: the function that write values
- * @closure: the closure for the write function
- *
- * Returns 0 in case of success, -1 with errno set in case of system error
- * a other negative value in case of error.
- */
-typedef int (*mustach_jansson_write_cb)(void *closure, const char *buffer, size_t size);
-extern int umustach_jansson(const char *template, json_t *root, mustach_jansson_write_cb writecb, void *closure);
-
-#endif
-
diff --git a/src/mustach/mustach-tool.c b/src/mustach/mustach-tool.c
deleted file mode 100644
index 364e34a8..00000000
--- a/src/mustach/mustach-tool.c
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- Author: José Bollo <jobol@nonadev.net>
- Author: José Bollo <jose.bollo@iot.bzh>
-
- https://gitlab.com/jobol/mustach
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-#define _GNU_SOURCE
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-#include <libgen.h>
-
-#include "mustach-json-c.h"
-
-static const size_t BLOCKSIZE = 8192;
-
-static const char *errors[] = {
- "??? unreferenced ???",
- "system",
- "unexpected end",
- "empty tag",
- "tag too long",
- "bad separators",
- "too depth",
- "closing",
- "bad unescape tag",
- "invalid interface",
- "item not found",
- "partial not found"
-};
-
-static void help(char *prog)
-{
- printf("usage: %s json-file mustach-templates...\n", basename(prog));
- exit(0);
-}
-
-static char *readfile(const char *filename)
-{
- int f;
- struct stat s;
- char *result;
- size_t size, pos;
- ssize_t rc;
-
- result = NULL;
- if (filename[0] == '-' && filename[1] == 0)
- f = dup(0);
- else
- f = open(filename, O_RDONLY);
- if (f < 0) {
- fprintf(stderr, "Can't open file: %s\n", filename);
- exit(1);
- }
-
- fstat(f, &s);
- switch (s.st_mode & S_IFMT) {
- case S_IFREG:
- size = s.st_size;
- break;
- case S_IFSOCK:
- case S_IFIFO:
- size = BLOCKSIZE;
- break;
- default:
- fprintf(stderr, "Bad file: %s\n", filename);
- exit(1);
- }
-
- pos = 0;
- result = malloc(size + 1);
- do {
- if (result == NULL) {
- fprintf(stderr, "Out of memory\n");
- exit(1);
- }
- rc = read(f, &result[pos], (size - pos) + 1);
- if (rc < 0) {
- fprintf(stderr, "Error while reading %s\n", filename);
- exit(1);
- }
- if (rc > 0) {
- pos += (size_t)rc;
- if (pos > size) {
- size = pos + BLOCKSIZE;
- result = realloc(result, size + 1);
- }
- }
- } while(rc > 0);
-
- close(f);
- result[pos] = 0;
- return result;
-}
-
-int main(int ac, char **av)
-{
- struct json_object *o;
- char *t;
- char *prog = *av;
- int s;
-
- (void)ac; /* unused */
-
- if (*++av) {
- if (!strcmp(*av, "-h") || !strcmp(*av, "--help"))
- help(prog);
- if (av[0][0] == '-' && !av[0][1])
- o = json_object_from_fd(0);
- else
- o = json_object_from_file(av[0]);
-#if JSON_C_VERSION_NUM >= 0x000D00
- if (json_util_get_last_err() != NULL) {
- fprintf(stderr, "Bad json: %s (file %s)\n", json_util_get_last_err(), av[0]);
- exit(1);
- }
- else
-#endif
- if (o == NULL) {
- fprintf(stderr, "Aborted: null json (file %s)\n", av[0]);
- exit(1);
- }
- while(*++av) {
- t = readfile(*av);
- s = fmustach_json_c(t, o, stdout);
- if (s != 0) {
- s = -s;
- if (s < 1 || s >= (int)(sizeof errors / sizeof * errors))
- s = 0;
- fprintf(stderr, "Template error %s (file %s)\n", errors[s], *av);
- }
- free(t);
- }
- json_object_put(o);
- }
- return 0;
-}
-
diff --git a/src/mustach/mustach.c b/src/mustach/mustach.c
deleted file mode 100644
index caa80dcc..00000000
--- a/src/mustach/mustach.c
+++ /dev/null
@@ -1,472 +0,0 @@
-/*
- Author: José Bollo <jobol@nonadev.net>
- Author: José Bollo <jose.bollo@iot.bzh>
-
- https://gitlab.com/jobol/mustach
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-#define _GNU_SOURCE
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <errno.h>
-#include <ctype.h>
-#ifdef _WIN32
-#include <malloc.h>
-#endif
-#ifdef __sun
-# include <alloca.h>
-#endif
-
-#include "mustach.h"
-
-#if defined(NO_EXTENSION_FOR_MUSTACH)
-# undef NO_COLON_EXTENSION_FOR_MUSTACH
-# define NO_COLON_EXTENSION_FOR_MUSTACH
-# undef NO_ALLOW_EMPTY_TAG
-# define NO_ALLOW_EMPTY_TAG
-#endif
-
-struct iwrap {
- int (*emit)(void *closure, const char *buffer, size_t size, int escape, FILE *file);
- void *closure; /* closure for: enter, next, leave, emit, get */
- int (*put)(void *closure, const char *name, int escape, FILE *file);
- void *closure_put; /* closure for put */
- int (*enter)(void *closure, const char *name);
- int (*next)(void *closure);
- int (*leave)(void *closure);
- int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf);
- int (*partial)(void *closure, const char *name, struct mustach_sbuf *sbuf);
- void *closure_partial; /* closure for partial */
-};
-
-#if !defined(NO_OPEN_MEMSTREAM)
-static FILE *memfile_open(char **buffer, size_t *size)
-{
- return open_memstream(buffer, size);
-}
-static void memfile_abort(FILE *file, char **buffer, size_t *size)
-{
- fclose(file);
- free(*buffer);
- *buffer = NULL;
- *size = 0;
-}
-static int memfile_close(FILE *file, char **buffer, size_t *size)
-{
- int rc;
-
- /* adds terminating null */
- rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
- fclose(file);
- if (rc == 0)
- /* removes terminating null of the length */
- (*size)--;
- else {
- free(*buffer);
- *buffer = NULL;
- *size = 0;
- }
- return rc;
-}
-#else
-static FILE *memfile_open(char **buffer, size_t *size)
-{
- /*
- * We can't provide *buffer and *size as open_memstream does but
- * at least clear them so the caller won't get bad data.
- */
- *buffer = NULL;
- *size = 0;
-
- return tmpfile();
-}
-static void memfile_abort(FILE *file, char **buffer, size_t *size)
-{
- fclose(file);
- *buffer = NULL;
- *size = 0;
-}
-static int memfile_close(FILE *file, char **buffer, size_t *size)
-{
- int rc;
- size_t s;
- char *b;
-
- s = (size_t)ftell(file);
- b = malloc(s + 1);
- if (b == NULL) {
- rc = MUSTACH_ERROR_SYSTEM;
- errno = ENOMEM;
- s = 0;
- } else {
- rewind(file);
- if (1 == fread(b, s, 1, file)) {
- rc = 0;
- b[s] = 0;
- } else {
- rc = MUSTACH_ERROR_SYSTEM;
- free(b);
- b = NULL;
- s = 0;
- }
- }
- *buffer = b;
- *size = s;
- return rc;
-}
-#endif
-
-static inline void sbuf_reset(struct mustach_sbuf *sbuf)
-{
- sbuf->value = NULL;
- sbuf->freecb = NULL;
- sbuf->closure = NULL;
-}
-
-static inline void sbuf_release(struct mustach_sbuf *sbuf)
-{
- if (sbuf->releasecb)
- sbuf->releasecb(sbuf->value, sbuf->closure);
-}
-
-static int iwrap_emit(void *closure, const char *buffer, size_t size, int escape, FILE *file)
-{
- size_t i, j;
-
- (void)closure; /* unused */
-
- if (!escape)
- return fwrite(buffer, size, 1, file) != 1 ? MUSTACH_ERROR_SYSTEM : MUSTACH_OK;
-
- i = 0;
- while (i < size) {
- j = i;
- while (j < size && buffer[j] != '<' && buffer[j] != '>' && buffer[j] != '&')
- j++;
- if (j != i && fwrite(&buffer[i], j - i, 1, file) != 1)
- return MUSTACH_ERROR_SYSTEM;
- if (j < size) {
- switch(buffer[j++]) {
- case '<':
- if (fwrite("&lt;", 4, 1, file) != 1)
- return MUSTACH_ERROR_SYSTEM;
- break;
- case '>':
- if (fwrite("&gt;", 4, 1, file) != 1)
- return MUSTACH_ERROR_SYSTEM;
- break;
- case '&':
- if (fwrite("&amp;", 5, 1, file) != 1)
- return MUSTACH_ERROR_SYSTEM;
- break;
- default: break;
- }
- }
- i = j;
- }
- return MUSTACH_OK;
-}
-
-static int iwrap_put(void *closure, const char *name, int escape, FILE *file)
-{
- struct iwrap *iwrap = closure;
- int rc;
- struct mustach_sbuf sbuf;
- size_t length;
-
- sbuf_reset(&sbuf);
- rc = iwrap->get(iwrap->closure, name, &sbuf);
- if (rc >= 0) {
- length = strlen(sbuf.value);
- if (length)
- rc = iwrap->emit(iwrap->closure, sbuf.value, length, escape, file);
- sbuf_release(&sbuf);
- }
- return rc;
-}
-
-static int iwrap_partial(void *closure, const char *name, struct mustach_sbuf *sbuf)
-{
- struct iwrap *iwrap = closure;
- int rc;
- FILE *file;
- size_t size;
- char *result;
-
- result = NULL;
- file = memfile_open(&result, &size);
- if (file == NULL)
- rc = MUSTACH_ERROR_SYSTEM;
- else {
- rc = iwrap->put(iwrap->closure_put, name, 0, file);
- if (rc < 0)
- memfile_abort(file, &result, &size);
- else {
- rc = memfile_close(file, &result, &size);
- if (rc == 0) {
- sbuf->value = result;
- sbuf->freecb = free;
- }
- }
- }
- return rc;
-}
-
-static int process(const char *template, struct iwrap *iwrap, FILE *file, const char *opstr, const char *clstr)
-{
- struct mustach_sbuf sbuf;
- char name[MUSTACH_MAX_LENGTH + 1], c, *tmp;
- const char *beg, *term;
- struct { const char *name, *again; size_t length; int enabled, entered; } stack[MUSTACH_MAX_DEPTH];
- size_t oplen, cllen, len, l;
- int depth, rc, enabled;
-
- enabled = 1;
- oplen = strlen(opstr);
- cllen = strlen(clstr);
- depth = 0;
- for(;;) {
- beg = strstr(template, opstr);
- if (beg == NULL) {
- /* no more mustach */
- if (enabled && template[0]) {
- rc = iwrap->emit(iwrap->closure, template, strlen(template), 0, file);
- if (rc < 0)
- return rc;
- }
- return depth ? MUSTACH_ERROR_UNEXPECTED_END : MUSTACH_OK;
- }
- if (enabled && beg != template) {
- rc = iwrap->emit(iwrap->closure, template, (size_t)(beg - template), 0, file);
- if (rc < 0)
- return rc;
- }
- beg += oplen;
- term = strstr(beg, clstr);
- if (term == NULL)
- return MUSTACH_ERROR_UNEXPECTED_END;
- template = term + cllen;
- len = (size_t)(term - beg);
- c = *beg;
- switch(c) {
- case '!':
- case '=':
- break;
- case '{':
- for (l = 0 ; clstr[l] == '}' ; l++);
- if (clstr[l]) {
- if (!len || beg[len-1] != '}')
- return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
- len--;
- } else {
- if (term[l] != '}')
- return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
- template++;
- }
- c = '&';
- /*@fallthrough@*/
- case '^':
- case '#':
- case '/':
- case '&':
- case '>':
-#if !defined(NO_COLON_EXTENSION_FOR_MUSTACH)
- case ':':
-#endif
- beg++; len--;
- default:
- while (len && isspace(beg[0])) { beg++; len--; }
- while (len && isspace(beg[len-1])) len--;
-#if !defined(NO_ALLOW_EMPTY_TAG)
- if (len == 0)
- return MUSTACH_ERROR_EMPTY_TAG;
-#endif
- if (len > MUSTACH_MAX_LENGTH)
- return MUSTACH_ERROR_TAG_TOO_LONG;
- memcpy(name, beg, len);
- name[len] = 0;
- break;
- }
- switch(c) {
- case '!':
- /* comment */
- /* nothing to do */
- break;
- case '=':
- /* defines separators */
- if (len < 5 || beg[len - 1] != '=')
- return MUSTACH_ERROR_BAD_SEPARATORS;
- beg++;
- len -= 2;
- for (l = 0; l < len && !isspace(beg[l]) ; l++);
- if (l == len)
- return MUSTACH_ERROR_BAD_SEPARATORS;
- oplen = l;
- tmp = alloca(oplen + 1);
- memcpy(tmp, beg, oplen);
- tmp[oplen] = 0;
- opstr = tmp;
- while (l < len && isspace(beg[l])) l++;
- if (l == len)
- return MUSTACH_ERROR_BAD_SEPARATORS;
- cllen = len - l;
- tmp = alloca(cllen + 1);
- memcpy(tmp, beg + l, cllen);
- tmp[cllen] = 0;
- clstr = tmp;
- break;
- case '^':
- case '#':
- /* begin section */
- if (depth == MUSTACH_MAX_DEPTH)
- return MUSTACH_ERROR_TOO_DEEP;
- rc = enabled;
- if (rc) {
- rc = iwrap->enter(iwrap->closure, name);
- if (rc < 0)
- return rc;
- }
- stack[depth].name = beg;
- stack[depth].again = template;
- stack[depth].length = len;
- stack[depth].enabled = enabled;
- stack[depth].entered = rc;
- if ((c == '#') == (rc == 0))
- enabled = 0;
- depth++;
- break;
- case '/':
- /* end section */
- if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len))
- return MUSTACH_ERROR_CLOSING;
- rc = enabled && stack[depth].entered ? iwrap->next(iwrap->closure) : 0;
- if (rc < 0)
- return rc;
- if (rc) {
- template = stack[depth++].again;
- } else {
- enabled = stack[depth].enabled;
- if (enabled && stack[depth].entered)
- iwrap->leave(iwrap->closure);
- }
- break;
- case '>':
- /* partials */
- if (enabled) {
- sbuf_reset(&sbuf);
- rc = iwrap->partial(iwrap->closure_partial, name, &sbuf);
- if (rc >= 0) {
- rc = process(sbuf.value, iwrap, file, opstr, clstr);
- sbuf_release(&sbuf);
- }
- if (rc < 0)
- return rc;
- }
- break;
- default:
- /* replacement */
- if (enabled) {
- rc = iwrap->put(iwrap->closure_put, name, c != '&', file);
- if (rc < 0)
- return rc;
- }
- break;
- }
- }
-}
-
-int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file)
-{
- int rc;
- struct iwrap iwrap;
-
- /* check validity */
- if (!itf->enter || !itf->next || !itf->leave || (!itf->put && !itf->get))
- return MUSTACH_ERROR_INVALID_ITF;
-
- /* init wrap structure */
- iwrap.closure = closure;
- if (itf->put) {
- iwrap.put = itf->put;
- iwrap.closure_put = closure;
- } else {
- iwrap.put = iwrap_put;
- iwrap.closure_put = &iwrap;
- }
- if (itf->partial) {
- iwrap.partial = itf->partial;
- iwrap.closure_partial = closure;
- } else if (itf->get) {
- iwrap.partial = itf->get;
- iwrap.closure_partial = closure;
- } else {
- iwrap.partial = iwrap_partial;
- iwrap.closure_partial = &iwrap;
- }
- iwrap.emit = itf->emit ? itf->emit : iwrap_emit;
- iwrap.enter = itf->enter;
- iwrap.next = itf->next;
- iwrap.leave = itf->leave;
- iwrap.get = itf->get;
-
- /* process */
- rc = itf->start ? itf->start(closure) : 0;
- if (rc == 0)
- rc = process(template, &iwrap, file, "{{", "}}");
- if (itf->stop)
- itf->stop(closure, rc);
- return rc;
-}
-
-int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd)
-{
- int rc;
- FILE *file;
-
- file = fdopen(fd, "w");
- if (file == NULL) {
- rc = MUSTACH_ERROR_SYSTEM;
- errno = ENOMEM;
- } else {
- rc = fmustach(template, itf, closure, file);
- fclose(file);
- }
- return rc;
-}
-
-int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size)
-{
- int rc;
- FILE *file;
- size_t s;
-
- *result = NULL;
- if (size == NULL)
- size = &s;
- file = memfile_open(result, size);
- if (file == NULL)
- rc = MUSTACH_ERROR_SYSTEM;
- else {
- rc = fmustach(template, itf, closure, file);
- if (rc < 0)
- memfile_abort(file, result, size);
- else
- rc = memfile_close(file, result, size);
- }
- return rc;
-}
-
diff --git a/src/mustach/mustach.h b/src/mustach/mustach.h
deleted file mode 100644
index ad952275..00000000
--- a/src/mustach/mustach.h
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- Author: José Bollo <jobol@nonadev.net>
- Author: José Bollo <jose.bollo@iot.bzh>
-
- https://gitlab.com/jobol/mustach
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-#ifndef _mustach_h_included_
-#define _mustach_h_included_
-
-struct mustach_sbuf; /* see below */
-
-/**
- * Current version of mustach and its derivates
- */
-#define MUSTACH_VERSION 99
-#define MUSTACH_VERSION_MAJOR (MUSTACH_VERSION / 100)
-#define MUSTACH_VERSION_MINOR (MUSTACH_VERSION % 100)
-
-/**
- * Maximum nested imbrications supported
- */
-#define MUSTACH_MAX_DEPTH 256
-
-/**
- * Maximum length of tags in mustaches {{...}}
- */
-#define MUSTACH_MAX_LENGTH 1024
-
-/**
- * mustach_itf - interface for callbacks
- *
- * All of this function should return a negative value to stop
- * the mustache processing. The returned negative value will be
- * then returned to the caller of mustach as is.
- *
- * The functions enter and next should return 0 or 1.
- *
- * All other functions should normally return MUSTACH_OK (zero).
- * If it returns a negative value, it means an error that stop
- * the process and that is reported to the caller.
- *
- * @start: If defined (can be NULL), starts the mustach processing
- * of the closure, called at the very beginning before any
- * mustach processing occurs.
- *
- * @put: If defined (can be NULL), writes the value of 'name'
- * to 'file' with 'escape' or not.
- * As an extension (see NO_ALLOW_EMPTY_TAG), the 'name' can be
- * the empty string. In that later case an implementation can
- * return MUSTACH_ERROR_EMPTY_TAG to refuse empty names.
- * If NULL and 'get' NULL the error MUSTACH_ERROR_INVALID_ITF
- * is returned.
- *
- * @enter: Enters the section of 'name' if possible.
- * Musts return 1 if entered or 0 if not entered.
- * When 1 is returned, the function 'leave' will always be called.
- * Conversely 'leave' is never called when enter returns 0 or
- * a negative value.
- * When 1 is returned, the function must activate the first
- * item of the section.
- *
- * @next: Activates the next item of the section if it exists.
- * Musts return 1 when the next item is activated.
- * Musts return 0 when there is no item to activate.
- *
- * @leave: Leaves the last entered section
- *
- * @partial: If defined (can be NULL), returns in 'sbuf' the content of the
- * partial of 'name'. @see mustach_sbuf
- * If NULL but 'get' not NULL, 'get' is used instead of partial.
- * If NULL and 'get' NULL and 'put' not NULL, 'put' is called with
- * a true FILE.
- *
- * @emit: If defined (can be NULL), writes the 'buffer' of 'size' with 'escape'.
- * If NULL the standard function 'fwrite' is used with a true FILE.
- * If not NULL that function is called instead of 'fwrite' to output
- * text.
- * It implies that if you define either 'partial' or 'get' callback,
- * the meaning of 'FILE *file' is abstract for mustach's process and
- * then you can use 'FILE*file' pass any kind of pointer (including NULL)
- * to the function 'fmustach'. An example of a such behaviour is given by
- * the implementation of 'umustach_json_c'.
- *
- * @get: If defined (can be NULL), returns in 'sbuf' the value of 'name'.
- * As an extension (see NO_ALLOW_EMPTY_TAG), the 'name' can be
- * the empty string. In that later case an implementation can
- * return MUSTACH_ERROR_EMPTY_TAG to refuse empty names.
- * If NULL and 'put' NULL the error MUSTACH_ERROR_INVALID_ITF
- * is returned.
- *
- * @stop: If defined (can be NULL), stops the mustach processing
- * of the closure, called at the very end after all mustach
- * processing occurerd. The status returned by the processing
- * is passed to the stop.
- *
- * The array below summarize status of callbacks:
- *
- * FULLY OPTIONAL: start partial
- * MANDATORY: enter next leave
- * COMBINATORIAL: put emit get
- *
- * Not definig a MANDATORY callback returns error MUSTACH_ERROR_INVALID_ITF.
- *
- * For COMBINATORIAL callbacks the array below summarize possible combinations:
- *
- * combination : put : emit : get : abstract FILE
- * -------------+---------+---------+---------+-----------------------
- * HISTORIC : defined : NULL : NULL : NO: standard FILE
- * MINIMAL : NULL : NULL : defined : NO: standard FILE
- * CUSTOM : NULL : defined : defined : YES: abstract FILE
- * DUCK : defined : NULL : defined : NO: standard FILE
- * DANGEROUS : defined : defined : any : YES or NO, depends on 'partial'
- * INVALID : NULL : any : NULL : -
- *
- * The DUCK case runs on one leg. 'get' is not used if 'partial' is defined
- * but is used for 'partial' if 'partial' is NULL. Thus for clarity, do not use
- * it that way but define 'partial' and let 'get' NULL.
- *
- * The DANGEROUS case is special: it allows abstract FILE if 'partial' is defined
- * but forbids abstract FILE when 'partial' is NULL.
- *
- * The INVALID case returns error MUSTACH_ERROR_INVALID_ITF.
- */
-struct mustach_itf {
- int (*start)(void *closure);
- int (*put)(void *closure, const char *name, int escape, FILE *file);
- int (*enter)(void *closure, const char *name);
- int (*next)(void *closure);
- int (*leave)(void *closure);
- int (*partial)(void *closure, const char *name, struct mustach_sbuf *sbuf);
- int (*emit)(void *closure, const char *buffer, size_t size, int escape, FILE *file);
- int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf);
- void (*stop)(void *closure, int status);
-};
-
-/**
- * mustach_sbuf - Interface for handling zero terminated strings
- *
- * That structure is used for returning zero terminated strings -in 'value'-
- * to mustach. The callee can provide a function for releasing the returned
- * 'value'. Three methods for releasing the string are possible.
- *
- * 1. no release: set either 'freecb' or 'releasecb' with NULL (done by default)
- * 2. release without closure: set 'freecb' to its expected value
- * 3. release with closure: set 'releasecb' and 'closure' to their expected values
- *
- * @value: The value of the string. That value is not changed by mustach -const-.
- *
- * @freecb: The function to call for freeing the value without closure.
- * For convenience, signature of that callback is compatible with 'free'.
- * Can be NULL.
- *
- * @releasecb: The function to release with closure.
- * Can be NULL.
- *
- * @closure: The closure to use for 'releasecb'.
- */
-struct mustach_sbuf {
- const char *value;
- union {
- void (*freecb)(void*);
- void (*releasecb)(const char *value, void *closure);
- };
- void *closure;
-};
-
-/*
- * Definition of error codes returned by mustach
- */
-#define MUSTACH_OK 0
-#define MUSTACH_ERROR_SYSTEM -1
-#define MUSTACH_ERROR_UNEXPECTED_END -2
-#define MUSTACH_ERROR_EMPTY_TAG -3
-#define MUSTACH_ERROR_TAG_TOO_LONG -4
-#define MUSTACH_ERROR_BAD_SEPARATORS -5
-#define MUSTACH_ERROR_TOO_DEEP -6
-#define MUSTACH_ERROR_CLOSING -7
-#define MUSTACH_ERROR_BAD_UNESCAPE_TAG -8
-#define MUSTACH_ERROR_INVALID_ITF -9
-#define MUSTACH_ERROR_ITEM_NOT_FOUND -10
-#define MUSTACH_ERROR_PARTIAL_NOT_FOUND -11
-
-/* You can use definition below for user specific error */
-#define MUSTACH_ERROR_USER_BASE -100
-#define MUSTACH_ERROR_USER(x) (MUSTACH_ERROR_USER_BASE-(x))
-
-/**
- * fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'.
- *
- * @template: the template string to instantiate
- * @itf: the interface to the functions that mustach calls
- * @closure: the closure to pass to functions called
- * \@file: the file where to write the result
- *
- * Returns 0 in case of success, -1 with errno set in case of system error
- * a other negative value in case of error.
- */
-extern int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file);
-
-/**
- * fmustach - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'.
- *
- * @template: the template string to instantiate
- * @itf: the interface to the functions that mustach calls
- * @closure: the closure to pass to functions called
- * @fd: the file descriptor number where to write the result
- *
- * Returns 0 in case of success, -1 with errno set in case of system error
- * a other negative value in case of error.
- */
-extern int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd);
-
-/**
- * fmustach - Renders the mustache 'template' in 'result' for 'itf' and 'closure'.
- *
- * @template: the template string to instantiate
- * @itf: the interface to the functions that mustach calls
- * @closure: the closure to pass to functions called
- * @result: the pointer receiving the result when 0 is returned
- * @size: the size of the returned result
- *
- * Returns 0 in case of success, -1 with errno set in case of system error
- * a other negative value in case of error.
- */
-extern int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size);
-
-#endif
-
diff --git a/src/mustach/run-original-tests.sh b/src/mustach/run-original-tests.sh
deleted file mode 100755
index 9c7d34cd..00000000
--- a/src/mustach/run-original-tests.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-set -eu
-# The build fails if libjson-c-dev is not installed.
-# That's OK, we don't otherwise need it and don't
-# even bother testing for it in configure.ac.
-# However, in that case, skip the test suite.
-
-make -f Makefile.orig mustach || exit 77
-make -f Makefile.orig test
-make -f Makefile.orig clean || true
diff --git a/src/mustach/test1/.gitignore b/src/mustach/test1/.gitignore
deleted file mode 100644
index 4d897daa..00000000
--- a/src/mustach/test1/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-resu.last
-vg.last
diff --git a/src/mustach/test1/Makefile b/src/mustach/test1/Makefile
deleted file mode 100644
index de53086b..00000000
--- a/src/mustach/test1/Makefile
+++ /dev/null
@@ -1,11 +0,0 @@
-.PHONY: test clean
-
-test: ../mustach
- @echo starting test
- @valgrind ../mustach json must > resu.last 2> vg.last
- @sed -i 's:^==[0-9]*== ::' vg.last
- @diff resu.ref resu.last && echo "result ok" || echo "ERROR! Result differs"
-
-clean:
- rm -f resu.last
-
diff --git a/src/mustach/test1/json b/src/mustach/test1/json
deleted file mode 100644
index 5b2e3d83..00000000
--- a/src/mustach/test1/json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "name": "Chris",
- "value": 10000,
- "taxed_value": 6000,
- "in_ca": true,
- "person": false,
- "repo": [
- { "name": "resque", "who": [ { "committer": "joe" }, { "reviewer": "avrel" }, { "committer": "william" } ] },
- { "name": "hub", "who": [ { "committer": "jack" }, { "reviewer": "avrel" }, { "committer": "greg" } ] },
- { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "committer": "greg" } ] }
- ],
- "person?": { "name": "Jon" },
- "special": "----{{extra}}----",
- "extra": 3.14159,
- "#sharp": "#",
- "!bang": "!",
- "/slash": "/",
- "^circ": "^",
- "=equal": "=",
- ":colon": ":",
- ">greater": ">",
- "~tilde": "~"
-}
diff --git a/src/mustach/test1/must b/src/mustach/test1/must
deleted file mode 100644
index 723f966c..00000000
--- a/src/mustach/test1/must
+++ /dev/null
@@ -1,43 +0,0 @@
-Hello {{name}}
-You have just won {{value}} dollars!
-{{#in_ca}}
-Well, {{taxed_value}} dollars, after taxes.
-{{/in_ca}}
-Shown.
-{{#person}}
- Never shown!
-{{/person}}
-{{^person}}
- No person
-{{/person}}
-
-{{#repo}}
- <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} committers:{{#who}} {{committer}}{{/who}}
-{{/repo}}
-
-{{#person?}}
- Hi {{name}}!
-{{/person?}}
-
-{{=%(% %)%=}}
-=====================================
-%(%! gros commentaire %)%
-%(%#repo%)%
- <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% committers:%(%#who%)% %(%committer%)%%(%/who%)%
-%(%/repo%)%
-=====================================
-%(%={{ }}=%)%
-ggggggggg
-{{> special}}
-jjjjjjjjj
-end
-
-{{:#sharp}}
-{{:!bang}}
-{{:~tilde}}
-{{:/~0tilde}}
-{{:/~1slash}} see json pointers IETF RFC 6901
-{{:^circ}}
-{{:\=equal}}
-{{::colon}}
-{{:>greater}}
diff --git a/src/mustach/test1/resu.ref b/src/mustach/test1/resu.ref
deleted file mode 100644
index 545e5857..00000000
--- a/src/mustach/test1/resu.ref
+++ /dev/null
@@ -1,49 +0,0 @@
-Hello Chris
-You have just won 10000 dollars!
-
-Well, 6000 dollars, after taxes.
-
-Shown.
-
-
- No person
-
-
-
- <b>resque</b> reviewers: avrel committers: joe william
-
- <b>hub</b> reviewers: avrel committers: jack greg
-
- <b>rip</b> reviewers: joe jack committers: greg
-
-
-
- Hi Jon!
-
-
-
-=====================================
-
-
- <b>resque</b> reviewers: avrel committers: joe william
-
- <b>hub</b> reviewers: avrel committers: jack greg
-
- <b>rip</b> reviewers: joe jack committers: greg
-
-=====================================
-
-ggggggggg
-----3.14159----
-jjjjjjjjj
-end
-
-#
-!
-~
-~
-/ see json pointers IETF RFC 6901
-^
-=
-:
-&gt;
diff --git a/src/mustach/test2/.gitignore b/src/mustach/test2/.gitignore
deleted file mode 100644
index 4d897daa..00000000
--- a/src/mustach/test2/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-resu.last
-vg.last
diff --git a/src/mustach/test2/Makefile b/src/mustach/test2/Makefile
deleted file mode 100644
index a493de60..00000000
--- a/src/mustach/test2/Makefile
+++ /dev/null
@@ -1,10 +0,0 @@
-.PHONY: test clean
-
-test: ../mustach
- @echo starting test
- @valgrind ../mustach json must > resu.last 2> vg.last
- @sed -i 's:^==[0-9]*== ::' vg.last
- @diff resu.ref resu.last && echo "result ok" || echo "ERROR! Result differs"
-
-clean:
- rm -f resu.last
diff --git a/src/mustach/test2/json b/src/mustach/test2/json
deleted file mode 100644
index 8c668b3b..00000000
--- a/src/mustach/test2/json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "header": "Colors",
- "items": [
- {"name": "red", "first": true, "url": "#Red"},
- {"name": "green", "link": true, "url": "#Green"},
- {"name": "blue", "link": true, "url": "#Blue"}
- ],
- "empty": false
-}
diff --git a/src/mustach/test2/must b/src/mustach/test2/must
deleted file mode 100644
index aa6da707..00000000
--- a/src/mustach/test2/must
+++ /dev/null
@@ -1,17 +0,0 @@
-<h1>{{header}}</h1>
-{{#bug}}
-{{/bug}}
-
-{{#items}}
- {{#first}}
- <li><strong>{{name}}</strong></li>
- {{/first}}
- {{#link}}
- <li><a href="{{url}}">{{name}}</a></li>
- {{/link}}
-{{/items}}
-
-{{#empty}}
- <p>The list is empty.</p>
-{{/empty}}
-
diff --git a/src/mustach/test2/resu.ref b/src/mustach/test2/resu.ref
deleted file mode 100644
index 67d1f547..00000000
--- a/src/mustach/test2/resu.ref
+++ /dev/null
@@ -1,22 +0,0 @@
-<h1>Colors</h1>
-
-
-
-
- <li><strong>red</strong></li>
-
-
-
-
-
- <li><a href="#Green">green</a></li>
-
-
-
-
- <li><a href="#Blue">blue</a></li>
-
-
-
-
-
diff --git a/src/mustach/test3/.gitignore b/src/mustach/test3/.gitignore
deleted file mode 100644
index 4d897daa..00000000
--- a/src/mustach/test3/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-resu.last
-vg.last
diff --git a/src/mustach/test3/Makefile b/src/mustach/test3/Makefile
deleted file mode 100644
index de53086b..00000000
--- a/src/mustach/test3/Makefile
+++ /dev/null
@@ -1,11 +0,0 @@
-.PHONY: test clean
-
-test: ../mustach
- @echo starting test
- @valgrind ../mustach json must > resu.last 2> vg.last
- @sed -i 's:^==[0-9]*== ::' vg.last
- @diff resu.ref resu.last && echo "result ok" || echo "ERROR! Result differs"
-
-clean:
- rm -f resu.last
-
diff --git a/src/mustach/test3/json b/src/mustach/test3/json
deleted file mode 100644
index 79278817..00000000
--- a/src/mustach/test3/json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "name": "Chris",
- "company": "<b>GitHub & Co</b>",
- "names": ["Chris", "Kross"],
- "skills": ["JavaScript", "PHP", "Java"],
- "age": 18
-}
diff --git a/src/mustach/test3/must b/src/mustach/test3/must
deleted file mode 100644
index 5c490469..00000000
--- a/src/mustach/test3/must
+++ /dev/null
@@ -1,15 +0,0 @@
-* {{name}}
-* {{age}}
-* {{company}}
-* {{&company}}
-* {{{company}}}
-{{=<% %>=}}
-* <%company%>
-* <%&company%>
-* <%{company}%>
-
-<%={{ }}=%>
-* <ul>{{#names}}<li>{{.}}</li>{{/names}}</ul>
-* skills: <ul>{{#skills}}<li>{{.}}</li>{{/skills}}</ul>
-{{#age}}* age: {{.}}{{/age}}
-
diff --git a/src/mustach/test3/resu.ref b/src/mustach/test3/resu.ref
deleted file mode 100644
index e89ce902..00000000
--- a/src/mustach/test3/resu.ref
+++ /dev/null
@@ -1,15 +0,0 @@
-* Chris
-* 18
-* &lt;b&gt;GitHub &amp; Co&lt;/b&gt;
-* <b>GitHub & Co</b>
-* <b>GitHub & Co</b>
-
-* &lt;b&gt;GitHub &amp; Co&lt;/b&gt;
-* <b>GitHub & Co</b>
-* <b>GitHub & Co</b>
-
-
-* <ul><li>Chris</li><li>Kross</li></ul>
-* skills: <ul><li>JavaScript</li><li>PHP</li><li>Java</li></ul>
-* age: 18
-
diff --git a/src/mustach/test4/.gitignore b/src/mustach/test4/.gitignore
deleted file mode 100644
index 4d897daa..00000000
--- a/src/mustach/test4/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-resu.last
-vg.last
diff --git a/src/mustach/test4/Makefile b/src/mustach/test4/Makefile
deleted file mode 100644
index de53086b..00000000
--- a/src/mustach/test4/Makefile
+++ /dev/null
@@ -1,11 +0,0 @@
-.PHONY: test clean
-
-test: ../mustach
- @echo starting test
- @valgrind ../mustach json must > resu.last 2> vg.last
- @sed -i 's:^==[0-9]*== ::' vg.last
- @diff resu.ref resu.last && echo "result ok" || echo "ERROR! Result differs"
-
-clean:
- rm -f resu.last
-
diff --git a/src/mustach/test4/json b/src/mustach/test4/json
deleted file mode 100644
index a1083607..00000000
--- a/src/mustach/test4/json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "person": { "name": "Jon", "age": 25 },
- "person.name": "Fred",
- "person.name=Fred": "The other Fred.",
- "persons": [
- { "name": "Jon", "age": 25, "lang": "en" },
- { "name": "Henry", "age": 27, "lang": "en" },
- { "name": "Amed", "age": 24, "lang": "fr" } ],
- "fellows": {
- "Jon": { "age": 25, "lang": "en" },
- "Henry": { "age": 27, "lang": "en" },
- "Amed": { "age": 24, "lang": "fr" } }
-}
diff --git a/src/mustach/test4/must b/src/mustach/test4/must
deleted file mode 100644
index 003b9366..00000000
--- a/src/mustach/test4/must
+++ /dev/null
@@ -1,58 +0,0 @@
-This are extensions!!
-
-{{person.name}}
-{{person.age}}
-
-{{person\.name}}
-{{person\.name\=Fred}}
-
-{{#person.name=Jon}}
-Hello Jon
-{{/person.name=Jon}}
-
-{{^person.name=Jon}}
-No Jon? Hey Jon...
-{{/person.name=Jon}}
-
-{{^person.name=Harry}}
-No Harry? Hey Calahan...
-{{/person.name=Harry}}
-
-{{#person\.name=Fred}}
-Hello Fred
-{{/person\.name=Fred}}
-
-{{^person\.name=Fred}}
-No Fred? Hey Fred...
-{{/person\.name=Fred}}
-
-{{#person\.name\=Fred=The other Fred.}}
-Hello Fred#2
-{{/person\.name\=Fred=The other Fred.}}
-
-{{^person\.name\=Fred=The other Fred.}}
-No Fred#2? Hey Fred#2...
-{{/person\.name\=Fred=The other Fred.}}
-
-{{#persons}}
-{{#lang=!fr}}Hello {{name}}, {{age}} years{{/lang=!fr}}
-{{#lang=fr}}Salut {{name}}, {{age}} ans{{/lang=fr}}
-{{/persons}}
-
-{{#persons}}
-{{name}}: {{age=24}}/{{age}}/{{age=!27}}
-{{/persons}}
-
-{{#fellows.*}}
-{{*}}: {{age=24}}/{{age}}/{{age=!27}}
-{{/fellows.*}}
-
-{{#*}}
- (1) {{*}}: {{.}}
- {{#*}}
- (2) {{*}}: {{.}}
- {{#*}}
- (3) {{*}}: {{.}}
- {{/*}}
- {{/*}}
-{{/*}}
diff --git a/src/mustach/test4/resu.ref b/src/mustach/test4/resu.ref
deleted file mode 100644
index 2d48918a..00000000
--- a/src/mustach/test4/resu.ref
+++ /dev/null
@@ -1,100 +0,0 @@
-This are extensions!!
-
-Jon
-25
-
-Fred
-The other Fred.
-
-
-Hello Jon
-
-
-
-
-
-No Harry? Hey Calahan...
-
-
-
-Hello Fred
-
-
-
-
-
-Hello Fred#2
-
-
-
-
-
-Hello Jon, 25 years
-
-
-Hello Henry, 27 years
-
-
-
-Salut Amed, 24 ans
-
-
-
-Jon: /25/25
-
-Henry: /27/
-
-Amed: 24/24/24
-
-
-
-Jon: /25/25
-
-Henry: /27/
-
-Amed: 24/24/24
-
-
-
- (1) person: { "name": "Jon", "age": 25 }
-
- (2) name: Jon
-
-
- (2) age: 25
-
-
-
- (1) person.name: Fred
-
-
- (1) person.name=Fred: The other Fred.
-
-
- (1) persons: [ { "name": "Jon", "age": 25, "lang": "en" }, { "name": "Henry", "age": 27, "lang": "en" }, { "name": "Amed", "age": 24, "lang": "fr" } ]
-
-
- (1) fellows: { "Jon": { "age": 25, "lang": "en" }, "Henry": { "age": 27, "lang": "en" }, "Amed": { "age": 24, "lang": "fr" } }
-
- (2) Jon: { "age": 25, "lang": "en" }
-
- (3) age: 25
-
- (3) lang: en
-
-
- (2) Henry: { "age": 27, "lang": "en" }
-
- (3) age: 27
-
- (3) lang: en
-
-
- (2) Amed: { "age": 24, "lang": "fr" }
-
- (3) age: 24
-
- (3) lang: fr
-
-
-
diff --git a/src/mustach/test5/.gitignore b/src/mustach/test5/.gitignore
deleted file mode 100644
index 4d897daa..00000000
--- a/src/mustach/test5/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-resu.last
-vg.last
diff --git a/src/mustach/test5/Makefile b/src/mustach/test5/Makefile
deleted file mode 100644
index de53086b..00000000
--- a/src/mustach/test5/Makefile
+++ /dev/null
@@ -1,11 +0,0 @@
-.PHONY: test clean
-
-test: ../mustach
- @echo starting test
- @valgrind ../mustach json must > resu.last 2> vg.last
- @sed -i 's:^==[0-9]*== ::' vg.last
- @diff resu.ref resu.last && echo "result ok" || echo "ERROR! Result differs"
-
-clean:
- rm -f resu.last
-
diff --git a/src/mustach/test5/json b/src/mustach/test5/json
deleted file mode 100644
index 5b2e3d83..00000000
--- a/src/mustach/test5/json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "name": "Chris",
- "value": 10000,
- "taxed_value": 6000,
- "in_ca": true,
- "person": false,
- "repo": [
- { "name": "resque", "who": [ { "committer": "joe" }, { "reviewer": "avrel" }, { "committer": "william" } ] },
- { "name": "hub", "who": [ { "committer": "jack" }, { "reviewer": "avrel" }, { "committer": "greg" } ] },
- { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "committer": "greg" } ] }
- ],
- "person?": { "name": "Jon" },
- "special": "----{{extra}}----",
- "extra": 3.14159,
- "#sharp": "#",
- "!bang": "!",
- "/slash": "/",
- "^circ": "^",
- "=equal": "=",
- ":colon": ":",
- ">greater": ">",
- "~tilde": "~"
-}
diff --git a/src/mustach/test5/must b/src/mustach/test5/must
deleted file mode 100644
index 44305df2..00000000
--- a/src/mustach/test5/must
+++ /dev/null
@@ -1,23 +0,0 @@
-=====================================
-from json
-{{> special}}
-=====================================
-not found
-{{> notfound}}
-=====================================
-without extension first
-{{> must2 }}
-=====================================
-last with extension
-{{> must3 }}
-=====================================
-Ensure must3 didn't change specials
-
-{{#person?}}
- Hi {{name}}!
-{{/person?}}
-
-%(%#person?%)%
- Hi %(%name%)%!
-%(%/person?%)%
-
diff --git a/src/mustach/test5/must2 b/src/mustach/test5/must2
deleted file mode 100644
index d4a1d378..00000000
--- a/src/mustach/test5/must2
+++ /dev/null
@@ -1,14 +0,0 @@
-must2 == BEGIN
-Hello {{name}}
-You have just won {{value}} dollars!
-{{#in_ca}}
-Well, {{taxed_value}} dollars, after taxes.
-{{/in_ca}}
-Shown.
-{{#person}}
- Never shown!
-{{/person}}
-{{^person}}
- No person
-{{/person}}
-must2 == END
diff --git a/src/mustach/test5/must2.mustache b/src/mustach/test5/must2.mustache
deleted file mode 100644
index 33f1ead3..00000000
--- a/src/mustach/test5/must2.mustache
+++ /dev/null
@@ -1 +0,0 @@
-must2.mustache ==SHOULD NOT BE SEEN==
diff --git a/src/mustach/test5/must3.mustache b/src/mustach/test5/must3.mustache
deleted file mode 100644
index 821aaac3..00000000
--- a/src/mustach/test5/must3.mustache
+++ /dev/null
@@ -1,17 +0,0 @@
-must3.mustache == BEGIN
-{{#repo}}
- <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} committers:{{#who}} {{committer}}{{/who}}
-{{/repo}}
-
-{{#person?}}
- Hi {{name}}!
-{{/person?}}
-
-{{=%(% %)%=}}
-=====================================
-%(%! big comment %)%
-%(%#repo%)%
- <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% committers:%(%#who%)% %(%committer%)%%(%/who%)%
-%(%/repo%)%
-=====================================
-must3.mustache == END
diff --git a/src/mustach/test5/resu.ref b/src/mustach/test5/resu.ref
deleted file mode 100644
index afc39659..00000000
--- a/src/mustach/test5/resu.ref
+++ /dev/null
@@ -1,60 +0,0 @@
-=====================================
-from json
-----3.14159----
-=====================================
-not found
-
-=====================================
-without extension first
-must2 == BEGIN
-Hello Chris
-You have just won 10000 dollars!
-
-Well, 6000 dollars, after taxes.
-
-Shown.
-
-
- No person
-
-must2 == END
-
-=====================================
-last with extension
-must3.mustache == BEGIN
-
- <b>resque</b> reviewers: avrel committers: joe william
-
- <b>hub</b> reviewers: avrel committers: jack greg
-
- <b>rip</b> reviewers: joe jack committers: greg
-
-
-
- Hi Jon!
-
-
-
-=====================================
-
-
- <b>resque</b> reviewers: avrel committers: joe william
-
- <b>hub</b> reviewers: avrel committers: jack greg
-
- <b>rip</b> reviewers: joe jack committers: greg
-
-=====================================
-must3.mustache == END
-
-=====================================
-Ensure must3 didn't change specials
-
-
- Hi Jon!
-
-
-%(%#person?%)%
- Hi %(%name%)%!
-%(%/person?%)%
-
diff --git a/src/mustach/test5/special b/src/mustach/test5/special
deleted file mode 100644
index 02d9975c..00000000
--- a/src/mustach/test5/special
+++ /dev/null
@@ -1 +0,0 @@
-special ==SHOULD NOT BE SEEN==
diff --git a/src/mustach/test5/special.mustache b/src/mustach/test5/special.mustache
deleted file mode 100644
index 70a771fd..00000000
--- a/src/mustach/test5/special.mustache
+++ /dev/null
@@ -1 +0,0 @@
-special.mustache ==SHOULD NOT BE SEEN==
diff --git a/src/mustach/test6/.gitignore b/src/mustach/test6/.gitignore
deleted file mode 100644
index 62f4d919..00000000
--- a/src/mustach/test6/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-resu.last
-vg.last
-test-custom-write
-!test-custom-write.c
diff --git a/src/mustach/test6/Makefile b/src/mustach/test6/Makefile
deleted file mode 100644
index b8a63375..00000000
--- a/src/mustach/test6/Makefile
+++ /dev/null
@@ -1,15 +0,0 @@
-.PHONY: test clean
-
-test-custom-write: test-custom-write.c ../mustach-json-c.h ../mustach-json-c.c ../mustach.h ../mustach.c
- @echo building test-custom-write
- $(CC) $(CFLAGS) -g -o test-custom-write test-custom-write.c ../mustach.c ../mustach-json-c.c -ljson-c
-
-test: test-custom-write
- @echo starting test
- @valgrind ./test-custom-write json -U must -l must -x must > resu.last 2> vg.last
- @sed -i 's:^==[0-9]*== ::' vg.last
- @diff resu.ref resu.last && echo "result ok" || echo "ERROR! Result differs"
-
-clean:
- rm -f resu.last test-custom-write
-
diff --git a/src/mustach/test6/json b/src/mustach/test6/json
deleted file mode 100644
index 5b2e3d83..00000000
--- a/src/mustach/test6/json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "name": "Chris",
- "value": 10000,
- "taxed_value": 6000,
- "in_ca": true,
- "person": false,
- "repo": [
- { "name": "resque", "who": [ { "committer": "joe" }, { "reviewer": "avrel" }, { "committer": "william" } ] },
- { "name": "hub", "who": [ { "committer": "jack" }, { "reviewer": "avrel" }, { "committer": "greg" } ] },
- { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "committer": "greg" } ] }
- ],
- "person?": { "name": "Jon" },
- "special": "----{{extra}}----",
- "extra": 3.14159,
- "#sharp": "#",
- "!bang": "!",
- "/slash": "/",
- "^circ": "^",
- "=equal": "=",
- ":colon": ":",
- ">greater": ">",
- "~tilde": "~"
-}
diff --git a/src/mustach/test6/must b/src/mustach/test6/must
deleted file mode 100644
index 723f966c..00000000
--- a/src/mustach/test6/must
+++ /dev/null
@@ -1,43 +0,0 @@
-Hello {{name}}
-You have just won {{value}} dollars!
-{{#in_ca}}
-Well, {{taxed_value}} dollars, after taxes.
-{{/in_ca}}
-Shown.
-{{#person}}
- Never shown!
-{{/person}}
-{{^person}}
- No person
-{{/person}}
-
-{{#repo}}
- <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} committers:{{#who}} {{committer}}{{/who}}
-{{/repo}}
-
-{{#person?}}
- Hi {{name}}!
-{{/person?}}
-
-{{=%(% %)%=}}
-=====================================
-%(%! gros commentaire %)%
-%(%#repo%)%
- <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% committers:%(%#who%)% %(%committer%)%%(%/who%)%
-%(%/repo%)%
-=====================================
-%(%={{ }}=%)%
-ggggggggg
-{{> special}}
-jjjjjjjjj
-end
-
-{{:#sharp}}
-{{:!bang}}
-{{:~tilde}}
-{{:/~0tilde}}
-{{:/~1slash}} see json pointers IETF RFC 6901
-{{:^circ}}
-{{:\=equal}}
-{{::colon}}
-{{:>greater}}
diff --git a/src/mustach/test6/resu.ref b/src/mustach/test6/resu.ref
deleted file mode 100644
index 345d3aef..00000000
--- a/src/mustach/test6/resu.ref
+++ /dev/null
@@ -1,147 +0,0 @@
-HELLO CHRIS
-YOU HAVE JUST WON 10000 DOLLARS!
-
-WELL, 6000 DOLLARS, AFTER TAXES.
-
-SHOWN.
-
-
- NO PERSON
-
-
-
- <B>RESQUE</B> REVIEWERS: AVREL COMMITTERS: JOE WILLIAM
-
- <B>HUB</B> REVIEWERS: AVREL COMMITTERS: JACK GREG
-
- <B>RIP</B> REVIEWERS: JOE JACK COMMITTERS: GREG
-
-
-
- HI JON!
-
-
-
-=====================================
-
-
- <B>RESQUE</B> REVIEWERS: AVREL COMMITTERS: JOE WILLIAM
-
- <B>HUB</B> REVIEWERS: AVREL COMMITTERS: JACK GREG
-
- <B>RIP</B> REVIEWERS: JOE JACK COMMITTERS: GREG
-
-=====================================
-
-GGGGGGGGG
-----3.14159----
-JJJJJJJJJ
-END
-
-#
-!
-~
-~
-/ SEE JSON POINTERS IETF RFC 6901
-^
-=
-:
-&GT;
-hello chris
-you have just won 10000 dollars!
-
-well, 6000 dollars, after taxes.
-
-shown.
-
-
- no person
-
-
-
- <b>resque</b> reviewers: avrel committers: joe william
-
- <b>hub</b> reviewers: avrel committers: jack greg
-
- <b>rip</b> reviewers: joe jack committers: greg
-
-
-
- hi jon!
-
-
-
-=====================================
-
-
- <b>resque</b> reviewers: avrel committers: joe william
-
- <b>hub</b> reviewers: avrel committers: jack greg
-
- <b>rip</b> reviewers: joe jack committers: greg
-
-=====================================
-
-ggggggggg
-----3.14159----
-jjjjjjjjj
-end
-
-#
-!
-~
-~
-/ see json pointers ietf rfc 6901
-^
-=
-:
-&gt;
-Hello Chris
-You have just won 10000 dollars!
-
-Well, 6000 dollars, after taxes.
-
-Shown.
-
-
- No person
-
-
-
- <b>resque</b> reviewers: avrel committers: joe william
-
- <b>hub</b> reviewers: avrel committers: jack greg
-
- <b>rip</b> reviewers: joe jack committers: greg
-
-
-
- Hi Jon!
-
-
-
-=====================================
-
-
- <b>resque</b> reviewers: avrel committers: joe william
-
- <b>hub</b> reviewers: avrel committers: jack greg
-
- <b>rip</b> reviewers: joe jack committers: greg
-
-=====================================
-
-ggggggggg
-----3.14159----
-jjjjjjjjj
-end
-
-#
-!
-~
-~
-/ see json pointers IETF RFC 6901
-^
-=
-:
-&gt;
diff --git a/src/mustach/test6/test-custom-write.c b/src/mustach/test6/test-custom-write.c
deleted file mode 100644
index cc50a47c..00000000
--- a/src/mustach/test6/test-custom-write.c
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- Author: José Bollo <jobol@nonadev.net>
- Author: José Bollo <jose.bollo@iot.bzh>
-
- https://gitlab.com/jobol/mustach
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-#define _GNU_SOURCE
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <ctype.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-#include <libgen.h>
-
-#include "../mustach-json-c.h"
-
-static const size_t BLOCKSIZE = 8192;
-
-static char *readfile(const char *filename)
-{
- int f;
- struct stat s;
- char *result;
- size_t size, pos;
- ssize_t rc;
-
- result = NULL;
- if (filename[0] == '-' && filename[1] == 0)
- f = dup(0);
- else
- f = open(filename, O_RDONLY);
- if (f < 0) {
- fprintf(stderr, "Can't open file: %s\n", filename);
- exit(1);
- }
-
- fstat(f, &s);
- switch (s.st_mode & S_IFMT) {
- case S_IFREG:
- size = s.st_size;
- break;
- case S_IFSOCK:
- case S_IFIFO:
- size = BLOCKSIZE;
- break;
- default:
- fprintf(stderr, "Bad file: %s\n", filename);
- exit(1);
- }
-
- pos = 0;
- result = malloc(size + 1);
- do {
- if (result == NULL) {
- fprintf(stderr, "Out of memory\n");
- exit(1);
- }
- rc = read(f, &result[pos], (size - pos) + 1);
- if (rc < 0) {
- fprintf(stderr, "Error while reading %s\n", filename);
- exit(1);
- }
- if (rc > 0) {
- pos += (size_t)rc;
- if (pos > size) {
- size = pos + BLOCKSIZE;
- result = realloc(result, size + 1);
- }
- }
- } while(rc > 0);
-
- close(f);
- result[pos] = 0;
- return result;
-}
-
-enum { None, Upper, Lower } mode = None;
-
-int uwrite(void *closure, const char *buffer, size_t size)
-{
- switch(mode) {
- case None:
- fwrite(buffer, size, 1, stdout);
- break;
- case Upper:
- while(size--)
- fputc(toupper(*buffer++), stdout);
- break;
- case Lower:
- while(size--)
- fputc(tolower(*buffer++), stdout);
- break;
- }
- return 0;
-}
-
-int main(int ac, char **av)
-{
- struct json_object *o;
- char *t;
- char *prog = *av;
- int s;
-
- if (*++av) {
- o = json_object_from_file(av[0]);
- if (o == NULL) {
- fprintf(stderr, "Aborted: null json (file %s)\n", av[0]);
- exit(1);
- }
- while(*++av) {
- if (!strcmp(*av, "-U"))
- mode = Upper;
- else if (!strcmp(*av, "-l"))
- mode = Lower;
- else if (!strcmp(*av, "-x"))
- mode = None;
- else {
- t = readfile(*av);
- s = umustach_json_c(t, o, uwrite, NULL);
- if (s != 0)
- fprintf(stderr, "Template error %d\n", s);
- free(t);
- }
- }
- json_object_put(o);
- }
- return 0;
-}
-
diff --git a/src/mustach/test_mustach_jansson.c b/src/mustach/test_mustach_jansson.c
deleted file mode 100644
index 11af86fa..00000000
--- a/src/mustach/test_mustach_jansson.c
+++ /dev/null
@@ -1,163 +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 test_mustach_jansson.c
- * @brief testcase to test the mustach/jansson integration
- * @author Florian Dold
- */
-#include "platform.h"
-#include "mustach-jansson.h"
-
-
-static void
-assert_template (const char *template,
- json_t *root,
- const char *expected)
-{
- char *r;
- size_t sz;
-
- GNUNET_assert (0 == mustach_jansson (template,
- root,
- &r,
- &sz));
- GNUNET_assert (0 == strcmp (r,
- expected));
- GNUNET_free (r);
-}
-
-
-int
-main (int argc,
- char *const *argv)
-{
- json_t *root = json_object ();
- json_t *arr = json_array ();
- json_t *obj = json_object ();
- json_t *contract;
- /* test 1 */
- const char *t1 = "hello world";
- const char *x1 = "hello world";
- /* test 2 */
- const char *t2 = "hello {{ v1 }}";
- const char *x2 = "hello world";
- /* test 3 */
- const char *t3 = "hello {{ v3.x }}";
- const char *x3 = "hello baz";
- /* test 4 */
- const char *t4 = "hello {{# v2 }}{{ . }}{{/ v2 }}";
- const char *x4 = "hello foobar";
- /* test 5 */
- const char *t5 = "hello {{# v3 }}{{ y }}/{{ x }}{{ z }}{{/ v3 }}";
- const char *x5 = "hello quux/baz";
- /* test 6 */
- const char *t6 = "hello {{ v2!stringify }}";
- const char *x6 = "hello [\n \"foo\",\n \"bar\"\n]";
- /* test 7 */
- const char *t7 = "amount: {{ amt!amount_decimal }} {{ amt!amount_currency }}";
- const char *x7 = "amount: 123.00 EUR";
- /* test 8 */
- const char *t8 = "{{^ v4 }}fallback{{/ v4 }}";
- const char *x8 = "fallback";
-
- /* contract test 8 (contract) */
- const char *tc = "summary: {{ summary!i18n }}";
- const char *xc_en = "summary: ENGLISH";
- const char *xc_de = "summary: DEUTSCH";
- const char *xc_fr = "summary: FRANCAISE";
-
- GNUNET_assert (NULL != root);
- GNUNET_assert (NULL != arr);
- GNUNET_assert (NULL != obj);
- GNUNET_assert (0 ==
- json_object_set_new (root,
- "v1",
- json_string ("world")));
- GNUNET_assert (0 ==
- json_object_set_new (root,
- "v4",
- json_array ()));
- GNUNET_assert (0 ==
- json_array_append_new (arr,
- json_string ("foo")));
- GNUNET_assert (0 ==
- json_array_append_new (arr,
- json_string ("bar")));
- GNUNET_assert (0 ==
- json_object_set_new (root,
- "v2",
- arr));
- GNUNET_assert (0 ==
- json_object_set_new (root,
- "v3",
- obj));
- GNUNET_assert (0 ==
- json_object_set_new (root,
- "amt",
- json_string ("EUR:123.00")));
- GNUNET_assert (0 ==
- json_object_set_new (obj,
- "x",
- json_string ("baz")));
- GNUNET_assert (0 ==
- json_object_set_new (obj,
- "y",
- json_string ("quux")));
- contract = json_pack ("{ s:s, s:{s:s, s:s}}",
- "summary",
- "ENGLISH",
- "summary_i18n",
- "de",
- "DEUTSCH",
- "fr",
- "FRANCAISE");
- GNUNET_assert (NULL != contract);
-
- assert_template (t1, root, x1);
- assert_template (t2, root, x2);
- assert_template (t3, root, x3);
- assert_template (t4, root, x4);
- assert_template (t5, root, x5);
- assert_template (t6, root, x6);
- assert_template (t7, root, x7);
- assert_template (t8, root, x8);
- assert_template (tc, contract, xc_en);
-
- GNUNET_assert (0 ==
- json_object_set_new (contract,
- "$language",
- json_string ("de")));
- assert_template (tc, contract, xc_de);
-
- GNUNET_assert (0 ==
- json_object_set_new (contract,
- "$language",
- json_string ("fr")));
- assert_template (tc, contract, xc_fr);
-
- GNUNET_assert (0 ==
- json_object_set_new (contract,
- "$language",
- json_string ("it")));
- assert_template (tc, contract, xc_en);
- json_decref (root);
- json_decref (contract);
- return 0;
-}
diff --git a/src/testing/.gitignore b/src/testing/.gitignore
index 7abd228e..07fd2be3 100644
--- a/src/testing/.gitignore
+++ b/src/testing/.gitignore
@@ -2,6 +2,7 @@ taler-bank.err
test_rotation_home/
*.err
*.out
+*.pid
test_merchant_api_home/.local/share/taler/exchange-offline/secm_tofus.pub
test_merchant_api_home/.local/share/taler/exchange-secmod-eddsa/
test_merchant_api_home/.local/share/taler/exchange-secmod-rsa/
@@ -12,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 e12fac6e..67bbbced 100644
--- a/src/testing/Makefile.am
+++ b/src/testing/Makefile.am
@@ -8,61 +8,79 @@ 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_product_creation.sh \
- test_merchant_reserve_creation.sh \
test_merchant_order_creation.sh \
- test_merchant_instance_purge.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_reserve.c \
+ testing_api_cmd_delete_template.c \
+ testing_api_cmd_delete_webhook.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_patch_instance.c \
- testing_api_cmd_patch_product.c \
+ testing_api_cmd_post_templates.c \
+ testing_api_cmd_post_tokenfamilies.c \
+ testing_api_cmd_post_using_templates.c \
+ testing_api_cmd_post_webhooks.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
@@ -71,11 +89,13 @@ libtalermerchanttesting_la_LIBADD = \
-ltalerbank \
-ltalerexchange \
-ltalerjson \
+ -ltalermhd \
-ltalerutil \
-lgnunetcurl \
-lgnunetjson \
-lgnunetutil \
-ljansson \
+ -lmicrohttpd \
-ltalertesting \
$(XLIB)
@@ -112,7 +132,6 @@ test_merchant_api_twisted_cs_LDADD = \
-ltalerexchange \
-ltalerjson \
-ltalerutil \
- -lgnunettesting \
-lgnunetjson \
-lgnunetcurl \
-lgnunetutil \
@@ -134,7 +153,6 @@ test_merchant_api_twisted_rsa_LDADD = \
-ltalerexchange \
-ltalerjson \
-ltalerutil \
- -lgnunettesting \
-lgnunetjson \
-lgnunetcurl \
-lgnunetutil \
@@ -155,7 +173,6 @@ test_merchant_api_cs_LDADD = \
-ltalerexchange \
-ltalerjson \
-ltalerutil \
- -lgnunettesting \
-lgnunetjson \
-lgnunetcurl \
-lgnunetutil \
@@ -175,7 +192,6 @@ test_merchant_api_rsa_LDADD = \
-ltalerexchange \
-ltalerjson \
-ltalerutil \
- -lgnunettesting \
-lgnunetjson \
-lgnunetcurl \
-lgnunetutil \
@@ -202,10 +218,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 \
@@ -213,8 +229,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 e8b947c9..00000000
--- a/src/testing/initialize_taler_system.sh
+++ /dev/null
@@ -1,249 +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
- rm -rf $CONF $WALLET_DB $TMP_DIR $LAST_RESPONSE
- wait
-}
-
-# 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 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 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}/
-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 "Printing software version..."
-
-echo -n "taler-wallet-cli "
-taler-wallet-cli --version
-taler-exchange-httpd --version
-taler-merchant-httpd --version
-
-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 -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 &
-taler-auditor-httpd -L INFO -c $CONF 2> taler-auditor-httpd.log &
-
-echo " OK"
-
-echo -n "Waiting for the bank"
-# Wait for bank to be available (usually the slowest)
-for n in `seq 1 30`
-do
- echo -n "."
- sleep 1
- OK=0
- # bank
- wget --waitretry=0 --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
-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 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 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 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 TESTKUDOS:0.01 \
- global-fee now TESTKUDOS:0.01 TESTKUDOS:0.01 TESTKUDOS:0.01 TESTKUDOS:0.01 1h 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 98a78efa..c3c048d1 100755
--- a/src/testing/test-merchant-walletharness.sh
+++ b/src/testing/test-merchant-walletharness.sh
@@ -22,8 +22,23 @@
set -eu
+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-wallet-cli testing run-integrationtests --dry --suites merchant 2&>/dev/null || res=$?
+taler-harness run-integrationtests --dry --suites merchant 2&>/dev/null || res=$?
if [[ $res -ne 0 ]]; then
echo "skipping wallet test harness"
@@ -31,4 +46,4 @@ if [[ $res -ne 0 ]]; then
fi
-exec taler-wallet-cli testing run-integrationtests --suites merchant
+exec taler-harness run-integrationtests --suites merchant
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 ee45472f..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_ms" : 3600000},"default_pay_delay":{"d_ms": 3600000}}' \
- -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 '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 '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 '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 c1bc0d56..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-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,7 +17,7 @@
<http://www.gnu.org/licenses/>
*/
/**
- * @file test_merchant_api.c
+ * @file test_kyc_api.c
* @brief testcase to test exchange's HTTP API interface
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
* @author Christian Grothoff
@@ -43,8 +43,6 @@
*/
#define CONFIG_FILE "test_kyc_api.conf"
-#define PAYTO_I1 "payto://x-taler-bank/localhost/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,149 +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"),
- /* FIXME-#7052: this should ideally not be needed... */
- TALER_TESTING_cmd_merchant_get_order ("get-order",
- merchant_url,
- "create-proposal-1",
- TALER_MERCHANT_OSC_PAID,
- false,
- MHD_HTTP_OK),
- TALER_TESTING_cmd_merchant_kyc_get ("kyc-pending",
- merchant_url,
- NULL,
- NULL,
- EXCHANGE_URL,
- MHD_HTTP_ACCEPTED),
- TALER_TESTING_cmd_proof_kyc ("kyc-do",
- "kyc-pending",
- "pass",
- "state",
- MHD_HTTP_SEE_OTHER),
+ 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-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_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",
- 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",
- "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);
}
@@ -305,68 +460,28 @@ int
main (int argc,
char *const *argv)
{
- unsigned int ret;
-
- /* These environment variables get in the way... */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup ("test-kyc-api",
- "DEBUG",
- 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);
- 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;
-
+ payer_payto =
+ "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;
+ merchant_payto =
+ "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 eea5538c..bbbfc17c 100644
--- a/src/testing/test_kyc_api.conf
+++ b/src/testing/test_kyc_api.conf
@@ -1,143 +1,93 @@
# 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/
-
-
-#######################################################
-# 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 #
-###################################################
+[auditordb-postgres]
+CONFIG = postgres:///talercheck
[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/"
-
-
-KYC_MODE = OAUTH2
-
-KYC_WALLET_BALANCE_LIMIT = EUR:1
-
-KYC_WITHDRAW_PERIOD = "31 days"
-
-KYC_WITHDRAW_LIMIT = EUR:20
-
-[exchange-kyc-oauth2]
-
-KYC_OAUTH2_AUTH_URL = http://localhost:6666/oauth/v2/token
-KYC_OAUTH2_LOGIN_URL = http://localhost:6666/oauth/v2/login
-KYC_INFO_URL = http://localhost:6666/api/user/me
+STEFAN_ABS = "EUR:5"
+
+[kyc-provider-test-oauth2]
+COST = 0
+LOGIC = oauth2
+USER_TYPE = BUSINESS
+PROVIDED_CHECKS = DUMMY
+KYC_OAUTH2_VALIDITY = forever
+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
+REQUIRED_CHECKS = DUMMY
+THRESHOLD = EUR:0
+TIMEFRAME = 1d
-[exchangedb-postgres]
-CONFIG = "postgres:///talercheck"
-
+[kyc-legitimization-withdraw]
+OPERATION_TYPE = WITHDRAW
+REQUIRED_CHECKS = DUMMY
+THRESHOLD = EUR:20
+TIMEFRAME = 30d
-[auditordb-postgres]
-CONFIG = postgres:///talercheck
+[kyc-legitimization-balance-high]
+OPERATION_TYPE = BALANCE
+REQUIRED_CHECKS = DUMMY
+THRESHOLD = EUR:1
+[exchangedb-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"
+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 006fb644..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"
-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 b0f2b49e..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"
-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 8fc0f4ad..3f9136bc 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
@@ -24,6 +24,7 @@
* @author Marcello Stanisci
*/
#include "platform.h"
+#include <gnunet/gnunet_time_lib.h>
#include <taler/taler_util.h>
#include <taler/taler_signatures.h>
#include <taler/taler_exchange_service.h>
@@ -59,46 +60,37 @@
*/
static char *config_file;
-#define PAYTO_I1 "payto://x-taler-bank/localhost/3"
+#define PAYTO_I1 "payto://x-taler-bank/localhost/3?receiver-name=3"
/**
* Exchange base URL. Could also be taken from config.
*/
#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 +98,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 +117,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 +192,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 +211,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,
@@ -242,7 +245,8 @@ run (void *cls,
"create-proposal-4",
TALER_MERCHANT_OSC_UNPAID,
false,
- MHD_HTTP_OK),
+ MHD_HTTP_OK,
+ NULL),
TALER_TESTING_cmd_merchant_delete_order ("delete-order-4",
merchant_url,
"4",
@@ -303,9 +307,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,
@@ -314,10 +319,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,
@@ -327,9 +340,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,
@@ -354,6 +368,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",
@@ -375,13 +390,14 @@ run (void *cls,
false,
false,
false,
- MHD_HTTP_OK),
+ MHD_HTTP_PAYMENT_REQUIRED),
TALER_TESTING_cmd_merchant_get_order ("get-order-merchant-1",
merchant_url,
"create-proposal-1",
TALER_MERCHANT_OSC_CLAIMED,
false,
- MHD_HTTP_OK),
+ MHD_HTTP_OK,
+ NULL),
TALER_TESTING_cmd_wallet_poll_order_start ("poll-order-wallet-start-1",
merchant_url,
"create-proposal-1",
@@ -412,9 +428,16 @@ 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_OK,
+ MHD_HTTP_PAYMENT_REQUIRED,
NULL,
"poll-order-wallet-start-1x",
"1"),
@@ -422,7 +445,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",
@@ -430,12 +453,13 @@ 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,
false,
- MHD_HTTP_OK),
+ MHD_HTTP_OK,
+ NULL),
TALER_TESTING_cmd_merchant_get_orders ("get-orders-1-paid",
merchant_url,
MHD_HTTP_OK,
@@ -458,23 +482,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,
@@ -486,19 +512,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",
@@ -529,7 +555,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",
@@ -546,7 +572,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",
@@ -574,6 +600,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",
@@ -596,18 +636,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",
@@ -621,6 +663,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",
@@ -633,6 +676,7 @@ run (void *cls,
"",
NULL),
TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-p3",
+ cred.cfg,
merchant_url,
MHD_HTTP_OK,
"order-p3",
@@ -644,23 +688,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",
@@ -718,6 +786,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",
@@ -820,6 +889,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",
@@ -864,6 +934,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",
@@ -882,8 +953,8 @@ run (void *cls,
TALER_TESTING_cmd_check_bank_transfer (
"check_bank_transfer-paid-unincreased-refund",
EXCHANGE_URL,
- "EUR:8.98", /* '4.98 from above', plus 4.99 from 'pay-for-refund-1r'
- and MINUS 0.1 MINUS 0.9 PLUS 0.01 (deposit fee) from
+ "EUR:8.97", /* '4.98 from above', plus 4.99 from 'pay-for-refund-1r'
+ and MINUS 0.1 MINUS 0.9 from
'refund-increase-1r' and 'refund-increase-1r-2' */
exchange_payto,
merchant_payto),
@@ -894,9 +965,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",
@@ -984,207 +1059,41 @@ 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-10",
- "EUR:10.02"),
- cmd_exec_wirewatch ("wirewatch-10"),
+ cmd_transfer_to_exchange ("create-reserve-20",
+ "EUR:20.04"),
+ cmd_exec_wirewatch ("wirewatch-20"),
TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-10",
- "EUR:10.02",
+ "EUR:20.04",
payer_payto,
exchange_payto,
- "create-reserve-10"),
+ "create-reserve-20"),
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-10a",
- "create-reserve-10",
+ "create-reserve-20",
"EUR:5",
0,
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-10b",
- "create-reserve-10",
+ "create-reserve-20",
"EUR:5",
0,
MHD_HTTP_OK),
- TALER_TESTING_cmd_status ("withdraw-status-10",
- "create-reserve-10",
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-10c",
+ "create-reserve-20",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-10d",
+ "create-reserve-20",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_status ("withdraw-status-20",
+ "create-reserve-20",
"EUR:0",
MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_post_orders ("create-proposal-10",
+ cred.cfg,
merchant_url,
MHD_HTTP_OK,
"10",
@@ -1207,6 +1116,14 @@ run (void *cls,
"EUR:5",
"EUR:4.99",
NULL),
+ TALER_TESTING_cmd_merchant_pay_order ("pay-too-much-10",
+ merchant_url,
+ MHD_HTTP_CONFLICT,
+ "create-proposal-10",
+ "withdraw-coin-10c;withdraw-coin-10d",
+ "EUR:5",
+ "EUR:4.99",
+ NULL),
CMD_EXEC_AGGREGATOR ("run-aggregator-10"),
TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-9.97-10",
EXCHANGE_URL,
@@ -1241,6 +1158,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",
@@ -1249,7 +1167,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",
@@ -1272,343 +1190,826 @@ run (void *cls,
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",
- 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",
- "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,
+ 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_instances ("instances-empty",
+ TALER_TESTING_cmd_merchant_get_templates ("get-templates-empty",
merchant_url,
MHD_HTTP_OK,
NULL),
- TALER_TESTING_cmd_merchant_post_instances ("instance-create-default-setup",
+ TALER_TESTING_cmd_merchant_post_templates ("post-templates-t1",
merchant_url,
- "default",
- PAYTO_I1,
- "EUR",
+ "template-1",
+ "a template",
MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_post_instances ("instance-create-i1",
+ TALER_TESTING_cmd_merchant_post_templates ("post-templates-t1-idem",
merchant_url,
- "i1",
- PAYTO_I1,
- "EUR",
+ "template-1",
+ "a template",
MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_get_instances ("instances-get-i1",
+ TALER_TESTING_cmd_merchant_post_templates ("post-templates-t1-non-idem",
+ merchant_url,
+ "template-1",
+ "a different template",
+ MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_merchant_get_templates ("get-templates-t1",
merchant_url,
MHD_HTTP_OK,
- "instance-create-i1",
- "instance-create-default-setup",
+ "post-templates-t1",
NULL),
- TALER_TESTING_cmd_merchant_get_instance ("instances-get-i1",
+ TALER_TESTING_cmd_merchant_get_template ("get-template-t1",
merchant_url,
- "i1",
+ "template-1",
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",
+ "post-templates-t1"),
+ TALER_TESTING_cmd_merchant_post_templates ("post-templates-t2",
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,
+ "template-2",
+ "a template",
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_merchant_patch_instance (
- "instance-patch-i1-inactivate-account",
+ TALER_TESTING_cmd_merchant_patch_template (
+ "patch-templates-t2",
merchant_url,
- "i1",
- 1,
- 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,
+ "template-2",
+ "another template",
+ NULL,
+ 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_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",
+ TALER_TESTING_cmd_merchant_get_template ("get-template-t2",
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/",
- "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",
+ "template-2",
+ MHD_HTTP_OK,
+ "patch-templates-t2"),
+ TALER_TESTING_cmd_merchant_get_template ("get-template-nx",
merchant_url,
- "i2",
+ "template-nx",
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",
+ 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",
+ "otp-dev",
+ GNUNET_JSON_PACK (
+ 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",
+ merchant_url,
+ "template-amount",
+ "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")),
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_using_templates (
+ "using-templates-t1",
+ "post-templates-t1",
+ NULL,
+ merchant_url,
+ "1",
+ "summary-1",
+ "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,
- "i1",
+ "t1",
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",
+ TALER_TESTING_cmd_merchant_delete_template ("get-templates-empty",
+ merchant_url,
+ "template-1",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_using_templates (
+ "post-templates-t1-deleted",
+ "post-templates-t1",
+ NULL,
merchant_url,
- "default",
- PAYTO_I1,
- "EUR",
+ "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_get_products ("get-products-empty",
+
+ 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 ()
+ };
+
+ struct TALER_TESTING_Command webhooks[] = {
+ TALER_TESTING_cmd_merchant_get_webhooks ("get-webhooks-empty",
merchant_url,
MHD_HTTP_OK,
NULL),
- TALER_TESTING_cmd_merchant_post_products ("post-products-p1",
+ TALER_TESTING_cmd_merchant_post_webhooks ("post-webhooks-w1",
merchant_url,
- "product-1",
- "a product",
- "EUR:1",
+ "webhook-1",
+ "Paid",
MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_post_products ("post-products-p1-idem",
+ TALER_TESTING_cmd_merchant_post_webhooks ("post-webhooks-w1-idem",
merchant_url,
- "product-1",
- "a product",
- "EUR:1",
+ "webhook-1",
+ "Paid",
MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_post_products ("post-products-p1-non-idem",
+ TALER_TESTING_cmd_merchant_post_webhooks ("post-webhooks-w1-non-idem",
merchant_url,
- "product-1",
- "a different product",
- "EUR:1",
+ "webhook-1",
+ "Refund",
MHD_HTTP_CONFLICT),
- TALER_TESTING_cmd_merchant_get_products ("get-products-p1",
+ TALER_TESTING_cmd_merchant_get_webhooks ("get-webhooks-w1",
merchant_url,
MHD_HTTP_OK,
- "post-products-p1",
+ "post-webhooks-w1",
NULL),
- TALER_TESTING_cmd_merchant_get_product ("get-product-p1",
+ TALER_TESTING_cmd_merchant_get_webhook ("get-webhook-w1",
merchant_url,
- "product-1",
+ "webhook-1",
MHD_HTTP_OK,
- "post-products-p1"),
- TALER_TESTING_cmd_merchant_post_products ("post-products-p2",
+ "post-webhooks-w1"),
+ TALER_TESTING_cmd_merchant_post_webhooks ("post-webhooks-w2",
merchant_url,
- "product-2",
- "a product",
- "EUR:1",
+ "webhook-2",
+ "Paid",
MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_patch_product ("patch-products-p2",
+ TALER_TESTING_cmd_merchant_patch_webhook ("patch-webhooks-w2",
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),
+ "webhook-2",
+ "Refund2",
+ "http://localhost:38188/",
+ "POST",
+ "Authorization:WHWOXZXPLL",
+ "Amount",
MHD_HTTP_NO_CONTENT),
- TALER_TESTING_cmd_merchant_get_product ("get-product-p2",
+ TALER_TESTING_cmd_merchant_get_webhook ("get-webhook-w2",
merchant_url,
- "product-2",
+ "webhook-2",
MHD_HTTP_OK,
- "patch-products-p2"),
- TALER_TESTING_cmd_merchant_get_product ("get-product-nx",
+ "patch-webhooks-w2"),
+ TALER_TESTING_cmd_merchant_get_webhook ("get-webhook-nx",
merchant_url,
- "product-nx",
+ "webhook-nx",
MHD_HTTP_NOT_FOUND,
NULL),
- TALER_TESTING_cmd_merchant_patch_product ("patch-products-p3-nx",
+ TALER_TESTING_cmd_merchant_patch_webhook ("patch-webhooks-w3-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),
+ "webhook-3",
+ "Paid2",
+ "https://example.com",
+ "POST",
+ "Authorization:WHWOXZXPLL",
+ "Amount",
MHD_HTTP_NOT_FOUND),
- TALER_TESTING_cmd_merchant_delete_product ("get-products-empty",
+ TALER_TESTING_cmd_merchant_delete_webhook ("get-webhooks-empty",
merchant_url,
- "p1",
+ "w1",
MHD_HTTP_NOT_FOUND),
- TALER_TESTING_cmd_merchant_delete_product ("get-products-empty",
+ TALER_TESTING_cmd_merchant_delete_webhook ("get-webhooks-empty",
merchant_url,
- "product-1",
+ "webhook-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_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 tokens[] = {
+ /**
+ * Move money to the exchange's bank account.
+ */
+ cmd_transfer_to_exchange ("create-reserve-tokens",
+ "EUR:10.02"),
+ /**
+ * Make a reserve exist, according to the previous transfer.
+ */
+ cmd_exec_wirewatch ("wirewatch-1"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-tokens",
+ "EUR:10.02",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-tokens"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+ "create-reserve-tokens",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+ "create-reserve-tokens",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_tokenfamilies ("create-tokenfamily",
+ merchant_url,
+ MHD_HTTP_NO_CONTENT,
+ "subscription-1",
+ "Subscription",
+ "A subscription.",
+ NULL,
+ GNUNET_TIME_timestamp_get (),
+ GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_YEARS),
+ GNUNET_TIME_UNIT_MONTHS,
+ "subscription"),
+ TALER_TESTING_cmd_merchant_post_orders_choices ("create-order-with-choices",
+ cred.cfg,
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-tokenfamily",
+ "5-choices",
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
+ "EUR:5.0"),
+
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command commands[] = {
+ /* general setup */
+ 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",
+ merchant_url,
+ "i1",
+ "bob-the-merchant",
+ 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_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,
+ 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_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",
@@ -1619,19 +2020,24 @@ run (void *cls,
pay_abort),
TALER_TESTING_cmd_batch ("refund",
refund),
- TALER_TESTING_cmd_batch ("tip",
- tip),
+ TALER_TESTING_cmd_batch ("templates",
+ templates),
+ TALER_TESTING_cmd_batch ("webhooks",
+ webhooks),
TALER_TESTING_cmd_batch ("auth",
auth),
+ TALER_TESTING_cmd_batch ("repurchase",
+ repurchase),
+ TALER_TESTING_cmd_batch ("tokens",
+ tokens),
/**
* 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);
}
@@ -1639,73 +2045,37 @@ 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],
- "DEBUG",
- 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;
-
- 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;
+ {
+ 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;
+ exchange_payto =
+ "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;
+ 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 2cc26f25..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,28 +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",
- "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. */
@@ -359,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",
@@ -395,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",
@@ -403,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",
@@ -418,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);
}
@@ -432,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);
}
@@ -442,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],
- "DEBUG",
- 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
new file mode 100755
index 00000000..85857b4f
--- /dev/null
+++ b/src/testing/test_merchant_instance_auth.sh
@@ -0,0 +1,238 @@
+#!/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/>
+#
+
+# 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
+}
+
+. 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 ..." >&2
+
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ http://localhost:9966/management/instances \
+ -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
+ exit_fail "Expected 204, instance created. got: $STATUS" >&2
+fi
+
+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
+
+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 &
+# Install cleanup handler (except for kill -9)
+trap my_cleanup EXIT
+
+echo -n "Waiting for the merchant..." >&2
+# Wait for merchant to be available (usually the slowest)
+for n in $(seq 1 50)
+do
+ echo -n "." >&2
+ sleep 0.1
+ OK=0
+ # merchant
+ wget --waitretry=0 --timeout=1 http://localhost:9966/ -o /dev/null -O /dev/null >/dev/null || continue
+ OK=1
+ break
+done
+
+if [ "x$OK" != "x1" ]
+then
+ exit_fail "Failed to (re)start merchant backend"
+fi
+
+echo -n "Creating order to test auth is ok..." >&2
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ 'http://localhost:9966/private/orders' \
+ -H 'Authorization: Bearer '"$NEW_SECRET" \
+ -d '{"order":{"amount":"TESTKUDOS:1","summary":"payme"}}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200, order created. got: $STATUS"
+fi
+
+ORDER_ID=$(jq -e -r .order_id < "$LAST_RESPONSE")
+TOKEN=$(jq -e -r .token < "$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
+ 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")
+
+echo "OK order ${ORDER_ID} with ${TOKEN} and ${PAY_URL}" >&2
+
+echo -n "Configuring 'second' instance ..." >&2
+
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ -H 'Authorization: Bearer '"$NEW_SECRET" \
+ http://localhost:9966/management/instances \
+ -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
+ exit_fail "Expected 204, instance created. got: $STATUS"
+fi
+
+echo "OK" >&2
+
+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" \
+ 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
+ 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 "Test PASSED"
+
+exit 0
diff --git a/src/testing/test_merchant_instance_creation.sh b/src/testing/test_merchant_instance_creation.sh
index 04410a56..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_ms" : 3600000},"default_pay_delay":{"d_ms": 3600000}}' \
+ -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_ms" : 3600000},"default_pay_delay":{"d_ms": 3600000}}' \
+ -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_ms" : 3600000},"default_pay_delay":{"d_ms": 3600000}}' \
+ -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 e064457e..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_ms" : 3600000},"default_pay_delay":{"d_ms": 3600000}}' \
+ -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_ms" : 3600000},"default_pay_delay":{"d_ms": 3600000}}' \
+ -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 54d2559e..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_ms" : 3600000},"default_pay_delay":{"d_ms": 3600000}}' \
+ -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 b57efc95..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_ms" : 50000},"default_pay_delay":{"d_ms": 60000}}' \
+ -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 cca646cf..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 '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_ms" : 50000},"default_pay_delay":{"d_ms": 60000}}' \
+ "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_ms" : 50000},"default_pay_delay":{"d_ms": 60000}}' \
+ 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 582394b4..2336ad4e 100755
--- a/src/testing/test_merchant_order_creation.sh
+++ b/src/testing/test_merchant_order_creation.sh
@@ -3,28 +3,87 @@
set -eu
-. initialize_taler_system.sh
-
-echo -n "First prepare wallet with coins..."
-rm $WALLET_DB
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api 'withdrawTestBalance' \
+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 -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 "."
+# FIXME-MS: add logic to have nexus check immediately here.
+# sleep 10
+echo -n "."
+# NOTE: once libeufin can do long-polling, we should
+# be able to reduce the delay here and run wirewatch
+# always in the background via setup
+taler-exchange-wirewatch \
+ -a "$ACCOUNT" \
+ -L "INFO" \
+ -c "$CONF" \
+ -t &> taler-exchange-wirewatch.out
+echo -n "."
+taler-wallet-cli \
+ --wallet-db="$WALLET_DB" \
+ run-until-done \
+ 2>wallet-withdraw-finish-1.err \
+ >wallet-withdraw-finish-1.out
echo " OK"
-CURRENCY_COUNT=$(taler-wallet-cli --wallet-db=$WALLET_DB balance | jq '.balances|length')
-if [ "$CURRENCY_COUNT" != "1" ]
+CURRENCY_COUNT=$(taler-wallet-cli --wallet-db="$WALLET_DB" balance | jq '.balances|length')
+if [ "$CURRENCY_COUNT" = "0" ]
then
- echo 'should have one currency, withdrawal failed. check log.'
- exit 1
+ exit_fail "Expected least one currency, withdrawal failed. check log."
fi
#
@@ -33,35 +92,77 @@ fi
echo -n "Configuring merchant instance ..."
-# 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_ms" : 50000},"default_pay_delay":{"d_ms": 60000}}' \
+ -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"
+
+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":["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_ms" : 50000},"default_pay_delay":{"d_ms": 60000}}' \
+ "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'
#
@@ -69,125 +170,189 @@ 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 "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
+echo "OK"
#
# CREATE AN ORDER WITHOUT TOKEN WITH FULLFILMENT URL
#
echo -n "Creating order without TOKEN and fullfilment URL..."
-STATUS=$(curl 'http://localhost:9966/instances/default/private/orders' \
- -d '{"create_token":false,"order":{"fulfillment_url":"go_here_please", "amount":"TESTKUDOS:7","summary":"3","products":[{"description":"desct","image":"'$RANDOM_IMG'","price":"TESTKUDOS:1","taxes":[],"unit":"u","quantity":1}]}}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+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_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
+echo "OK"
+
+#
+# CREATE TOKEN FAMILY AND V1 ORDER WITH CHOICES
+#
+echo -n "Creating token family ..."
+NOW=$(date +%s)
+IN_A_YEAR=$((NOW + 31536000))
+STATUS=$(curl 'http://localhost:9966/private/tokenfamilies' \
+ -d '{"slug":"test-sub","kind":"subscription","description":"Test token family","name":"Test Subscription","valid_after":{"t_s":'$NOW'},"valid_before":{"t_s":'$IN_A_YEAR'},"duration": {"d_us": 2592000000}}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "204" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 204, token family created. got: $STATUS"
+fi
+
+echo " OK"
+
+echo -n "Creating v1 order with token family ..."
+STATUS=$(curl 'http://localhost:9966/private/orders' \
+ -d '{"order":{"version":"1","amount":"TESTKUDOS:7","summary":"with_subscription","fulfillment_message":"Paid successfully","choices":[{"inputs":[{"kind":"token","count":1,"token_family_slug":"test-sub","valid_after":{"t_s":'$NOW'}}],"outputs":[{"kind":"token","count":1,"token_family_slug":"test-sub","valid_after":{"t_s":'$NOW'}}]}]}}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200, order created. got: $STATUS"
+fi
+
+echo " OK"
+
+echo -n "Claming order with token family ..."
+
+ORDER_ID=$(jq -r .order_id < "$LAST_RESPONSE")
+TOKEN=$(jq -r .token < "$LAST_RESPONSE")
+
+STATUS=$(curl http://localhost:9966/orders/"$ORDER_ID"/claim \
+ -d '{"nonce":"","token":"'"$TOKEN"'"}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200, order claimed. got: $STATUS"
+fi
+
+echo " OK"
+
+# echo -n "Fetching pay URL for order ..."
+# STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+# -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+# if [ "$STATUS" != "200" ]
+# then
+# jq . < "$LAST_RESPONSE"
+# exit_fail "Expected 200, getting order info before claming it. got: $STATUS"
+# fi
+
+# PAY_URL=$(jq -e -r .taler_pay_uri < "$LAST_RESPONSE")
+
+# echo " OK"
+
+# NOW=$(date +%s)
+
+# echo -n "Pay for order ${PAY_URL} ..."
+# taler-wallet-cli --no-throttle --wallet-db="$WALLET_DB" handle-uri "${PAY_URL}" -y 2> wallet-pay1.err > wallet-pay1.log
+# taler-wallet-cli --no-throttle --wallet-db="$WALLET_DB" run-until-done 2> wallet-finish-pay1.err > wallet-finish-pay1.log
+# NOW2=$(date +%s)
+# echo " OK (took $(( NOW2 - NOW )) secs )"
#
# CREATE ORDER WITH NON-INVENTORY AND CHECK
#
echo -n "Creating order with non-inventory products..."
-STATUS=$(curl 'http://localhost:9966/instances/default/private/orders' \
- -d '{"order":{"amount":"TESTKUDOS:7","summary":"3","products":[{"description":"desct","image":"'$RANDOM_IMG'","price":"TESTKUDOS:1","taxes":[],"unit":"u","quantity":1}]}}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+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"
#
@@ -195,65 +360,62 @@ echo OK
#
echo -n "Creating product..."
-STATUS=$(curl 'http://localhost:9966/instances/default/private/products' \
- -d '{"product_id":"2","description":"product with id 2 and price :15","price":"TESTKUDOS:15","total_stock":2,"description_i18n":{},"unit":"","image":"'$RANDOM_IMG'","taxes":[],"address":{},"next_restock":{"t_ms":"never"}}' \
+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' \
- -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_ms":"never"}}' \
+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
#
@@ -261,143 +423,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 ..."
-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 )"
+echo -n "Pay first order ${PAY_URL} ..."
+taler-wallet-cli --no-throttle --wallet-db="$WALLET_DB" handle-uri "${PAY_URL}" -y 2> wallet-pay1.err > wallet-pay1.log
+taler-wallet-cli --no-throttle --wallet-db="$WALLET_DB" run-until-done 2> wallet-finish-pay1.err > wallet-finish-pay1.log
+NOW2=$(date +%s)
+echo " OK (took $(( NOW2 - NOW )) secs )"
-STATUS=$(curl "http://localhost:9966/instances/default/private/orders/${ORDER_ID}" \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+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_ms < $LAST_RESPONSE`
-WIRE_DEADLINE=`jq -r .contract_terms.wire_transfer_deadline.t_ms < $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 /1000) - $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..."
-
-# First, extract the wire transfer data from the bank.
-# As there is no "nice" API, we do this by dumping the
-# bank database and grabbing the 'right' wire transfer,
-# which is the one outgoing from the exchange (account 2).
-BANKDATA=`taler-bank-manage -c $CONF django dumpdata 2>/dev/null | tail -n1 | jq '.[] | select(.model=="app.banktransaction")' | jq 'select(.fields.debit_account==2)'`
-SUBJECT=`echo $BANKDATA | jq -r .fields.subject`
-WTID=`echo $SUBJECT | awk '{print $1}'`
-WURL=`echo $SUBJECT | awk '{print $2}'`
-CREDIT_AMOUNT=`echo $BANKDATA | jq -r .fields.amount`
-TARGET=`echo $BANKDATA | jq -r .fields.credit_account`
-# 'TARGET' is now the numeric value of the account, we need to get the actual account *name*:
-BANKADATA=`taler-bank-manage -c $CONF django dumpdata 2>/dev/null | tail -n1 | jq '.[] | select(.model=="auth.user")' | jq 'select(.pk=='$TARGET')'`
-ACCOUNT_NAME=`echo $BANKADATA | jq -r .fields.username`
-TARGET_PAYTO="payto://x-taler-bank/localhost:8082/$ACCOUNT_NAME"
+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
@@ -405,23 +547,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
@@ -429,150 +571,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
-#
+echo -n "Testing taler-merchant-exchange ..."
+set -e
+taler-merchant-exchange -L INFO -c "$CONF" -t &> taler-merchant-exchange.log
+echo " OK"
-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 "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"
-echo -n "Removing password from account 43 ..."
-taler-bank-manage -c $CONF --with-db postgres:///$TALER_DB django changepassword_unsafe 43 x >/dev/null 2>/dev/null
-
-ACCOUNT_PASSWORD="43:x"
-BANK_HOST="localhost:8082"
-
-STATUS=$(curl "http://$ACCOUNT_PASSWORD@$BANK_HOST/accounts/43" \
- -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
+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
-
-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
-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 78b95d4e..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,215 +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/"
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":["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_ms" : 50000},"default_pay_delay":{"d_ms": 60000}}' \
+ "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 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" \
- -d '{"lock_uuid":"luck","duration":{"d_ms": 100000},"quantity":10}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+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")
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" \
- -d '{"lock_uuid":"luck","duration":{"d_ms": 100000},"quantity":1}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
+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")
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 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 'withdrawTestBalance' \
+echo -n "First withdraw wallet ..."
+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" \
--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 "."
+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 "."
-NOW=`date +%s`
+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 "${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 8f34a038..00000000
--- a/src/testing/test_merchant_reserve_creation.sh
+++ /dev/null
@@ -1,172 +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/>
-#
-
-. initialize_taler_system.sh
-
-echo -n "Configuring merchant instance ..."
-
-# create 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/'$BANK_URL'/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_ms" : 50000},"default_pay_delay":{"d_ms": 60000}}' \
- -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 "creating reserve ..."
-
-STATUS=$(curl 'http://localhost:9966/instances/default/private/reserves' \
- -d '{"initial_balance":"TESTKUDOS:2","exchange_url":"'$EXCHANGE_URL'","wire_method":"x-taler-bank"}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
-
-
-if [ "$STATUS" != "200" ]
-then
- echo 'should respond ok, reserve created. got:' $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 be funded if we just created. got:' $STATUS 'is founded: ' $FUNDED
- cat $LAST_RESPONSE
- exit 1
-fi
-
-
-echo -n Wire transferring...
-STATUS=$(curl http://Exchange:x@localhost:$BANK_PORT/taler-wire-gateway/Exchange/admin/add-incoming \
- -d '{"reserve_pub":"'$RESERVE_PUB'","debit_account":"payto://x-taler-bank/localhost:'$BANK_PORT'/43","amount":"TESTKUDOS:2"}' \
- -w "%{http_code}" -s -o $LAST_RESPONSE)
-
-if [ "$STATUS" != "200" ]
-then
- echo 'should respond ok, wire transfer executed. got:' $STATUS
- exit 1
-fi
-
-echo OK
-
-taler-exchange-wirewatch -c $CONF -t -L INFO
-
-#there seems to be a race condition here so we wait
-sleep 1
-
-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":"x-taler-bank"}' \
- -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 b677a062..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 '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 "."
+# 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,100 +99,143 @@ echo " OK"
#
echo -n "Configuring merchant default instance ..."
-
+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":["payto://x-taler-bank/localhost:8082/Tor","payto://x-taler-bank/localhost:8082/GNUnet"],"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_ms" : 50000},"default_pay_delay":{"d_ms": 60000}}' \
+ -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 -n "Configuring merchant test instance ..."
+echo "OK"
+echo -n "Configuring merchant test instance ..."
# 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/Survey","payto://x-taler-bank/localhost:8082/Tutorial"],"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_ms" : 50000},"default_pay_delay":{"d_ms": 60000}}' \
+ -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_ms < $LAST_RESPONSE`
-WIRE_DEADLINE=`jq -r .contract_terms.wire_transfer_deadline.t_ms < $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 /1000) - $NOW ))`
+TO_SLEEP=$(( WIRE_DEADLINE - NOW ))
echo "waiting $TO_SLEEP secs for wire transfer"
echo -n "Perform wire transfers ..."
@@ -143,114 +245,109 @@ echo " DONE"
echo -n "Obtaining wire transfer details from bank..."
-# First, extract the wire transfer data from the bank.
-# As there is no "nice" API, we do this by dumping the
-# bank database and grabbing the 'right' wire transfer,
-# which is the one outgoing from the exchange (account 2).
-export BANKDATA=`taler-bank-manage -c $CONF django dumpdata 2>/dev/null | tail -n1 | jq '.[] | select(.model=="app.banktransaction")' | jq 'select(.fields.debit_account==2)'`
-export SUBJECT=`echo $BANKDATA | jq -r .fields.subject`
-export WTID=`echo $SUBJECT | awk '{print $1}'`
-export WURL=`echo $SUBJECT | awk '{print $2}'`
-export CREDIT_AMOUNT=`echo $BANKDATA | jq -r .fields.amount`
-export TARGET=`echo $BANKDATA | jq -r .fields.credit_account`
-# 'TARGET' is now the numeric value of the account, we need to get the actual account *name*:
-BANKADATA=`taler-bank-manage -c $CONF django dumpdata 2>/dev/null | tail -n1 | jq '.[] | select(.model=="auth.user")' | jq 'select(.pk=='$TARGET')'`
-ACCOUNT_NAME=`echo $BANKADATA | jq -r .fields.username`
-TARGET_PAYTO="payto://x-taler-bank/localhost:8082/$ACCOUNT_NAME"
-
+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" > /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"
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' \
- -d '{"credit_amount":"'$CREDIT_AMOUNT'","wtid":"'$WTID'","payto_uri":"payto://x-taler-bank/localhost:8082/Tor","exchange_url":"'$WURL'"}' \
+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"
@@ -259,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 "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 "================== 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 '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 "================== 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 20fee4b8..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,37 +67,46 @@ 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]
-DATABASE = postgres:///taler-auditor-basedb
-MAX_DEBT = TESTKUDOS:50.0
-MAX_DEBT_BANK = TESTKUDOS:100000.0
-HTTP_PORT = 8082
-SUGGESTED_EXCHANGE = http://localhost:8081/
-SUGGESTED_EXCHANGE_PAYTO = payto://x-taler-bank/localhost/2
-ALLOW_REGISTRATIONS = YES
-SERVE = http
-
[exchangedb]
IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
LEGAL_RESERVE_EXPIRATION_TIME = 7 years
[exchange-account-1]
-PAYTO_URI = payto://x-taler-bank/localhost/Exchange
-enable_debit = yes
-enable_credit = yes
+# PAYTO_URI comes by patching.
+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
+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 9c41d53e..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
@@ -114,10 +114,8 @@ build_coins (struct TALER_MERCHANT_AbortCoin **ac,
return GNUNET_SYSERR;
}
}
- // FIXME: ci not used!?
{
const struct TALER_TESTING_Command *coin_cmd;
- const char **exchange_url;
coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
token);
@@ -136,15 +134,14 @@ build_coins (struct TALER_MERCHANT_AbortCoin **ac,
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_coin_priv (coin_cmd,
- 0,
+ ci,
&coin_priv));
GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
&icoin->coin_pub.eddsa_pub);
}
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;
@@ -166,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);
}
@@ -226,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;
@@ -254,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);
@@ -300,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 !=
@@ -321,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 d5d8b283..163538ca 100644
--- a/src/testing/testing_api_cmd_delete_order.c
+++ b/src/testing/testing_api_cmd_delete_order.c
@@ -121,11 +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,
- &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
new file mode 100644
index 00000000..6227c543
--- /dev/null
+++ b/src/testing/testing_api_cmd_delete_template.c
@@ -0,0 +1,179 @@
+/*
+ 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_template.c
+ * @brief command to test DELETE /templates/$ID
+ * @author Priscilla HUANG
+ */
+#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 /templates/$ID" CMD.
+ */
+struct DeleteTemplateState
+{
+
+ /**
+ * Handle for a "DELETE template" request.
+ */
+ struct TALER_MERCHANT_TemplateDeleteHandle *tdh;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * ID of the template to run DELETE for.
+ */
+ const char *template_id;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a /delete/templates/$ID operation.
+ *
+ * @param cls closure for this function
+ * @param hr response being processed
+ */
+static void
+delete_template_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct DeleteTemplateState *dis = cls;
+
+ dis->tdh = NULL;
+ if (dis->http_status != hr->http_status)
+ {
+ TALER_TESTING_unexpected_status_with_body (dis->is,
+ hr->http_status,
+ dis->http_status,
+ hr->reply);
+ 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 template.\n",
+ hr->http_status);
+ }
+ TALER_TESTING_interpreter_next (dis->is);
+}
+
+
+/**
+ * Run the "DELETE template" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+delete_template_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct DeleteTemplateState *dis = cls;
+
+ dis->is = is;
+ 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);
+}
+
+
+/**
+ * Free the state of a "DELETE template" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+delete_template_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct DeleteTemplateState *dis = cls;
+
+ if (NULL != dis->tdh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "DELETE /templates/$ID operation did not complete\n");
+ TALER_MERCHANT_template_delete_cancel (dis->tdh);
+ }
+ GNUNET_free (dis);
+}
+
+
+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)
+{
+ struct DeleteTemplateState *dis;
+
+ dis = GNUNET_new (struct DeleteTemplateState);
+ dis->merchant_url = merchant_url;
+ dis->template_id = template_id;
+ dis->http_status = http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = dis,
+ .label = label,
+ .run = &delete_template_run,
+ .cleanup = &delete_template_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_delete_template.c */
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
new file mode 100644
index 00000000..12654fe6
--- /dev/null
+++ b/src/testing/testing_api_cmd_delete_webhook.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_webhook.c
+ * @brief command to test DELETE /webhooks/$ID
+ * @author Priscilla HUANG
+ */
+#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 /webhooks/$ID" CMD.
+ */
+struct DeleteWebhookState
+{
+
+ /**
+ * Handle for a "DELETE webhook" request.
+ */
+ struct TALER_MERCHANT_WebhookDeleteHandle *wdh;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * ID of the webhook to run DELETE for.
+ */
+ const char *webhook_id;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a /delete/webhooks/$ID operation.
+ *
+ * @param cls closure for this function
+ * @param hr response being processed
+ */
+static void
+delete_webhook_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct DeleteWebhookState *dis = cls;
+
+ dis->wdh = 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 webhook.\n",
+ hr->http_status);
+ }
+ TALER_TESTING_interpreter_next (dis->is);
+}
+
+
+/**
+ * Run the "DELETE webhook" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+delete_webhook_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct DeleteWebhookState *dis = cls;
+
+ dis->is = is;
+ 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);
+}
+
+
+/**
+ * Free the state of a "DELETE webhook" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+delete_webhook_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct DeleteWebhookState *dis = cls;
+
+ if (NULL != dis->wdh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "DELETE /webhooks/$ID operation did not complete\n");
+ TALER_MERCHANT_webhook_delete_cancel (dis->wdh);
+ }
+ GNUNET_free (dis);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_webhook (const char *label,
+ const char *merchant_url,
+ const char *webhook_id,
+ unsigned int http_status)
+{
+ struct DeleteWebhookState *dis;
+
+ dis = GNUNET_new (struct DeleteWebhookState);
+ dis->merchant_url = merchant_url;
+ dis->webhook_id = webhook_id;
+ dis->http_status = http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = dis,
+ .label = label,
+ .run = &delete_webhook_run,
+ .cleanup = &delete_webhook_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_delete_webhook.c */
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 f328cc75..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,19 +71,12 @@ 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)
{
- /* FIXME, deeper checks should be implemented here (for accounts). */
struct GetInstanceState *gis = cls;
const struct TALER_TESTING_Command *instance_cmd;
@@ -117,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;
}
@@ -332,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);
}
@@ -354,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);
}
@@ -386,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,
@@ -400,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 5abe92ab..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,42 +395,36 @@ 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)
{
- /* FIXME, deeper checks should be implemented here. */
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
- // check if the data returned matches that from the POST / PATCH
+ // FIXME: use order references to check if the data returned matches that from the POST / PATCH
break;
default:
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);
@@ -461,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 03b6a071..00000000
--- a/src/testing/testing_api_cmd_get_reserve.c
+++ /dev/null
@@ -1,333 +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[])
-{
- /* FIXME, deeper checks should be implemented here. */
- 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:
- // FIXME: use grs->reserve_reference here to
- // check if the data returned matches that from the POST / PATCH
- {
- 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 d56b9ce5..00000000
--- a/src/testing/testing_api_cmd_get_reserves.c
+++ /dev/null
@@ -1,273 +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[])
-{
- /* FIXME, deeper checks should be implemented here. */
- 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;
- }
- // FIXME: 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
new file mode 100644
index 00000000..377ffe44
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_template.c
@@ -0,0 +1,242 @@
+/*
+ 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_template.c
+ * @brief command to test GET /templates/$ID
+ * @author Priscilla HUANG
+ */
+#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 template" CMD.
+ */
+struct GetTemplateState
+{
+
+ /**
+ * Handle for a "GET template" request.
+ */
+ struct TALER_MERCHANT_TemplateGetHandle *igh;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * ID of the template to run GET for.
+ */
+ const char *template_id;
+
+ /**
+ * Reference for a POST or PATCH /templates CMD (optional).
+ */
+ const char *template_reference;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a /get/templates/$ID operation.
+ *
+ * @param cls closure for this function
+ * @param tgr HTTP response details
+ */
+static void
+get_template_cb (void *cls,
+ const struct TALER_MERCHANT_TemplateGetResponse *tgr)
+{
+ struct GetTemplateState *gis = cls;
+ const struct TALER_TESTING_Command *template_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;
+
+ template_cmd = TALER_TESTING_interpreter_lookup_command (
+ gis->is,
+ gis->template_reference);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_template_description (template_cmd,
+ &expected_description))
+ TALER_TESTING_interpreter_fail (gis->is);
+ if (0 != strcmp (tgr->details.ok.template_description,
+ expected_description))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Template description does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ }
+ {
+ const char *expected_otp_id;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_otp_id (template_cmd,
+ &expected_otp_id))
+ TALER_TESTING_interpreter_fail (gis->is);
+ 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 pos_key `%s' does not match `%s'\n",
+ tgr->details.ok.otp_id,
+ expected_otp_id);
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ }
+ {
+ const json_t *expected_template_contract;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_template_contract (template_cmd,
+ &expected_template_contract))
+ TALER_TESTING_interpreter_fail (gis->is);
+ if (1 != json_equal (tgr->details.ok.template_contract,
+ expected_template_contract))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Template contract 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 template" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_template_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct GetTemplateState *gis = cls;
+
+ gis->is = is;
+ 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);
+}
+
+
+/**
+ * Free the state of a "GET template" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_template_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct GetTemplateState *gis = cls;
+
+ if (NULL != gis->igh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "GET /templates/$ID operation did not complete\n");
+ TALER_MERCHANT_template_get_cancel (gis->igh);
+ }
+ GNUNET_free (gis);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_template (const char *label,
+ const char *merchant_url,
+ const char *template_id,
+ unsigned int http_status,
+ const char *template_reference)
+{
+ struct GetTemplateState *gis;
+
+ gis = GNUNET_new (struct GetTemplateState);
+ gis->merchant_url = merchant_url;
+ gis->template_id = template_id;
+ gis->http_status = http_status;
+ gis->template_reference = template_reference;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = gis,
+ .label = label,
+ .run = &get_template_run,
+ .cleanup = &get_template_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_get_template.c */
diff --git a/src/testing/testing_api_cmd_get_templates.c b/src/testing/testing_api_cmd_get_templates.c
new file mode 100644
index 00000000..bc971dc2
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_templates.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_templates.c
+ * @brief command to test GET /templates
+ * @author Priscilla HUANG
+ */
+#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 templates" CMD.
+ */
+struct GetTemplatesState
+{
+
+ /**
+ * Handle for a "GET template" request.
+ */
+ struct TALER_MERCHANT_TemplatesGetHandle *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 template references.
+ */
+ const char **templates;
+
+ /**
+ * Length of @e templates.
+ */
+ unsigned int templates_length;
+
+};
+
+
+/**
+ * Callback for a GET /templates operation.
+ *
+ * @param cls closure for this function
+ * @param tgr response details
+ */
+static void
+get_templates_cb (void *cls,
+ const struct TALER_MERCHANT_TemplatesGetResponse *tgr)
+{
+ struct GetTemplatesState *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.templates_length != gis->templates_length)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Length of templates found does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ for (unsigned int i = 0; i < gis->templates_length; ++i)
+ {
+ const struct TALER_TESTING_Command *template_cmd;
+
+ template_cmd = TALER_TESTING_interpreter_lookup_command (
+ gis->is,
+ gis->templates[i]);
+
+ {
+ const char *template_id;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_template_id (template_cmd,
+ &template_id))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not fetch template id\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ if (0 != strcmp (tgr->details.ok.templates[i].template_id,
+ template_id))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Template 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 /templates" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_templates_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct GetTemplatesState *gis = cls;
+
+ gis->is = is;
+ gis->igh = TALER_MERCHANT_templates_get (
+ TALER_TESTING_interpreter_get_context (is),
+ gis->merchant_url,
+ &get_templates_cb,
+ gis);
+ GNUNET_assert (NULL != gis->igh);
+}
+
+
+/**
+ * Free the state of a "GET template" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_templates_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct GetTemplatesState *gis = cls;
+
+ if (NULL != gis->igh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "GET /templates operation did not complete\n");
+ TALER_MERCHANT_templates_get_cancel (gis->igh);
+ }
+ GNUNET_array_grow (gis->templates,
+ gis->templates_length,
+ 0);
+ GNUNET_free (gis);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_templates (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ ...)
+{
+ struct GetTemplatesState *gis;
+
+ gis = GNUNET_new (struct GetTemplatesState);
+ 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->templates,
+ gis->templates_length,
+ clabel);
+ }
+ va_end (ap);
+ }
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = gis,
+ .label = label,
+ .run = &get_templates_run,
+ .cleanup = &get_templates_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_get_templates.c */
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
new file mode 100644
index 00000000..aef6c555
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_webhook.c
@@ -0,0 +1,285 @@
+/*
+ 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_webhook.c
+ * @brief command to test GET /webhooks/$ID
+ * @author Priscilla HUANG
+ */
+#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 webhook" CMD.
+ */
+struct GetWebhookState
+{
+
+ /**
+ * Handle for a "GET webhook" request.
+ */
+ struct TALER_MERCHANT_WebhookGetHandle *igh;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * ID of the webhook to run GET for.
+ */
+ const char *webhook_id;
+
+ /**
+ * Reference for a POST or PATCH /webhooks CMD (optional).
+ */
+ const char *webhook_reference;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a /get/webhooks/$ID operation.
+ *
+ * @param cls closure for this function
+ * @param hr HTTP response details
+ * @param event_type event of the webhook
+ * @param url use by the customer
+ * @param http_method method use by the merchant
+ * @param header_template of the webhook
+ * @param body_template of the webhook
+ */
+static void
+get_webhook_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const char *event_type,
+ const char *url,
+ const char *http_method,
+ const char *header_template,
+ const char *body_template)
+{
+ struct GetWebhookState *gis = cls;
+ const struct TALER_TESTING_Command *webhook_cmd;
+
+ gis->igh = NULL;
+ if (gis->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 (gis->is));
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ const char *expected_event_type;
+
+ webhook_cmd = TALER_TESTING_interpreter_lookup_command (
+ gis->is,
+ gis->webhook_reference);
+ if (GNUNET_OK !=
+ 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))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Event type does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ }
+ {
+ 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))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "URL does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ }
+ {
+ 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))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "http_method does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ }
+ {
+ 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 ( ( (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");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ }
+ {
+ 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 ( ( (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");
+ 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 webhook" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_webhook_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct GetWebhookState *gis = cls;
+
+ gis->is = is;
+ gis->igh = TALER_MERCHANT_webhook_get (TALER_TESTING_interpreter_get_context (
+ is),
+ gis->merchant_url,
+ gis->webhook_id,
+ &get_webhook_cb,
+ gis);
+ GNUNET_assert (NULL != gis->igh);
+}
+
+
+/**
+ * Free the state of a "GET webhook" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_webhook_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct GetWebhookState *gis = cls;
+
+ if (NULL != gis->igh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "GET /webhooks/$ID operation did not complete\n");
+ TALER_MERCHANT_webhook_get_cancel (gis->igh);
+ }
+ GNUNET_free (gis);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_webhook (const char *label,
+ const char *merchant_url,
+ const char *webhook_id,
+ unsigned int http_status,
+ const char *webhook_reference)
+{
+ struct GetWebhookState *gis;
+
+ gis = GNUNET_new (struct GetWebhookState);
+ gis->merchant_url = merchant_url;
+ gis->webhook_id = webhook_id;
+ gis->http_status = http_status;
+ gis->webhook_reference = webhook_reference;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = gis,
+ .label = label,
+ .run = &get_webhook_run,
+ .cleanup = &get_webhook_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_get_webhook.c */
diff --git a/src/testing/testing_api_cmd_get_webhooks.c b/src/testing/testing_api_cmd_get_webhooks.c
new file mode 100644
index 00000000..56bf43e8
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_webhooks.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 General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received 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_webhooks.c
+ * @brief command to test GET /webhooks
+ * @author Priscilla HUANG
+ */
+#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 webhooks" CMD.
+ */
+struct GetWebhooksState
+{
+
+ /**
+ * Handle for a "GET webhook" request.
+ */
+ struct TALER_MERCHANT_WebhooksGetHandle *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 webhook references.
+ */
+ const char **webhooks;
+
+ /**
+ * Length of @e webhooks.
+ */
+ unsigned int webhooks_length;
+
+};
+
+
+/**
+ * Callback for a GET /webhooks operation.
+ *
+ * @param cls closure for this function
+ * @param wgr response details
+ */
+static void
+get_webhooks_cb (void *cls,
+ const struct TALER_MERCHANT_WebhooksGetResponse *wgr)
+{
+ struct GetWebhooksState *gis = cls;
+
+ gis->igh = NULL;
+ if (gis->http_status != wgr->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ wgr->hr.http_status,
+ (int) wgr->hr.ec,
+ TALER_TESTING_interpreter_get_current_label (gis->is));
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ switch (wgr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ if (wgr->details.ok.webhooks_length != gis->webhooks_length)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Length of webhooks found does not match\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ for (unsigned int i = 0; i < gis->webhooks_length; ++i)
+ {
+ const struct TALER_TESTING_Command *webhook_cmd;
+
+ webhook_cmd = TALER_TESTING_interpreter_lookup_command (
+ gis->is,
+ gis->webhooks[i]);
+
+ {
+ const char *webhook_id;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_webhook_id (webhook_cmd,
+ &webhook_id))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not fetch webhook id\n");
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ if (0 != strcmp (wgr->details.ok.webhooks[i].webhook_id,
+ webhook_id))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Webhook 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",
+ wgr->hr.http_status,
+ wgr->hr.ec);
+ }
+ TALER_TESTING_interpreter_next (gis->is);
+}
+
+
+/**
+ * Run the "GET /webhooks" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_webhooks_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct GetWebhooksState *gis = cls;
+
+ gis->is = is;
+ gis->igh = TALER_MERCHANT_webhooks_get (
+ TALER_TESTING_interpreter_get_context (is),
+ gis->merchant_url,
+ &get_webhooks_cb,
+ gis);
+ GNUNET_assert (NULL != gis->igh);
+}
+
+
+/**
+ * Free the state of a "GET webhook" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_webhooks_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct GetWebhooksState *gis = cls;
+
+ if (NULL != gis->igh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "GET /webhooks operation did not complete\n");
+ TALER_MERCHANT_webhooks_get_cancel (gis->igh);
+ }
+ GNUNET_array_grow (gis->webhooks,
+ gis->webhooks_length,
+ 0);
+ GNUNET_free (gis);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_webhooks (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ ...)
+{
+ struct GetWebhooksState *gis;
+
+ gis = GNUNET_new (struct GetWebhooksState);
+ 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->webhooks,
+ gis->webhooks_length,
+ clabel);
+ }
+ va_end (ap);
+ }
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = gis,
+ .label = label,
+ .run = &get_webhooks_run,
+ .cleanup = &get_webhooks_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_get_webhooks.c */
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 f35a0028..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,16 +131,45 @@ 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;
const char *end;
char *dec;
const char *eq;
+ 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);
@@ -148,7 +182,7 @@ kyc_get_cb (void *cls,
(void) GNUNET_STRINGS_urldecode (tok,
toklen,
&dec);
- eq = strrchr (dec, '/');
+ eq = strstr (dec, "/kyc-proof/");
if (NULL == eq)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -158,21 +192,33 @@ kyc_get_cb (void *cls,
GNUNET_free (dec);
TALER_TESTING_FAIL (cs->is);
}
- eq++;
+ 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);
+ TALER_TESTING_FAIL (cs->is);
+ }
+ eq += strlen ("&state=");
+ nq = strchr (eq, '&');
+ if (NULL == nq)
+ nq = eq + strlen (eq);
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (eq,
- strlen (eq),
+ nq - eq,
&cs->h_payto,
sizeof (cs->h_payto)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Received unexpected KYC URL `%s' (%s)\n",
+ "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;
}
@@ -219,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,
@@ -227,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);
}
@@ -257,7 +305,7 @@ kyc_get_traits (void *cls,
{
struct KycGetState *cs = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_h_payto (
+ TALER_TESTING_make_trait_h_payto (
&cs->h_payto),
TALER_TESTING_trait_end ()
};
@@ -270,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;
@@ -285,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 85ba174f..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;
@@ -130,51 +152,59 @@ apply_forget (void *cls,
* Callback to process a GET /orders/$ID request
*
* @param cls closure
- * @param hr HTTP response details
- * @param osr order status response details (on success)
+ * @param osr order status response details
*/
static void
merchant_get_order_cb (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
const struct TALER_MERCHANT_OrderStatusResponse *osr)
{
- /* FIXME, deeper checks should be implemented here. */
struct MerchantGetOrderState *gos = cls;
gos->ogh = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"GET /private/orders/$ID completed with status %u\n",
- hr->http_status);
- if (gos->http_status != hr->http_status)
+ osr->hr.http_status);
+ if (gos->http_status != osr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
+ osr->hr.http_status,
+ (int) osr->hr.ec,
TALER_TESTING_interpreter_get_current_label (gos->is));
TALER_TESTING_interpreter_fail (gos->is);
return;
}
- switch (hr->http_status)
+ switch (osr->hr.http_status)
{
case MHD_HTTP_OK:
- // FIXME: use gts->tip_reference here to
- // check if the data returned matches that from the POST / PATCH
- if (gos->osc != osr->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->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);
@@ -217,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 !=
@@ -233,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);
@@ -241,7 +271,7 @@ merchant_get_order_cb (
}
if (1 != json_equal (ct,
- osr->details.paid.contract_terms))
+ osr->details.ok.details.paid.contract_terms))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Order contract terms do not match\n");
@@ -251,14 +281,14 @@ merchant_get_order_cb (
json_decref (ct);
}
- if (gos->wired != osr->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.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");
@@ -285,7 +315,8 @@ merchant_get_order_cb (
return;
}
if (0 != GNUNET_memcmp (wtid,
- &osr->details.paid.wts[i].wtid))
+ &osr->details.ok.details.paid.wts[i].
+ wtid))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Wire transfer id does not match\n");
@@ -294,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,
@@ -305,8 +336,9 @@ merchant_get_order_cb (
TALER_TESTING_interpreter_fail (gos->is);
return;
}
- if (0 != strcmp (*exchange_url,
- osr->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");
@@ -314,54 +346,16 @@ 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.paid.wts[
- i]
- .total_amount)) ||
- (0 != TALER_amount_cmp (&transfer_total,
- &osr->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.paid.refunded)
+ if (gos->refunded != osr->details.ok.details.paid.refunded)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Order refunded does not match\n");
TALER_TESTING_interpreter_fail (gos->is);
return;
}
- if (gos->refunds_length != osr->details.paid.refunds_len)
+ if (gos->refunds_length !=
+ osr->details.ok.details.paid.refunds_len)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Number of refunds found does not match\n");
@@ -369,10 +363,11 @@ merchant_get_order_cb (
return;
}
if (0 < gos->refunds_length)
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (
- osr->details.paid.refund_amount.currency,
- &refunded_total));
+ GNUNET_assert (
+ GNUNET_OK ==
+ TALER_amount_set_zero (
+ osr->details.ok.details.paid.refund_amount.currency,
+ &refunded_total));
for (unsigned int i = 0; i < gos->refunds_length; ++i)
{
const struct TALER_TESTING_Command *refund_cmd;
@@ -383,7 +378,7 @@ merchant_get_order_cb (
{
const struct TALER_Amount *expected_amount;
struct TALER_Amount *amount_found =
- &osr->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,8 +416,10 @@ merchant_get_order_cb (
TALER_TESTING_interpreter_fail (gos->is);
return;
}
- if (0 != strcmp (*expected_reason,
- osr->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");
@@ -432,7 +429,7 @@ merchant_get_order_cb (
}
}
- if (gos->wired != osr->details.paid.wired)
+ if (gos->wired != osr->details.ok.details.paid.wired)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Order wired does not match\n");
@@ -443,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:
{
- /* FIXME: Check all of the members of `pud` */
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.unpaid.taler_pay_uri,
- &pud))
+ TALER_MERCHANT_parse_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.unpaid.taler_pay_uri);
+ osr->details.ok.details.unpaid.taler_pay_uri);
TALER_TESTING_interpreter_fail (gos->is);
return;
}
@@ -483,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.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);
@@ -541,8 +558,8 @@ merchant_get_order_cb (
return;
}
TALER_MERCHANT_parse_pay_uri_free (&pud);
+ break;
}
- break;
}
break;
default:
@@ -567,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 (
@@ -585,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);
}
@@ -611,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,
@@ -628,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;
@@ -672,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;
@@ -692,12 +711,6 @@ TALER_TESTING_cmd_merchant_get_order2 (const char *label,
gos->wired = wired;
gos->refunded = refunded;
gos->http_status = http_status;
- gos->transfers = NULL;
- gos->transfers_length = 0;
- gos->refunds = NULL;
- gos->refunds_length = 0;
- gos->forgets = NULL;
- gos->forgets_length = 0;
if (wired)
{
for (const char **clabel = transfers; *clabel != NULL; ++clabel)
@@ -738,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
{
/**
@@ -860,17 +935,15 @@ conclude_task (void *cls)
* Callback to process a GET /private/orders/$ID request
*
* @param cls closure
- * @param hr HTTP response details
- * @param osr order status response details (on success)
+ * @param osr order status response details
*/
static void
merchant_poll_order_cb (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
const struct TALER_MERCHANT_OrderStatusResponse *osr)
{
- /* FIXME, deeper checks should be implemented here. */
struct MerchantPollOrderStartState *pos = cls;
+ const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr;
pos->ogh = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -914,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! */
@@ -954,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;
@@ -1049,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 a4841da1..00000000
--- a/src/testing/testing_api_cmd_merchant_get_tip.c
+++ /dev/null
@@ -1,383 +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 hr http response
- * @param total_authorized the total amount authorized for the tip
- * @param total_picked_up the total amount of the tip that has been picked up
- * @param reason why the tip was authorized
- * @param expiration when the tip will expire
- * @param reserve_pub public key of the reserve the tip is drawing from
- * @param pickups_length number of pickups associated with the tip
- * @param pickups the array of pickups associated with the tip
- */
-static void
-merchant_get_tip_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_Amount *total_authorized,
- const struct TALER_Amount *total_picked_up,
- const char *reason,
- struct GNUNET_TIME_Timestamp expiration,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- unsigned int pickups_length,
- const struct TALER_MERCHANT_PickupDetail pickups[])
-{
- /* FIXME, deeper checks should be implemented here. */
- 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;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (total_picked_up->currency,
- &expected_total_picked_up));
- 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:
- // FIXME: use gts->tip_reference here to
- // check if the data returned matches that from the POST / PATCH
- {
- const struct TALER_Amount *initial_amount;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_amount (authorize_cmd,
- &initial_amount))
- TALER_TESTING_FAIL (gts->is);
- if ((GNUNET_OK !=
- TALER_amount_cmp_currency (total_authorized,
- initial_amount)) ||
- (0 != TALER_amount_cmp (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 (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,
- !=,
- expiration))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Tip authorized expiration does not match\n");
- TALER_TESTING_interpreter_fail (gts->is);
- return;
- }
- }
- if (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 < 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 != 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,
- &pickups[i].requested_amount)) ||
- (0 != TALER_amount_cmp (total,
- &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,
- total_picked_up)) ||
- (0 !=
- TALER_amount_cmp (&expected_total_picked_up,
- 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,
- 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
new file mode 100644
index 00000000..8ad9d9dc
--- /dev/null
+++ b/src/testing/testing_api_cmd_patch_template.c
@@ -0,0 +1,243 @@
+/*
+ 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_template.c
+ * @brief command to test PATCH /template
+ * @author Priscilla HUANG
+ */
+#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 /template" CMD.
+ */
+struct PatchTemplateState
+{
+
+ /**
+ * Handle for a "GET template" request.
+ */
+ struct TALER_MERCHANT_TemplatePatchHandle *iph;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * ID of the template to run GET for.
+ */
+ const char *template_id;
+
+ /**
+ * description of the template
+ */
+ const char *template_description;
+
+ /**
+ * OTP device ID
+ */
+ char *otp_id;
+
+ /**
+ * Contract of the company
+ */
+ json_t *template_contract;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a PATCH /templates/$ID operation.
+ *
+ * @param cls closure for this function
+ * @param hr response being processed
+ */
+static void
+patch_template_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct PatchTemplateState *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 /templates/ID.\n",
+ hr->http_status);
+ }
+ TALER_TESTING_interpreter_next (pis->is);
+}
+
+
+/**
+ * Run the "PATCH /templates/$ID" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+patch_template_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PatchTemplateState *pis = cls;
+
+ pis->is = is;
+ 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);
+}
+
+
+/**
+ * Offers information from the PATCH /templates 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_template_traits (void *cls,
+ 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_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_trait_end (),
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Free the state of a "GET template" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+patch_template_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PatchTemplateState *pis = cls;
+
+ if (NULL != pis->iph)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "PATCH /templates/$ID operation did not complete\n");
+ TALER_MERCHANT_template_patch_cancel (pis->iph);
+ }
+ GNUNET_free (pis->otp_id);
+ json_decref (pis->template_contract);
+ GNUNET_free (pis);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_patch_template (
+ const char *label,
+ const char *merchant_url,
+ const char *template_id,
+ const char *template_description,
+ const char *otp_id,
+ json_t *template_contract,
+ unsigned int http_status)
+{
+ struct PatchTemplateState *pis;
+
+ pis = GNUNET_new (struct PatchTemplateState);
+ pis->merchant_url = merchant_url;
+ pis->template_id = template_id;
+ pis->http_status = http_status;
+ pis->template_description = template_description;
+ pis->otp_id = (NULL == otp_id) ? NULL : GNUNET_strdup (otp_id);
+ pis->template_contract = template_contract; /* ownership taken */
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pis,
+ .label = label,
+ .run = &patch_template_run,
+ .cleanup = &patch_template_cleanup,
+ .traits = &patch_template_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_patch_template.c */
diff --git a/src/testing/testing_api_cmd_patch_webhook.c b/src/testing/testing_api_cmd_patch_webhook.c
new file mode 100644
index 00000000..0b066371
--- /dev/null
+++ b/src/testing/testing_api_cmd_patch_webhook.c
@@ -0,0 +1,259 @@
+/*
+ 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_webhook.c
+ * @brief command to test PATCH /webhook
+ * @author Priscilla HUANG
+ */
+#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 /webhook" CMD.
+ */
+struct PatchWebhookState
+{
+
+ /**
+ * Handle for a "GET webhook" request.
+ */
+ struct TALER_MERCHANT_WebhookPatchHandle *iph;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * ID of the webhook to run GET for.
+ */
+ const char *webhook_id;
+
+ /**
+ * event of the webhook
+ */
+ const char *event_type;
+
+ /**
+ * url use by the customer
+ */
+ const char *url;
+
+ /**
+ * http_method use by the merchant
+ */
+ const char *http_method;
+
+ /**
+ * header of the webhook
+ */
+ const char *header_template;
+
+ /**
+ * body_template of the webhook
+ */
+ const char *body_template;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a PATCH /webhooks/$ID operation.
+ *
+ * @param cls closure for this function
+ * @param hr response being processed
+ */
+static void
+patch_webhook_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct PatchWebhookState *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 /webhooks/ID.\n",
+ hr->http_status);
+ }
+ TALER_TESTING_interpreter_next (pis->is);
+}
+
+
+/**
+ * Run the "PATCH /webhooks/$ID" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+patch_webhook_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PatchWebhookState *pis = cls;
+
+ pis->is = is;
+ 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);
+}
+
+
+/**
+ * Offers information from the PATCH /webhooks 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_webhook_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ 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_trait_end (),
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Free the state of a "GET webhook" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+patch_webhook_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PatchWebhookState *pis = cls;
+
+ if (NULL != pis->iph)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "PATCH /webhooks/$ID operation did not complete\n");
+ TALER_MERCHANT_webhook_patch_cancel (pis->iph);
+ }
+ GNUNET_free (pis);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_patch_webhook (
+ const char *label,
+ const char *merchant_url,
+ const char *webhook_id,
+ const char *event_type,
+ const char *url,
+ const char *http_method,
+ const char *header_template,
+ const char *body_template,
+ unsigned int http_status)
+{
+ struct PatchWebhookState *pis;
+
+ pis = GNUNET_new (struct PatchWebhookState);
+ pis->merchant_url = merchant_url;
+ pis->webhook_id = webhook_id;
+ pis->http_status = http_status;
+ pis->event_type = event_type;
+ pis->url = url;
+ pis->http_method = http_method;
+ 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,
+ .label = label,
+ .run = &patch_webhook_run,
+ .cleanup = &patch_webhook_cleanup,
+ .traits = &patch_webhook_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_patch_webhook.c */
diff --git a/src/testing/testing_api_cmd_pay_order.c b/src/testing/testing_api_cmd_pay_order.c
index be2649c0..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, '/');
@@ -184,8 +208,8 @@ build_coins (struct TALER_MERCHANT_PayCoin **pc,
&denom_value));
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_h_age_commitment (coin_cmd,
- 0,
- &h_age_commitment));
+ 0,
+ &h_age_commitment));
icoin->coin_priv = *coin_priv;
icoin->denom_pub = denom_pub->key;
icoin->denom_sig = *denom_sig;
@@ -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;
@@ -218,30 +242,63 @@ build_coins (struct TALER_MERCHANT_PayCoin **pc,
* HTTP response code matches our expectation.
*
* @param cls closure with the interpreter state
- * @param hr HTTP response
- * @param merchant_sig signature affirming payment,
- * NULL on errors
+ * @param pr HTTP response
*/
static void
pay_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct TALER_MerchantSignatureP *merchant_sig)
+ const struct TALER_MERCHANT_PayResponse *pr)
{
struct PayState *ps = cls;
ps->oph = NULL;
- if (ps->http_status != hr->http_status)
+ if (ps->http_status != pr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
+ pr->hr.http_status,
+ (int) pr->hr.ec,
TALER_TESTING_interpreter_get_current_label (ps->is));
TALER_TESTING_FAIL (ps->is);
}
- if (MHD_HTTP_OK == hr->http_status)
+ if (MHD_HTTP_OK == pr->hr.http_status)
{
- ps->merchant_sig = *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);
}
@@ -269,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 (
@@ -289,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 */
@@ -306,11 +374,10 @@ 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
- * add minimum age */
+ /* FIXME oec: parse minimum age, use data later? */
GNUNET_JSON_spec_end ()
};
@@ -366,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,
@@ -433,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;
@@ -469,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 b3f15fc8..8f7bd46d 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
@@ -24,6 +24,10 @@
*/
#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_time_lib.h>
+#include <jansson.h>
+#include <stdint.h>
#include <taler/taler_exchange_service.h>
#include <taler/taler_testing_lib.h>
#include "taler_merchant_service.h"
@@ -46,11 +50,21 @@ 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;
/**
+ * Reference to a POST /tokenfamilies command. Can be NULL.
+ */
+ const char *token_family_reference;
+
+ /**
* Contract terms obtained from the backend.
*/
json_t *contract_terms;
@@ -61,6 +75,11 @@ struct OrdersState
json_t *order_terms;
/**
+ * Choices array with inputs and outputs for v1 order.
+ */
+ json_t *choices;
+
+ /**
* Contract terms hash code.
*/
struct TALER_PrivateContractHashP h_contract_terms;
@@ -157,7 +176,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,37 +200,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,
+ 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))
@@ -230,16 +253,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
@@ -253,20 +274,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;
@@ -345,13 +365,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);
}
@@ -377,7 +398,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));
@@ -390,7 +411,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,
@@ -450,7 +472,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));
@@ -508,7 +530,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,
@@ -526,20 +548,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,
@@ -553,6 +577,118 @@ orders_run2 (void *cls,
/**
+ * Constructs the json for a the choices of an order request.
+ *
+ * @param slug the name of the order to add, can be NULL.
+ * @param valid_after valid_after date for the input and output token.
+ * @param[out] choices where to write the json string.
+ */
+static void
+make_choices_json (
+ const char *input_slug,
+ const char *output_slug,
+ uint16_t input_count,
+ uint16_t output_count,
+ struct GNUNET_TIME_Timestamp input_valid_after,
+ struct GNUNET_TIME_Timestamp output_valid_after,
+ json_t **choices)
+{
+ json_t *c;
+
+ c = json_pack("[{s:o, s:o}]",
+ "inputs", json_pack("[{s:s, s:i, s:s, s:o}]",
+ "kind", "token",
+ "count", input_count,
+ "token_family_slug", input_slug,
+ "valid_after", GNUNET_JSON_from_timestamp(input_valid_after)),
+ "outputs", json_pack("[{s:s, s:i, s:s, s:o}]",
+ "kind", "token",
+ "count", output_count,
+ "token_family_slug", output_slug,
+ "valid_after", GNUNET_JSON_from_timestamp(output_valid_after)));
+
+ *choices = c;
+}
+
+
+/**
+ * Run a "orders" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+orders_run3 (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct OrdersState *ps = cls;
+ struct GNUNET_TIME_Absolute now;
+ const char *slug;
+
+ ps->is = is;
+ now = GNUNET_TIME_absolute_get_monotonic (ps->cfg);
+ if (NULL == json_object_get (ps->order_terms,
+ "order_id"))
+ {
+ char *order_id;
+
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &now,
+ sizeof (now));
+ GNUNET_assert (0 ==
+ json_object_set_new (ps->order_terms,
+ "order_id",
+ json_string (order_id)));
+ GNUNET_free (order_id);
+ }
+
+ {
+ const struct TALER_TESTING_Command *token_family_cmd;
+ token_family_cmd =
+ TALER_TESTING_interpreter_lookup_command (is,
+ ps->token_family_reference);
+ if (NULL == token_family_cmd)
+ TALER_TESTING_FAIL (is);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_token_family_slug (token_family_cmd,
+ &slug))
+ TALER_TESTING_FAIL (is);
+ }
+ make_choices_json (slug, slug,
+ 1, 1,
+ GNUNET_TIME_absolute_to_timestamp(now),
+ GNUNET_TIME_absolute_to_timestamp(now),
+ &ps->choices);
+
+ GNUNET_assert (0 ==
+ json_object_set_new (ps->order_terms,
+ "choices",
+ ps->choices)
+ );
+ GNUNET_assert (0 ==
+ json_object_set_new (ps->order_terms,
+ "version",
+ json_string ("1"))
+ );
+
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &ps->nonce,
+ sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
+ ps->po = TALER_MERCHANT_orders_post (TALER_TESTING_interpreter_get_context (
+ is),
+ ps->merchant_url,
+ ps->order_terms,
+ GNUNET_TIME_UNIT_ZERO,
+ &order_cb,
+ ps);
+ GNUNET_assert (NULL != ps->po);
+}
+
+
+/**
* Free the state of a "orders" CMD, and possibly
* cancel it if it did not complete.
*
@@ -641,8 +777,7 @@ make_order_json (const char *order_id,
"dummy_array", /* For testing forgetting parts of arrays */
"item", "speakers",
"item", "headphones",
- "item", "earbuds"
- );
+ "item", "earbuds");
GNUNET_assert (GNUNET_OK ==
TALER_JSON_expand_path (contract_terms,
"$.dummy_obj",
@@ -678,7 +813,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,
@@ -696,6 +830,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,
@@ -706,6 +841,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,
@@ -732,6 +868,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,
@@ -747,6 +884,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,
@@ -773,3 +911,85 @@ 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;
+ }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_orders_choices (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *token_family_reference,
+ const char *order_id,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ 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);
+ ps->http_status = http_status;
+ ps->token_family_reference = token_family_reference;
+ ps->expected_order_id = order_id;
+ ps->merchant_url = merchant_url;
+ ps->with_claim = true;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ps,
+ .label = label,
+ .run = &orders_run3,
+ .cleanup = &orders_cleanup,
+ .traits = &orders_traits
+ };
+
+ return cmd;
+ }
+} \ No newline at end of file
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..c841f1b1 100644
--- a/src/testing/testing_api_cmd_post_products.c
+++ b/src/testing/testing_api_cmd_post_products.c
@@ -35,7 +35,7 @@ struct PostProductsState
{
/**
- * Handle for a "GET product" request.
+ * Handle for a "POST /products" request.
*/
struct TALER_MERCHANT_ProductsPostHandle *iph;
@@ -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
new file mode 100644
index 00000000..f0b6d713
--- /dev/null
+++ b/src/testing/testing_api_cmd_post_templates.c
@@ -0,0 +1,271 @@
+/*
+ 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_templates.c
+ * @brief command to test POST /templates
+ * @author Priscilla HUANG
+ */
+#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 /templates" CMD.
+ */
+struct PostTemplatesState
+{
+
+ /**
+ * Handle for a "GET template" request.
+ */
+ struct TALER_MERCHANT_TemplatesPostHandle *iph;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * ID of the template to run POST for.
+ */
+ const char *template_id;
+
+ /**
+ * description of the template
+ */
+ const char *template_description;
+
+ /**
+ * OTP device ID.
+ */
+ char *otp_id;
+
+ /**
+ * Contract of the company
+ */
+ json_t *template_contract;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a POST /templates operation.
+ *
+ * @param cls closure for this function
+ * @param hr response being processed
+ */
+static void
+post_templates_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct PostTemplatesState *tis = cls;
+
+ tis->iph = NULL;
+ if (tis->http_status != hr->http_status)
+ {
+ TALER_TESTING_unexpected_status_with_body (tis->is,
+ hr->http_status,
+ tis->http_status,
+ hr->reply);
+ 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 /templates.\n",
+ hr->http_status);
+ }
+ TALER_TESTING_interpreter_next (tis->is);
+}
+
+
+/**
+ * Run the "POST /templates" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+post_templates_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PostTemplatesState *tis = cls;
+
+ tis->is = is;
+ 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);
+ TALER_TESTING_interpreter_fail (tis->is);
+ return;
+ }
+}
+
+
+/**
+ * Offers information from the POST /templates 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_templates_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct PostTemplatesState *pts = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ 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_trait_end (),
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Free the state of a "POST template" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+post_templates_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PostTemplatesState *tis = cls;
+
+ if (NULL != tis->iph)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "POST /templates operation did not complete\n");
+ TALER_MERCHANT_templates_post_cancel (tis->iph);
+ }
+ GNUNET_free (tis->otp_id);
+ json_decref (tis->template_contract);
+ GNUNET_free (tis);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_templates2 (
+ const char *label,
+ const char *merchant_url,
+ const char *template_id,
+ const char *template_description,
+ const char *otp_id,
+ json_t *template_contract,
+ unsigned int http_status)
+{
+ struct PostTemplatesState *tis;
+
+ GNUNET_assert ((NULL == template_contract) ||
+ json_is_object (template_contract));
+
+ tis = GNUNET_new (struct PostTemplatesState);
+ tis->merchant_url = merchant_url;
+ tis->template_id = template_id;
+ tis->http_status = http_status;
+ tis->template_description = template_description;
+ tis->otp_id = (NULL == otp_id) ? NULL : GNUNET_strdup (otp_id);
+ tis->template_contract = template_contract;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = tis,
+ .label = label,
+ .run = &post_templates_run,
+ .cleanup = &post_templates_cleanup,
+ .traits = &post_templates_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_templates (const char *label,
+ const char *merchant_url,
+ const char *template_id,
+ const char *template_description,
+ unsigned int http_status)
+{
+ return TALER_TESTING_cmd_merchant_post_templates2 (
+ label,
+ merchant_url,
+ template_id,
+ template_description,
+ NULL,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("minimum_age", 0),
+ GNUNET_JSON_pack_time_rel ("pay_duration",
+ GNUNET_TIME_UNIT_MINUTES)),
+ http_status);
+}
+
+
+/* end of testing_api_cmd_post_templates.c */
diff --git a/src/testing/testing_api_cmd_post_tokenfamilies.c b/src/testing/testing_api_cmd_post_tokenfamilies.c
new file mode 100644
index 00000000..aafff9ef
--- /dev/null
+++ b/src/testing/testing_api_cmd_post_tokenfamilies.c
@@ -0,0 +1,272 @@
+/*
+ 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_api_cmd_post_tokenfamilies.c
+ * @brief command to run POST /tokenfamilies
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <gnunet/gnunet_time_lib.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 /tokenfamilies" CMD.
+ */
+struct PostTokenFamiliesState
+{
+
+ /**
+ * Expected status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Handle for a "POST /tokenfamilies" request.
+ */
+ struct TALER_MERCHANT_TokenFamiliesPostHandle *handle;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * Slug of the token family.
+ */
+ const char *slug;
+
+ /**
+ * Name of the token family.
+ */
+ const char *name;
+
+ /**
+ * Description of the token family.
+ */
+ const char *description;
+
+ /**
+ * Map from IETF BCP 47 language tags to localized descriptions.
+ */
+ json_t *description_i18n;
+
+ /**
+ * Start of the validity period.
+ */
+ struct GNUNET_TIME_Timestamp valid_after;
+
+ /**
+ * End of the validity period.
+ */
+ struct GNUNET_TIME_Timestamp valid_before;
+
+ /**
+ * Validity duation of issued tokens of this family.
+ */
+ struct GNUNET_TIME_Relative duration;
+
+ /**
+ * Kind of the token family. "subscription" or "discount".
+ */
+ const char *kind;
+};
+
+
+/**
+ * Callback for a POST /tokenfamilies operation.
+ *
+ * @param cls closure for this function
+ * @param hr response being processed
+ */
+static void
+post_tokenfamilies_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct PostTokenFamiliesState *state = cls;
+
+ state->handle = NULL;
+ if (state->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 (state->is));
+ TALER_TESTING_interpreter_fail (state->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;
+ default:
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status %u for POST /tokenfamilies.\n",
+ hr->http_status);
+ }
+ TALER_TESTING_interpreter_next (state->is);
+}
+
+/**
+ * Run the "POST /tokenfamilies" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+post_tokenfamilies_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PostTokenFamiliesState *state = cls;
+
+ state->is = is;
+ state->handle = TALER_MERCHANT_token_families_post (
+ TALER_TESTING_interpreter_get_context (is),
+ state->merchant_url,
+ state->slug,
+ state->name,
+ state->description,
+ state->description_i18n,
+ state->valid_after,
+ state->valid_before,
+ state->duration,
+ state->kind,
+ &post_tokenfamilies_cb,
+ state);
+ GNUNET_assert (NULL != state->handle);
+}
+
+/**
+ * Offers information from the "POST /tokenfamilies" 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_tokenfamilies_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct PostTokenFamiliesState *state = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_token_family_slug (state->slug),
+ TALER_TESTING_make_trait_timestamp (0,
+ &state->valid_after),
+ TALER_TESTING_make_trait_timestamp (1,
+ &state->valid_before),
+ TALER_TESTING_make_trait_token_family_duration (&state->duration),
+ TALER_TESTING_make_trait_token_family_kind (state->kind),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+/**
+ * Free the state of a "POST /tokenfamilies" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+post_tokenfamilies_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PostTokenFamiliesState *state = cls;
+
+ if (NULL != state->handle)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "POST /tokenfamilies operation did not complete\n");
+ TALER_MERCHANT_token_families_post_cancel (state->handle);
+ }
+ json_decref (state->description_i18n);
+ GNUNET_free (state);
+}
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_tokenfamilies (
+ const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *slug,
+ const char *name,
+ const char *description,
+ json_t *description_i18n,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct GNUNET_TIME_Timestamp valid_before,
+ struct GNUNET_TIME_Relative duration,
+ const char *kind) /* "subscription" or "discount" */
+{
+ struct PostTokenFamiliesState *state;
+
+ GNUNET_assert ((NULL == description_i18n) ||
+ json_is_object (description_i18n));
+ state = GNUNET_new (struct PostTokenFamiliesState);
+ state->merchant_url = merchant_url;
+ state->http_status = http_status;
+ state->slug = slug;
+ state->name = name;
+ state->description = description;
+ state->description_i18n = description_i18n; /* ownership taken */
+ state->valid_after = valid_after;
+ state->valid_before = valid_before;
+ state->duration = duration;
+ state->kind = kind;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = state,
+ .label = label,
+ .run = &post_tokenfamilies_run,
+ .cleanup = &post_tokenfamilies_cleanup,
+ .traits = &post_tokenfamilies_traits
+ };
+
+ return cmd;
+ }
+} \ No newline at end of file
diff --git a/src/testing/testing_api_cmd_post_transfers.c b/src/testing/testing_api_cmd_post_transfers.c
index 571cfb9a..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
@@ -55,12 +55,12 @@ struct PostTransfersState
const char *merchant_url;
/**
- * URL of the bank to run history on (set once @e found is set).
+ * URL of the bank to run history on.
*/
char *exchange_url;
/**
- * Credit account of the merchant (set once @e found is set).
+ * Credit account of the merchant.
*/
char *credit_account;
@@ -70,12 +70,17 @@ 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;
/**
- * Set once we discovered the WTID and thus @e found is true.
+ * Set once we discovered the WTID.
*/
struct TALER_WireTransferIdentifierRawP wtid;
@@ -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,15 +110,6 @@ struct PostTransfersState
*/
unsigned int deposits_length;
- /**
- * Set to true once @e wtid and @e exchange_url are initialized.
- */
- bool found;
-
- /**
- * When the exchange executed the transfer.
- */
- struct GNUNET_TIME_Timestamp execution_time;
};
@@ -126,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;
@@ -316,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);
}
@@ -341,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 (),
};
@@ -376,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);
}
@@ -393,38 +225,45 @@ post_transfers_run2 (void *cls,
* the bank for the debit transaction history.
*
* @param cls closure with a `struct PostTransfersState *`
- * @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
- * @param json detailed response from the HTTPD, or NULL if reply was not in JSON
- * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
+ * @param reply details from the HTTP response code
*/
-static int
+static void
debit_cb (
void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- uint64_t serial_id,
- const struct TALER_BANK_DebitDetails *details,
- const json_t *json)
+ const struct TALER_BANK_DebitHistoryResponse *reply)
{
struct PostTransfersState *pts = cls;
- if (MHD_HTTP_NO_CONTENT == http_status)
+ pts->dhh = NULL;
+ switch (reply->http_status)
{
- pts->dhh = NULL;
- if (! pts->found)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (pts->is);
- return GNUNET_OK;
- }
- GNUNET_assert (NULL != pts->exchange_url);
+ case MHD_HTTP_OK:
+ /* handled below */
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pts->is);
+ return;
+ default:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pts->is);
+ return;
+ }
+ for (unsigned int i = 0; i<reply->details.ok.details_length; i++)
+ {
+ const struct TALER_BANK_DebitDetails *details
+ = &reply->details.ok.details[i];
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Bank reports transfer of %s to %s\n",
+ TALER_amount2s (&details->amount),
+ details->credit_account_uri);
+ if (0 != TALER_amount_cmp (&pts->credit_amount,
+ &details->amount))
+ continue;
+ pts->wtid = details->wtid;
+ pts->credit_account = GNUNET_strdup (details->credit_account_uri);
+ pts->exchange_url = GNUNET_strdup (details->exchange_base_url);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Bank transfer found, checking with merchant backend at %s about %s from %s to %s with %s\n",
pts->merchant_url,
@@ -432,38 +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);
- return GNUNET_OK;
+ break;
}
- if (MHD_HTTP_OK != http_status)
+ if (NULL == pts->pth)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (pts->is);
- pts->dhh = NULL;
- return GNUNET_SYSERR;
+ return;
}
- if (pts->found)
- return GNUNET_OK;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Bank reports transfer of %s to %s\n",
- TALER_amount2s (&details->amount),
- details->credit_account_uri);
- if (0 != TALER_amount_cmp (&pts->credit_amount,
- &details->amount))
- return GNUNET_OK;
- pts->found = true;
- pts->wtid = details->wtid;
- pts->credit_account = GNUNET_strdup (details->credit_account_uri);
- pts->exchange_url = GNUNET_strdup (details->exchange_base_url);
- return GNUNET_OK;
}
@@ -487,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,
@@ -548,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
new file mode 100644
index 00000000..7aeec33d
--- /dev/null
+++ b/src/testing/testing_api_cmd_post_using_templates.c
@@ -0,0 +1,607 @@
+/*
+ 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 testing_api_cmd_post_using_templates.c
+ * @brief command to test POST /using-templates
+ * @author Priscilla HUANG
+ */
+#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 /templates" CMD.
+ */
+struct PostUsingTemplatesState
+{
+
+ /**
+ * Handle for a "GET using-template" request.
+ */
+ struct TALER_MERCHANT_UsingTemplatesPostHandle *iph;
+
+ /**
+ * The interpreter state.
+ */
+ 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;
+
+ /**
+ * Amount given by the customer.
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Label of a command that created the template we should use.
+ */
+ 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.
+ *
+ * @param cls closure for this function
+ * @param por response being processed
+ */
+static void
+post_using_templates_cb (void *cls,
+ const struct TALER_MERCHANT_PostOrdersReply *por)
+{
+ struct PostUsingTemplatesState *tis = cls;
+
+ tis->iph = NULL;
+ if (tis->http_status != por->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ por->hr.http_status,
+ (int) por->hr.ec,
+ TALER_TESTING_interpreter_get_current_label (tis->is));
+ 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:
+ 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:
+ 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:
+ {
+ 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;
+ }
+
+ 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);
+}
+
+
+/**
+ * Run the "POST /using-templates" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+post_using_templates_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PostUsingTemplatesState *tis = cls;
+ const struct TALER_TESTING_Command *ref;
+ const char *template_id;
+
+ tis->is = is;
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ tis->template_ref);
+ if (GNUNET_OK !=
+ 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 (
+ TALER_TESTING_interpreter_get_context (is),
+ tis->merchant_url,
+ template_id,
+ tis->summary,
+ TALER_amount_is_valid (&tis->amount)
+ ? &tis->amount
+ : NULL,
+ &post_using_templates_cb,
+ tis);
+ GNUNET_assert (NULL != tis->iph);
+}
+
+
+/**
+ * Offers information from the POST /using-templates 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_using_templates_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ 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 (),
+ };
+
+ (void) pts;
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Free the state of a "POST using-template" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+post_using_templates_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PostUsingTemplatesState *tis = cls;
+
+ if (NULL != tis->iph)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "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,
+ 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,
+ &tis->amount));
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = tis,
+ .label = label,
+ .run = &post_using_templates_run,
+ .cleanup = &post_using_templates_cleanup,
+ .traits = &post_using_templates_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_post_using_templates.c */
diff --git a/src/testing/testing_api_cmd_post_webhooks.c b/src/testing/testing_api_cmd_post_webhooks.c
new file mode 100644
index 00000000..c3a8d1b3
--- /dev/null
+++ b/src/testing/testing_api_cmd_post_webhooks.c
@@ -0,0 +1,280 @@
+/*
+ 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_webhooks.c
+ * @brief command to test POST /webhooks
+ * @author Priscilla HUANG
+ */
+#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 /webhooks" CMD.
+ */
+struct PostWebhooksState
+{
+
+ /**
+ * Handle for a "GET webhook" request.
+ */
+ struct TALER_MERCHANT_WebhooksPostHandle *iph;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * ID of the webhook to run POST for.
+ */
+ const char *webhook_id;
+
+ /**
+ * event of the webhook
+ */
+ const char *event_type;
+
+ /**
+ * url use by the customer
+ */
+ const char *url;
+
+ /**
+ * http_method use by the merchant
+ */
+ const char *http_method;
+
+ /**
+ * header of the webhook
+ */
+ const char *header_template;
+
+ /**
+ * body of the webhook
+ */
+ const char *body_template;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a POST /webhooks operation.
+ *
+ * @param cls closure for this function
+ * @param hr response being processed
+ */
+static void
+post_webhooks_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct PostWebhooksState *wis = cls;
+
+ wis->iph = NULL;
+ if (wis->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 (wis->is));
+ TALER_TESTING_interpreter_fail (wis->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 /templates.\n",
+ hr->http_status);
+ }
+ TALER_TESTING_interpreter_next (wis->is);
+}
+
+
+/**
+ * Run the "POST /webhooks" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+post_webhooks_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PostWebhooksState *wis = cls;
+
+ wis->is = is;
+ 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);
+}
+
+
+/**
+ * Offers information from the POST /webhooks 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 int
+post_webhooks_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ 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_trait_end (),
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Free the state of a "POST webhook" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+post_webhooks_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PostWebhooksState *wis = cls;
+
+ if (NULL != wis->iph)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "POST /webhooks operation did not complete\n");
+ TALER_MERCHANT_webhooks_post_cancel (wis->iph);
+ }
+ GNUNET_free (wis);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_webhooks2 (
+ const char *label,
+ const char *merchant_url,
+ const char *webhook_id,
+ const char *event_type,
+ const char *url,
+ const char *http_method,
+ const char *header_template,
+ const char *body_template,
+ unsigned int http_status)
+{
+ struct PostWebhooksState *wis;
+
+ wis = GNUNET_new (struct PostWebhooksState);
+ wis->merchant_url = merchant_url;
+ wis->webhook_id = webhook_id;
+ wis->http_status = http_status;
+ wis->event_type = event_type;
+ wis->url = url;
+ wis->http_method = http_method;
+ 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,
+ .label = label,
+ .run = &post_webhooks_run,
+ .cleanup = &post_webhooks_cleanup,
+ .traits = &post_webhooks_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_webhooks (const char *label,
+ const char *merchant_url,
+ const char *webhook_id,
+ const char *event_type,
+ unsigned int http_status)
+{
+ return TALER_TESTING_cmd_merchant_post_webhooks2 (
+ label,
+ merchant_url,
+ webhook_id,
+ event_type,
+ "http://localhost:12345/",
+ "POST",
+ "Taler-test-header: EFEHYJS-Bakery",
+ "5.0 EUR",
+ http_status);
+}
+
+
+/* end of testing_api_cmd_post_webhooks.c */
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 f9e89d63..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;
@@ -79,37 +91,15 @@ struct WalletGetOrderState
* Callback to process a GET /orders/$ID request
*
* @param cls closure
- * @param hr HTTP response details
- * @param paid #GNUNET_YES if the payment is settled, #GNUNET_NO if not
- * settled, #GNUNET_SYSERR on error
- * (note that refunded payments are returned as paid!)
- * @param refunded #GNUNET_YES if there is at least on refund on this payment,
- * #GNUNET_NO if refunded, #GNUNET_SYSERR or error
- * @param refund_pending #GNUNET_YES if there are refunds waiting to be
- * obtained, #GNUNET_NO if all refunds have been obtained, #GNUNET_SYSERR
- * on error.
- * @param refund_amount amount that was refunded, NULL if there
- * was no refund
- * @param taler_pay_uri the URI that instructs the wallets to process
- * the payment
- * @param already_paid_order_id equivalent order that this customer
- * paid already, or NULL for none
+ * @param owgr response details
*/
static void
wallet_get_order_cb (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- enum GNUNET_GenericReturnValue paid,
- enum GNUNET_GenericReturnValue refunded,
- enum GNUNET_GenericReturnValue refund_pending,
- struct TALER_Amount *refund_amount,
- const char *taler_pay_uri,
- const char *already_paid_order_id)
+ const struct TALER_MERCHANT_OrderWalletGetResponse *owgr)
{
struct WalletGetOrderState *gos = cls;
- bool paid_b = (paid == GNUNET_YES);
- bool refunded_b = (refunded == GNUNET_YES);
- bool refund_pending_b = (refund_pending == GNUNET_YES);
+ const struct TALER_MERCHANT_HttpResponse *hr = &owgr->hr;
gos->ogh = NULL;
if (gos->http_status != hr->http_status)
@@ -125,44 +115,61 @@ wallet_get_order_cb (
switch (hr->http_status)
{
case MHD_HTTP_OK:
- // FIXME: use gos->order_reference here to
- // check if the data returned matches that from the POST / PATCH
- if (gos->paid != paid_b)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Order paid does not match\n");
- TALER_TESTING_interpreter_fail (gos->is);
- return;
- }
- if (gos->refunded != refunded_b)
+ 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 != refund_pending_b)
+ if (gos->refund_pending != owgr->details.ok.refund_pending)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Order refund pending does not match\n");
TALER_TESTING_interpreter_fail (gos->is);
return;
}
- if (! paid_b)
+ break;
+ case MHD_HTTP_PAYMENT_REQUIRED:
{
- /* FIXME: Check all of the members of `pud` */
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 (taler_pay_uri,
- &pud))
+ TALER_MERCHANT_parse_pay_uri (
+ owgr->details.payment_required.taler_pay_uri,
+ &pud))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Taler pay uri `%s' is malformed\n",
- taler_pay_uri);
+ owgr->details.payment_required.taler_pay_uri);
TALER_TESTING_interpreter_fail (gos->is);
return;
}
@@ -188,35 +195,19 @@ 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))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Order pay uri `%s' does not match `%s'\n",
- taler_pay_uri,
+ owgr->details.payment_required.taler_pay_uri,
pud.order_id);
TALER_TESTING_interpreter_fail (gos->is);
TALER_MERCHANT_parse_pay_uri_free (&pud);
@@ -265,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 (
@@ -283,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);
}
@@ -311,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);
@@ -319,13 +311,14 @@ 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;
@@ -349,6 +342,42 @@ TALER_TESTING_cmd_wallet_get_order (const char *label,
}
+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;
+
+ gos = GNUNET_new (struct WalletGetOrderState);
+ gos->merchant_url = merchant_url;
+ 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,
+ .label = label,
+ .run = &wallet_get_order_run,
+ .cleanup = &wallet_get_order_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
struct WalletPollOrderConcludeState
{
/**
@@ -565,34 +594,15 @@ conclude_task (void *cls)
* Process response from a GET /orders/$ID request
*
* @param cls a `struct WalletPollOrderStartState *`
- * @param hr HTTP response details
- * @param paid #GNUNET_YES if the payment is settled, #GNUNET_NO if not
- * settled, #GNUNET_SYSERR on error
- * (note that refunded payments are returned as paid!)
- * @param refunded #GNUNET_YES if there is at least on refund on this payment,
- * #GNUNET_NO if refunded, #GNUNET_SYSERR or error
- * @param refund_pending #GNUNET_YES if there are refunds waiting to be
- * obtained, #GNUNET_NO if all refunds have been obtained, #GNUNET_SYSERR
- * on error.
- * @param refund_amount amount that was refunded, NULL if there
- * was no refund
- * @param taler_pay_uri the URI that instructs the wallets to process
- * the payment
- * @param already_paid_order_id equivalent order that this customer
- * paid already, or NULL for none
+ * @param owgr response details
*/
static void
wallet_poll_order_cb (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- enum GNUNET_GenericReturnValue paid,
- enum GNUNET_GenericReturnValue refunded,
- enum GNUNET_GenericReturnValue refund_pending,
- struct TALER_Amount *refund_amount,
- const char *taler_pay_uri,
- const char *already_paid_order_id)
+ const struct TALER_MERCHANT_OrderWalletGetResponse *owgr)
{
struct WalletPollOrderStartState *pos = cls;
+ const struct TALER_MERCHANT_HttpResponse *hr = &owgr->hr;
pos->ogh = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -602,13 +612,16 @@ wallet_poll_order_cb (
switch (hr->http_status)
{
case MHD_HTTP_OK:
- pos->paid = (GNUNET_YES == paid);
- pos->refunded = (GNUNET_YES == refunded);
- pos->refund_pending = (GNUNET_YES == refund_pending);
- if (NULL != refund_amount)
- pos->refund_available = *refund_amount;
- if (NULL != already_paid_order_id)
- pos->already_paid_order_id = GNUNET_strdup (already_paid_order_id);
+ pos->paid = true;
+ 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)
+ pos->already_paid_order_id = GNUNET_strdup (
+ owgr->details.payment_required.already_paid_order_id);
break;
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@@ -639,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 (
@@ -661,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 f7786973..00000000
--- a/src/testing/testing_api_cmd_wallet_get_tip.c
+++ /dev/null
@@ -1,265 +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)
-{
- /* FIXME, deeper checks should be implemented here. */
- 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:
- // FIXME: use gts->tip_reference here to
- // check if the data returned matches that from the POST / PATCH
- 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 659bb694..dbc7a6eb 100644
--- a/src/testing/testing_api_helpers.c
+++ b/src/testing/testing_api_helpers.c
@@ -21,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))
- {
- fprintf (stderr,
- "Required port %llu not available, skipping.\n",
- port);
- MERCHANT_FAIL ();
- }
-
- /* 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)))
+ if (hosts[1] != '/')
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to run taler-merchant-dbinit. Check your PATH.\n");
- MERCHANT_FAIL ();
+ GNUNET_break (0);
+ return NULL;
}
-
- if (GNUNET_SYSERR ==
- GNUNET_OS_process_wait_status (dbinit_proc,
- &type,
- &code))
- {
- GNUNET_OS_process_destroy (dbinit_proc);
- MERCHANT_FAIL ();
- }
- if ( (type == GNUNET_OS_PROCESS_EXITED) &&
- (0 != code) )
+ hosts += 2;
+ pstr = strchr (hosts, ':');
+ if (NULL == pstr)
{
- fprintf (stderr,
- "Failed to setup database\n");
- MERCHANT_FAIL ();
+ hend = &hosts[strlen (hosts)];
+ pstr = "80";
+ pend = &pstr[2];
}
- if ( (type != GNUNET_OS_PROCESS_EXITED) ||
- (0 != code) )
+ else
{
- fprintf (stderr,
- "Unexpected error running"
- " `taler-merchant-dbinit'!\n");
- MERCHANT_FAIL ();
+ hend = pstr;
+ pstr++;
+ pend = strchr (pstr, '/');
}
- 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;
}