FIFA Connect ID .NET SDK, v6.1

1. Introduction

Main responsibility of FIFA Connect ID Service (FCI) is to generate a unique FIFA_ID for a football/futsal/beach soccer stakeholder. Football Management Systems operating in different Member Associations integrate with the FCI in order to register persons, organisations and facilities, and have FIFA_ID generated for these entities. Integration can be done in two ways:

This documentation is a reference point for a developer who wants to use SDK to integrate Football Management System with FCI.

Documentation is organised into three main chapters which discuss all aspects of a certain entity type management.

Chapters 2 and 6 provide - respectively - a brief introduction to setting up the integration and an overview of latest changes to SDK.

1.1 Clarification on the class names

Class names – although sometimes confusing – follow the FIFA Data Standard and as such they cannot be subject of modification.

1.2 Unexpected Setters

Some SDK classes that represent read-only data provided by the server side (e.g. PersonDuplicateType) contain setter methods. This data isn’t normally modified on the client side. The reason to have them, however, is that the client can easily simulate possible response from service by setting values to objects – and that can be useful for testing.

1.3 Date and time

All SDK methods require date and time in the UTC time zone. If date/time is passed as an argument to the SDK method and is not in UTC then Exception is thrown.

Note: pay special attention to birth dates so that they are not affected by time zones changes. For example, both persons born in Chile and Japan on 1.1.2012 should be registered as born on 1.1.2012 0:00 UTC. Consequently, all birth dates returned from the SDK should represent a date without a time zone (time zone should be disregarded when comparing dates). For example, birth date 1.1.2012 0:00 [UTC-3] represents the same birth date as 1.1.2012 0:00 [UTC+9].

1.4 Thread-safety

Any instance members are not guaranteed to be thread safe. In particular all methods on FifaConnectServiceBusClient cannot be called from multiple threads simultaneously. If needed, new instance of FifaConnectServiceBusClient should be created per thread.

2. Setup

SDK libraries to reference:

3. Person

Current version of the SDK supports two approaches to operations on Person entity:

Two following sections describe implementation steps which are necessary to cover both scenarios:

Note that in most cases you would need to use Registration Facade instance, not the client. It is, however possible to get FIFA Connect ID Client out of facade as shown in below example.

var client = facade.ConnectIdClient;

3.1 Using Registration Facade

The RegistrationFacade class simplifies the usage of FifaConnectIdClient and FifaConnectServiceBusClient, in order to cover full scenario of registering a person in FIFA Connect ID. If potential duplicates are found, Registration Facade will send requests for person details to other MAs using Connect Service Bus and wait for their response (with timeout). The same logic needs to apply when one tries to update Person details or search for duplicates but does not intend to register a person even if no duplicates are found.

3.1.1 (Step 1) Get FIFA identifier of your organisation and client credentials

In order to start registering entities (persons, facilities, organisations), you will need a FIFA identifier of your organisation (i.e. MA), a set of client credentials (to authorize your instance of SDK with Connect Id services) and a pair of public and private certificates generated (so SDK could encrypt and decrypt outgoing and incoming data).

Instruction of how to generate and upload certificate can be found in fifa-connectservicebus-certificates-generation.html. After it's done, a next step is to provide generated private key to the client. The easiest way of doing that is to use PrivateKeyMemoryStorage class. In order to create its instance, you need to provide X509Certificate2 object(s) as its constructor parameter(s). Each X509Certificate2 object consists of the key (from generated file) and password (optionally). Please take a look at the following example:

var pathToCertificate = @"C:\Encrypt\certificate.pfx";
var certificate = new X509Certificate2();
certificate.Import(pathToCertificate, "password", X509KeyStorageFlags.DefaultKeySet);

var privateStore = new PrivateKeyMemoryStorage(certificate);

Note that in above example we used PrivateKeyMemoryStorage class, which implements IPrivateKeyStorage interface. In fact, any implementation of IPrivateKeyStorage could be used here - we don't restrict using own code at this point.

3.1.2 (Step 2) Create Registration Facade

Then create RegistrationFacade object:

var environment = ConnectIdEnvironment.Beta;
var clientCredentials = new ClientCredentials("clientId", "secretKey");
var privateKeyStorage = new YourPrivateKeyStorage();

var facade = new RegistrationFacade(environment, clientCredentials, privateKeyStorage);

3.1.3 (Step 3) Setup Service Bus listener

After creating facade, you can set up the background listener, which will listen and process new messages received from Connect Service Bus. This is the key element that every MA needs to implement. It allows all MAs to exchange persons’ details from their local systems via Service Bus in order to catch international duplicates and ensure that the same person will not be registered twice.

When RegistrationFacade and listener are running under the same application SDK uses PersonDetailsMemoryCache. This default implementation of the interface IPersonDetailsCache puts XML that is created in the response for Person details request to the cache. As a result method RegisterPersonAndWaitForDetailsInCaseOfDuplicates gets the XML from the cache as well.

Before starting listener implementation consider reading chapter 3.1.8 [Optional] Multi-server scenario as guidance in case when listener will be hosted as a separate web job. In that scenario own implementation of IPersonDetailsCache must be done.

When BackgroundListener receives a message with a request for person's details it will call a method GetPersonDetails on PersonDetailsProvider passing FIFA ID of a person. Therefore you have to provide implementation of IPersonDetailsProvider that makes a lookup (e.g. in your database) and returns a valid PersonLocal XML from FIFA Data Standard (http://data.fifaconnect.org). If for any reason record for a requested person is not found or cannot be returned, PersonDetailsProvider should return null or empty string.

Code snippet below shows how to provide sample PersonDetailsProvider.

var provider = new YourPersonDetailsProvider();
var listener = facade.ConfigureListener(provider);

// start listening for new messages
await listener.RunWithTimeout(TimeSpan.FromMinutes(10)).ConfigureAwait(false);

XML below is an example of data that should be returned by PersonDetailsProvider

<?xml version="1.0" encoding="UTF-8"?>
<PersonLocal xmlns="http://fifa.com/fc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xmime="http://www.w3.org/2005/05/xmlmime" PersonFIFAId="105C3Z1" LocalFirstName="ΑΓΓΕΛΗΣ" LocalLastName="ΧΑΡΑΛΑΜΠΟΥΣ" LocalLanguage="gre" LocalCountry="CY" LocalSystemMAId="1234567" InternationalFirstName="ANGELIS" InternationalLastName="CHARALAMBOUS" Gender="male" DateOfBirth="1989-05-31" PlaceOfBirth="Larnaca" RegionOfBirth="Larnaca" CountryOfBirth="CY" Nationality="CY">
    <NationalIdentifier Identifier="GHKJKLD123132132" NationalIdentifierNature="PassportNumber" Country="CY" DateFrom="2010-08-13" DateTo="2020-08-13"/>
    <PlayerRegistration PersonFIFAId="105C3Z1" Status="active" OrganisationFIFAId="105C40I " RegistrationValidFrom="2003-04-11" RegistrationValidTo="2009-06-15" Discipline="Football" Level="amateur" RegistrationNature="Registration"/>
    <PlayerRegistration PersonFIFAId="105C3Z1" Status="active" OrganisationFIFAId="105C40I " RegistrationValidFrom="2009-06-16" Discipline="Football" Level="pro" RegistrationNature="Registration"/>
    <TeamOfficialRegistration PersonFIFAId="105C3Z1" Status="active" OrganisationFIFAId="105C40I" RegistrationValidFrom="2012-08-13" RegistrationValidTo="2014-08-13" Discipline="Futsal" TeamOfficialRole="Coach"/>
</PersonLocal>

3.1.3.1 Prepare data for XML serialization

SDK doesn't restrict any method of generating string with XML content. Recommended and by far the easiest method, however, is using PersonLocalXmlSerializer class, that we provided in SDK.

Important note: The SDK does XML validation using an XSD schema you can find in the Resources directory in the SDK package. By default, the SDK assumes that the Resources directory is located next to the executing assembly (typically in bin/Debug/ or bin/Release). If the SDK fails to locate it, any serialization attempt will fail with an error message indicating that the file scenarios.xsd could not be found. However, there is a way to tell the SDK to look for XSD files in a different location. You can instantiate PathToScenariosXsdResolver using a constructor accepting an argument pointing to a directory where scenarios.xsd is located. The resolver then needs to be passed into the overloaded constructor PersonLocalXmlSerializer(IPathResolver xsdPathResolver).

var xsdFilesResolver = new PathToScenariosXsdResolver(@"C:\custom\directory\where\scenarios.xsd\is\located");
var serializer = new PersonLocalXmlSerializer(xsdFilesResolver);

All that is required is to run the method Serialize() which takes PersonLocal object as an input. All required fields (in accordance with FIFA Data Standard) must be provided, otherwise any deficiencies will be detected during validation. This rule applies for all model classes used in XML serialization process. Check below example to see how to create PersonLocal object with required and optional fields.

// creating an instance of the object
// required fields
var personLocal = new PersonLocal("ΧΑΡΑΛΑΜΠΟΥΣ", Gender.Male, ISO3166CountryCode.CY,
    new DateTime(1990, 11, 11), ISO316613CountryCode.CY, "Larnaca", ISO6392LanguageCode.Gre,
    ISO3166CountryCode.CY, "105C3Z1");

