From 85e44ceea61303ee3f2b4464a72742f85b2688ec Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 27 Jan 2023 17:42:56 +0100 Subject: [PATCH] work on kycaid attribute extraction --- src/exchangedb/Makefile.am | 4 +- src/include/taler_attributes.h | 36 ++++ src/include/taler_kyclogic_plugin.h | 2 - src/kyclogic/plugin_kyclogic_kycaid.c | 227 ++++++++++++++++++++++---- 4 files changed, 237 insertions(+), 32 deletions(-) diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index 2ae5fe426..8ea9a4123 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -291,7 +291,9 @@ libtalerexchangedb_la_LDFLAGS = \ check_PROGRAMS = \ - test-exchangedb-postgres \ + test-exchangedb-postgres + +noinst_PROGRAMS = \ bench-db-postgres\ perf-exchangedb-reserves-in-insert-postgres\ test-exchangedb-by-j-postgres\ diff --git a/src/include/taler_attributes.h b/src/include/taler_attributes.h index 6c2a2b033..22155aee4 100644 --- a/src/include/taler_attributes.h +++ b/src/include/taler_attributes.h @@ -34,6 +34,17 @@ extern "C" { #endif #endif +/** + * Legal name of the business/company. + */ +#define TALER_ATTRIBUTE_COMPANY_NAME "company_name" + +/** + * Legal country of registration of the business/company, + * 2-letter country code using ISO 3166-2. + */ +#define TALER_ATTRIBUTE_REGISTRATION_COUNTRY "registration_country" + /** * Full name, when known/possible using "Lastname, Firstname(s)" format, * but "Firstname(s) Lastname" or "Firstname M. Lastname" should also be @@ -43,6 +54,22 @@ extern "C" { */ #define TALER_ATTRIBUTE_FULL_NAME "full_name" +/** + * True/false indicator if the individual is a politically + * exposed person. + */ +#define TALER_ATTRIBUTE_PEP "pep" + +/** + * Phone number (of business or individual). + */ +#define TALER_ATTRIBUTE_PHONE "phone" + +/** + * Email address (of business or individual). + */ +#define TALER_ATTRIBUTE_EMAIL "email" + /** * Birthdate of the person, as far as known. YYYY-MM-DD, a value * of 0 (for DD, MM or even YYYY) is to be used for 'unknown' @@ -63,6 +90,15 @@ extern "C" { */ #define TALER_ATTRIBUTE_NATIONALITIES "nationalities" +/** + * Residence countries(s) of the person using 2-letter country codes ("US", + * "DE", "FR", "IT", etc.) separated by commas if multiple residences are + * confirmed ("EN,US,DE"). Note that in the latter case it is not guaranteed + * that all residences were necessarily recorded. Empty string for + * international nomads. NULL for not collected. + */ +#define TALER_ATTRIBUTE_RESIDENCES "residences" + #if 0 /* keep Emacsens' auto-indent happy */ { diff --git a/src/include/taler_kyclogic_plugin.h b/src/include/taler_kyclogic_plugin.h index a7e222179..22e4f894f 100644 --- a/src/include/taler_kyclogic_plugin.h +++ b/src/include/taler_kyclogic_plugin.h @@ -77,8 +77,6 @@ enum TALER_KYCLOGIC_KycStatus * The provider is still checking. */ TALER_KYCLOGIC_STATUS_PROVIDER_PENDING - - = TALER_KYCLOGIC_STATUS_PROVIDER | TALER_KYCLOGIC_STATUS_PENDING, diff --git a/src/kyclogic/plugin_kyclogic_kycaid.c b/src/kyclogic/plugin_kyclogic_kycaid.c index e34795540..9d0bc5262 100644 --- a/src/kyclogic/plugin_kyclogic_kycaid.c +++ b/src/kyclogic/plugin_kyclogic_kycaid.c @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022, 2023 Taler Systems SA Taler is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -19,6 +19,8 @@ * @author Christian Grothoff */ #include "platform.h" +#include "taler_attributes.h" +#include "taler_kyclogic_lib.h" #include "taler_kyclogic_plugin.h" #include "taler_mhd_lib.h" #include "taler_curl_lib.h" @@ -749,7 +751,7 @@ log_failure (json_t *verifications) /** * Function called when we're done processing the - * HTTP "/verifications/{verification_id}" request. + * HTTP "/applicants/{verification_id}" request. * * @param cls the `struct TALER_KYCLOGIC_WebhookHandle` * @param response_code HTTP response code, 0 on error @@ -769,30 +771,116 @@ handle_webhook_finished (void *cls, { case MHD_HTTP_OK: { - const char *applicant_id; - const char *verification_id; - const char *status; - bool verified; - json_t *verifications; + const char *type; + const char *profile_status; + const char *first_name = NULL; + const char *last_name = NULL; + const char *middle_name = NULL; + const char *dob = NULL; + const char *residence_country = NULL; + const char *gender = NULL; + bool pep = false; + bool no_pep = false; + const char *company_name = NULL; + const char *business_activity_id = NULL; + const char *registration_country = NULL; + const char *email = NULL; + const char *phone = NULL; + json_t *addresses = NULL; + json_t *documents = NULL; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("applicant_id", - &applicant_id), - GNUNET_JSON_spec_string ("verification_id", - &verification_id), - GNUNET_JSON_spec_string ("status", - &status), /* completed, pending, ... */ - GNUNET_JSON_spec_bool ("verified", - &verified), - GNUNET_JSON_spec_json ("verifications", - &verifications), + GNUNET_JSON_spec_string ("type", + &type), + GNUNET_JSON_spec_string ("profile_status", + &profile_status), /* valid, invalid, pending */ + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("email", + &email), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("phone", + &phone), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_json ("addresses", + &addresses), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_json ("documents", + &documents), + NULL), GNUNET_JSON_spec_end () }; + struct GNUNET_JSON_Specification bspec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("company_name", + &company_name), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("business_activity_id", + &business_activity_id), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("registration_country", + ®istration_country), + NULL), + GNUNET_JSON_spec_end () + }; + struct GNUNET_JSON_Specification pspec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("first_name", + &first_name), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("middle_name", + &middle_name), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("last_name", + &last_name), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("dob", + &dob), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("residence_country", + &residence_country), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("gender", + &gender), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("pep", + &pep), + &no_pep), + GNUNET_JSON_spec_end () + }; + struct GNUNET_JSON_Specification *ispec = NULL; struct GNUNET_TIME_Absolute expiration; + bool no_parse; + enum TALER_KYCLOGIC_KycUserType ut; - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) + no_parse = (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)); + if (! no_parse) + { + ut = (0 == strcasecmp ("person", + type)) + ? TALER_KYCLOGIC_KYC_UT_INDIVIDUAL + : TALER_KYCLOGIC_KYC_UT_BUSINESS; + ispec = (ut == TALER_KYCLOGIC_KYC_UT_INDIVIDUAL) + ? pspec + : bspec; + no_parse = (GNUNET_OK != + GNUNET_JSON_parse (j, + ispec, + NULL, NULL)); + } + if (no_parse) { GNUNET_break_op (0); json_dumpf (j, @@ -816,20 +904,94 @@ handle_webhook_finished (void *cls, resp); break; } - if (! verified) + if (0 == strcasecmp ("valid", + profile_status)) { - log_failure (verifications); + log_failure (json_object_get (j, + "decline_reasons")); } resp = MHD_create_response_from_buffer (0, "", MHD_RESPMEM_PERSISTENT); - if (verified) + if (0 == strcasecmp ("valid", + profile_status)) { json_t *attr; - attr = json_object (); - // FIXME: initialize attributes! - GNUNET_assert (NULL != attr); + if (ut == TALER_KYCLOGIC_KYC_UT_INDIVIDUAL) + { + char *name = NULL; + + if ( (NULL != last_name) || + (NULL != first_name) || + (NULL != middle_name) ) + { + GNUNET_asprintf (&name, + "%s, %s %s", + (NULL != last_name) + ? last_name + : "", + (NULL != first_name) + ? first_name + : "", + (NULL != middle_name) + ? middle_name + : ""); + } + attr = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ( + TALER_ATTRIBUTE_BIRTHDATE, + dob)), + GNUNET_JSON_pack_allow_null ( + no_pep + ? GNUNET_JSON_pack_string ( + TALER_ATTRIBUTE_PEP, + NULL) + : GNUNET_JSON_pack_bool ( + TALER_ATTRIBUTE_PEP, + pep)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ( + TALER_ATTRIBUTE_FULL_NAME, + name)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ( + TALER_ATTRIBUTE_PHONE, + phone)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ( + TALER_ATTRIBUTE_EMAIL, + email)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ( + TALER_ATTRIBUTE_RESIDENCES, + residence_country)) + ); + GNUNET_free (name); + } + else + { + attr = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ( + TALER_ATTRIBUTE_COMPANY_NAME, + company_name)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ( + TALER_ATTRIBUTE_PHONE, + phone)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ( + TALER_ATTRIBUTE_EMAIL, + email)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ( + TALER_ATTRIBUTE_REGISTRATION_COUNTRY, + residence_country)) + ); + } + // FIXME: do something about addresses & documents! expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity); wh->cb (wh->cb_cls, wh->process_row, @@ -846,18 +1008,25 @@ handle_webhook_finished (void *cls, } else { + enum TALER_KYCLOGIC_KycStatus ks; + + ks = (0 == strcasecmp ("pending", + profile_status)) + ? TALER_KYCLOGIC_STATUS_PENDING + : TALER_KYCLOGIC_STATUS_USER_ABORTED; wh->cb (wh->cb_cls, wh->process_row, &wh->h_payto, wh->pd->section, wh->applicant_id, wh->verification_id, - TALER_KYCLOGIC_STATUS_USER_ABORTED, + ks, GNUNET_TIME_UNIT_ZERO_ABS, NULL, MHD_HTTP_NO_CONTENT, resp); } + GNUNET_JSON_parse_free (ispec); GNUNET_JSON_parse_free (spec); } break; @@ -1201,7 +1370,7 @@ kycaid_webhook (void *cls, } GNUNET_asprintf (&wh->url, - "https://api.kycaid.com/verifications/%s", + "https://api.kycaid.com/applicants/%s", verification_id); GNUNET_break (CURLE_OK == curl_easy_setopt (eh,