Mangopay / mangopay2-php-sdk

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

Can't get RegistrationData to update the CardRegistration with #571

Closed thomas-c-p-fischer closed 1 year ago

thomas-c-p-fischer commented 1 year ago

Hello MangoPay team,

As it is said in the title we can't get the RegistrationData, we work on symfony, we've created a service in which we create our funcion to call on the api, in our controller. Until we tried to create a cardRegistration we had no issue, we've successfully created our natural user, wallet, bank account and kycDocument but now we've encountered a dead end.

So when we use our function to create the cardRegistration it works, it is what comes next that doesn't work more precisely the RegistrationData, we can't seem to GET it.

This is our function, in the service, this one works:

public function createCardRegistration(User $user)

{
    $userId = $user->getIdMangopay();
    $cardRegistration = new CardRegistration();
    $cardRegistration->UserId = $userId;
    $cardRegistration->Currency = "EUR";
    $cardRegistration->CardType = "CB_VISA_MASTERCARD";
    return $this->mangoPayApi->CardRegistrations->Create($cardRegistration);
}

In our form we have set the method to POST and the action to the cardRegistrationURL, but I have a feeling that's it's not really sending anything since we can't get the registrationData and we can't see anything on the mangopay dashboard.

What we would like is to understand how to get the cardRegistrationData so that we can get on with what comes next, cardRegistrationupdate and directPayin.

Really need your help here, thank you in advance and have a nice evening.

H4wKs commented 1 year ago

Hi @thomas-c-p-fischer ,

First, be aware I am not a staff member of MangoPay, just a user and a contributor to the MangoPay PHP SDK.

When you create the form, you set a returnURL. Once your form is posted, MangoPay redirect you to that URL and the the cardRegistrationData is added as a get parameter called data What does your form look like ?

thomas-c-p-fischer commented 1 year ago

Hi @H4wKs,

Thank you for your time ^^.

so here is the template of the form : codeForm

here the formType : codeFormType

here is our controller to handle the form and create the cardRegistration : codeFormController

We didn't use the return URL right away, we use it now, we don't have an error message but there's no redirection either to that URL.

thomas-c-p-fischer commented 1 year ago

In this example : https://github.com/Mangopay/mangopay2-php-sdk/blob/master/demos/paymentDirect/non_js.php at line 41 there is payment.php but the thing is we don't know if we have to replace it with a controller or simple php page, sorry we're not very experienced developpers we're actually on internship (the whole team) so we're not very sure what would be the same kind of page in symfony as this payment.php page.

H4wKs commented 1 year ago

From what I see, the problem is on your controller that handle the form. You initiate only the variable if the form is posted, but you need these value in the form to be able to submit it.

Also, you will never enter the isSubmitted, because the form is posted to MangoPay, not to your controller. Remove the "if" part, and you should be good for the form submission.

As you are not going to handle this form, I would skip the Symfony Form and FormType, and just add raw html form here.

note: it's better to share code using markdown, rather than post screenshot exemple:

echo "Hello World!"; 

sorry we're not very experienced developpers we're actually on internship (the whole team) so we're not very sure what would be the same kind of page in symfony as this payment.php page.

Don't be sorry, we were all beginners once ;)

M-Adrien74 commented 1 year ago

Hi @H4wKs I am a @thomas-c-p-fischer collobator we work on the back and CardRegistration together.

We have 2 controllers, one that manages the form part and the other one the returnURL. And the service where we do our methods. here the controller who manage the form

$returnUrl = 'http' . (isset($_SERVER['HTTPS']) ? 's' : '') . '://' . $_SERVER['HTTP_HOST'];
        $returnUrl .= substr($_SERVER['REQUEST_URI'], 0, strripos($_SERVER['REQUEST_URI'], '/') + 1);
        $returnUrl .= 'PaiementController.php';
  $cardRegistration = $service->createCardRegistration($userConnect);
        $accessKey = $cardRegistration->AccessKey;
        $preregistrationData = $cardRegistration->PreregistrationData;
        $this->redirectToRoute('paiement_updateRegistrationCard');
        return $this->renderForm("annonce/annoncePaiement.html.twig",
            compact(
                'paiementForm',
                'annonce',
                'accessKey',
                'preregistrationData',
                'returnUrl'
            ));

