summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpriscilla <priscilla.huang@efrei.net>2022-11-25 08:42:25 -0500
committerpriscilla <priscilla.huang@efrei.net>2022-11-25 08:42:25 -0500
commit7bd9e0d6c7b1dec25b986a3db77027334e6a6085 (patch)
treeb60249b3a5ae22b7227232a5872e4782e70c141b
parentf6492a1e124bb71bd8082ffc1e07d6a663c81393 (diff)
downloadmerchant-7bd9e0d6c7b1dec25b986a3db77027334e6a6085.tar.gz
merchant-7bd9e0d6c7b1dec25b986a3db77027334e6a6085.tar.bz2
merchant-7bd9e0d6c7b1dec25b986a3db77027334e6a6085.zip
backenddb webhook
-rw-r--r--src/backenddb/merchant-0004.sql28
-rw-r--r--src/backenddb/merchantdb_helper.c12
-rw-r--r--src/backenddb/plugin_merchantdb_postgres.c344
-rw-r--r--src/backenddb/test_merchantdb.c425
-rw-r--r--src/include/taler_merchantdb_lib.h10
-rw-r--r--src/include/taler_merchantdb_plugin.h143
6 files changed, 919 insertions, 43 deletions
diff --git a/src/backenddb/merchant-0004.sql b/src/backenddb/merchant-0004.sql
index 8e341583..10897f1a 100644
--- a/src/backenddb/merchant-0004.sql
+++ b/src/backenddb/merchant-0004.sql
@@ -44,7 +44,7 @@ COMMENT ON COLUMN merchant_template.template_contract
COMMIT;
-CREATE TABLE IF NOT EXISTS merchant_webhooks
+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
@@ -52,22 +52,22 @@ CREATE TABLE IF NOT EXISTS merchant_webhooks
,event_type VARCHAR NOT NULL
,url VARCHAR NOT NULL
,http_method VARCHAR NOT NULL
- ,header_template VARCHAR NOT NULL
- ,body_template VARCHAR NOT NULL
- ,UNIQUE (merchant_serial, webhooks_id)
+ ,header_template VARCHAR
+ ,body_template VARCHAR
+ ,UNIQUE (merchant_serial, webhook_id)
);
-COMMENT ON TABLE merchant_webhooks
- IS 'webhooks used by the merchant (may be incomplete, frontend can override)';
-COMMENT ON COLUMN merchant_webhooks.event_type
- IS 'Event of the webhooks';
-COMMENT ON COLUMN merchant_webhooks.url
+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 use by the customer';
-COMMENT ON COLUMN merchant_webhooks.http_method
+COMMENT ON COLUMN merchant_webhook.http_method
IS 'http method use by the merchant';
-COMMENT ON COLUMN merchant_webhooks.header_template
- IS 'Header of the webhooks';
-COMMENT ON COLUMN merchant_webhooks.body_template
- IS 'Body of the webhooks';
+COMMENT ON COLUMN merchant_webhook.header_template
+ IS 'Header of the webhook';
+COMMENT ON COLUMN merchant_webhook.body_template
+ IS 'Body of the webhook';
COMMIT;
diff --git a/src/backenddb/merchantdb_helper.c b/src/backenddb/merchantdb_helper.c
index 51991ca7..e7d9f459 100644
--- a/src/backenddb/merchantdb_helper.c
+++ b/src/backenddb/merchantdb_helper.c
@@ -48,5 +48,17 @@ TALER_MERCHANTDB_template_details_free (
}
+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);
+}
+
+
/* end of merchantdb_helper.c */
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c
index 6a2bcb65..1176cdd5 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -6850,22 +6850,22 @@ postgres_delete_template (void *cls,
* @param cls closure
* @param instance_id instance to insert template for
* @param template_id template identifier of template to insert
- * @param pd the template details to insert
+ * @param td the template details to insert
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_template (void *cls,
const char *instance_id,
const char *template_id,
- const struct TALER_MERCHANTDB_TemplateDetails *pd)
+ 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 (pd->template_description),
- GNUNET_PQ_query_param_string (pd->image),
- TALER_PQ_query_param_json (pd->template_contract),
+ GNUNET_PQ_query_param_string (td->template_description),
+ GNUNET_PQ_query_param_string (td->image),
+ TALER_PQ_query_param_json (td->template_contract),
GNUNET_PQ_query_param_end
};
@@ -6883,7 +6883,7 @@ postgres_insert_template (void *cls,
* @param cls closure
* @param instance_id instance to update template for
* @param template_id template to update
- * @param pd update to the template details on success, can be NULL
+ * @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.
@@ -6892,15 +6892,15 @@ static enum GNUNET_DB_QueryStatus
postgres_update_template (void *cls,
const char *instance_id,
const char *template_id,
- const struct TALER_MERCHANTDB_TemplateDetails *pd)
+ 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 (pd->template_description),
- GNUNET_PQ_query_param_string (pd->image),
- TALER_PQ_query_param_json (pd->template_contract),
+ GNUNET_PQ_query_param_string (td->template_description),
+ GNUNET_PQ_query_param_string (td->image),
+ TALER_PQ_query_param_json (td->template_contract),
GNUNET_PQ_query_param_end
};
@@ -7025,7 +7025,7 @@ postgres_lookup_templates (void *cls,
* @param cls closure
* @param instance_id instance to lookup template for
* @param template_id template to lookup
- * @param[out] pd set to the template details on success, can be NULL
+ * @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
*/
@@ -7033,7 +7033,7 @@ static enum GNUNET_DB_QueryStatus
postgres_lookup_template (void *cls,
const char *instance_id,
const char *template_id,
- struct TALER_MERCHANTDB_TemplateDetails *pd)
+ struct TALER_MERCHANTDB_TemplateDetails *td)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
@@ -7042,7 +7042,7 @@ postgres_lookup_template (void *cls,
GNUNET_PQ_query_param_end
};
- if (NULL == pd)
+ if (NULL == td)
{
struct GNUNET_PQ_ResultSpec rs_null[] = {
GNUNET_PQ_result_spec_end
@@ -7058,11 +7058,11 @@ postgres_lookup_template (void *cls,
{
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("template_description",
- &pd->template_description),
+ &td->template_description),
GNUNET_PQ_result_spec_string ("image",
- &pd->image),
+ &td->image),
TALER_PQ_result_spec_json ("template_contract",
- &pd->template_contract),
+ &td->template_contract),
GNUNET_PQ_result_spec_end
};
@@ -7082,25 +7082,263 @@ postgres_lookup_template (void *cls,
* @param webhook_id webhook to delete
* @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
* if webhook unknown.
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_delete_webhook (void *cls,
+ const char *instance_id,
+ const char *webhook_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (webhook_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_webhook",
+ params);
+}
+
+
+/**
+ * Insert details about a particular webhook.
*
+ * @param cls closure
+ * @param instance_id instance to insert template for
+ * @param webhook_id webhook identifier of webhook to insert
+ * @param wb the webhook details to insert
+ * @return database result code
+ */
static enum GNUNET_DB_QueryStatus
-postgres_delete_template (void *cls,
+postgres_insert_webhook (void *cls,
const char *instance_id,
- const char *template_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 (template_id),
+ GNUNET_PQ_query_param_string (webhook_id),
+ GNUNET_PQ_query_param_string (wb->event_type),
+ GNUNET_PQ_query_param_string (wb->url),
+ GNUNET_PQ_query_param_string (wb->http_method),
+ GNUNET_PQ_query_param_string (wb->header_template),
+ GNUNET_PQ_query_param_string (wb->body_template),
GNUNET_PQ_query_param_end
+
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_template",
+ "insert_webhook",
+ params);
+}
+
+
+/**
+ * Update details about a particular webhook.
+ *
+ * @param cls closure
+ * @param instance_id instance to update template for
+ * @param webhook_id template to update
+ * @param wb update to the webhook details on success, can be NULL
+ * (in that case we only want to check if the webhook exists)
+ * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the webhook
+ * does not yet exist.
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_update_webhook (void *cls,
+ const char *instance_id,
+ const char *webhook_id,
+ const struct TALER_MERCHANTDB_WebhookDetails *wb)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (webhook_id),
+ GNUNET_PQ_query_param_string (wb->event_type),
+ GNUNET_PQ_query_param_string (wb->url),
+ GNUNET_PQ_query_param_string (wb->http_method),
+ GNUNET_PQ_query_param_string (wb->header_template),
+ GNUNET_PQ_query_param_string (wb->body_template),
+ GNUNET_PQ_query_param_end
+ };
+
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_webhook",
params);
}
- */
+
+
+/**
+ * Context used for postgres_lookup_webhook().
+ */
+struct LookupWebhookContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_WebhooksCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about webhook.
+ *
+ * @param[in,out] cls of type `struct LookupWebhookContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_webhooks_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupWebhookContext *wlc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *webhook_id;
+ char *event_type;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("webhook_id",
+ &webhook_id),
+ GNUNET_PQ_result_spec_string ("event_type",
+ &event_type),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ wlc->extract_failed = true;
+ return;
+ }
+ wlc->cb (wlc->cb_cls,
+ webhook_id,
+ event_type);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+/**
+ * Lookup all of the webhooks the given instance has configured.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup webhook for
+ * @param cb function to call on all webhook found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_webhooks (void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_WebhooksCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupWebhookContext wlc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ /* Can be overwritten by the lookup_webhook_cb */
+ .extract_failed = false,
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_webhooks",
+ params,
+ &lookup_webhooks_cb,
+ &wlc);
+ /* If there was an error inside lookup_webhook_cb, return a hard error. */
+ if (wlc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
+
+
+/**
+ * Lookup details about a particular webhook.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup webhook for
+ * @param webhook_id webhook to lookup
+ * @param[out] wb set to the webhook details on success, can be NULL
+ * (in that case we only want to check if the webhook exists)
+ * @return database result code
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_webhook (void *cls,
+ const char *instance_id,
+ const char *webhook_id,
+ struct TALER_MERCHANTDB_WebhookDetails *wb)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (webhook_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ if (NULL == wb)
+ {
+ struct GNUNET_PQ_ResultSpec rs_null[] = {
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_webhook",
+ params,
+ rs_null);
+ }
+ else
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("event_type",
+ &wb->event_type),
+ GNUNET_PQ_result_spec_string ("url",
+ &wb->url),
+ GNUNET_PQ_result_spec_string ("http_method",
+ &wb->http_method),
+ GNUNET_PQ_result_spec_string ("header_template",
+ &wb->header_template),
+ GNUNET_PQ_result_spec_string ("body_template",
+ &wb->body_template),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_webhook",
+ params,
+ rs);
+ }
+}
+
/**
* Establish connection to the database.
@@ -9601,6 +9839,65 @@ postgres_connect (void *cls)
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND template_id=$2"),
+ /* for postgres_lookup_webhooks() */
+ GNUNET_PQ_make_prepare ("lookup_webhooks",
+ "SELECT"
+ " webhook_id"
+ ",event_type"
+ " FROM merchant_webhook"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"),
+ /* for postgres_lookup_webhook() */
+ GNUNET_PQ_make_prepare ("lookup_webhook",
+ "SELECT"
+ " event_type"
+ ",url"
+ ",http_method"
+ ",header_template"
+ ",body_template"
+ " FROM merchant_webhook"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"
+ " AND merchant_webhook.webhook_id=$2"),
+ /* for postgres_delete_webhook() */
+ GNUNET_PQ_make_prepare ("delete_webhook",
+ "DELETE"
+ " FROM merchant_webhook"
+ " WHERE merchant_template.merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND merchant_webhook.webhook_id=$2"),
+ /* for postgres_insert_webhook() */
+ GNUNET_PQ_make_prepare ("insert_webhook",
+ "INSERT INTO merchant_webhook"
+ "(merchant_serial"
+ ",webhook_id"
+ ",event_type"
+ ",url"
+ ",http_method"
+ ",header_template"
+ ",body_template"
+ ")"
+ " SELECT merchant_serial,"
+ " $2, $3, $4, $5, $6, $7"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1"),
+ /* for postgres_update_webhook() */
+ GNUNET_PQ_make_prepare ("update_webhook",
+ "UPDATE merchant_webhook SET"
+ " event_type=$3"
+ ",url=$4"
+ ",http_method=$5"
+ ",header_template=$6"
+ ",body_template=$7"
+ " WHERE merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND webhook_id=$2"),
GNUNET_PQ_PREPARED_STATEMENT_END
};
struct GNUNET_PQ_ExecuteStatement es[] = {
@@ -9755,6 +10052,11 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
plugin->delete_template = &postgres_delete_template;
plugin->insert_template = &postgres_insert_template;
plugin->update_template = &postgres_update_template;
+ plugin->lookup_webhooks = &postgres_lookup_webhooks;
+ plugin->lookup_webhook = &postgres_lookup_webhook;
+ plugin->delete_webhook = &postgres_delete_webhook;
+ plugin->insert_webhook = &postgres_insert_webhook;
+ plugin->update_webhook = &postgres_update_webhook;
return plugin;
}
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
index 5a5b7d6b..1b186d9c 100644
--- a/src/backenddb/test_merchantdb.c
+++ b/src/backenddb/test_merchantdb.c
@@ -6889,7 +6889,7 @@ free_template_data (struct TemplateData *template)
/**
- * Compare two template for equality.
+ * Compare two templates for equality.
*
* @param a the first template.
* @param b the second template.
@@ -7262,6 +7262,428 @@ test_templates (void)
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://example.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))
+ 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;
+}
+
+
+/**
+ * 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[2];
+};
+
+
+/**
+ * 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";
+}
+
+
+/**
+ * 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=
+ "https://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 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));
+ TEST_RET_ON_FAIL (test_lookup_webhooks (&cls->instance,
+ 1,
+ 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;
+}
+
+
+
+
/**
* Function that runs all tests.
*
@@ -7280,6 +7702,7 @@ run_tests (void)
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 ());
return 0;
}
diff --git a/src/include/taler_merchantdb_lib.h b/src/include/taler_merchantdb_lib.h
index 8d84db79..09184661 100644
--- a/src/include/taler_merchantdb_lib.h
+++ b/src/include/taler_merchantdb_lib.h
@@ -68,6 +68,16 @@ 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);
+
#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 1fa58ecd..0e70ec02 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -337,6 +337,53 @@ struct TALER_MERCHANTDB_TemplateDetails
/**
+ * Typically called by `lookup_webhooks`.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param webhook_id ID 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;
+
+};
+
+
+/**
* Filter preferences.
*/
struct TALER_MERCHANTDB_OrderFilter
@@ -2442,7 +2489,7 @@ struct TALER_MERCHANTDB_Plugin
* @param cls closure
* @param instance_id instance to lookup template for
* @param template_id template to lookup
- * @param[out] pd set to the template details on success, can be NULL
+ * @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
*/
@@ -2450,13 +2497,13 @@ struct TALER_MERCHANTDB_Plugin
(*lookup_template)(void *cls,
const char *instance_id,
const char *template_id,
- struct TALER_MERCHANTDB_TemplateDetails *pd);
+ struct TALER_MERCHANTDB_TemplateDetails *td);
/**
* Delete information about a template.
*
* @param cls closure
- * @param instance_id instance to delete product of
+ * @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.
@@ -2473,14 +2520,14 @@ struct TALER_MERCHANTDB_Plugin
* @param cls closure
* @param instance_id instance to insert template for
* @param template_id template identifier of template to insert
- * @param pd the template details to insert
+ * @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,
- const struct TALER_MERCHANTDB_TemplateDetails *pd);
+ const struct TALER_MERCHANTDB_TemplateDetails *td);
/**
@@ -2489,7 +2536,7 @@ struct TALER_MERCHANTDB_Plugin
* @param cls closure
* @param instance_id instance to update template for
* @param template_id template to update
- * @param pd update to the template details on success, can be NULL
+ * @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.
@@ -2499,7 +2546,89 @@ struct TALER_MERCHANTDB_Plugin
(*update_template)(void *cls,
const char *instance_id,
const char *template_id,
- const struct TALER_MERCHANTDB_TemplateDetails *pd);
+ 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 database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*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 char *webhook_id);
+
+
+/**
+ * Insert details about a particular webhook.
+ *
+ * @param cls closure
+ * @param instance_id instance to insert template 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
+ (*insert_webhook)(void *cls,
+ const char *instance_id,
+ const char *webhook_id,
+ const struct TALER_MERCHANTDB_WebhookDetails *wb);
+
+
+/**
+ * Update details about a particular webhook.
+ *
+ * @param cls closure
+ * @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
+ (*update_webhook)(void *cls,
+ const char *instance_id,
+ const char *webhook_id,
+ const struct TALER_MERCHANTDB_WebhookDetails *wb);
};