Skip to content

Commit

Permalink
Add support for the client link api (#325)
Browse files Browse the repository at this point in the history
* Create boilerplate code for the ClientLink API

* Add unit test for create client link endpoint

* Add method to generate a client link URL with parameters

* Add documentation on ClientLinkClient

* Fix spacing issue in documentation links

* Increase version number to 3.2.0.0

---------

Co-authored-by: Vincent Kok <vincent@vinkosolutions.nl>
  • Loading branch information
Viincenttt and Vincent Kok authored Sep 12, 2023
1 parent e24fb58 commit e00ec77
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 4 deletions.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Have you spotted a bug or want to add a missing feature? All pull requests are w
[14. Payment link Api](#14-payment-link-api)
[15. Balances Api](#15-balances-api)
[16. Terminal Api](#16-terminal-api)
[17. Client Link Api](#17-client-link-api)

## 1. Getting started

Expand Down Expand Up @@ -75,6 +76,7 @@ This library currently supports the following API's:
- Onboarding API
- Balances API
- Terminal API
- ClientLink API

### Supported .NET versions
This library is built using .NET standard 2.0. This means that the package supports the following .NET implementations:
Expand Down Expand Up @@ -984,3 +986,40 @@ using ITerminalClient client = new TerminalClient({yourApiKey});
TerminalResponse response = await client.GetTerminalAsync({yourTerminalId});
```

## 17. Client Link Api
### Create a client link
Link a new organization to your OAuth application, in effect creating a new client.
```C#
var request = new ClientLinkRequest
{
Owner = new ClientLinkOwner
{
Email = "norris@chucknorrisfacts.net",
GivenName = "Chuck",
FamilyName = "Norris",
Locale = "en_US"
},
Address = new AddressObject()
{
StreetAndNumber = "Keizersgracht 126",
PostalCode = "1015 CW",
City = "Amsterdam",
Country = "NL"
},
Name = "Mollie B.V.",
RegistrationNumber = "30204462",
VatNumber = "NL815839091B01"
};
using IClientLinkClient client = new ClientLinkClient({yourClientId}, {yourClientSecret});
ClientLinkResponse response = await clientLinkClient.CreateClientLinkAsync(request);
```

### Generate the client link URL
To the ClientLink link you created through the API, you can then add the OAuth details of your application, the client_id, scope you want to request
```C#
using IClientLinkClient client = new ClientLinkClient({yourClientId}, {yourClientSecret});
ClientLinkResponse response = await clientLinkClient.CreateClientLinkAsync(request);
var clientLinkUrl = response.Links.ClientLink;
string result = clientLinkClient.GenerateClientLinkWithParameters(clientLinkUrl, {yourState}, {yourScope}, {forceApprovalPrompt});
```

18 changes: 18 additions & 0 deletions src/Mollie.Api/Client/Abstract/IClientLinkClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Mollie.Api.Models.ClientLink.Request;
using Mollie.Api.Models.ClientLink.Response;

namespace Mollie.Api.Client.Abstract
{
public interface IClientLinkClient
{
Task<ClientLinkResponse> CreateClientLinkAsync(ClientLinkRequest request);

string GenerateClientLinkWithParameters(
string clientLinkUrl,
string state,
List<string> scopes,
bool forceApprovalPrompt = false);
}
}
42 changes: 42 additions & 0 deletions src/Mollie.Api/Client/ClientLinkClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Mollie.Api.Client.Abstract;
using Mollie.Api.Extensions;
using Mollie.Api.Models.ClientLink.Request;
using Mollie.Api.Models.ClientLink.Response;

namespace Mollie.Api.Client {
public class ClientLinkClient : OauthBaseMollieClient, IClientLinkClient
{
private readonly string _clientId;

public ClientLinkClient(string clientId, string oauthAccessToken, HttpClient httpClient = null)
: base(oauthAccessToken, httpClient)
{
this._clientId = clientId;
}

public async Task<ClientLinkResponse> CreateClientLinkAsync(ClientLinkRequest request)
{
return await this.PostAsync<ClientLinkResponse>($"client-links", request)
.ConfigureAwait(false);
}

public string GenerateClientLinkWithParameters(
string clientLinkUrl,
string state,
List<string> scopes,
bool forceApprovalPrompt = false)
{
var parameters = new Dictionary<string, string> {
{"client_id", _clientId},
{"state", state},
{"scope", string.Join(" ", scopes)},
{"approval_prompt", forceApprovalPrompt ? "force" : "auto"}
};

return clientLinkUrl + parameters.ToQueryString();
}
}
}
2 changes: 2 additions & 0 deletions src/Mollie.Api/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public static IServiceCollection AddMollieApi(
new SubscriptionClient(mollieOptions.ApiKey, httpClient), retryPolicy);
RegisterMollieApiClient<ITerminalClient, TerminalClient>(services, httpClient =>
new TerminalClient(mollieOptions.ApiKey, httpClient), retryPolicy);
RegisterMollieApiClient<IClientLinkClient, ClientLinkClient>(services, httpClient =>
new ClientLinkClient(mollieOptions.ClientId, mollieOptions.ApiKey, httpClient), retryPolicy);

return services;
}
Expand Down
28 changes: 28 additions & 0 deletions src/Mollie.Api/Models/ClientLink/Request/ClientLinkOwner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace Mollie.Api.Models.ClientLink.Request
{
public class ClientLinkOwner
{
/// <summary>
/// The email address of your customer.
/// </summary>
public string Email { get; set; }

/// <summary>
/// The given name (first name) of your customer.
/// </summary>
public string GivenName { get; set; }

/// <summary>
/// The family name (surname) of your customer.
/// </summary>
public string FamilyName { get; set; }

/// <summary>
/// Allows you to preset the language to be used in the login / authorize flow. When this parameter is
/// omitted, the browser language will be used instead. You can provide any xx_XX format ISO 15897 locale,
/// but the authorize flow currently only supports the following languages:
/// en_US nl_NL nl_BE fr_FR fr_BE de_DE es_ES it_IT
/// </summary>
public string Locale { get; set; }
}
}
33 changes: 33 additions & 0 deletions src/Mollie.Api/Models/ClientLink/Request/ClientLinkRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace Mollie.Api.Models.ClientLink.Request
{
public class ClientLinkRequest
{
/// <summary>
/// Personal data of your customer which is required for this endpoint.
/// </summary>
public ClientLinkOwner Owner { get; set; }

/// <summary>
/// Name of the organization.
/// </summary>
public string Name { get; set; }

/// <summary>
/// Address of the organization. Note that the country parameter
/// must always be provided.
/// </summary>
public AddressObject Address { get; set; }

/// <summary>
/// The Chamber of Commerce (or local equivalent) registration number
/// of the organization.
/// </summary>
public string RegistrationNumber { get; set; }

/// <summary>
/// The VAT number of the organization, if based in the European Union
/// or the United Kingdom.
/// </summary>
public string VatNumber { get; set; }
}
}
14 changes: 14 additions & 0 deletions src/Mollie.Api/Models/ClientLink/Response/ClientLinkResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Newtonsoft.Json;

namespace Mollie.Api.Models.ClientLink.Response
{
public class ClientLinkResponse
{
public string Id { get; set; }

public string Resource { get; set; }

[JsonProperty("_links")]
public ClientLinkResponseLinks Links { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Mollie.Api.Models.Url;

namespace Mollie.Api.Models.ClientLink.Response
{
public class ClientLinkResponseLinks
{
public UrlLink ClientLink { get; set; }
public UrlLink Documentation { get; set; }
}
}
8 changes: 4 additions & 4 deletions src/Mollie.Api/Mollie.Api.csproj
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Version>3.0.0.0</Version>
<Version>3.2.0.0</Version>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Authors>Vincent Kok</Authors>
<Description>This is a wrapper for the Mollie REST webservice. All payment methods and webservice calls are supported.</Description>
<Company>Vincent Kok</Company>
<Product>Mollie Payment API</Product>
<PackageProjectUrl>https://github.com/Viincenttt/MollieApi</PackageProjectUrl>
<PackageTags>Mollie</PackageTags>
<AssemblyVersion>3.1.0.0</AssemblyVersion>
<FileVersion>3.1.0.0</FileVersion>
<PackageVersion>3.1.0.0</PackageVersion>
<AssemblyVersion>3.2.0.0</AssemblyVersion>
<FileVersion>3.2.0.0</FileVersion>
<PackageVersion>3.2.0.0</PackageVersion>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
Expand Down
89 changes: 89 additions & 0 deletions tests/Mollie.Tests.Unit/Client/ClientLinkClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using FluentAssertions;
using Mollie.Api.Client;
using Mollie.Api.Models.Chargeback;
using Mollie.Api.Models.ClientLink.Request;
using Mollie.Api.Models.ClientLink.Response;
using RichardSzalay.MockHttp;
using Xunit;

namespace Mollie.Tests.Unit.Client;

public class ClientLinkClientTests : BaseClientTests
{
[Fact]
public async Task CreateClientLinkAsync_ResponseIsDeserializedInExpectedFormat()
{
// Given: We create a payment link
const string clientLinkId = "csr_vZCnNQsV2UtfXxYifWKWH";
const string clientLinkUrl = "myurl";
string clientLinkResponseJson = CreateClientLinkResponseJson(clientLinkId, clientLinkUrl);
var mockHttp = new MockHttpMessageHandler();
mockHttp.When( HttpMethod.Post, $"{BaseMollieClient.ApiEndPoint}client-links")
.Respond("application/json", clientLinkResponseJson);
HttpClient httpClient = mockHttp.ToHttpClient();
ClientLinkClient clientLinkClient = new ClientLinkClient("clientId", "access_1234", httpClient);

// When: We send the request
ClientLinkResponse response = await clientLinkClient.CreateClientLinkAsync(new ClientLinkRequest());

// Then
response.Id.Should().Be(clientLinkId);
response.Links.ClientLink.Href.Should().Be(clientLinkUrl);
mockHttp.VerifyNoOutstandingRequest();
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void GenerateClientLinkWithParameters_GeneratesExpectedUrl(bool forceApprovalPrompt)
{
// Arrange
const string clientId = "app_j9Pakf56Ajta6Y65AkdTtAv";
const string clientLinkUrl = "https://my.mollie.com/dashboard/client-link/csr_vZCnNQsV2UtfXxYifWKWH";
const string state = "decafbad";
var scopes = new List<string>()
{
"onboarding.read",
"organizations.read",
"payments.write",
"payments.read",
"profiles.write",
};
ClientLinkClient clientLinkClient = new ClientLinkClient(
clientId, "access_1234", new MockHttpMessageHandler().ToHttpClient());

// Act
string result = clientLinkClient.GenerateClientLinkWithParameters(
clientLinkUrl, state, scopes, forceApprovalPrompt);

// Assert
string expectedApprovalPrompt = forceApprovalPrompt ? "force" : "auto";
result.Should()
.Be("https://my.mollie.com/dashboard/client-link/csr_vZCnNQsV2UtfXxYifWKWH" +
$"?client_id={clientId}" +
$"&state={state}" +
"&scope=onboarding.read+organizations.read+payments.write+payments.read+profiles.write" +
$"&approval_prompt={expectedApprovalPrompt}");
}

private string CreateClientLinkResponseJson(string id, string clientLinkUrl)
{
return $@"{{
""id"": ""{id}"",
""resource"": ""client-link"",
""_links"": {{
""clientLink"": {{
""href"": ""{clientLinkUrl}"",
""type"": ""text/html""
}},
""documentation"": {{
""href"": ""https://docs.mollie.com/reference/v2/clients-api/create-client-link"",
""type"": ""text/html""
}}
}}
}}";
}
}

0 comments on commit e00ec77

Please sign in to comment.