The one who manages the returnURL

public function updateRegistrationCard(
        MangoPayService $service
    ): Response
    {
        session_start();
        // update register card with registration data from Payline service
        $cardRegister = $this->mangoPayApi->CardRegistrations->Get($_SESSION['cardRegisterId']);
        $cardRegister->RegistrationData = isset($_GET['data']) ? 'data=' . $_GET['data'] :
            $updatedCard = $service->updateCardRegistration($cardRegister);

        return $this->render('annonce/annoncePaiement.html.twig');
    }

And the service

public function createCardRegistration(User $user): CardRegistration
    {
        try {
            $userId = $user->getIdMangopay();
            $cardRegistration = new CardRegistration();
            $cardRegistration->UserId = $userId;
            $cardRegistration->Currency = "EUR";
            $cardRegistration->CardType = "CB_VISA_MASTERCARD";
            $createdCardRegister = $this->mangoPayApi->CardRegistrations->Create($cardRegistration);
            $_SESSION['cardRegisterId'] = $createdCardRegister->Id;
        } catch (Exception $e) {
            $result = null;
            dump($e);
        }
        return $createdCardRegister;
    }

    public function updateCardRegistration($cardRegistration)
    {
        try {
            $cardInfo = $this->mangoPayApi->CardRegistrations->Update($cardRegistration);
        } catch (Exception $e) {
            $cardInfo = null;
            dump($e);
        }
        return $cardInfo;
    }

Now the problem is that when we submit the form, either we get a bad request or nothing leaves. But when I do a dd($cardRegistration) , I have an object of my new card. The problem is really the sending on this tokenization server.

H4wKs commented 1 year ago

Your first controller never render the form because you put

$this->redirectToRoute('paiement_updateRegistrationCard')

before

return $this->renderForm("annonce/annoncePaiement.html.twig",
            compact(
                'paiementForm',
                'annonce',
                'accessKey',
                'preregistrationData',
                'returnUrl'
            ));

You do not want to do that.

Here is what I would do if I were in your situation.

I would get ride of that Symfony Form / FormType because it's useless as you are not going to process that form in the app controller. Go from the MangoPay example:

<form action="<?php print $createdCardRegister->CardRegistrationURL; ?>" method="post">
    <input type="hidden" name="data" value="<?php print $createdCardRegister->PreregistrationData; ?>" />
    <input type="hidden" name="accessKeyRef" value="<?php print $createdCardRegister->AccessKey; ?>" />
    <input type="hidden" name="returnURL" value="<?php print $returnUrl; ?>" />

    <label for="cardNumber">Card Number</label>
    <input type="text" name="cardNumber" value="" />
    <div class="clear"></div>

    <label for="cardExpirationDate">Expiration Date</label>
    <input type="text" name="cardExpirationDate" value="" />
    <div class="clear"></div>

    <label for="cardCvx">CVV</label>
    <input type="text" name="cardCvx" value="" />
    <div class="clear"></div>

    <input type="submit" value="Pay" />
</form>

Replace all the php in the form by the injected twig values. Should look like this according to your previous post:

<form action="{{ cardRegistrationUrl }}" method="post">
    <input type="hidden" name="data" value="{{ preregistrationData }}" />
    <input type="hidden" name="accessKeyRef" value="{{ accessKey }}" />
    <input type="hidden" name="returnURL" value="{{ returnUrl }}" />

    <label for="cardNumber">Card Number</label>
    <input type="text" name="cardNumber" value="" />
    <div class="clear"></div>

    <label for="cardExpirationDate">Expiration Date</label>
    <input type="text" name="cardExpirationDate" value="" />
    <div class="clear"></div>

    <label for="cardCvx">CVV</label>
    <input type="text" name="cardCvx" value="" />
    <div class="clear"></div>

    <input type="submit" value="Pay" />
</form>

In your last post, you forgot to pass the cardRegistrationUrl to the form unless you did that somewhere else.

Use inspection tools of your browser before submitting your form to check that all values are good.

To make it easier to debug, don't process the payment in your callback controller, just print the data you receive as debug. Once you have the right data, and the callback URL is working, start trying to process the payment in the callback controller et add debug to see what the problem is.

