/* This file is part of TALER Copyright (C) 2018, 2019, 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 */ /** * @file merchant_api_merchant_get_order.c * @brief Implementation of the GET /private/orders/$ORDER request * @author Christian Grothoff * @author Marcello Stanisci * @author Florian Dold */ #include "platform.h" #include #include #include /* just for HTTP status codes */ #include #include #include "taler_merchant_service.h" #include #include /** * @brief A GET /private/orders/$ORDER handle */ struct TALER_MERCHANT_OrderMerchantGetHandle { /** * The url for this request. */ char *url; /** * Handle for the request. */ struct GNUNET_CURL_Job *job; /** * Function to call with the result. */ TALER_MERCHANT_OrderMerchantGetCallback 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 GET /private/orders/$ORDER * request and we got an HTTP status of OK and the order was unpaid. Parse * the response and call the callback. * * @param omgh handle for the request * @param[in,out] hr HTTP response we got */ static void handle_unpaid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh, struct TALER_MERCHANT_HttpResponse *hr) { const char *taler_pay_uri = json_string_value (json_object_get (hr->reply, "taler_pay_uri")); const char *already_paid_order_id = json_string_value (json_object_get (hr->reply, "already_paid_order_id")); if (NULL == taler_pay_uri) { GNUNET_break_op (0); hr->http_status = 0; hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED; omgh->cb (omgh->cb_cls, hr, NULL); return; } { struct TALER_MERCHANT_OrderStatusResponse osr = { .status = TALER_MERCHANT_OSC_UNPAID, .details.unpaid.taler_pay_uri = taler_pay_uri, .details.unpaid.already_paid_order_id = already_paid_order_id }; omgh->cb (omgh->cb_cls, hr, &osr); } } /** * Function called when we're done processing the GET /private/orders/$ORDER * request and we got an HTTP status of OK and the order was claimed but not * paid. Parse the response and call the callback. * * @param omgh handle for the request * @param[in,out] hr HTTP response we got */ static void handle_claimed (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh, struct TALER_MERCHANT_HttpResponse *hr) { 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_end () }; if (GNUNET_OK != GNUNET_JSON_parse (hr->reply, spec, NULL, NULL)) { GNUNET_break_op (0); hr->http_status = 0; hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED; omgh->cb (omgh->cb_cls, hr, NULL); return; } omgh->cb (omgh->cb_cls, hr, &osr); GNUNET_JSON_parse_free (spec); } /** * Function called when we're done processing the GET /private/orders/$ORDER * request and we got an HTTP status of OK and the order was paid. Parse * the response and call the callback. * * @param omgh handle for the request * @param[in,out] hr HTTP response we got */ static void handle_paid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh, struct TALER_MERCHANT_HttpResponse *hr) { 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 }; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_bool ("refunded", &osr.details.paid.refunded), GNUNET_JSON_spec_bool ("refund_pending", &osr.details.paid.refund_pending), GNUNET_JSON_spec_bool ("wired", &osr.details.paid.wired), TALER_JSON_spec_amount ("deposit_total", &osr.details.paid.deposit_total), GNUNET_JSON_spec_uint32 ("exchange_ec", &ec32), GNUNET_JSON_spec_uint32 ("exchange_hc", &hc32), TALER_JSON_spec_amount ("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), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (hr->reply, spec, NULL, NULL)) { GNUNET_break_op (0); hr->http_status = 0; hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED; omgh->cb (omgh->cb_cls, hr, NULL); 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_MERCHANT_ORDER_GET_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; { 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; iexchange_url), GNUNET_JSON_spec_fixed_auto ("wtid", &wt->wtid), TALER_JSON_spec_absolute_time ("execution_time", &wt->execution_time), TALER_JSON_spec_amount ("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_MERCHANT_ORDER_GET_REPLY_MALFORMED; omgh->cb (omgh->cb_cls, hr, NULL); GNUNET_JSON_parse_free (spec); return; } } for (unsigned int i = 0; ihint), /* FIXME: should return "detail" instead! */ GNUNET_JSON_spec_uint32 ("exchange_ec", &eec32), GNUNET_JSON_spec_uint32 ("exchange_hc", &ehs32), GNUNET_JSON_spec_fixed_auto ("coin_pub", &wr->coin_pub), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (w, ispec, NULL, NULL)) { GNUNET_break_op (0); hr->http_status = 0; hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED; omgh->cb (omgh->cb_cls, hr, NULL); GNUNET_JSON_parse_free (spec); 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; irefund_amount), GNUNET_JSON_spec_string ("reason", &ro->reason), TALER_JSON_spec_absolute_time ("timestamp", &ro->refund_time), 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_MERCHANT_ORDER_GET_REPLY_MALFORMED; omgh->cb (omgh->cb_cls, hr, NULL); GNUNET_JSON_parse_free (spec); 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); } GNUNET_JSON_parse_free (spec); } /** * Function called when we're done processing the GET /private/orders/$ORDER * request. * * @param cls the `struct TALER_MERCHANT_OrderMerchantGetHandle` * @param response_code HTTP response code, 0 on error * @param response response body, NULL if not in JSON */ static void handle_merchant_order_get_finished (void *cls, long response_code, const void *response) { 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 }; omgh->job = NULL; switch (response_code) { case MHD_HTTP_NOT_FOUND: hr.ec = TALER_JSON_get_error_code (json); hr.hint = TALER_JSON_get_error_hint (json); omgh->cb (omgh->cb_cls, &hr, NULL); TALER_MERCHANT_merchant_order_get_cancel (omgh); return; case MHD_HTTP_OK: /* see below */ break; default: hr.ec = TALER_JSON_get_error_code (json); 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); GNUNET_break_op (0); omgh->cb (omgh->cb_cls, &hr, NULL); TALER_MERCHANT_merchant_order_get_cancel (omgh); return; } 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_MERCHANT_ORDER_GET_REPLY_MALFORMED; omgh->cb (omgh->cb_cls, &hr, NULL); TALER_MERCHANT_merchant_order_get_cancel (omgh); return; } if (0 == strcmp ("paid", order_status)) { handle_paid (omgh, &hr); } else if (0 == strcmp ("claimed", order_status)) { handle_claimed (omgh, &hr); } else if (0 == strcmp ("unpaid", order_status)) { handle_unpaid (omgh, &hr); } else { GNUNET_break_op (0); hr.http_status = 0; hr.ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED; omgh->cb (omgh->cb_cls, &hr, NULL); 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) { struct TALER_MERCHANT_OrderMerchantGetHandle *omgh; unsigned long long tms; long tlong; GNUNET_assert (NULL != backend_url); GNUNET_assert (NULL != order_id); 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); omgh = GNUNET_new (struct TALER_MERCHANT_OrderMerchantGetHandle); omgh->ctx = ctx; omgh->cb = cb; omgh->cb_cls = cb_cls; { char *path; char timeout_ms[32]; GNUNET_snprintf (timeout_ms, sizeof (timeout_ms), "%llu", tms); GNUNET_asprintf (&path, "private/orders/%s", order_id); omgh->url = TALER_url_join (backend_url, path, "session_id", session_id, "transfer", transfer ? "YES" : "NO", (0 != tms) ? "timeout_ms" : NULL, timeout_ms, NULL); } if (NULL == omgh->url) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not construct request URL.\n"); GNUNET_free (omgh); return NULL; } { CURL *eh; eh = curl_easy_init (); if (NULL == eh) { GNUNET_break (0); GNUNET_free (omgh->url); GNUNET_free (omgh); return NULL; } if (CURLE_OK != curl_easy_setopt (eh, CURLOPT_URL, omgh->url)) { GNUNET_break (0); curl_easy_cleanup (eh); GNUNET_free (omgh->url); GNUNET_free (omgh); return NULL; } if (CURLE_OK != curl_easy_setopt (eh, CURLOPT_TIMEOUT_MS, tlong)) { GNUNET_break (0); curl_easy_cleanup (eh); GNUNET_free (omgh->url); GNUNET_free (omgh); return NULL; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Getting order status from %s\n", omgh->url); if (NULL == (omgh->job = GNUNET_CURL_job_add (ctx, eh, &handle_merchant_order_get_finished, omgh))) { GNUNET_break (0); GNUNET_free (omgh->url); GNUNET_free (omgh); return NULL; } } return omgh; } void TALER_MERCHANT_merchant_order_get_cancel ( struct TALER_MERCHANT_OrderMerchantGetHandle *omgh) { if (NULL != omgh->job) { GNUNET_CURL_job_cancel (omgh->job); omgh->job = NULL; } GNUNET_free (omgh->url); GNUNET_free (omgh); } /* end of merchant_api_merchant_get_order.c */