Skip to content

Commit 33eff67

Browse files
authored
Adds a new account identifier type of BIC (#533)
* Adjusted payout and related models for new payout BIC and country code fields. * Updated account identifier logic.
1 parent 8d7c7fb commit 33eff67

9 files changed

+76
-24
lines changed

src/NoFrixion.MoneyMoov/Enums/AccountIdentifierType.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,10 @@ public enum AccountIdentifierType
4242
/// <summary>
4343
/// Bitcoin address.
4444
/// </summary>
45-
BTC = 4
45+
BTC = 4,
46+
47+
/// <summary>
48+
/// Bank Identifier Code. Used for international payments. In addition to the BIC an account number and country code are required.
49+
/// </summary>
50+
BIC = 5
4651
}

src/NoFrixion.MoneyMoov/Mapping/CounterpartyMappers.cs

100644100755
File mode changed.

src/NoFrixion.MoneyMoov/Models/Account/AccountIdentifier.cs

+35-18
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// History:
99
// 21 10 2021 Donal O'Connor Created, Carmichael House, Dublin, Ireland.
1010
// 19 09 2023 Aaron Clauson Added Bitcoin support.
11+
// 20 03 2025 Aaron Clauson Added support for BIC identifier type.
1112
//
1213
// License:
1314
// MIT.
@@ -30,14 +31,18 @@ public AccountIdentifierType Type
3031
{
3132
get
3233
{
33-
if(!string.IsNullOrWhiteSpace(IBAN))
34+
if (!string.IsNullOrWhiteSpace(IBAN))
3435
{
3536
return AccountIdentifierType.IBAN;
3637
}
37-
else if(!string.IsNullOrWhiteSpace(SortCode) && !string.IsNullOrWhiteSpace(AccountNumber))
38+
else if (!string.IsNullOrWhiteSpace(SortCode))
3839
{
3940
return AccountIdentifierType.SCAN;
4041
}
42+
else if (!string.IsNullOrWhiteSpace(BIC))
43+
{
44+
return AccountIdentifierType.BIC;
45+
}
4146

4247
return AccountIdentifierType.Unknown;
4348
}
@@ -157,6 +162,7 @@ public string BitcoinAddress
157162
public string Summary =>
158163
Type == AccountIdentifierType.IBAN ? Type.ToString() + ": " + IBAN :
159164
Type == AccountIdentifierType.SCAN ? Type.ToString() + ": " + DisplayScanSummary :
165+
Type == AccountIdentifierType.BIC ? Type.ToString() + ": " + DisplayBicSummary :
160166
"No identifier.";
161167

162168
/// <summary>
@@ -165,13 +171,16 @@ public string BitcoinAddress
165171
public string DisplaySummary =>
166172
Type == AccountIdentifierType.IBAN ? IBAN :
167173
Type == AccountIdentifierType.SCAN ? DisplayScanSummary :
174+
Type == AccountIdentifierType.BIC ? DisplayBicSummary :
168175
"No identifier.";
169176

170177
public string DisplayScanSummary =>
171178
Currency == CurrencyTypeEnum.GBP && !string.IsNullOrEmpty(SortCode) && !string.IsNullOrEmpty(AccountNumber) && SortCode.Length == GBP_SORT_CODE_LENGTH
172179
? $"{SortCode[..2]}-{SortCode.Substring(2, 2)}-{SortCode.Substring(4, 2)} {AccountNumber}"
173180
: $"{SortCode} {AccountNumber}";
174181

182+
public string DisplayBicSummary => $"{BIC} {AccountNumber}";
183+
175184
public bool IsSameDestination(AccountIdentifier other)
176185
{
177186
if (other == null)
@@ -188,6 +197,7 @@ public bool IsSameDestination(AccountIdentifier other)
188197
{
189198
AccountIdentifierType.IBAN => IBAN == other.IBAN,
190199
AccountIdentifierType.SCAN => SortCode == other.SortCode && AccountNumber == other.AccountNumber,
200+
AccountIdentifierType.BIC => BIC == other.BIC && AccountNumber == other.AccountNumber,
191201
_ => false
192202
};
193203
}
@@ -200,7 +210,8 @@ public virtual Dictionary<string, string> ToDictionary(string keyPrefix)
200210
{ keyPrefix + nameof(BIC), BIC ?? string.Empty},
201211
{ keyPrefix + nameof(IBAN), IBAN ?? string.Empty},
202212
{ keyPrefix + nameof(SortCode), SortCode ?? string.Empty},
203-
{ keyPrefix + nameof(AccountNumber), AccountNumber ?? string.Empty}
213+
{ keyPrefix + nameof(AccountNumber), AccountNumber ?? string.Empty},
214+
{ keyPrefix + nameof(BIC), BIC ?? string.Empty},
204215
};
205216
}
206217

