buckaroo-it / BuckarooSdk_DotNet

Software Development Kit which can be used for easy access to the Buckaroo API.
MIT License
6 stars 18 forks source link

Buckaroo iDeal push is not sending authorization header when using httppost #67

Open sander1095 opened 1 year ago

sander1095 commented 1 year ago

Hello!

We are using buckaroo to accept ideal payments. Creating a transaction and redirecting the user works, but handling the push messages doesn't.

We use .NET, and we use the following code to check if the incoming push message is safe and sound:

        var push = _sdkClient
            .GetPushHandler(_paymentProviderOptions.Authentication.SecretKey)
            .DeserializePush(
                paymentStatusRetrieval.RequestBody,
                paymentStatusRetrieval.RequestUri.Authority + paymentStatusRetrieval.RequestUri.PathAndQuery,
                paymentStatusRetrieval.RequestAuthorizationHeader);

This throws a nullreference exception, the only reason why I can imagine this happens is because the RequestAuthorizationHeader is in fact empty.

When checking the incoming request we also don't see an authorization header.

How can we fix this? Do we need we to do something special with the request to ensure that Buckaroo sends us an authorization header? Is the mistake on our end? Or can we work around this somehow?

I'd love some advice. Thanks!

sander1095 commented 1 year ago

I saw that @michael-buckaroo responded to a similarly sounding issue in #61 , so perhaps they have some advice?

sander1095 commented 1 year ago

I came across some code that generates the authHeader in the following link:

https://github.com/happy-geeks/geeks-core-library/blob/d559879a1d611cd06ed055ba7f6930cabf7597ef/GeeksCoreLibrary/Modules/Payments/Services/BuckarooService.cs#L215

However, I am unsure if this is the correct way to generate it. Shouldn't the nonce and requestTimestamp values match the ones we sent in the request to Buckaroo? When these values are different, a different HMAC signature is generated, so I have doubts about the correctness of this approach.

Despite this, I still encountered errors when I attempted to use this solution.

At my current company, another project obtains an authorization header from the PUSH webhook from Buckaroo, so I am convinced that I should be getting a request including an authorization header.

Michael-Buckaroo commented 1 year ago

Hey @sander1095, i think my hunch there was in the right direction; in DeserializePush you can remove the entire line that encodes the URL and just pass the requestUri to the VerifySignature, then then the validation seems to succeed.

Note; i havent tested more cases than this, so not sure if removing that line is the real fix or adding the encode in another place so it matches. But hope it helps!

In case you need it, this is the JSON i used in the Unit Test:

{
   "Transaction":{
      "Key":"F948CAA0C8FC4371892126BB03AB997C",
      "Invoice":"NL1802813331",
      "ServiceCode":"ideal",
      "Status":{
         "Code":{
            "Code":190,
            "Description":"Succes"
         },
         "SubCode":{
            "Code":"S060",
            "Description":"De consument is teruggekeerd, transactiebevestiging succesvol."
         },
         "DateTime":"2018-03-23T14:39:03"
      },
      "IsTest":false,
      "Order":null,
      "Currency":"EUR",
      "AmountDebit":71.84,
      "TransactionType":"C021",
      "Services":[
         {
            "Name":"ideal",
            "Action":null,
            "Parameters":[
               {
                  "Name":"consumerIssuer",
                  "Value":"ING Bank"
               },
               {
                  "Name":"transactionId",
                  "Value":"1150000854689906"
               },
               {
                  "Name":"consumerName",
                  "Value":"Mw Sadsasdas"
               },
               {
                  "Name":"consumerIBAN",
                  "Value":"NL89INGB0002056758"
               },
               {
                  "Name":"consumerBIC",
                  "Value":"INGBNL2A"
               }
            ],
            "VersionAsProperty":2
         }
      ],
      "AdditionalParameters":{
         "List":[
            {
               "Name":"businessProcessId",
               "Value":"2215020"
            },
            {
               "Name":"customerType",
               "Value":"CON-RES"
            }
         ]
      },
      "MutationType":1,
      "RelatedTransactions":null,
      "IsCancelable":false,
      "IssuingCountry":null,
      "StartRecurrent":false,
      "Recurring":false,
      "CustomerName":"Mw Sadsasdas",
      "PayerHash":"63b739d2abcb0ee47e08deff1470fe73d3370bfab65fe925d27e2608021bdef1fce5b43af396f72c0df1b08595d4a9589985d545a49363dd388fb89c6b8465a0",
      "PaymentKey":"A9CF3A004C0144A2B1709EFAE7841388"
   }
}
sander1095 commented 1 year ago

I've discovered the actual source of the problem.