You can also keep all the code in the controller and once it work, you can refactor the code and put it inside a service.

Cheers,

Marc

thomas-c-p-fischer commented 1 year ago

Hi @H4wKs and thanks again for your insight, so we did as you advised us, but I think there's an issue with how we handle this whole returnURL, so for the form we did as you said, we abandonned the form type of symfony to do it in html like this :

<form action="{{ cardRegistrationUrl }}" method="post">
        <input type="hidden" name="data" value="{{ preregistrationData }}" />
        <input type="hidden" name="accessKeyRef" value="{{ accessKey }}" />
        <input type="hidden" name="returnURL" value="{{ returnUrl }}" />

        <label for="cardNumber">Card Number</label>
        <input type="text" name="cardNumber" value="" />
        <div class="clear"></div>

        <label for="cardExpirationDate">Expiration Date</label>
        <input type="text" name="cardExpirationDate" value="" />
        <div class="clear"></div>

        <label for="cardCvx">CVV</label>
        <input type="text" name="cardCvx" value="" />
        <div class="clear"></div>

        <input type="submit" value="Pay" />
    </form>

When inspecting the browser the values are what they're suppose to be so this part is good I think. The problem I think is the callback controller because I tried to dump the data in the callback controller but saw nothing so, the only thing different was the url after i press the Pay button : https://127.0.0.1:8000/annonce/paiement/PaiementController.php?data=ZgGHZaUYonymV8qEHPuD2jr6i1sXF4ytGVUNvXyYl1qunosBCHha-FT5un5zsoK5AtuHdx9OBLvMOAh9Pi_HzG7_1MFXbFlzgLA2SUeqbd3MHLIUEQAO1crcKxpgUsi6_uh-M22NjZ6dU5YsJBBYuA so as you can see there's the registration data in the url but then i checked on the MangoPay dashboard there wasn't anything in the cards section of my user

this what we do in our controller :

$returnUrl = 'http' . ( isset($_SERVER['HTTPS']) ? 's' : '' ) . '://' . $_SERVER['HTTP_HOST'];
        $returnUrl .= substr($_SERVER['REQUEST_URI'], 0, strripos($_SERVER['REQUEST_URI'], '/') + 1);
        $returnUrl .= 'PaiementController.php';
$mail = $this->getUser()->getUserIdentifier();
        $userConnect = $userRepository->findOneBy(['email' => $mail]);
        $cardRegistration = $service->createCardRegistration($userConnect);
        $_SESSION['cardRegisterId'] = $cardRegistration->Id;
        $cardRegistrationUrl = $cardRegistration->CardRegistrationURL;
        $accessKey = $cardRegistration->AccessKey;
        $preregistrationData = $cardRegistration->PreregistrationData;

        return $this->render("annonce/annoncePaiement.html.twig",
            compact(
                'annonce',
                'accessKey',
                'preregistrationData',
                'cardRegistrationUrl',
                'returnUrl'
            ));

And then well I think we don't reach it since the cardRegistration update isn't reached, but this is our callback controller

    #[NoReturn] #[Route('/', name: '_updateRegistrationCard')]
    public function updateRegistrationCard(
        MangoPayService $service
    ): Void
    {
        session_start();
        // update register card with registration data from Payline service
        $cardRegister = $this->mangoPayApi->CardRegistrations->Get($_SESSION['cardRegisterId']);
        $cardRegister->RegistrationData = isset($_GET['data']) ? 'data=' . $_GET['data'] : 'errorCode=' . $_GET['errorCode'];
        $updatedCard = $service->updateCardRegistration($cardRegister);
   } 
M-Adrien74 commented 1 year ago

UPDATE Now we can get the registration data but something is wrong...

 +AccessKey: "1X0m87dmM2LiwFgxPLBJ"
  +PreregistrationData: "pMUiqKEKexdo_NolxfBzif-OZx0-ERBwCyAY7uUZeMK4LG_fBTvm17pMO_Ot7CXB2ddFLVXdicolcUIkv_kKEA"
  +CardRegistrationURL: "https://homologation-webpayment.payline.com/webpayment/getToken"
  +CardId: null
  +RegistrationData: "data=PmP_YCFK80KwhMM2xWr58dy2aorO_YNxQUNOK2xa3o-52q_k_5r9Mv3soJBRBDWeJ71ZQZn99VsWw8Juz9che2xhe0gMjsZpZIVB9TtT-3a_Q6-b_Gmv2miGA_aB_xWgNx0xgKTVnyDj15oG8jR88g"
  +ResultCode: "105299"
  +ResultMessage: "Token input Error"
  +Currency: "EUR"
  +Status: "ERROR"