// optional fields
personLocal.LocalFirstName = "ΑΓΓΕΛΗΣ";
personLocal.LocalSystemMAId = "1234567";
personLocal.InternationalFirstName = "ANGELIS";
personLocal.InternationalLastName = "CHARALAMBOUS";
personLocal.RegionOfBirth = "Larnaca";
personLocal.SecondNationality = ISO3166CountryCode.GR;
personLocal.PopularName = "Angelis Angeli";

PersonLocal created this way contains general information of the person whose details will be sent to other MA. Another business context are registrations. Each registration type can be included in XML file by adding new elements to lists by following methods: AddPlayerRegistration(), AddTeamOfficialRegistration(), AddMatchOfficialRegistration() and AddOrganisationOfficialRegistration(). As all types are very similar to each other (as all extend Registration class), let's focus on how to set up PlayerRegistration and link it with PersonLocal object:

// PersonDetailsRequest is available in GetPersonDetails() method of IPersonDetailsProvider interface
var request = new PersonDetailsRequest("replyTo", "fifaId", "correlationId", null);
var personFIFAId = request.UniqueFifaId;
var clubOrMemberAssociationId = "105C40I";

// creating an instance of the object
var personLocal = new PersonLocal("ΧΑΡΑΛΑΜΠΟΥΣ", Gender.Male, ISO3166CountryCode.CY,
    new DateTime(1990, 11, 11), ISO316613CountryCode.CY, "Larnaca", ISO6392LanguageCode.Gre,
    ISO3166CountryCode.CY, personFIFAId);

// create an instance of player registration
var playerRegistration = new PlayerRegistration(personFIFAId, Status.Active, clubOrMemberAssociationId, new DateTime(2003, 4, 11),
    RegistrationLevel.Amateur, Discipline.Football, PlayerRegistrationNature.Registration);

// optional fields
playerRegistration.RegistrationValidTo = new DateTime(2006, 6, 15);
playerRegistration.ClubTrainingCategory = ClubTrainingCategory.Category3;

// include player registration in PersonLocal
personLocal.AddPlayerRegistration(playerRegistration);

Another important field, which can be set in PersonLocal in similar way as registrations, is Photo. This property itself is not required, but when using it, a mandatory instance of PictureEmbedded class needs to be passed as a constructor argument. SDK gives two possibilities when preparing PictureEmbedded object: using byte[] array (new PictureEmbedded(byte[] image)) or using path to local file (new PictureEmbedded(string localPath)). Below example shows what's the approach here:

// creating an instance of the object
var personLocal = new PersonLocal("ΧΑΡΑΛΑΜΠΟΥΣ", Gender.Male, ISO3166CountryCode.CY,
    new DateTime(1990, 11, 11), ISO316613CountryCode.CY, "Larnaca", ISO6392LanguageCode.Gre,
    ISO3166CountryCode.CY, "105C3Z1");

// add all mandatory fields
var absolutePathToPhoto = "C:\\image.png";
var picture = new PictureEmbedded(absolutePathToPhoto);

// include photo in PersonLocal
personLocal.Photo = new Photo(picture);

SDK supports following image formats: jpg, png, gif, tif, bmp. Please mind that the maximum size of a person details message sent through Service Bus is approximately 10MB. You might want to ensure that the size of the photo does not make the entire PersonLocal object exceed that limit. A reasonable boundary for a PNG file is 8MB.

NationalIdentifier and LocalPersonName are both optional in PersonLocal scope and there is no difference in rules with setting their properties comparing to other fields. See example of NationalIdentifier below:

// creating an instance of the object
var personLocal = new PersonLocal("ΧΑΡΑΛΑΜΠΟΥΣ", Gender.Male, ISO3166CountryCode.CY,
        new DateTime(1990, 11, 11), ISO316613CountryCode.CY, "Larnaca", ISO6392LanguageCode.Gre,
        ISO3166CountryCode.CY, "105C3Z1");

// create an instance of player registration
var nationalIdentifier = new NationalIdentifier("9876543210", NationalIdentifierNature.PassportNumber, ISO3166CountryCode.CY);

// optional fields
nationalIdentifier.Description = "NationalIdentifier test description";
nationalIdentifier.DateFrom = new DateTime(2003, 4, 11);
nationalIdentifier.DateTo = new DateTime(2004, 6, 1);

// include national identifier in PersonLocal
personLocal.AddNationalIdentified(nationalIdentifier);

3.1.3.2 Serialize XML document

After PersonLocal object is created, you need to pass it to Serialize() method of PersonLocalXmlSerializer service. Under the hood, this method creates XML string and executes validation against FIFA Data Standard. In case validation fails, XmlSerializationException will be thrown giving a message with all possible issues that were found during validation. As all of this happens in background listener, you should implement a proper error handling when catching the exception, so to get notified in case when XML response was not generated. Note that in case of exception thrown, MA that was asking for person details, won't receive a correct answer, so it's important to keep data structure valid and try to limit number of exceptions.

// creating an instance of the object
var personLocal = new PersonLocal("ΧΑΡΑΛΑΜΠΟΥΣ", Gender.Male, ISO3166CountryCode.CY,
    new DateTime(1990, 11, 11), ISO316613CountryCode.CY, "Larnaca", ISO6392LanguageCode.Gre,
    ISO3166CountryCode.CY, "105C3Z1");

var xmlSerializer = new PersonLocalXmlSerializer();

String xmlString = null;
try {
    xmlString = xmlSerializer.Serialize(personLocal);
}
catch (XmlSerializationException e) {
    var validationErrors = e.XmlValidationErrors;
    // handle serialization exception
}

3.1.3.3 (Optional) XML validation

As PersonLocalXmlSerializer includes XML validation, there is no need of running additional validation over it. But, in case of internal unit tests or when instead of our helper tool, custom solution produces XML content, it is possible to check whether output is compliant with FIFA Data Standard. To achieve that, use XsdSchemaValidator as on following example:

//Invalid xml string
var xmlString = "xmlString";
var xsdSchemaValidator = new XsdSchemaValidator();
var validationResult = xsdSchemaValidator.Validate(xmlString);

if (!validationResult.IsValid) {
    // handle validation errors
    var validationErrors = validationResult.ValidationFailures;
}

Important note: Alternatively you can use a custom IPathResolver for providing custom path to \Resources\Xsd\scenarios.xsd file location. To achieve that you need to create a class implementing IPathResolver and use overloaded constructor XsdSchemaValidator(IPathResolver scenariosPathResolver).

3.1.3.4 (Optional) Clean-up of old messages

When configuring the listener, an implementation of ICacheCleanJob can be provided, that will run alongside the listener and take care of the removal of old messages. You can use the default implementation provided by the SDK (PersonDetailsCacheCleanJob) or create your own.

var provider = new YourPersonDetailsProvider();
var cleanJobSettings = new PersonDetailsCacheCleanJobSettings(facadeSettings.PersonDetailsCache);
var cleanJob = new PersonDetailsCacheCleanJob(cleanJobSettings); /* or YourOwnCleanJob */

/* Please see section 6.1.1 for reference. If TMS integration is not within the scope of your integration, please provide a null value instead */
var tmsHandlers = TmsFacadeHandlersFactory.CreateMessageHandlers(responsesCache); 
var listener = facade.ConfigureListener(provider, tmsHandlers, cleanJob);

3.1.4 (Step 4) [Optional] Subscribe to other events from Connect Service Bus

Additionally an instance of the BackgroundListener class exposes the following events:

These events are automatically raised by the listener. You can use them to register your own event handlers and provide additional logic for handling received messages.

listener.PersonDetailsReceived += details =>
{
    // do something with those details
};

listener.GenericMessageReceived += message =>
{
    var bytes = message.Content;
    var actionName = message.Action;
};

If a message received from Connect Service Bus is processed properly by all registered handlers (no exceptions are thrown), then the message is marked as processed and deleted from the Connect Service Bus queue. In case any handler throws an exception, a message will not be deleted from the Connect Service Bus but will be scheduled for later processing. BackgroundListener will try to process this message, but after a series of unsuccessful handles, a message will be moved to a dead messages queue, which will be monitored by FIFA Connect ID Helpdesk.

3.1.5 (Step 5) Register a new person

First create a person you want to register:

var organisationId = "id";

// setup player name(s)
var personNames = new List<PersonName>
{
    new PersonName("John", "Doe")
};

// setup birthdate
var dateOfBirth = new DateTime(1990, 10, 12);
var person = new PersonType
{
    DateOfBirth = dateOfBirth
};

// setup registrations
var registration = new PlayerRegistrationType
{
    OrganisationFIFAId = organisationId,
    Status = "active",
    Level = "Pro",
    Discipline = "Football",
    RegistrationNature = "Registration"
};

person.PlayerRegistrations = new List<PlayerRegistrationType>
{
    registration
};

// finally create PersonData
var personData = new PersonData(personNames, person);

Then try to register a person using Registration Facade. The code snippet below shows an example handling (writing to console) of both scenarios, i.e. successful registration and potential duplicates found.