In Buckaroo Plaza we have configured that we want to receive httppost in our push messages. Those don't send an authorization header, AND the SDK doesn't support this; deserialization will fail because it expects json. This is very dissapointing.. The docs also don't mention anything about this; funnily enough the docs only mention httppost, which doesn't work when using Buckaroo's official SDK's.

if we would switch to json instead of httppost in the plaza, the entire process works!

However, I can't switch to json because this would cause HUGE breaking changes in our codebase and other projects that are expecting httppost. Those projects have custom parser logic and don't verify the push messages, most likely because the SDK doesn't support this.

I see 2 solutions:

sander1095 commented 1 year ago

@Michael-Buckaroo Your answer is appreciated, but sadly this doesn't help me. As you can see in my previous comment, the problem lies with the SDK and buckaroo itself.

Your answer also expects us to use JSON, which we don't do. We use httppost and we would expect the SDK to support this.

Michael-Buckaroo commented 1 year ago

Any chance you could provide an unit test that reproduces your current scenario? Would be happy to look into it a bit further.

Another (a bit out of the box) way to find a way to get this to work would be to look at the other SDK's to see what is done in there, which seem to be maintained much better in the last few years.

sander1095 commented 1 year ago

Hi @Michael-Buckaroo.

Other SDK's

I think you're an employee of Buckaroo. Are you saying that Buckaroo doesn't maintain their own SDK's for its own platform?

I don't really want to install other SDK's because they are not as trustworthy as an official one; what if an unofficial SDK messes around with the transaction I send to Buckaroo? And what incentive does a third party have to maintain this SDK for free?

Regardless of the previous sentences, can you recommend other SDK's?

Unit test

I can try to create a unit test today, but I'm not sure if I'll be able to complete it.

If not, you can reproduce our problem like this:

Now change that push contenttype to JSON and perform another request. You'll see that an authorization header is returned.

Problem 1: The fact that JSON does give us an authorization header and httppost DOESN't, is problem 1! Unless there is a reason for this, which I don't understand.

Now that we have an authorization header, we can try to use the code from my post, which verifies and deserializes the push:

// request is the incoming Microsoft.AspNetCore.Http.HttpRequest
await using var ms = new MemoryStream();
await request.Body.CopyToAsync(ms);
var requestBody = ms.ToArray();

var secretKey = "YOUR_SECRET_KEY_HERE";
var requestUri = request.GetEncodedUrl();
var authHeader = request.Headers[HeaderNames.Authorization];

var push = new SdkClient()
    .GetPushHandler(secretKey)
    .DeserializePush(
        requestBody ,
        requestUri.Authority + requestUri.PathAndQuery,
        authHeader);

When trying this with push contenttype json, this will work.

Problem 2: However, with httppost, the authorization header is empty AND DeserializePush will fail because the code expects JSON, as you can see in the SDK code.

Michael-Buckaroo commented 1 year ago

I think you're an employee of Buckaroo.

That's correct, not a contributor of this repository though.

Are you saying that Buckaroo doesn't maintain their own SDK's for its own platform?

Seeing it has not been touched in quite some years, i think it probably needs a ReadMe that informs people about it's state. Currently it has some focus to get it updated (or replaced), but as in every company, i would expect this to take some time before it's done.

I can try to create a unit test today, but I'm not sure if I'll be able to complete it.

Only if you have time for it and it's not too much work! In the end, we're debugging the same thing.

Regardless of the previous sentences, can you recommend other SDK's?

The PHP SDK seems to be actively maintained.

umbracotrd commented 9 months ago

@sander1095 : welp, you saved me there. I've spent days going back and forth the docs, repositories and buckaroo email support to find out what the heck authorizationHeader is. It's always empty but it's mandatory in all SignatureCalculationService methods! Btw, do you use ConfiguredTransaction.NoServiceSelected by any chance? I was hoping that it will allow user to select a payment service when they're redirected to Buckaroo site but in the end, it's stuck with Paypal service.

sander1095 commented 9 months ago

Hey @umbracotrd .

It's been some time that I worked on this ,but I can tell you the following.

I think we ended up using the HTTP post method of receiving callbacks from Buckaroo. You can configure this in the Buckaroo portal. However, this other callback method is not supported by the BUckaroo SDK; the HTTP post does not give the PushValidator/SignatureCalculationService enough data to its job.

If you switch to the other callback method in the Buckaroo portal ( I can't remember its name), everything should work with the Pushvalidator/signaturecalculationservice. If that's possible for you, I recommend that you do this.

We were unable to switch to that other method because of reasons, so we ended up parsing the request body ourselves and wrote our own PushValidator (based on some other Buckaroo SDK's, might have been PHP?) to validate the incoming push and map the result into a BuckarooPaymentResult or something.

For now, this is all the info I can give you. I hope it works. Let me know if there's something else I can do.