this happens when i dd() my updateCardRegister.

   if (isset($_GET['data']) && $cardRegistration->Status != MangoPay\CardRegistrationStatus::Validated) {
            $cardRegistration->RegistrationData = 'data=' . $_GET['data'];
            $updatedCardRegister = $service->updateCardRegistration($cardRegistration);
            dd($updatedCardRegister);
        }

If you have any advice for us, we don't understand why we are stuck at this stage, but we know we are close to the solution. We've tried so many things, we're a bit lost.. thank's a lot.

H4wKs commented 1 year ago

Hi @M-Adrien74 @thomas-c-p-fischer, Sorry, long day yesterday, I didn't get a chance to answer you.

If you look at the MangoPay example you can see this :

if ($updatedCardRegister->Status != \MangoPay\CardRegistrationStatus::Validated || !isset($updatedCardRegister->CardId))
        die('<div style="color:red;">Cannot create card. Payment has not been `created.<div>');

This mean you do not want to process the card if $updatedCardRegister->Status != \MangoPay\CardRegistrationStatus::Validated and in your case you do the opposite

if (isset($_GET['data']) && $cardRegistration->Status != MangoPay\CardRegistrationStatus::Validated) {
            $cardRegistration->RegistrationData = 'data=' . $_GET['data'];
            $updatedCardRegister = $service->updateCardRegistration($cardRegistration);
            dd($updatedCardRegister);
        }

From there you just need to debug what is wrong.

I suggest you to use dump instead of dd, that way you can track all the values all the way from the beginning to the end, just add dump everywhere, and see where it's going wrong ... That's basically what our daily job is about :)

If that's easier for you, start using the demo version code, put all of it inside the controller .... and once this is working, start refactoring and dispatch code in services and call the services

M-Adrien74 commented 1 year ago

No worries for the answer! The detail is that we are trainees with no confirmed developer or tutor, only 4 trainees...

I tried with random dump, to insert the code of the demo in my controller, to make my own code, and always the same error. the data is well retrieved

RegistrationData: 
"data=PcO_MBWPYJ2y86GaHkcSp1eZX48o3v4iu4u05kjrfOV5cNwuBTZN4lnOjedC42XDa9floIuTZJGL1XTIrrrZ1cj5hqECz9A6ayzfaRjzVZvtF6IFDOCJ3GcaUvx3jrVb_uh-M22NjZ6dU5YsJBBYuA 
resultCode: 105299 
ResultMessage: "Token input Error"

Also, it can't find the key errorCode #message: "Warning: Undefined array key "errorCode""

I don't know what I can do wrong

H4wKs commented 1 year ago

No worries for the answer! The detail is that we are trainees with no confirmed developer or tutor, only 4 trainees...

We have all been a trainee at some point and believe me, it doesn't matter if you are a 5 years experienced developper or a trainee, because you will always face a problem you don't know the answer to, or not understand.

Have you tested to use just the demo version by itself and see if you make it work like this ? I mean the raw demo code, without using it in Symfony ?

M-Adrien74 commented 1 year ago

No, I didn't test without using Symfony, because honestly, I don't know how and where to place these files. I just use the code and adapt it to symfony in my controller.

H4wKs commented 1 year ago

I don't know how and where to place these files

Just pull the file, do a composer install and then set the root directory to the demos folder, and then access the url hitting paymentDirect/non_js.php

Have you made any progress ?

M-Adrien74 commented 1 year ago

Hello, thank's for your time. So i continu with Symfony and it's work. The process is to have 2 controllers , one to get the preregistrationData, the accessKey , set a returnUrl and one for the callBack who update the card registration et redirectTo something you think right. If you want to see our method with symfony or have more explains we can post some screenshot. Now we are trying to do some payIn. thanks again for your time. Regards, Adrien.