/* A record identifier in your database or any other key that is unique for a person.
This data will be stored hashed and cannot be decrypted.
For further information please consult section 3.1.5.2 Idempotency */
string personLocalId = "";
TimeSpan timeout = TimeSpan.FromSeconds(10);

var result = await facade.RegisterPersonAndWaitForDetailsInCaseOfDuplicates(
    personData, timeout, personLocalId).ConfigureAwait(false);

if (result.IsSuccessful)
{
    Console.WriteLine($"Person successfully registered with unique FIFA ID: {result.UniqueFifaId}.");
}
else {
    Console.WriteLine("Duplicates found.");

    foreach (var duplicateWithDetails in result.PersonDuplicateWithDetails)
    {
        var idOfDuplicatedPerson = duplicateWithDetails.Duplicate.PersonFifaId;
        var proximityScore = duplicateWithDetails.Duplicate.ProximityScore;
        var dataReceived = duplicateWithDetails.PersonDetails == null
            ? "<detailed person data not received from other MA - timeout>"
            : duplicateWithDetails.PersonDetails.XmlData;

        Console.WriteLine($"FIFA ID: {idOfDuplicatedPerson}, score: {proximityScore}, FIFA Data XML: {dataReceived}");
    }
}

Service Bus listener must be up and running to receive person details. There may be cases where a person has no registrations, but still appears on the list of duplicates as FIFA Data Standard does not require any registrations to construct a valid object. Connect Id stores computed hashes, but has no way of knowing which MA to ask for the details, so as a result null XML will be returned. In addition to that, for every potential duplicate there will be a hash score computed, ranging from 0 (no resemblance) to 1 - exact match. Your system should be prepared to handle duplicates, considering both the possibility of receiving incomplete data and the value of the hash score.

Troubleshooting

Please keep in mind that, especially in the Beta environment, some of the listeners of other Member Associations might not be working. The method waits a specified period of time for the details to arrive and returns without them afterwards.

If you are certain that both your and the other MA's listener are up and running and you still do not receive person details, make sure that a valid implementation of IPersonDetailsCache is used. More on the subject can be found in the chapter 3.1.8.

Detailed explanation of handling forementioned issues can be found under this FAQ article.

3.1.5.1 Handling multiple names (local, international and maiden/birth names)

SDK provides support for registering a person with multiple names. Two scenarios will usually require such a feature:

Support for such a scenario depends on the way person registration is used.

XML in FIFA Data Standard

If you use RegisterPersonAsync which takes XML in FIFA Data Standard as a parameter, make sure that the following attributes of PersonLocal (PersonLocal schema) are filled:

You don't need to do anything else, as SDK will take care of parsing XML and sending data to the server.

Native SDK objects of type Fifa.ConnectId.Sdk.Core.XmlParser.Models.PersonData

In this case, before registering a person, you need to create a list of pairs of names (List<PersonName>) . The following example shows the most sophisticated scenario when a single person has both: local name, international name and maiden name provided. Although two pairs of names will be used in minority of cases and three pairs hardly ever, the example is instructive.

var internationalFirstName = "Olga";
var internationalLastName = " Terekhova";
var localFirstName = "Ольга";
var localLastName = "Терехова";
var localMaidenName = "Бори́совна";

var personInternationalName = new PersonName(internationalFirstName, internationalLastName);
var personLocalName = new PersonName(localFirstName, localLastName);
var personMaidenName = new PersonName(localFirstName, localMaidenName);

// setup player name(s)
var personNames = new List<PersonName>();

personNames.Add(personInternationalName);
personNames.Add(personLocalName);
personNames.Add(personMaidenName);

Idempotency ensures that the same record is saved only once even when multiple registration requests are sent. To ensure that the same record is not registered in the system twice we recommend to pass person local identifier when registering person (Person local id can be a record identifier in your database or any other key that is unique for a person). Local identifier will be useful in case of retries on the client side (e.g. due to network issues or occasional client time-outs). It is sent and stored on the server in a hashed form (SHA-512 is used by default by SDK) and cannot be decrypted.

If you choose not to take advantage of the idempotency mechanism, you should expect that in rare situations FIFA_ID will be created on/by the server but you will not receive it on the client. Should this happen, the next time you try to register the same person, you will receive a duplicate pointing to your Member Association, but without any details (as your listener will not be able to provide details for the person which FIFA_ID you have not received).

Using person local identifier

The only thing that you need to do to start using idempotency mechanism is to pass additional parameter (local person identifier) to ForceRegisterPerson or RegisterPersonAndWaitForDetailsInCaseOfDuplicates methods on RegistrationFacade. Based on the method that is used the system behaves as follows:

var personLocalIdentifier = "Person Local Id";
var result = await facade.RegisterPersonAndWaitForDetailsInCaseOfDuplicates(personData.Person.ToString(), TimeSpan.FromSeconds(10), personLocalIdentifier).ConfigureAwait(false);

In the example above, the RegisterPersonAndWaitForDetailsInCaseOfDuplicates will check it there is no other registration with the same person local identifier. If the registration with the same person local identifier exists, it will be returned as a result. There will be no second registration nor the update of an existing one. If there is no registration with the same person local identifier, the method will run the default logic of searching for duplicates and registering a new person. Person local identifier will be stored on the server in the hashed form for the newly created record.

Please note, that the use of the ForceRegisterPerson does not change the behaviour, i.e. a new person will not be created if the same person local identifier is used.

Custom hashing method for encrypting local identifier

Optionally, you are allowed to change a default mechanism (SHA-512 hashing) of encrypting local identifier. Code snippet below presents how to do that by implementing IHashedPersonProcessor interface. Provided implementation needs to be passed to RegistrationFacadeSettings class when creating registration facade. Please take a look at the following example:

public class CustomHashedPersonHashingMechanismProcessor : IHashedPersonProcessor
{
    public HashedPersonType Process(HashedPersonType hashedPersonType)
    {
        var localPersonIdHash = "Make hashing here";
        return new HashedPersonType(hashedPersonType.NameHashes, hashedPersonType.Person, localPersonIdHash);
    }
}
var settings = new RegistrationFacadeSettings()
{
    HashedPersonProcessor = new CustomHashedPersonHashingMechanismProcessor()
};

var facade = new RegistrationFacade(connectIdEnvironment, clientCredentials, privateKeyStorage, settings);

3.1.6 Update person

First create two persons and register them:

// setup player name(s)
var person1Names = new List<PersonName>
{
    new PersonName("John", "Doe")
};
var person2Names = new List<PersonName>
{
    new PersonName("John", "Kowalsky")
};

// setup birthdate
var dateOfBirth = new DateTime(1990, 10, 12);
var person = new PersonType
{
    DateOfBirth = dateOfBirth,
    Gender = "male"
};

// setup registrations
var registration = new PlayerRegistrationType
{
    OrganisationFIFAId = organisationId,
    Status = "active",
    Level = "Pro",
    Discipline = "Football",
    RegistrationNature = "Registration"
};

person.PlayerRegistrations = new List<PlayerRegistrationType>
{
    registration
};

// finally create PersonData
var person1Data = new PersonData(person1Names, person);
var person2Data = new PersonData(person2Names, person);

//register two different players
facade.ForceRegisterPerson(person1Data);
facade.ForceRegisterPerson(person2Data);

Then try to update second person using Registration Facade with names of first person. The code snippet below shows an example handling (writing to console) of both scenarios, i.e. successful update and potential duplicates found. Please note that this operation will throw an InvalidClientDataException if you lack the permissions to perform it (the player has active registrations, none of which belongs to your MA).

var result = await facade.UpdatePersonAndWaitForDetailsInCaseOfDuplicates(personId, TimeSpan.FromSeconds(10), names, dateOfBirth, gender).ConfigureAwait(false);

if (result.IsSuccessful)
{
    Console.WriteLine($"Person updated successfully.");
}
else
{
    Console.WriteLine("Duplicates found.");

    foreach (var duplicateWithDetails in result.PersonDuplicateWithDetails)
    {
        var idOfDuplicatedPerson = duplicateWithDetails.Duplicate.PersonFifaId;
        var proximityScore = duplicateWithDetails.Duplicate.ProximityScore;
        var dataReceived = duplicateWithDetails.PersonDetails == null
            ? "<detailed person data not received from other MA - timeout>"
            : duplicateWithDetails.PersonDetails.XmlData;

        Console.WriteLine($"FIFA ID: {idOfDuplicatedPerson}, score: {proximityScore}, FIFA Data XML: {dataReceived}");
    }
}

3.1.7 [Optional] Get person duplicates (without registering)

If you need to get list of duplicates for a person, method GetDuplicates can be used. This methods looks for duplicates in Connect ID service. If any duplicate is found requests for person details are sent to relevant MAs. It is possible to look for duplicates only within specific organisations by providing organisationsIds argument.

var foundDuplicates = await facade.GetDuplicates(personData, TimeSpan.FromSeconds(10)).ConfigureAwait(false);

if (foundDuplicates.Count > 0)
{
    Console.WriteLine("Duplicates found.");

    foreach (var duplicateWithDetails in foundDuplicates)
    {
        var idOfDuplicatedPerson = duplicateWithDetails.Duplicate.PersonFifaId;
        var proximityScore = duplicateWithDetails.Duplicate.ProximityScore;
        var dataReceived = duplicateWithDetails.PersonDetails == null
            ? "<detailed person data not received from other MA - timeout>"
            : duplicateWithDetails.PersonDetails.XmlData;

        Console.WriteLine($"FIFA ID: {idOfDuplicatedPerson}, score: {proximityScore}, FIFA Data XML: {dataReceived}");
    }
}

