4

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.

  • Did you ever get this to work, Lennart? We are having similar difficulties integrating with Swish on an Azure App Service. – grimdog_john Nov 04 '21 at 09:26
  • Actually yes! It has to do with that the code above works perfectly well on only Windows. How are your certicifates managed on Azure? @grimdog_john – Lennart Hammarström Nov 05 '21 at 09:41
  • Hi Lennart. Thanks for getting back to me. They're Public Key Certificates which are added to the App Service under TLS/SSL Settings. – grimdog_john Nov 18 '21 at 13:32
  • @grimdog_john Hello, were you able to get it to work on an Azure App Service? Everything works well for me locally, but I'm struggling with "Handshake failure" with test certificates on my App Service. – Lucas Arrefelt Jan 05 '22 at 14:18
  • Hi Lucas. Unfortunately no. I did get it to work for a short period of time, but when I republished my app service it broke. It all seems very flimsy. I get the same error as you, the Handshake Failure. Same code with exact same certificates/thumbprints works perfectly on local machine. Did you have any luck yourself? – grimdog_john Jan 12 '22 at 21:39
  • @LucasArrefelt I've tried so many different things today I don't know where I am with this. I even re-wrote my code so it pulls the certs from Azure KeyVault but still the same issue. Works locally but not in Azure. So so strange. I've never in my 15+ years of programming encountred an issue so baffling and time-consuming :( – grimdog_john Jan 13 '22 at 17:39
  • @grimdog_john Yeah same here, I've tried so many approaches but unfortunately no success. I've put the project on hold for a while but I will get back to it soon, plan is to instead host it in an Azure VM. This should give me full access to certificate management in comparison to App Service and hence start working just as it does locally. Have you been able to try this? – Lucas Arrefelt Jan 23 '22 at 01:56
  • @LucasArrefelt No sadly its not an option for us at the moment but we may have to consider it... I had a small bit of success by changing the "Target Runtime" to win-x86/win-x64 when publishing from Visual Studio. This has been working for me for about a week while i was tidying up some code on my development machine, but it randomly stopped working when another developer published the project. Definitely feels as if there is something that happens during a publish that has a knock on effect with the certificates... – grimdog_john Jan 24 '22 at 15:53
  • So I had this working for around a week and then when I published my site again it decided to break. I had changed no code related to Swish. When I inspect the packets in Wireshark I can see it has stopped sending the CER certificates in the TLS handshake, even though my debug shows them added to the handler. Completely bizarre behaviour. – grimdog_john Feb 09 '22 at 11:40
  • Hope you've had more luck after the last time we spoke, I got back to the project and decided to go with a VM instead since I ran out of ideas to try with the App Service. It works as you would expect when hosted in a VM (same way as it does locally) and it wasn't too much work to set it up but yeah, might seem like a lot of effort just for the sake of certificates. – Lucas Arrefelt Mar 22 '22 at 00:51
  • Any uppdates on this? I'm having the same problem. – uggeh Mar 29 '22 at 14:26
  • So from what I can see on our web-applications hosted in Azure it will stop working intermittently, and I can not FOR THE LIFE OF ME FIGURE OUT WHY because we aren't changing anything. It will just stop working for no reason. – Lennart Hammarström Mar 31 '22 at 18:49

0 Answers0