Mangopay / mangopay2-php-sdk

PHP SDK for MANGOPAY
https://packagist.org/packages/mangopay/php-sdk-v2
MIT License
123 stars 134 forks source link

3DSV2 Gather up browser data #469

Closed Dekari closed 3 years ago

Dekari commented 3 years ago

Hello,

as part of 3dsv2 we need to gather up some data from browser etc in order to pass in pay in direct/ preauth.

Is there any examples on how to gather up? I guess from Client side? (javascript)

Any demo sample? or code snippets?

thanks in advance

spiritinlife commented 3 years ago

@Dekari i have been in the same process and here are some interesting links that helped figure things out.

  1. Mangopay integration pdf https://mcusercontent.com/c117c88a1044269f57b201fbe/files/83288c60-83ad-4df3-816d-9620625099b6/3DS2_integration_guide_1_.pdf . States all browser info required with examples on how to retrieve them
  2. Also here is a js script from adyen that gathers all needed data https://github.com/Adyen/adyen-3ds2-js-utils/blob/master/browser/index.js
fredericdelordm commented 3 years ago

Hello @Dekari,

We are currently working on a proper documentation regarding 3DSV2. I will keep you updated.

Have a nice day,

launay12u commented 3 years ago

Hello any updates ? I'm currently looking to integrate 3DS2 in my PHP website and honestly I'm a bit lost... Isn't possible to add integration exemple in the demo workflow ? Even in the test case there is nothing about BrowserInfo and the IP adress is filled with static value Thanks in advance

launay12u commented 3 years ago

Hey after few try this afternoon, I finally managed to integrate the 3DS2 on PayIn direct !

For the ipAddress I just retrive it with a function on serverside :

public function getIpAddress(){
//whether ip is from the share internet  
    if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    }
    //whether ip is from the proxy  
    elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    }
    //whether ip is from the remote address  
    else {
        $ip = $_SERVER['REMOTE_ADDR'];
    }
    return $ip;
}

For the BrowserInfo, I'm not sure if it's a good/best option but I did a JS script, on my paiement page, who send needed data to a PHP function who save these data in $_SESSION (I use getallheaders() in PHP for User-Agent and Accept Header).

Js script :

$.ajax({
    type: "POST",
    url: "/sendBrowserInfo",
    data: {
        JavaEnabled: navigator.javaEnabled(),
        Language: navigator.language || navigator.userLanguage,
        ColorDepth: screen.colorDepth,
        ScreenHeight: screen.height,
        ScreenWidth: screen.width,
        TimeZoneOffset: new Date().getTimezoneOffset().toString()
    },
});

Php who save in session :

public function sendBrowserInfo(){
    if(!empty($_POST)){
        foreach($_POST as $key => $value){
            $_SESSION[$key] = $value;
        }
    }
}

Final function who get all BrowserInfo :

public function getBrowserInfo(){
    $headers = getallheaders();
    $result = new BrowserInfo();
    $result->AcceptHeader = $headers['Accept'];
    $result->JavaEnabled = $_SESSION['JavaEnabled'];
    $result->Language = $_SESSION['Language'];
    $result->ColorDepth = $_SESSION['ColorDepth'];
    $result->ScreenHeight = $_SESSION['ScreenHeight'];
    $result->ScreenWidth = $_SESSION['ScreenWidth'];
    $result->TimeZoneOffset = $_SESSION['TimeZoneOffset'];
    $result->UserAgent = getallheaders()['User-Agent'];
    $result->JavascriptEnabled = true;
    return $result;
}

So for PayIn Direct it's work perfectly.

But I have a problem with PreAuth... Indeed, when I make a PreAuth with 3DS2 frictionless (card 4970105191923460) flow the PreAuth response is succeed but $PreAuth->SecureModeNeededis true and $PreAuth->SecureModeRedirectURL is NULLso my function fail because I can't redirect to 3DS page ( I don't know if it's a sandbox problem or a inplementation problem). There is no problem with the challenge flow.

Here is my code :