3.1.7.1 [Optional] Get person duplicates by FIFA identifier

To get the list of duplicates for a registered person, use method GetDuplicatesExceptTrustedKnownDuplicates. To ignore duplicates marked as known duplicate by trusted organisations, please provide optional trustedOrganisationsIds argument.

const string personFifaId = "105C40T";
var foundDuplicates = await facade.GetDuplicatesExceptTrustedKnownDuplicates(personFifaId, TimeSpan.FromSeconds(10)).ConfigureAwait(false);

3.1.8 Advanced listener configuration

If your infrastructure for the application using SDK consists of multiple servers or your listener and registration facade are running in separate processes, then additional configuration is required.

One of the most important things to consider when choosing the way you want to host your listener is that it requires a shared storage between all instances of the SDK. The request has to be matched with response at some point. In order for this correlation to work, an ID of the request message (so called Correlation Id) needs to be stored and later retrieved by our code. For example, when sending the requests like: RegisterPersonAndWaitForDetailsInCaseOfDuplicates or GetDuplicates the Correlation Id is created for them. Once the information is back from another NRS or ConnectID it also contains the same Correlation Id - this way the listener knows which response should be matched with the request.

By default, this matching is done in the memory and you don't need to code anything. However, it may not always be true, especially when hosting the listener in the cloud (e.g. as Azure WebJob) or when having multiple instances of NRS system (and in effect: multiple instances of the SDK). In such case, some additional configuration is required. SDK defines the IPersonDetailsCache interface, which must be implemented by the client of the SDK and passed into RegistrationFacadeSettings.

Examples of implementation of IPersonDetailsCache are:

Code snippet below presents how to register YourSharedStorage in RegistrationFacade:

var settings = new RegistrationFacadeSettings
{
    PersonDetailsCache = new YourSharedStorage()
};

var facade = new RegistrationFacade(environment, clientCredentials, settings);

In the above example YourSharedStorage implements mentioned IPersonDetailsCache interface by providing three methods:

public interface IPersonDetailsCache
{
    /// <summary>
    /// Puts person details into storage mechanism.
    /// </summary>
    void Put(PersonDetails personDetails);

    /// <summary>
    /// Get person details with given correlation id from storage mechanism.
    /// </summary>
    PersonDetails GetByCorrelationId(string correlationId);

    /// <summary>
    /// Deletes the entries from storage mechanism that are older than given date.
    /// </summary>
    void DeleteOlderThan(TimeSpan duration);
}

3.1.8.1 [Optional] Best practices for hosting Listener as Azure WebJob

3.1.9 Deserialize XML with person details

When running operations that could possibly return person’s duplicates (such us registering persons, updating persons, etc.), methods will return responses with list of PersonDuplicateWithDetails. PersonDetails that have been received here contains an XML content, which is available in XmlData property. At this point application can store all details in local database (or handle them in a different way - depending on a need), so that it is possible to compare person being processed with their potential duplicates.

Optionally, after receiving XML string from XmlData, you may parse it into object. This can be done with a custom implementation, however the suggested solution here is to use PersonLocalXmlSerializer class (provided by our SDK) and its Deserialize() method returning PersonLocal object. Take a look at the following example:

var xmlSerializer = new PersonLocalXmlSerializer();

try
{
    var personLocal = xmlSerializer.Deserialize(personDuplicateWithDetails.PersonDetails.XmlData);
}
catch (XmlDeserializationException ex)
{
    var xmlContent = ex.Xml;
    var innerException = ex.InnerException;
}

3.1.10 Known limitations of Registration Facade

Available operations

Registration Facade covers scenario of person registration/update/search, but for other functionalities FIFA ID Client described in the next chapter must be used.

In particular FIFA Connect ID Client must be used for:

Concurrent requests

Currently there is no request throttling in API, but it's recommended not to exceed 10 concurrent connections. Too many requests to API may result in requests time outs. In case you plan to do a batch import of person records, please contact support team so that the infrastructure can be scaled up accordingly.

3.2 Using FIFA Connect ID Client

This chapter discusses all operations on a Person entity, e.g. Registration, Update, Merge, etc. It is different from chapter 3.1 Using Registration Facade in that operations described here do not involve a duplicate check. A developer who wants to perform a duplicate check via Service Bus should either refer to chapter 3.1 or implement communication via Service Bus by themselves.

3.2.1 Creating a client

The entry point of the SDK is FifaConnectIdClient. With a single instance of this class all the requests can be made. In order to authenticate to the service you have to provide a set of client credentials.

Note that in most cases you don't need to create FifaConnectIdClient instance. Having RegistrationFacade object, you can get the client as shown in below example:

var client = facade.ConnectIdClient;

3.2.1.1 Basic instance

var environment = ConnectIdEnvironment.Beta;
var credentials = new ClientCredentials("clientId", "secretKey");

var client = new FifaConnectIdClient(environment, credentials);

where environment is an instance of type ConnectIdEnvironment. It provides information about the service to make request to.

3.2.1.2 Logging

By default, the above constructor uses a NullLogger, which does nothing. However, to use logging, provide your own implementation of ILogger.

ILogger logger = new YourOwnLogger();
var client = new FifaConnectIdClient(environment, clientCredentials, logger);

3.2.1.3 Actor

In addition you can provide information about the current user interacting with the Registration System. This can be accomplished by providing implementation of IActorResolver interface. Such actor resolver could be much more advanced than SimpleActorResolver provided as a default implementation. In case of web application, actor could be fetched from current web request context.

IActorResolver actorResolver = new SimpleActorResolver("John Doe");
var client = new FifaConnectIdClient(environment, clientCredentials, logger, actorResolver);

3.2.1.4 Creating custom environment instance

As there are some pre-defined environments defined in the ConnectIdEnvironment class, it's also possible to create a custom instance using environment code. In order to achieve that, environment code needs to be provided to the static factory method.

var environmentCode = "envcode";
var environment = ConnectIdEnvironment.Create(environmentCode);

Use one of the existing environments:

Environment Environment code
Integration int
Test test
Beta beta
Preproduction prep
Production prod
Sandbox #1 sbx1

3.2.2 Registering a person

In order to register a person in FIFA Connect ID service, provide a properly formatted xml string. The root of the xml should be PersonLocal, not PersonData. It is required to define a local name in the document by giving LocalFirstName and LocalLastName attributes. In addition to that, LocalBirthName can by used - in that case the pair of names will be created together with LocalFirstName. International version (optional) of the name can by specified with attributes InternationalFirstName and InternationalLastName. See details:

And an example:

/* A record identifier in your database or any other key that is unique for a person.
This data will be stored hashed and cannot be decrypted.
For further information please consult section 3.1.5.2 Idempotency */
string personLocalId = "";

