Skip to content

Commit 72b0b1e

Browse files
committed
Merge branch 'release/comp-1.8.28'
2 parents f00ed6f + e0af839 commit 72b0b1e

File tree

7 files changed

+265
-6
lines changed

7 files changed

+265
-6
lines changed

src/NoFrixion.MoneyMoov/ApiClients/PayoutClient.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// History:
1010
// 05 Feb 2023 Aaron Clauson Created, Stillorgan Wood, Dublin, Ireland.
1111
// 19 Apr 2023 Aaron Clauson Added batch payout methods.
12+
// 25 Oct 2024 Aaron Clauson Added send payout method with HMAC signature authentication.
1213
//
1314
// License:
1415
// MIT.
@@ -18,7 +19,6 @@
1819
using Microsoft.Extensions.Logging.Abstractions;
1920
using NoFrixion.MoneyMoov.Models;
2021
using System.Net;
21-
using System.Net.Http.Json;
2222

2323
namespace NoFrixion.MoneyMoov;
2424

@@ -41,6 +41,8 @@ public interface IPayoutClient
4141
Task<RestApiResponse> SubmitBatchPayoutAsync(string strongUserAccessToken, Guid batchPayoutID);
4242

4343
Task<RestApiResponse> DeletePayoutAsync(string accessToken, Guid payoutID);
44+
45+
Task<RestApiResponse<Payout>> SendPayoutAsync(Guid appID, string secret, Guid merchantID, PayoutCreate payoutCreate);
4446
}
4547