$CardPreAuthorization = new CardPreAuthorization();
$CardPreAuthorization->AuthorId = $authorId;
$CardPreAuthorization->DebitedFunds = new Money();
$CardPreAuthorization->DebitedFunds->Currency = "EUR";
$CardPreAuthorization->DebitedFunds->Amount = $amount * 100;
$CardPreAuthorization->CardId = $resultCardRegistration->CardId;
$CardPreAuthorization->SecureModeReturnURL = getenv('BASE_URL')."/confirm.php";
$CardPreAuthorization->Culture = "FR";
$CardPreAuthorization->Requested3DSVersion = "V2_1";
$CardPreAuthorization->SecureMode = "NO_CHOICE";
$CardPreAuthorization->IpAddress = $author->getIpAddress();
$CardPreAuthorization->BrowserInfo = $author->getBrowserInfo();
$ResultPreAuth = $this->apiMangopay->CardPreAuthorizations->Create($CardPreAuthorization);

Here the result :

object(MangoPay\CardPreAuthorization)#312 (29) {
  ["AuthorId"]=>
  string(9) "XXXXXX"
  ["DebitedFunds"]=>
  object(MangoPay\Money)#344 (2) {
    ["Currency"]=>
    string(3) "EUR"
    ["Amount"]=>
    int(1000)
  }
  ["Status"]=>
  string(9) "SUCCEEDED"
  ["PaymentStatus"]=>
  string(7) "WAITING"
  ["ResultCode"]=>
  string(6) "000000"
  ["ResultMessage"]=>
  string(7) "Success"
  ["StatementDescriptor"]=>
  NULL
  ["ExecutionType"]=>
  string(6) "DIRECT"
  ["SecureMode"]=>
  string(9) "NO_CHOICE"
  ["CardId"]=>
  string(9) "XXXXXX"
  ["SecureModeNeeded"]=>
  bool(true)
  ["SecureModeRedirectURL"]=>
  NULL
  ["SecureModeReturnURL"]=>
  string(76) "http://localhost:8888/confirm.php?preAuthorizationId=XXXX"
  ["ExpirationDate"]=>
  int(1622173491)
  ["AuthorizationDate"]=>
  int(1621611891)
  ["PaymentType"]=>
  string(4) "CARD"
  ["PayInId"]=>
  NULL
  ["SecurityInfo"]=>
  object(MangoPay\SecurityInfo)#351 (1) {
    ["AVSResult"]=>
    string(8) "NO_CHECK"
  }
  ["MultiCapture"]=>
  bool(false)
  ["RemainingFunds"]=>
  object(MangoPay\Money)#343 (2) {
    ["Currency"]=>
    string(3) "EUR"
    ["Amount"]=>
    int(1000)
  }
  ["IpAddress"]=>
  string(3) "::1"
  ["BrowserInfo"]=>
  object(stdClass)#294 (9) {
    ["AcceptHeader"]=>
    string(135) "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
    ["JavaEnabled"]=>
    bool(false)
    ["Language"]=>
    string(5) "fr-FR"
    ["ColorDepth"]=>
    int(24)
    ["ScreenHeight"]=>
    int(900)
    ["ScreenWidth"]=>
    int(1440)
    ["TimeZoneOffset"]=>
    int(-120)
    ["UserAgent"]=>
    string(121) "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"
    ["JavascriptEnabled"]=>
    bool(true)
  }
  ["Requested3DSVersion"]=>
  string(4) "V2_1"
  ["Applied3DSVersion"]=>
  string(4) "V2_1"
  ["Id"]=>
  string(9) "XXXX"
  ["CreationDate"]=>
  int(1621611889)
}

And the last problem I face, once again I'm not sure if it's normal or not but now with the 3DS2 active all payement even under 30€ need a card with 3DS activated. If I try with the card listed for payement under 50€, it's always fail. This will be the same on production ? Because I found this very sad :(

I hope I can help someone and I also hope someone can help me 🤣

PS : sorry for the english fault, isn't my mother tongue and I try too reduce my use of Google translate to improve myself 😉

Asenar commented 3 years ago

Hi @launay12u,

I'm implementing 3DS2 too and I made pretty much the same implementation as you did.

