I will literally pull my hair out. This should not be hard to implement, but anyway I cannot get it to work. I am trying to implement the Swedish Swish API for payments. All we need is to send a payload and add a certificate to the HttpClientHandler.
I am trying to implement the PaymentRequest-method as described here: https://developer.swish.nu/api/payment-request/v2
My code is as follows, firstly a configuration class:
public class SwishApiDetails
{
public string BaseUrl { get; set; } // Base URL for the API
public string CallbackUrl { get; set; } // Optional callback URL
public string ClientCertificate { get; set; } // Client certificate thumbprint
public string RootCertificateV1 { get; set; } // First CA certificate
public string RootCertificateV2 { get; set; } // Second CA certificate
}
Code for the actual API calling (yes I know I am not disposing the client, but that is not the issue right now (well unless it is))
public class SwishApi
{
private readonly HttpClient _client;
private readonly SwishApiDetails _configuration;
public SwishApi(SwishApiDetails configuration)
{
_configuration = configuration;
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (
sender,
certificate,
chain,
sslPolicyErrors) => true,
ClientCertificateOptions = ClientCertificateOption.Manual
};
var thumbprints = new List<string>
{
_configuration.ClientCertificate,
_configuration.RootCertificateV1,
_configuration.RootCertificateV2
};
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
foreach (var thumbprint in thumbprints)
{
var certificates = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
handler.ClientCertificates.AddRange(certificates);
}
_client = new HttpClient(handler) {BaseAddress = new Uri(_configuration.BaseUrl)};
}
public async Task<(bool HasError, string Message)> MakePaymentRequest(
int amountToPay,
string reference,
string phoneNumber,
string receiverAlias,
string messageToCustomer)
{
var phoneAlias = phoneNumber.Replace(" ", "").Replace("+", "");
if (phoneAlias.StartsWith("0")) phoneAlias = phoneAlias.Substring(1, phoneAlias.Length);
var result = await _client.SendAsync(new HttpRequestMessage
{
Method = HttpMethod.Put,
RequestUri = new Uri($"{_configuration.BaseUrl}/api/v2/paymentrequests/{reference}"),
Content = JsonContent.Create(new
{
amount = amountToPay,
payerAlias = phoneAlias,
payeeAlias = receiverAlias,
message = messageToCustomer,
payeePaymentReference = reference,
currency = SwishDefaults.Currency.Sek,
callbackUrl = _configuration.CallbackUrl
})
});
return result.IsSuccessStatusCode
? (false, string.Empty)
: (true, await result.Content.ReadAsStringAsync());
}
}
To test it all I am using this code:
public class Test
{
private readonly SwishApi _swish;
public Test()
{
_swish = new SwishApi(new SwishApiDetails
{
BaseUrl = "https://cpc.getswish.net/swish-cpcapi",
CallbackUrl = null,
ClientCertificate = "F06644FAF53150D5B31716ABF121FE112A225AF1", // Local thumbprint
RootCertificateV1 = "A8985D3A65E5E5C4B2D7D66D40C6DD2FB19C5436", // Local thumbprint
RootCertificateV2 = "03BFF7B54C712504C5BE5A8528163C931618A3C0" // Local thumbprint
});
}
[Fact]
public async Task TestPaymentRequest()
{
var (hasError, _) = await _swish.MakePaymentRequest(
1,
Guid.NewGuid().ToString("N").ToUpper(),
"4671111111",
"Swish Test",
"Testing");
Assert.False(hasError);
}
}
The certificates are downloaded from here: https://assets.ctfassets.net/4dca8u8ebqnn/5B6HqHPnsnGf0klDoeQSJh/1d4a6bf66a1269859cf00c01b312c601/mss_test_1.9.zip
They are public for the test environment.
The certificates are not by a real CA, they are signed by Swish themselves so it might be some issues with that. Other than that I have literally no idea whats wrong and why other people can get it to work.
Could it be OS-specific, or that the cipher-suites on my machine are busted?
If you want to try this out, go to this repo: https://github.com/lhammarstrom/swish-net
And if you want to try it out with a local certificate file then you can go to this branch: https://github.com/lhammarstrom/swish-net/tree/feature/with_cert (feature/with_cert). The client certificate is included in the files with the test project.
Also, for clarification, the error I am getting is:
System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
System.Net.Http.HttpRequestException
The SSL connection could not be established, see inner exception.
at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
at Swish.Services.SwishApi.MakePaymentRequest(Int32 amountToPay, String reference, String phoneNumber, String receiverAlias, String messageToCustomer) in /Users/leni/Projects/Swish/Swish/Services/SwishApi.cs:line 73
at Swish.Tests.Test.TestPaymentRequest() in /Users/leni/Projects/Swish/Swish.Tests/Tests.cs:line 30
at Xunit.Sdk.TestInvoker`1.<>c__DisplayClass48_1.<<InvokeTestMethodAsync>b__1>d.MoveNext() in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\Runners\TestInvoker.cs:line 264
--- End of stack trace from previous location ---
at Xunit.Sdk.ExecutionTimer.AggregateAsync(Func`1 asyncAction) in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\ExecutionTimer.cs:line 48
at Xunit.Sdk.ExceptionAggregator.RunAsync(Func`1 code) in C:\Dev\xunit\xunit\src\xunit.core\Sdk\ExceptionAggregator.cs:line 90
System.Security.Authentication.AuthenticationException
Authentication failed, see inner exception.
at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
Interop+AppleCrypto+SslException
handshake failure
Exception doesn't have a stacktrace
Edit:
I have followed this guide (https://johan.driessen.se/posts/Calling-the-Swish-Payment-API-from-Azure-AppService/). They got it to work and I dont. Furthermore this repository which claims to work on .NET 5 (https://github.com/RickardPettersson/swish-api-csharp) won't work on my machine either.