@@ -236,39 +247,45 @@ public NoFrixionProblem Validate()
236247

237248
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
238249
{
239-
switch (Currency)
250+
switch (Type)
240251
{
241-
// GBP & USD support both IBAN and SCAN.
242-
case CurrencyTypeEnum.GBP:
243-
case CurrencyTypeEnum.USD:
252+
case AccountIdentifierType.IBAN:
253+
if (string.IsNullOrWhiteSpace(IBAN))
244254
{
245-
if (string.IsNullOrWhiteSpace(IBAN) &&
246-
(string.IsNullOrWhiteSpace(SortCode) || string.IsNullOrWhiteSpace(AccountNumber)))
255+
yield return new ValidationResult(
256+
$"The IBAN value is required for a {AccountIdentifierType.IBAN} identifier.",
257+
[nameof(IBAN)]);
258+
}
259+
break;
260+
261+
case AccountIdentifierType.SCAN:
262+
{
263+
if (string.IsNullOrWhiteSpace(SortCode) || string.IsNullOrWhiteSpace(AccountNumber))
247264
{
248265
yield return new ValidationResult(
249-
$"Either the IBAN or Sort code and account number are required for a {Currency} account identifier.",
250-
[nameof(IBAN), nameof(SortCode), nameof(AccountNumber)]);
266+
$"The sort code and account number are required for a {AccountIdentifierType.SCAN} identifier.",
267+
[nameof(SortCode), nameof(AccountNumber)]);
251268
}
252269

253270
break;
254271
}
255272

256-
// EUR only supports IBAN.
257-
case CurrencyTypeEnum.EUR:
273+
case AccountIdentifierType.BIC:
258274
{
259-
if (string.IsNullOrEmpty(IBAN))
275+
if (string.IsNullOrWhiteSpace(BIC) || string.IsNullOrWhiteSpace(AccountNumber))
260276
{
261-
yield return new ValidationResult("IBAN is required for EUR account identifier.",
262-
[nameof(IBAN)]);
277+
yield return new ValidationResult(
278+
$"The BIC and account number are required for a {AccountIdentifierType.BIC} identifier.",
279+
[nameof(BIC), nameof(AccountNumber)]);
263280
}
264281

265282
break;
266283
}
267284

268285
default:
269286
{
270-
yield return new ValidationResult($"Currency {Currency} was not recognised when validating an account identifier.",
271-
[nameof(Currency)]);
287+
yield return new ValidationResult($"Identifier {Type} was not recognised when validating an account identifier.",
288+
[nameof(Type)]);
272289
break;
273290
}
274291
}

src/NoFrixion.MoneyMoov/Models/Account/AccountIdentifierCreate.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public string SortCode
8585
}
8686

8787
/// <summary>
88-
/// Bank account number. Only applicable for SCAN identifiers.
88+
/// Bank account number. Only applicable for SCAN and BIC identifiers.
8989
/// </summary>
9090
private string _accountNumber;
9191
public string AccountNumber

src/NoFrixion.MoneyMoov/Models/Account/CounterpartyCreate.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,18 @@ public class CounterpartyCreate
4242
/// </summary>
4343
public string? EmailAddress { get; set; }
4444

45-
/// An email address for the counterparty. Optional to set and depending on the payment
45+
/// <summary>
46+
/// A phone number for the counterparty. Optional to set and depending on the payment
4647
/// network does not always get set for pay ins.
48+
/// </summary>
4749
public string? PhoneNumber { get; set; }
4850

51+
/// <summary>
52+
/// A country code for the counterparty. Optional to set and depending on the payment
53+
/// network does not always get set for pay ins
54+
/// </summary>
55+
public string? CountryCode { get; set; }
56+
4957
/// <summary>
5058
/// The counterparty's account identifier. This identifier is what is used to send the payment
5159
/// to them, or for a pay in is the source of the payment.
@@ -61,6 +69,7 @@ public virtual Dictionary<string, string> ToDictionary(string keyPrefix)
6169
{ keyPrefix + nameof(Name), Name ?? string.Empty },
6270
{ keyPrefix + nameof(EmailAddress), EmailAddress ?? string.Empty },
6371
{ keyPrefix + nameof(PhoneNumber), PhoneNumber ?? string.Empty },
72+
{ keyPrefix + nameof(CountryCode), CountryCode ?? string.Empty },
6473
};
6574

6675
if(Identifier != null)

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

+9
Original file line numberDiff line numberDiff line change
@@ -308,13 +308,22 @@ public Counterparty? DestinationAccount
308308
/// For Bitcoin payouts, when this flag is set the network fee will be deducted from the send amount.
309309
/// THis is particularly useful for sweeps where it can be difficult to calculate the exact fee required.
310310
/// </summary>
311+
[Obsolete]
312+
[System.Text.Json.Serialization.JsonIgnore]
313+
[Newtonsoft.Json.JsonIgnore]
311314
public bool BitcoinSubtractFeeFromAmount { get; set; }
312315