try
{
    var uniqueFifaId = await client.RegisterPersonAsync(xml, personLocalId);
}
catch (XmlException ex)
{
    // the delivered XML is malformed
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch(AuthenticationException ex)
{
    // invalid client credentials
}
catch(UnauthorizedException ex)
{
    // unauthorized
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaDuplicatedEntityFoundException ex)
{
    // possible duplicates were found
    var duplicates = ex.GetDuplicatesResponseType.Duplicates;
}
catch (FifaConnectIdException ex)
{
    // some other error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

3.2.2.1 POCO for person data

Instead of using XML files, you can also populate fields of the PersonData object yourself. For your convenience all the methods accepting XML as an input also accept PersonData directly. Here is an example, analogical to 3.2.2:

var names = new[]
{
   new PersonName("ΑΓΓΕΛΗΣ", "ΧΑΡΑΛΑΜΠΟΥΣ"), // local name
   new PersonName("ANGELIS", "CHARALAMBOUS") // international name
};

var person = new PersonType
{
   DateOfBirth = new DateTime(1980, 10, 10),
   // fill up registrations
};

var personData = new PersonData(names, person);

/* A record identifier in your database or any other key that is unique for a person.
This data will be stored hashed and cannot be decrypted.
For further information please consult section 3.1.5.2 Idempotency */
string personLocalId = "";

try
{
   var uniqueFifaId = await client.RegisterPersonAsync(personData, personLocalId);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch(AuthenticationException ex)
{
    // invalid client credentials
}
catch(UnauthorizedException ex)
{
    // unauthorized
}
catch (InvalidClientDataException ex)
{
   // data sent to the service was invalid
   var details = ex.BadRequestResponse;
}
catch (FifaDuplicatedEntityFoundException ex)
{
   // possible duplicates were found
   var duplicates = ex.GetDuplicatesResponseType.Duplicates;
}
catch (FifaConnectIdException ex)
{
   // some other error occurred, see the details
   var operationResponse = ex.HttpOperationResponse;
}

3.2.2.2 Person bulk registration

In order to register persons in parallel you can use Task Parallel Library. Please note that in order to optimize costs of test environment we keep it small-scale. Let us know upfront if you plan to test bulk registration, so we can scale up the environment. Additionally we recommend not exceeding 20 concurrent registrations to avoid performance degradation of the service. Take a look at the following example:

var database = new LocalDatabase();
var players = database.Players.Where(x => x.FifaId == null);

players
    .AsParallel()
    .WithDegreeOfParallelism(10)
    .ForAll(player =>
    {
        PersonData personData = CreatePerson(player);
        string fifaId = client.RegisterPersonAsync(personData).Result;

        database.UpdateFifaId(player.Id, fifaId);
    });

3.2.3 Force registration

To force the registration process (proceed even if potential duplicates are found):

/* A record identifier in your database or any other key that is unique for a person.
This data will be stored hashed and cannot be decrypted.
For further information please consult section 3.1.5.2 Idempotency */
string personLocalId = "";

try
{
    var uniqueFifaId = await client.ForceRegisterPersonAsync(xml, personLocalId);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (XmlException ex)
{
    // the delivered XML is malformed
}
catch(AuthenticationException ex)
{
    // invalid client credentials
}
catch(UnauthorizedException ex)
{
    // unauthorized
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaConnectIdException ex)
{
    // some other error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

3.2.4 Getting person data

This method will throw FifaPersonMergedException when you try to get data for a person that has been previously merged (refer to section 3.2.5 Merging/unmerging persons). The exception will contain data about requested person together with primary person id. To get the information associated with the specified FIFA ID such as a date of birth or list of person's registrations:

try
{
    var uniqueFifaId = "BVGE8T6";
    var person = await client.GetPersonAsync(uniqueFifaId);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch(AuthenticationException ex)
{
    // invalid client credentials
}
catch(UnauthorizedException ex)
{
    // unauthorized
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaEntityNotFoundException ex)
{
    // could not find the person
    var operationResponse = ex.HttpOperationResponse;
}
 catch (FifaEntityDeletedException ex)
{
    // person has been deleted
    var operationResponse = ex.HttpOperationResponse;
}
catch (FifaPersonMergedException ex)
{
    // requested person has been merged
    var person = ex.RequestedEntity;
    var primaryPersonId = ex.PrimaryEntityId;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

3.2.5 Merging/unmerging persons

To merge two persons into a single one use MergePersonsAsync method available on FifaConnectIdClient. As a result, the primary person data will be kept. No more operations should be executed on the secondary person. Attempt to get secondary person's data will result in 301 error code. Please refer to section 3.2.4 Getting person data. This operation can end with conflict error under specific conditions. It can happen when previous merge/unmerge operation is not yet completed on the same persons. As a result FifaConflictException will be thrown and client should retry operation later.

Here is an example how to merge persons:

try
{
    var person = await client.MergePersonsAsync(primaryPersonId, secondaryPersonId).ConfigureAwait(false);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (AuthenticationException ex)
{
    // invalid client credentials
    var details = ex.HttpOperationResponse;
}
catch (UnauthorizedException ex)
{
    // unauthorized
    var details = ex.HttpOperationResponse;
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaConflictException ex)
{
    // a conflicting operation is currently in progress
    var details = ex.Response;
}
catch (FifaEntityNotFoundException ex)
{
    // could not find one of specified persons
    var operationResponse = ex.HttpOperationResponse;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

In order to revert merge operation call UnmergePersonsAsync method with the same arguments.

3.2.6 Getting duplicates

To get a list of people a person may be duplicated with:

try
{
    var potentialDuplicates = await client.GetDuplicatesAsync(xml);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (XmlException ex)
{
    // the delivered XML is malformed
}
catch(AuthenticationException ex)
{
    // invalid client credentials
}
catch(UnauthorizedException ex)
{
    // unauthorized
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

This method is expected to be used rarely, e.g. in cases when Registration System chooses to do a duplicate check without the registration. It is possible to look for duplicates only within specific organisations by providing organisationsIds argument.

XML is the same as described in 3.2.2.

3.2.6.1 Getting duplicates by FIFA identifier

To get a list of people a registered person may be duplicated with:

try
{
    const string personFifaId = "105C408";
    var duplicates = await client.GetDuplicatesExceptTrustedKnownDuplicates(personFifaId).ConfigureAwait(false);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

To ignore known duplicates registered by specific organisations, please provide optional trustedOrganisationsIds argument.

3.2.7 Add/Update registrations

It is possible to update an existing person record (provided that we know the person's FIFA ID) by adding or editing its registrations. In order to do that, use the class AddRegistrationsRequestType or UpdateRegistrationsRequestType (depending on the operation) and fill information about the registrations. Note, that each person always contains four collections of registrations:

In the examples below, only player registrations are used - the other 3 collections remain empty (they are not set).

3.2.7.1 Add registrations

Note that in the example below a registration is added in a National Association and not in a Club. This is not a coincidence - FIFA Connect ID Service, although allowed to store Clubs in general, is not allowed to store persons' assignments to Clubs. Server will return an error should the Client try to register a person within a Club.

const string personId = "105C408";
const string organisationId = "105KYBB";

var exampleRegistration = new PlayerRegistrationType();
exampleRegistration.Level = "Pro";
exampleRegistration.Discipline = "Football";
exampleRegistration.RegistrationNature = "Registration";
exampleRegistration.OrganisationFIFAId = organisationId;
exampleRegistration.Status = "Active";

var personRegistrations = new PersonRegistrationsType();
personRegistrations.PlayerRegistrations = new List<PlayerRegistrationType>();
personRegistrations.PlayerRegistrations.Add(exampleRegistration);

var request = new AddRegistrationsRequestType(personId, personRegistrations);

try
{
    var personType = await client.AddRegistrations(request, false).ConfigureAwait(false);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch(AuthenticationException ex)
{
    // invalid client credentials
}
catch(UnauthorizedException ex)
{
    // unauthorized
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaEntityNotFoundException ex)
{
    // could not find the person
    var operationResponse = ex.HttpOperationResponse;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

3.2.7.2 Update registrations

In order to execute a request we need to know the registrations we want to update and their new values. Note that the order of registrations is important. Operation will try to change the first item in "old" list with the first item in the "new" list, etc.

"Old" list should contain only those registrations, which you want to update. For example, when person has four registrations and you want to update only two of them, you just need to provide these two.

const string personId = "105C408";
const string organisationId = "105KYBB";

var person = await client.GetPersonAsync(personId).ConfigureAwait(false);

var oldPersonRegistrations = new PersonRegistrationsType();
oldPersonRegistrations.PlayerRegistrations = person.PlayerRegistrations;

var newPlayerRegistration = new PlayerRegistrationType();
newPlayerRegistration.Level = "Pro";
newPlayerRegistration.Discipline = "Football";
newPlayerRegistration.RegistrationNature = "Registration";
newPlayerRegistration.Status = "Active";
newPlayerRegistration.OrganisationFIFAId = organisationId;

var newPersonRegistrations = new PersonRegistrationsType();
newPersonRegistrations.PlayerRegistrations.Add(newPlayerRegistration);

var request = new UpdateRegistrationsRequestType(personId,
    oldPersonRegistrations,
    newPersonRegistrations
);

try
{
    var personType = await client.UpdateRegistrations(request, false).ConfigureAwait(false);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch(AuthenticationException ex)
{
    // invalid client credentials
}
catch(UnauthorizedException ex)
{
    // unauthorized
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaEntityNotFoundException ex)
{
    // could not find the person
    var operationResponse = ex.HttpOperationResponse;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

Please note that each time an operation succeed, you will receive an object of class PersonType containing the current status of a person with all their registrations. This feature may be used for troubleshooting but also to synchronize the local data with a central DB (ID Directory).

Also note that errors are not thrown if you perform an operation which would result in the object not changing its state. For example, if you want to add a registration for a Football Player, Amateur in France, and such a registration already exists, error will not be thrown and the operation will succeed.

3.2.7.3 Remove registrations

We do not provide SDK method for removing registrations. Correct operation here would be updating registration by setting its status to inactive (deactivation). If you are certain that a registration should be removed (e.g. it has been added by mistake), please contact our Helpdesk.

3.2.8 Updating a person

Note, that when updating the name of a person, you need to specify all pairs of names. For example if you originally registered a person with both local and international name, you need to specify both pairs even if you are changing only one name. Connect ID is only able to replace an existing set of names with a new set. The reason for this is personal data protection, which allows the service to store hashes but not names.

To update a person associated with the specified FIFA ID:

try
{
    var names = new List<PersonName>();
    names.Add(new PersonName("first name", "last name"));

    var updatedPerson = await client.UpdatePersonAsync("105C40T", names, new DateTime(1995, 01, 01), "male").ConfigureAwait(false);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (AuthenticationException ex)
{
    // invalid client credentials
    var details = ex.HttpOperationResponse;
}
catch (UnauthorizedException ex)
{
    // unauthorized
    var details = ex.HttpOperationResponse;
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid or the MA lacks permission to perform the operation
    var details = ex.BadRequestResponse;
}
catch (FifaEntityNotFoundException ex)
{
    // could not find the person
    var operationResponse = ex.HttpOperationResponse;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

3.2.9 Adding registrations handled by external system (SystemId)

By default, all registrations are stored in a single national registration system. It is however possible to connect additional system to FIFA Connect ID services. As an example we may consider the [main] national registration system being responsible for Amateur players only and an additional, external system – for Professionals. In such a setup it is important that Connect ID is able to properly route requests for player details sent by other MAs via Service Bus.

In order for this routing to work correctly, an external system, for each of registrations it manages (professional ones), must provide its unique identifier (System ID).

System ID can be set by using SystemId property. See below example:

var organisationId = "105C8LX";
var systemId = "105C8LX_SystemName";
var newPlayerRegistration = new PlayerRegistrationType();

newPlayerRegistration.Level = "Pro";
newPlayerRegistration.Discipline = "Football";
newPlayerRegistration.RegistrationNature = "Registration";
newPlayerRegistration.Status = "Active";
newPlayerRegistration.OrganisationFIFAId = organisationId;
newPlayerRegistration.SystemId = systemId; // The system identifier is created and passed along with the access credentials.

There is no need of setting System ID if registration is added by a default system. In this case the <null> value should be used.

3.2.10 Bulk deactivating registrations

It is possible to bulk deactivate a set of given registrations using BulkDeactivateRegistrations method, which is available in FifaConnectIdClient class. As an input method takes a list of person registrations that will be deactivated. Only listed registrations will be deactivated - all other registrations that exist in the context of a given person will be left unchanged. Each registration needs to represent the current state (including status), otherwise server won't recognize it and will return error message that registration doesn't exist. One deactivation request can have up to 500 registrations in total (player, match official, team official and organisation official). Below you can find an example of bulk deactivation usage:

var personsToDeactivate = new List<PersonDeactivateType>();

try
{
    //create a request based on a list of persons (e.g. taken from your database)
    foreach (var person in persons)
    {
        var personId = person.PersonId;
        var playerRegistrations = person.PlayerRegistrations;
        var teamOfficialRegistrations = person.TeamOfficialRegistrations;

        //You can take a subset of registrations that should be deactivated
        var playerRegistrationsToDeactivate = new List<PlayerRegistrationType>
        {
            playerRegistrations[0],
            playerRegistrations[2]
        };

        var teamOfficialRegistrationToDeactivate = new List<TeamOfficialRegistrationType>
        {
            teamOfficialRegistrations[3]
        };

        var personToDeactivate = new PersonDeactivateType(person.PersonId,
            new PersonRegistrationsType(playerRegistrationsToDeactivate,
                // deactivate all Match Official Registrations
                person.MatchOfficialRegistrations,
                teamOfficialRegistrationToDeactivate,
                //No organisation official registrations will be deactivated
                new List<OrganisationOfficialRegistrationType>()));
    }

    var response = await client
        .BulkDeactivateRegistrations(new BulkDeactivateRegistrationsRequestType(personsToDeactivate))
        .ConfigureAwait(false);

    // Get persons for whom at least one registration has failed during deactivation
    var failedRegistrations = response.Results.Where(result => result.Success == false).ToList();
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

BulkDeactivateRegistrations method returns BulkDeactivateRegistrationsResponseType. It has property Results, which is an array of BulkDeactivateRegistrationsResult. Each instance of BulkDeactivateRegistrationsResult contains information about a status of specific person oriented operation. Possible properties are: PersonId, Success, Exception. In case when deactivation will fail for given person, Success will be set as false and additional information will be provided in Exception.

Please note that bulk deactivation is a heavy operation that affects server resources. Before running it please

4.Organisation

4.1 Registering an organisation

To register a new organisation:

var organisation = new OrganisationLocalType
{
    Status = "active",
    OrganisationNature = "WorldFederation",
    // fill up all required data according to FIFA Data Standard
};

try
{
    var uniqueFifaId = await client.RegisterOrganisationAsync(organisation).ConfigureAwait(false);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (AuthenticationException)
{
    // invalid client credentials
}
catch (UnauthorizedException)
{
    // unauthorized
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaDuplicatedOrganisationFoundException ex)
{
    // possible duplicates were found
    var duplicates = ex.GetOrganisationDuplicatesResponse.Duplicates;
}
catch (FifaConnectIdException ex)
{
    // some other error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

If duplicated organisation is found then FifaDuplicatedOrganisationFoundException will be thrown. In order to resolve conflict and register new organisation use optional force parameter as presented below:

bool force = true;
string organisationFifaId = await client.RegisterOrganisationAsync(organisation, force).ConfigureAwait(false);

4.2 Getting organisation data

To get the organisation data associated with the specified FIFA ID use GetOrganisationAsync method. This method will throw FifaOrganisationMergedException when you try to get data for an organisation that has been previously merged and deactivated (refer to section "Merging organisations"). The exception will contain data about requested organisation together with primary organisation id.

try
{
    var uniqueFifaId = "BVGE8TG";
    var organisation = await client.GetOrganisationAsync(uniqueFifaId).ConfigureAwait(false);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (AuthenticationException ex)
{
    // invalid client credentials
    var response = ex.HttpOperationResponse;
}
catch (UnauthorizedException ex)
{
    // unauthorized
    var response = ex.HttpOperationResponse;
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaEntityNotFoundException ex)
{
    // could not find the organisation
    var operationResponse = ex.HttpOperationResponse;
}
catch (FifaOrganisationMergedException ex)
{
    // requested organisation has been merged and is inactive
    var organisation = ex.RequestedEntity;
    var primaryOrganisationId = ex.PrimaryEntityId;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

4.3 Merging/unmerging organisations

To merge two organisations into a single one use MergeOrganisationsAsync method available on FifaConnectIdClient. This operation will only work for clubs. In addition you can only merge those that belong to the same member association that you are a member of. As a result of this operation, secondary organisation status will be set to inactive. This affects getting secondary organisation data in the future. Please refer to section 4.2 Getting organisation data. Here is an example how to merge organisations:

try
{
    var organisation = await client.MergeOrganisationsAsync(primaryOrganisationId, secondaryOrganisationId).ConfigureAwait(false);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (AuthenticationException ex)
{
    // invalid client credentials
    var details = ex.HttpOperationResponse;
}
catch (UnauthorizedException ex)
{
    // unauthorized
    var details = ex.HttpOperationResponse;
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaConflictException ex)
{
    // a conflicting operation is currently in progress
    var details = ex.Response;
}
catch (FifaEntityNotFoundException ex)
{
    // could not find one of specified organisations
    var operationResponse = ex.HttpOperationResponse;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

In order to revert merge operation call UnmergeOrganisationsAsync method with the same arguments.

4.4 Updating an organisation

To update the organisation data use UpdateOrganisationAsync method.

try
{
    var organisationId = "BVGE8TG";
    var organisation = await client.GetOrganisationAsync(organisationId).ConfigureAwait(false);

    // modify data
    organisation.Status = "inactive";
    organisation.Email = "some@other.email";

    var updatedOrganisation = await client.UpdateOrganisationAsync(organisation).ConfigureAwait(false);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (AuthenticationException ex)
{
    // invalid client credentials
    var details = ex.HttpOperationResponse;
}
catch (UnauthorizedException ex)
{
    // unauthorized
    var details = ex.HttpOperationResponse;
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaEntityNotFoundException ex)
{
    // could not find the organisation
    var operationResponse = ex.HttpOperationResponse;
}
catch (FifaDuplicatedOrganisationFoundException ex)
{
    // possible duplicates were found
    var duplicates = ex.GetOrganisationDuplicatesResponse.Duplicates;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

If duplicated organisation is found then FifaDuplicatedOrganisationFoundException will be thrown. In order to resolve conflict and update organisation use optional force parameter.

4.5 Searching for an organisation

4.5.1 Search using 'FindOrganisations' method

It takes FindOrganisationsRequestType as a parameter that aggregates search criteria. It's possible to search by international and local names, parent organisation, organisation nature, address and last modification date. This method takes into consideration date and time when making a query using ModifiedFromDateTime and ModifiedToDateTime.

try
{
    var request = new FindOrganisationsRequestType
    {
        InternationalName = "Barcelona",
        ModifiedFromDateTime = DateTime.UtcNow.AddDays(-5),
        ModifiedToDateTime = DateTime.UtcNow,
        OfficialAddress = new SearchAddressType
        {
            Country = "ES",
            Town = "Barcelona"
        },
        NumberOfResults = 5
    };

    var client = facade.ConnectIdClient;
    var response = await client.FindOrganisations(request).ConfigureAwait(false);
    var foundOrganisations = response.Organisations;
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (AuthenticationException ex)
{
    // invalid client credentials
    var details = ex.HttpOperationResponse;
}
catch (UnauthorizedException ex)
{
    // unauthorized
    var details = ex.HttpOperationResponse;
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}

4.6 Get organisation duplicates

To get a list of duplicates for an organisation use GetOrganisationDuplicatesAsync method. This method looks for similar organisations already registered in FIFA Connect ID Service.

try
{
    var organisation = new OrganisationLocalType
    {
        Status = "active",
        InternationalName = "Organisation name",
        OrganisationNature = "Club",
        // fill up all required data according to FIFA Data Standard
    };

    var foundDuplicates = await client.GetOrganisationDuplicatesAsync(organisation).ConfigureAwait(false);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (AuthenticationException ex)
{
    // invalid client credentials
    var details = ex.HttpOperationResponse;
}
catch (UnauthorizedException ex)
{
    // unauthorized
    var details = ex.HttpOperationResponse;
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

4.7 Get National Associations onboarded status

To get a list with onboarded statuses (has any persons or suborganisations registered) for specific National Associations use GetNationalAssociationsOnboardedStatus method. It takes NationalAssociationsOnboardedRequestType as a parameter which should contain list of strings with National Associations FIFA IDs.

try
{
    var request = new NationalAssociationsOnboardedRequestType{
        NationalAssociationsFifaIds = new List<string>{"BVGE8TG","AHAR8SV"}
    };
    var response = await client.GetNationalAssociationsOnboardedStatus(request).ConfigureAwait(false);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (AuthenticationException ex)
{
    // invalid client credentials
    var details = ex.HttpOperationResponse;
}
catch (UnauthorizedException ex)
{
    // unauthorized
    var details = ex.HttpOperationResponse;
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaEntityNotFoundException ex)
{
    // could not find the organisation
    var operationResponse = ex.HttpOperationResponse;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

To get a list with onboarded statuses for all National Associations use GetAllNationalAssociationsOnboardedStatus method.

try
{
    var response = await client.GetAllNationalAssociationsOnboardedStatus().ConfigureAwait(false);
}
catch (AuthenticationException ex)
{
    // invalid client credentials
    var details = ex.HttpOperationResponse;
}
catch (UnauthorizedException ex)
{
    // unauthorized
    var details = ex.HttpOperationResponse;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

4.8 Get Member Association of a child organisation (e.g. a club)

Some MAs (Member Associations) may have a multi-level structure of children organisations, e.g. teams, clubs, schools, regions, etc. In many cases, however, there is a need to find out to which MA a given organisation (e.g. a club) belongs to. To get FIFA ID and short name of that MA use GetMemberAssociationForFifaId method with the organisation's FIFA ID as input.

Note, that in order to retrieve the full name of the MA, call GetOrganisationAsync passing FIFA ID of the MA.

try
{
    var uniqueFifaId = "BVGE8TG";
    var memberAssociation = await client.GetMemberAssociationForFifaId(uniqueFifaId).ConfigureAwait(false);
}
catch (FifaEntityNotFoundException ex)
{
    // could not find the memberAssociation
    var operationResponse = ex.HttpOperationResponse;
}
catch (InvalidClientDataException ex)
{
    // provided organisation id is not valid
    var details = ex.BadRequestResponse;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}
catch (AuthenticationException ex)
{
    // invalid client credentials
    var response = ex.HttpOperationResponse;
}
catch (UnauthorizedException ex)
{
    // unauthorized
    var response = ex.HttpOperationResponse;
}
catch (ValidationException)
{
    // provided data did not pass validation
}

5. Facility

5.1 Registering a facility

To register a new facility:

var facility = new FacilityLocalType()
{
    Status = "active",
    InternationalName = "Wembley Stadium",
    OrganisationFIFAId = "BVGE8TG"
    // fill up all required data according to FIFA Data Standard
};

try
{
    var uniqueFifaId = await client.RegisterFacilityAsync(facility).ConfigureAwait(false);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (AuthenticationException ex)
{
    // invalid client credentials
}
catch (UnauthorizedException ex)
{
    // unauthorized
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaDuplicatedFacilityFoundException ex)
{
    // possible duplicates were found
    var duplicates = ex.GetFacilityDuplicatesResponse.Duplicates;
}
catch (FifaConnectIdException ex)
{
    // some other error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

If duplicated facility is found then FifaDuplicatedFacilityFoundException will be thrown. In order to resolve conflict and register new facility use optional force parameter.

5.2 Getting facility data

To get the facility data associated with the specified FIFA ID:

try
{
    var uniqueFifaId = "105C40T";
    var facility = await client.GetFacilityAsync(uniqueFifaId).ConfigureAwait(false);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch(AuthenticationException ex)
{
    // invalid client credentials
}
catch(UnauthorizedException ex)
{
    // unauthorized
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaEntityNotFoundException ex)
{
    // could not find the facility
    var operationResponse = ex.HttpOperationResponse;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

5.3 Updating a facility

To update the facility data use UpdateFacilityAsync method.

try
{
    var facilityId = "105C40T";
    var facility = await client.GetFacilityAsync(facilityId).ConfigureAwait(false);

    // set the data to update
    facility.Email = "some@other.email";
    facility.Status = "inactive";

    var updatedFacility = await client.UpdateFacilityAsync(facility).ConfigureAwait(false);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (AuthenticationException ex)
{
    // invalid client credentials
    var details = ex.HttpOperationResponse;
}
catch (UnauthorizedException ex)
{
    // unauthorized
    var details = ex.HttpOperationResponse;
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaEntityNotFoundException ex)
{
    // could not find the facility
    var operationResponse = ex.HttpOperationResponse;
}
catch (FifaDuplicatedFacilityFoundException ex)
{
    // possible duplicates were found
    var duplicates = ex.GetFacilityDuplicatesResponse.Duplicates;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

If duplicated facility is found then FifaDuplicatedFacilityFoundException will be thrown. In order to resolve conflict and update facility use optional force parameter.

5.4 Searching for a facility

To search for a facility use FindFacilities method. It takes FindFacilitiesRequestType as a parameter that aggregates search criteria. It's possible to search by international and local names, parent facility, organisation, and address.

try
{
    var request = new FindFacilitiesRequestType
    {
        InternationalName = "Camp Nou",
        ModifiedFromDateTime = DateTime.UtcNow.AddDays(-5),
        ModifiedToDateTime = DateTime.UtcNow,
        OfficialAddress = new SearchAddressType
        {
            Country = "ES",
            Town = "Barcelona"
        },
        NumberOfResults = 5
    };

    var response = await client.FindFacilities(request).ConfigureAwait(false);
    var foundFacilities = response.Facilities;
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (AuthenticationException ex)
{
    // invalid client credentials
    var details = ex.HttpOperationResponse;
}
catch (UnauthorizedException ex)
{
    // unauthorized
    var details = ex.HttpOperationResponse;
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}

5.5 Get facility duplicates

To get a list of duplicates for a facility use GetFacilityDuplicatesAsync method. This method looks for similar facilities already registered in FIFA Connect ID service.

try
{
    var facility = new FacilityLocalType
    {
        Status = "active",
        InternationalName = "Facility name",
        // fill up all required data according to FIFA Data Standard
    };

    var foundDuplicates = await client.GetFacilityDuplicatesAsync(facility).ConfigureAwait(false);
}
catch (ValidationException)
{
    // provided data did not pass validation
}
catch (AuthenticationException ex)
{
    // invalid client credentials
    var details = ex.HttpOperationResponse;
}
catch (UnauthorizedException ex)
{
    // unauthorized
    var details = ex.HttpOperationResponse;
}
catch (InvalidClientDataException ex)
{
    // data sent to the service was invalid
    var details = ex.BadRequestResponse;
}
catch (FifaConnectIdException ex)
{
    // some error occurred, see the details
    var operationResponse = ex.HttpOperationResponse;
}

6. [Optional] Transfer Matching System (TMS) integration

This chapter is directed only to MAs who have TMS integration in their scope. Please note, that this chapter focuses on the technical implementation only and does not give details about business scenarios that different methods should be used in. For the latter, check our support portal.

6.1 Create TMS facade

In order to integrate with TMS, you need to instantiate a dedicated facade object that will serve as an entry point for all TMS-related operations.

var serviceBusClient = registrationFacade.ServiceBusClient;
ILogger logger = new YourOwnLogger();
ICachePollingResponseAwaitersFactory responseAwaiterFactory = new CachePollingResponseAwaitersFactory();
var responsesCache = new CorrelationIdBasedInMemoryCache<MessageReceived>(TimeSpan.FromMinutes(10));

TmsFacade tmsFacade = new TmsFacade(serviceBusClient, logger, responseAwaiterFactory, responsesCache);

6.1.1 Configure listener

TMS Facade relies on the registration facade for listening for incoming reponses from TMS. In order to hook TMS facade into the existing listener infrastruture, you add a new argument to the current configureListener call:

var tmsMessageHandlers = TmsFacadeHandlersFactory.CreateMessageHandlers(responsesCache);
registrationFacade.ConfigureListener(personDetailsProvider, tmsMessageHandlers);

6.1.2 Advanced configuration

When running the listener in a separate process, you need to provide a shared storage for incoming confirmation messages. For more information on the subject please refer to section 3.1.8 Advanced listener configuration. Below are the interfaces that need to be implemented: - ICorrelationIdBasedItemProvider<MessageReceived> - Used by the TMS facade to get records from shared storage by id. If there is no entry associated with the given id, null value should be returned - ICorrelationIdBasedItemContainer<MessageReceived> - Used by the TMS message handlers factory to put records into the shared storage

An implementation stub:

public class DomesticTransferConfirmationSharedStorage : 
    ICorrelationIdBasedItemContainer<MessageReceived>,
    ICorrelationIdBasedItemProvider<MessageReceived>
{
    public void Put(string correlationId, MessageReceived itemToCache)
    {
        throw new NotImplementedException();
    }

    public MessageReceived GetByCorrelationId(string correlationId)
    {
        throw new NotImplementedException();
    }
}

6.2 Add person to TMS

In order to add a person to TMS, you need to build a PersonLocal object and pass it to AddPlayer method. PersonLocal type is compliant with FIFA Data Standard.

var personLocal = new PersonLocal(
    "ΧΑΡΑΛΑΜΠΟΥΣ", Gender.Male, ISO3166CountryCode.CY, new DateTime(1990, 11, 11)ISO316613CountryCode.CY,
    "Larnaca", ISO6392LanguageCode.Gre, ISO3166CountryCode.CY, "1XDZR79");
try
{
    string correlationId = await tmsFacade.AddPlayer(personLocal).ConfigureAwait(false);
}
catch (TmsOperationException ex)
{
    // issue occurred during TMS operation
    var details = ex.InnerException;
}

Notice that in this scenario you don't wait for TMS to answer. correlationId does not prove that the message has been picked up by TMS, only that it has been successfully sent. Although correlationId is not required for any further processing, we recommend that you log it.

6.3 Update person in TMS

In order to add a person to TMS, you need to build a PersonLocal object and pass it to UpdatePlayer method. PersonLocal type is compliant with FIFA Data Standard.

var personLocal = new PersonLocal(
    "ΧΑΡΑΛΑΜΠΟΥΣ", Gender.Male, ISO3166CountryCode.CY, new DateTime(1990, 11, 11)ISO316613CountryCode.CY,
    "Larnaca", ISO6392LanguageCode.Gre, ISO3166CountryCode.CY, "1XDZR79");
try
{
    string correlationId = await tmsFacade.UpdatePlayer(personLocal).ConfigureAwait(false);
}
catch (TmsOperationException ex)
{
    // issue occurred during TMS operation
    var details = ex.InnerException;
}

Notice that in this scenario you don't wait for TMS to answer. correlationId does not prove that the message has been picked up by TMS, only that it has been successfully sent. Although correlationId is not required for any further processing, we recommend that you log it.

6.4 Send Domestic Transfer Declaration

Sending a declaration is fairly straightforward:

//create person local
var personFifaId = "1XDZR79";
var personLocal = new PersonLocal(
"ΧΑΡΑΛΑΜΠΟΥΣ", Gender.Male, ISO3166CountryCode.CY, new DateTime(1990, 11, 11), ISO316613CountryCode.CY,
"Larnaca", ISO6392LanguageCode.Gre, ISO3166CountryCode.CY, personFifaId
);

//add player registration
personLocal.AddPlayerRegistration(new PlayerRegistration(
personFifaId, 
Status.Active,
"103C3AB", 
new DateTime(2020,10,10), 
RegistrationLevel.Pro,
Discipline.Football,
PlayerRegistrationNature.Registration ));

//create declaration
var engagingClub = new Club("ARZI7RL", "Q8ZZ8RT", "Engaging Club International Name");
var releasingClub = new Club("HNIUE01", "SW8FJ2P", "Releasing Club International Name");
var declaration = new DomesticTransferDeclaration(personLocal, engagingClub, releasingClub,
new DateTime(2027, 11, 11), RegistrationLevel.Pro, TransferNature.Permanent);

//add conditional transfer fees
var recipientClub = new Club("HNIUE01", "SW8FJ2P", "Transfer Fee Club International Name");

var conditionalTransferFeeDetails = new TransferFeeDetails(156, ISO4217CurrencyCode.AED, new DateTime(2020, 1, 1), recipientClub);
declaration.AddConditionalTransferFee(new ConditionalTransferFee(conditionalTransferFeeDetails)
{
Condition = "specify condition"
});

//add fixed transfer fees

//add release buyout transfer fees

//populate the declaration with data

try
{
long tmsTransferId = await tmsFacade
    .DeclareDomesticTransferAndWaitForTransferId(declaration, TimeSpan.FromSeconds(60))
    .ConfigureAwait(false);
}
catch (TmsOperationException operationException)
{
// Errors when trying to send message to TMS. Check inner exception for details
var details = operationException.Message;
var innerException = operationException.InnerException;
}
catch (DeclarationRejectedException declarationRejectedException)
{
// TMS rejected the declaration. See the reason for details:
var rejectionReason = declarationRejectedException.Reason;
}
catch (ResponseNotReceivedException ex)
{
// Possible reasons:
// 1. TMS has not responded within the timeout (consider increasing the timeout)
// 2. responsesCache is not populated by the listener (see section 6.1.1)
var details = ex.Message;
}
catch (Exception ex)
{
// Something else went wrong
var details = ex.Message;
}

TMS facade sends the declaration to the Service Bus. TMS is expected to read the declaration and send a Domestic Transfer Confirmation back with a TMS Transfer ID. The facade waits for a given period of time for the response. Should the response not arrive, ResponseNotReceivedException exception is thrown. Once the confirmation is received you have to store the tmsTransferId. It will be required for sending Proof Of Payment message later on. Should TMS consider the request invalid, DeclarationRejectedException will be thrown with a message from TMS providing the reason of the rejection.

6.5 Send Proof of Payment

In order to send a Proof of Payment to TMS, you need to build a ProofOfPayment object and pass it to ProvePaymentAndWaitForPaymentId method. Please mind that all properties are obligatory.

var paymentDetails = new PaymentDetails(156, ISO4217CurrencyCode.AED, new DateTime(2020, 1, 1));
var club = new Club("105KYBB", "105C6FI", "Organisation name");

var proofOfPayment = new ProofOfPayment(PaymentType.FixedTransferFee, 1234, paymentDetails, club);

try
{
    long tmsPaymentId = await tmsFacade
        .ProvePaymentAndWaitForPaymentId(proofOfPayment, TimeSpan.FromSeconds(60))
        .ConfigureAwait(false);
}
catch (TmsOperationException operationException)
{
    // Errors when trying to send message to TMS. Check inner exception for details
    var details = operationException.Message;
    var innerException = operationException.InnerException;
}
catch (DeclarationRejectedException declarationRejectedException)
{
    // TMS rejected the declaration. See the reason for details:
    var rejectionReason = declarationRejectedException.Reason;
}
catch (ResponseNotReceivedException ex)
{
    // Possible reasons:
    // 1. TMS has not responded within the timeout (consider increasing the timeout)
    // 2. responsesCache is not populated by the listener (see section 6.1.1)
    var errorDetails = ex.Message;
}
catch (Exception ex)
{
    // Something else went wrong
    var errorDetails = ex.Message;
}

TMS facade sends the proof to the Service Bus. TMS is expected to read the proof and send a Proof Of Payment Confirmation back with a TMS Payment ID. The facade waits for a given period of time for the response. Should the response not arrive, ResponseNotReceivedException exception is thrown. Should TMS consider the request invalid, DeclarationRejectedException will be thrown with a message from TMS providing the reason of the rejection. As of August 2020 there is no requirement to store tmsPaymentId. We recommend you log it as a confirmation that the message has been sent & received successfully.

6.6 Send First Pro Registration

Sending a registration is fairly straightforward:

var personLocal = new PersonLocal(
    "ΧΑΡΑΛΑΜΠΟΥΣ", Gender.Male, ISO3166CountryCode.CY, new DateTime(1990, 11, 11), ISO316613CountryCode.CY,
    "Larnaca", ISO6392LanguageCode.Gre, ISO3166CountryCode.CY, "1XDZR79"
);
var proRegistration = new PlayerRegistration(
    "1XDZR79", Status.Active, "105C6FI", new DateTime(2021, 03, 31), 
    RegistrationLevel.Pro, Discipline.Football, PlayerRegistrationNature.Registration
);
personLocal.PlayerRegistration.Add(proRegistration);

var club = new Club("105KYBB", "105C6FI", "Organisation name");
var firstProRegistration = new FirstProRegistration(personLocal, club, ClubStatus.Active, true);
try
{
    long tmsFirstRegistrationId = await tmsFacade
        .DeclareFirstProRegistrationAndWaitForRegistrationId(firstProRegistration, TimeSpan.FromSeconds(60))
        .ConfigureAwait(false);
}
catch (TmsOperationException operationException)
{
    // Errors when trying to send message to TMS. Check inner exception for details
    var details = operationException.Message;
    var innerException = operationException.InnerException;
}
catch (DeclarationRejectedException declarationRejectedException)
{
    // TMS rejected the declaration. See the reason for details:
    var rejectionReason = declarationRejectedException.Reason;
}
catch (ResponseNotReceivedException ex)
{
    // Possible reasons:
    // 1. TMS has not responded within the timeout (consider increasing the timeout)
    // 2. responsesCache is not populated by the listener (see section 6.1.1)
    var details = ex.Message;
}
catch (Exception ex)
{
    // Something else went wrong
    var details = ex.Message;
}

TMS facade sends the registration to the Service Bus. TMS is expected to read the registration and send a First Pro Registration Confirmation back with a TMS First Registration ID. The facade waits for a given period of time for the response. Should the response not arrive, ResponseNotReceivedException exception is thrown. Should TMS consider the request invalid, DeclarationRejectedException will be thrown with a message from TMS providing the reason of the rejection. As of August 2020 there is no requirement to store tmsFirstRegistrationId. We recommend you log it as a confirmation that the message has been sent & received successfully.