It seems the mangopay php sdk and its documentation are both really outdated.

About PayIn direct, did you added BrowserInfo in $payIn->BrowserInfo or in $payIn->ExecutionDetails->BrowserInfo ?

launay12u commented 3 years ago

Hey @Asenar !

For the Payin I added in $payIn->ExecutionDetails->BrowserInfo 😄 I did this because in PayInPaymentDetailsCard.php in mangopay libraries there is a BrowserInfo attribut

fredericdelordm commented 3 years ago

Hello @launay12u and @Asenar,

@Asenar We are currently working on improving our documentation. We are open to any suggestion. Don't hesitate to reach me.

@launay12u Thank you for taking the time to share your example with the community.

You can contact me anytime here

Asenar commented 3 years ago

Thanks you @launay12u ! I found that field a few minutes after posting this !

@fredericdelordm I think I will don't have time before a couple of weeks but I will keep that in mind !

About the field Requested3DSVersion, it's mentioned this is valid only in «sandbox». Does that mean this option can be safely defined in production and will have no impact ?

The first thing I think related to 3DS2 is to update workflows and code samples described on the demo page

An other thing that makes difficult the uses of the php-sdk is that some properties are missing but mandatory, and other have wrong php-doc comments (making false warnings from phpstan analysis for example). I don't remember which field is indicated as a object type, but is in fact a string.

fredericdelordm commented 3 years ago

Hello @Asenar,

About the field Requested3DSVersion, it's mentioned this is valid only in «sandbox». Does that mean this option can be safely defined in production and will have no impact ?

I confirm, no impact on production.

jeromebsr commented 1 year ago

Hey after few try this afternoon, I finally managed to integrate the 3DS2 on PayIn direct !

For the ipAddress I just retrive it with a function on serverside :

public function getIpAddress(){
//whether ip is from the share internet  
    if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    }
    //whether ip is from the proxy  
    elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    }
    //whether ip is from the remote address  
    else {
        $ip = $_SERVER['REMOTE_ADDR'];
    }
    return $ip;
}

For the BrowserInfo, I'm not sure if it's a good/best option but I did a JS script, on my paiement page, who send needed data to a PHP function who save these data in $_SESSION (I use getallheaders() in PHP for User-Agent and Accept Header).

Js script :

$.ajax({
    type: "POST",
    url: "/sendBrowserInfo",
    data: {
        JavaEnabled: navigator.javaEnabled(),
        Language: navigator.language || navigator.userLanguage,
        ColorDepth: screen.colorDepth,
        ScreenHeight: screen.height,
        ScreenWidth: screen.width,
        TimeZoneOffset: new Date().getTimezoneOffset().toString()
    },
});

Php who save in session :

public function sendBrowserInfo(){
  if(!empty($_POST)){
      foreach($_POST as $key => $value){
          $_SESSION[$key] = $value;
      }
  }
}

Final function who get all BrowserInfo :

public function getBrowserInfo(){
    $headers = getallheaders();
    $result = new BrowserInfo();
    $result->AcceptHeader = $headers['Accept'];
    $result->JavaEnabled = $_SESSION['JavaEnabled'];
    $result->Language = $_SESSION['Language'];
    $result->ColorDepth = $_SESSION['ColorDepth'];
    $result->ScreenHeight = $_SESSION['ScreenHeight'];
    $result->ScreenWidth = $_SESSION['ScreenWidth'];
    $result->TimeZoneOffset = $_SESSION['TimeZoneOffset'];
    $result->UserAgent = getallheaders()['User-Agent'];
    $result->JavascriptEnabled = true;
    return $result;
}

So for PayIn Direct it's work perfectly.

But I have a problem with PreAuth... Indeed, when I make a PreAuth with 3DS2 frictionless (card 4970105191923460) flow the PreAuth response is succeed but $PreAuth->SecureModeNeededis true and $PreAuth->SecureModeRedirectURL is NULLso my function fail because I can't redirect to 3DS page ( I don't know if it's a sandbox problem or a inplementation problem). There is no problem with the challenge flow.

Here is my code :