4648
public class PayoutClient : IPayoutClient
@@ -229,7 +231,7 @@ public Task<RestApiResponse> SubmitBatchPayoutAsync(string strongUserAccessToken
229231
/// </summary>
230232
/// <param name="accessToken">The user, or merchant, access token deleting the payout.</param>
231233
/// <param name="payoutID"></param>
232-
/// <returns></returns>
234+
/// <returns>An API response indicating the result of the delete attempt.</returns>
233235
public Task<RestApiResponse> DeletePayoutAsync(string accessToken, Guid payoutID)
234236
{
235237
var url = MoneyMoovUrlBuilder.PayoutsApi.PayoutUrl(_apiClient.GetBaseUri().ToString(), payoutID);
@@ -242,4 +244,20 @@ public Task<RestApiResponse> DeletePayoutAsync(string accessToken, Guid payoutID
242244
_ => Task.FromResult(new RestApiResponse(HttpStatusCode.PreconditionFailed, new Uri(url), prob))
243245
};
244246
}
247+
248+
/// <summary>
249+
/// Calls the Trusted Third Party (TTP) MoneyMoov Payout endpoint to send a payout WITHOUT requiring NoFrixion Strong
250+
/// Customer Authentication (SCA). This method relies on the caller to use ther own form of SCA.
251+
/// </summary>
252+
/// <param name="appID">The TTP application ID.</param>
253+
/// <param name="secret">The TTP secret.</param>
254+
/// <param name="merchantID">The merchant ID the send payout is being attempted for.</param>
255+
/// <param name="payoutCreate">The payout create object with details of the payout to send.</param>
256+
/// <returns>An API response indicating the result of the send attempt.</returns>
257+
public Task<RestApiResponse<Payout>> SendPayoutAsync(Guid appID, string secret, Guid merchantID, PayoutCreate payoutCreate)
258+
{
259+
var url = MoneyMoovUrlBuilder.PayoutsApi.SendPayoutUrl(_apiClient.GetBaseUri().ToString());
260+
261+
return _apiClient.PostAsync<Payout>(url, appID, secret, merchantID, payoutCreate.ToJsonContent());
262+
}
245263
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// -----------------------------------------------------------------------------
2+
// Filename: HmacAuthenticationConstants.cs
3+
//
4+
// Description: Constants for HMAC authentication:
5+
//
6+
// Author(s):
7+
// Donal O'Connor ([email protected])
8+
//
9+
// History:
10+
// 22 04 2024 Donal O'Connor Created, Harcourt St, Dublin, Ireland.
11+
//
12+
// License:
13+
// MIT.
14+
// -----------------------------------------------------------------------------
15+
16+
namespace NoFrixion.MoneyMoov;
17+
18+
public static class HmacAuthenticationConstants
19+
{
20+
public const string SIGNATURE_SCHEME_NAME = "Signature";
21+
public const string APP_ID_HEADER_NAME = "appId";
22+
public const string AUTHORIZATION_HEADER_NAME = "Authorization";
23+
public const string DATE_HEADER_NAME = "Date";
24+
public const string NONCE_HEADER_NAME = "x-mod-nonce";
25+
public const string MERCHANT_ID_HEADER_NAME = "x-nfx-merchantid";
26+
public const string NOFRIXION_SIGNATURE_HEADER_NAME = "x-nfx-signature";
27+
public const string HTTP_RETRY_HEADER_NAME = "x-mod-retry";
28+
public const string IDEMPOTENT_HEADER_NAME = "idempotency-key";
29+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// -----------------------------------------------------------------------------
2+
// Filename: HmacSignatureAuthHelper.cs
3+
//
4+
// Description: Used for generating HMAC signatures and verifying them.
5+
//
6+
// Author(s):
7+
// Donal O'Connor ([email protected])
8+
//
9+
// History:
10+
// 08 02 2022 Donal O'Connor Created, Carmichael House,
11+
// Dublin, Ireland.
12+
//
13+
// License:
14+
// MIT.
15+
// -----------------------------------------------------------------------------
16+
17+
using System.Net;
18+
using System.Security.Cryptography;
19+
using System.Text;
20+
21+
namespace NoFrixion.MoneyMoov;
22+
23+
public static class HmacSignatureAuthHelper
24+
{
25+
public static Dictionary<string, string> GetAppHeaders(string appId,
26+
string idempotencyKey,
27+
string secret,
28+
DateTime date,
29+
Guid merchantId)
30+
{
31+
var signature = GenerateSignature(idempotencyKey, date, secret, true);
32+
33+
var headers = new Dictionary<string, string>
34+
{
35+
{HmacAuthenticationConstants.AUTHORIZATION_HEADER_NAME, GenerateAppAuthHeaderContent(appId, signature)},
36+
{HmacAuthenticationConstants.DATE_HEADER_NAME, date.ToString("R")},
37+
{HmacAuthenticationConstants.IDEMPOTENT_HEADER_NAME, idempotencyKey},
38+
{HmacAuthenticationConstants.MERCHANT_ID_HEADER_NAME, merchantId.ToString()},
39+
};
40+
41+
return headers;
42+
}
43+
44+
public static Dictionary<string, string> GetHeaders(string keyId,
45+
string nonce,
46+
string secret,
47+
DateTime date,
48+
bool asRetry = false)
49+
{
50+
var signature = GenerateSignature(nonce, date, secret);
51+
52+
var headers = new Dictionary<string, string>
53+
{
54+
{HmacAuthenticationConstants.AUTHORIZATION_HEADER_NAME, GenerateAuthHeaderContent(keyId, signature)},
55+
{HmacAuthenticationConstants.DATE_HEADER_NAME, date.ToString("R")},
56+
{HmacAuthenticationConstants.NONCE_HEADER_NAME, nonce},
57+
{HmacAuthenticationConstants.HTTP_RETRY_HEADER_NAME, asRetry.ToString().ToLower()},
58+
{HmacAuthenticationConstants.NOFRIXION_SIGNATURE_HEADER_NAME, signature},
59+
};
60+
61+
return headers;
62+
}
63+
64+
public static string GenerateSignature(string nonce, DateTime date, string secret, bool hmac256 = false)
65+
{
66+
return hmac256 ?
67+
HashAndEncode256($"date: {date:R}\n{HmacAuthenticationConstants.IDEMPOTENT_HEADER_NAME}: {nonce}", secret) :
68+
HashAndEncode($"date: {date:R}\n{HmacAuthenticationConstants.NONCE_HEADER_NAME}: {nonce}", secret);
69+
}
70+
71+
private static string GenerateAppAuthHeaderContent(string apiKey, string signature)
72+
{
73+
return $"Signature appId=\"{apiKey}\",headers=\"date {HmacAuthenticationConstants.IDEMPOTENT_HEADER_NAME}\",signature=\"{signature}\"";
74+
}
75+
76+
private static string GenerateAuthHeaderContent(string apiKey, string signature)
77+
{
78+
return $"Signature keyId=\"{apiKey}\",headers=\"date x-mod-nonce\",signature=\"{signature}\"";
79+
}
80+
81+
private static string HashAndEncode(string message, string secret)
82+
{
83+
var ascii = Encoding.ASCII;
84+
85+
HMACSHA1 hmac = new HMACSHA1(ascii.GetBytes(secret));
86+
hmac.Initialize();
87+
88+
byte[] messageBuffer = ascii.GetBytes(message);
89+
byte[] hash = hmac.ComputeHash(messageBuffer);
90+
91+
return WebUtility.UrlEncode(Convert.ToBase64String(hash));
92+
}
93+
94+
private static string HashAndEncode256(string message, string secret)
95+
{
96+
var ascii = Encoding.ASCII;
97+
98+
var hmac = new HMACSHA256(ascii.GetBytes(secret));
99+
hmac.Initialize();
100+
101+
var messageBuffer = ascii.GetBytes(message);
102+
var hash = hmac.ComputeHash(messageBuffer);
103+
104+
return WebUtility.UrlEncode(Convert.ToBase64String(hash));
105+
}
106+
}

src/NoFrixion.MoneyMoov/Models/Payouts/Payout.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,12 @@ public Counterparty? DestinationAccount
390390
public PaymentRailEnum PaymentRail { get; set; }
391391

392392
public string? Nonce { get; set; }
393+
394+
/// <summary>
395+
/// Collection of payrun invoices associated with the payout.
396+
/// Will be empty if the payout is not associated with a payrun.
397+
/// </summary>
398+
public List<PayrunInvoice>? PayrunInvoices { get; set; }
393399

394400
public NoFrixionProblem Validate()
395401
{

src/NoFrixion.MoneyMoov/Models/Transaction/Transaction.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,9 @@ public class Transaction : IWebhookPayload
115115
/// If set it indicates the payin was to a virtual IBAN.
116116
/// </summary>
117117
public string VirtualIBAN { get; set; }
118+
119+
/// <summary>
120+
/// An optional list of descriptive tags attached to the transaction.
121+
/// </summary>
122+
public List<Tag> Tags { get; set; } = [];
118123
}

src/NoFrixion.MoneyMoov/MoneyMoovUrlBuilder.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ public static string CancelScheduledPayoutUrl(string moneyMoovBaseUrl, Guid payo
219219

220220
public static string RejectPayoutUrl(string moneyMoovBaseUrl, Guid payoutID)
221221
=> $"{moneyMoovBaseUrl}/{MoneyMoovResources.payouts}/reject/{payoutID}";
222+
223+
public static string SendPayoutUrl(string moneyMoovBaseUrl)
224+
=> $"{moneyMoovBaseUrl}/{MoneyMoovResources.payouts}/send";
222225
}
223226

224227
/// <summary>

0 commit comments

Comments
 (0)