313316
/// <summary>
314317
/// The Bitcoin fee rate to apply in Satoshis per virtual byte.
315318
/// </summary>
319+
[Obsolete]
320+
[System.Text.Json.Serialization.JsonIgnore]
321+
[Newtonsoft.Json.JsonIgnore]
316322
public int BitcoinFeeSatsPerVbyte { get; set; }
317323

324+
[Obsolete]
325+
[System.Text.Json.Serialization.JsonIgnore]
326+
[Newtonsoft.Json.JsonIgnore]
318327
public string FormattedBitcoinFee => $"{BitcoinFeeSatsPerVbyte} sats/vbyte" +
319328
(BitcoinSubtractFeeFromAmount ? " (fee will be subtracted from amount)" : "");
320329

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class PayoutCreate
3939
public CurrencyTypeEnum Currency { get; set; }
4040

4141
[Required(ErrorMessage = "Amount is required.")]
42-
[Range(0.00001, double.MaxValue,ErrorMessage = "Minimum value of 0.00001 is required for Amount")]
42+
[Range(0.00001, double.MaxValue,ErrorMessage = "Minimum value of 0.01 is required for Amount")]
4343
public decimal Amount { get; set; }
4444

4545
/// <summary>
@@ -151,11 +151,13 @@ public CounterpartyCreate? DestinationAccount
151151
/// <summary>
152152
/// For Bitcoin payouts, when this flag is set the network fee will be deducted from the send amount. This is particularly useful for sweeps where it can be difficult to calculate the exact fee required.
153153
/// </summary>
154+
[Obsolete]
154155
public bool BitcoinSubtractFeeFromAmount { get; set; }
155156

156157
/// <summary>
157158
/// The Bitcoin fee rate to apply in Satoshis per virtual byte.
158159
/// </summary>
160+
[Obsolete]
159161
public int BitcoinFeeSatsPerVbyte { get; set; }
160162

161163
/// <summary>

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ public static IEnumerable<ValidationResult> Validate(Payout payout, ValidationCo
371371
}
372372

373373
if (payout.Destination != null &&
374-
!(payout.Type == AccountIdentifierType.IBAN || payout.Type == AccountIdentifierType.SCAN))
374+
!(payout.Type == AccountIdentifierType.IBAN || payout.Type == AccountIdentifierType.SCAN || payout.Type == AccountIdentifierType.BIC))
375375
{
376376
yield return new ValidationResult("Only destination types of IBAN and SCAN are supported.", [ nameof(payout.Type) ]);
377377
}

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

+11-1
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,18 @@ public class Counterparty
5252
/// </summary>
5353
public string? EmailAddress { get; set; }
5454

55-
/// An email address for the counterparty. Optional to set and depending on the payment
55+
/// <summary>
56+
/// A phone number for the counterparty. Optional to set and depending on the payment
5657
/// network does not always get set for pay ins.
58+
/// </summary>
5759
public string? PhoneNumber { get; set; }
5860

61+
/// <summary>
62+
/// A country code for the counterparty. Optional to set and depending on the payment
63+
/// network does not always get set for pay ins
64+
/// </summary>
65+
public string? CountryCode { get; set; }
66+
5967
/// <summary>
6068
/// The counterparty's account identifier. This identifier is what is used to send the payment
6169
/// to them, or for a pay in is the source of the payment.
@@ -113,6 +121,7 @@ public virtual Dictionary<string, string> ToDictionary(string keyPrefix)
113121
{ keyPrefix + nameof(Name), Name ?? string.Empty },
114122
{ keyPrefix + nameof(EmailAddress), EmailAddress ?? string.Empty },
115123
{ keyPrefix + nameof(PhoneNumber), PhoneNumber ?? string.Empty },
124+
{ keyPrefix + nameof(CountryCode), CountryCode ?? string.Empty },
116125
};
117126

118127
if(Identifier != null)
@@ -135,6 +144,7 @@ public virtual string GetApprovalHash()
135144
(!string.IsNullOrEmpty(Name) ? Name : string.Empty) +
136145
(!string.IsNullOrEmpty(EmailAddress) ? EmailAddress : string.Empty) +
137146
(!string.IsNullOrEmpty(PhoneNumber) ? PhoneNumber : string.Empty) +
147+
(!string.IsNullOrEmpty(CountryCode) ? CountryCode : string.Empty) +
138148
(Identifier != null ? Identifier.GetApprovalHash() : string.Empty);
139149
return HashHelper.CreateHash(input);
140150
}

0 commit comments

Comments
 (0)