$CardPreAuthorization = new CardPreAuthorization();
$CardPreAuthorization->AuthorId = $authorId;
$CardPreAuthorization->DebitedFunds = new Money();
$CardPreAuthorization->DebitedFunds->Currency = "EUR";
$CardPreAuthorization->DebitedFunds->Amount = $amount * 100;
$CardPreAuthorization->CardId = $resultCardRegistration->CardId;
$CardPreAuthorization->SecureModeReturnURL = getenv('BASE_URL')."/confirm.php";
$CardPreAuthorization->Culture = "FR";
$CardPreAuthorization->Requested3DSVersion = "V2_1";
$CardPreAuthorization->SecureMode = "NO_CHOICE";
$CardPreAuthorization->IpAddress = $author->getIpAddress();
$CardPreAuthorization->BrowserInfo = $author->getBrowserInfo();
$ResultPreAuth = $this->apiMangopay->CardPreAuthorizations->Create($CardPreAuthorization);

Here the result :

object(MangoPay\CardPreAuthorization)#312 (29) {
  ["AuthorId"]=>
  string(9) "XXXXXX"
  ["DebitedFunds"]=>
  object(MangoPay\Money)#344 (2) {
    ["Currency"]=>
    string(3) "EUR"
    ["Amount"]=>
    int(1000)
  }
  ["Status"]=>
  string(9) "SUCCEEDED"
  ["PaymentStatus"]=>
  string(7) "WAITING"
  ["ResultCode"]=>
  string(6) "000000"
  ["ResultMessage"]=>
  string(7) "Success"
  ["StatementDescriptor"]=>
  NULL
  ["ExecutionType"]=>
  string(6) "DIRECT"
  ["SecureMode"]=>
  string(9) "NO_CHOICE"
  ["CardId"]=>
  string(9) "XXXXXX"
  ["SecureModeNeeded"]=>
  bool(true)
  ["SecureModeRedirectURL"]=>
  NULL
  ["SecureModeReturnURL"]=>
  string(76) "http://localhost:8888/confirm.php?preAuthorizationId=XXXX"
  ["ExpirationDate"]=>
  int(1622173491)
  ["AuthorizationDate"]=>
  int(1621611891)
  ["PaymentType"]=>
  string(4) "CARD"
  ["PayInId"]=>
  NULL
  ["SecurityInfo"]=>
  object(MangoPay\SecurityInfo)#351 (1) {
    ["AVSResult"]=>
    string(8) "NO_CHECK"
  }
  ["MultiCapture"]=>
  bool(false)
  ["RemainingFunds"]=>
  object(MangoPay\Money)#343 (2) {
    ["Currency"]=>
    string(3) "EUR"
    ["Amount"]=>
    int(1000)
  }
  ["IpAddress"]=>
  string(3) "::1"
  ["BrowserInfo"]=>
  object(stdClass)#294 (9) {
    ["AcceptHeader"]=>
    string(135) "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
    ["JavaEnabled"]=>
    bool(false)
    ["Language"]=>
    string(5) "fr-FR"
    ["ColorDepth"]=>
    int(24)
    ["ScreenHeight"]=>
    int(900)
    ["ScreenWidth"]=>
    int(1440)
    ["TimeZoneOffset"]=>
    int(-120)
    ["UserAgent"]=>
    string(121) "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"
    ["JavascriptEnabled"]=>
    bool(true)
  }
  ["Requested3DSVersion"]=>
  string(4) "V2_1"
  ["Applied3DSVersion"]=>
  string(4) "V2_1"
  ["Id"]=>
  string(9) "XXXX"
  ["CreationDate"]=>
  int(1621611889)
}

And the last problem I face, once again I'm not sure if it's normal or not but now with the 3DS2 active all payement even under 30€ need a card with 3DS activated. If I try with the card listed for payement under 50€, it's always fail. This will be the same on production ? Because I found this very sad :(

I hope I can help someone and I also hope someone can help me 🤣

PS : sorry for the english fault, isn't my mother tongue and I try too reduce my use of Google translate to improve myself 😉

Thank you so much !