H4wKs commented 1 year ago

Happy that you finally find out what was going wrong :) Have a nice week,

Marc.

thomas-c-p-fischer commented 1 year ago

Thanks @H4wKs . Here is the solution we found :

in our callback controller

    #[Route('/paiement', name: '_updateRegistrationCard')]
    public function updateRegistrationCard(
        MangoPayService $service,
        UserRepository  $userRepository,
        Session         $session,
    ): Response
    {
        session_start();
        $mangoPayApi = new MangoPay\MangoPayApi();
        $mangoPayApi->Config->ClientId = $_ENV['CLIENT_ID'];
        $mangoPayApi->Config->ClientPassword = $_ENV['API_KEY'];
        $mangoPayApi->Config->BaseUrl = 'https://api.sandbox.mangopay.com';
        $mangoPayApi->Config->TemporaryFolder = $_ENV['TMP_PATH'];
        $cardRegister = $mangoPayApi->CardRegistrations->Get($_SESSION['idCard']);
        $cardRegister->RegistrationData = isset($_GET['data']) ? 'data=' . $_GET['data'] : 'errorCode=' . $_GET['errorCode'];
        $service->updateCardRegistration($cardRegister);

        return $this->redirectToRoute('annonce_ajouter');

our main controller

 #[Route('/paiement/{id}', name: '_paiement')]
    public function paiement(
        Request               $request,
        MangoPayService       $service,
        UserRepository        $userRepository,
        AnnonceRepository     $annonceRepository,
        HttpClientInterface   $client,
                              $id,
        UrlGeneratorInterface $generator,

    ): Response
    {
        $returnURL = $this->generateUrl('paiement_updateRegistrationCard', [], UrlGeneratorInterface::ABSOLUTE_URL);
        $annonce = $annonceRepository->findOneBy(["id" => $id]);
        $mail = $this->getUser()->getUserIdentifier();
        $userConnect = $userRepository->findOneBy(['email' => $mail]);
        $cardRegistration = $service->createCardRegistration($userConnect);
        $accessKey = $cardRegistration->AccessKey;
        $preregistrationData = $cardRegistration->PreregistrationData;
        $cardRegistrationUrl = $cardRegistration->CardRegistrationURL;

        return $this->render("annonce/annoncePaiement.html.twig",
            compact(
                'annonce',
                'accessKey',
                'preregistrationData',
                'returnURL',
                'cardRegistrationUrl'
            ));
    }

our form

<form action="{{ cardRegistrationUrl }}" method="post">
        <input type="hidden" name="data" value="{{ preregistrationData }}"/>
        <input type="hidden" name="accessKeyRef" value="{{ accessKey }}"/>
        <input type="hidden" name="returnURL" value="{{ returnURL }}"/>

        <label for="cardNumber">Numéro de carte</label>
        <input type="text" name="cardNumber" value=""/>
        <div class="clear"></div>

        <label for="cardExpirationDate">Date d'expiration</label>
        <input type="text" name="cardExpirationDate" value=""/>
        <div class="clear"></div>

        <label for="cardCvx">Cryptogramme</label>
        <input type="text" name="cardCvx" value=""/>
        <div class="clear"></div>

        <input type="submit" value="Pay"/>
    </form>

and our service

public function createCardRegistration(User $user)
    {
        try {
            $userId = $user->getIdMangopay();
            $cardRegistration = new CardRegistration();
            $cardRegistration->UserId = $userId;
            $cardRegistration->Currency = 'EUR';
            $cardRegistration->CardType = 'CB_VISA_MASTERCARD';
            $createdCardRegister = $this->mangoPayApi->CardRegistrations->Create($cardRegistration);
            $_SESSION['idCard'] = $createdCardRegister->Id;
        } catch (Exception $e) {
            $createdCardRegister = null;
            dump($e);
        }
        return $createdCardRegister;
    }

    public function updateCardRegistration($cardRegistration)
    {
        try {
            $cardInfo = $this->mangoPayApi->CardRegistrations->Update($cardRegistration);
        } catch (Exception $e) {
            $cardInfo = null;
            dump($e);
        }
        return $cardInfo;
    }