5

Option 4.2 seems like the best direction for me. Does anyone have any other suggestions?

Is there a way to access response in any of the below scenarios or I need to rewrite whole logic?


I need to perform a form POST to a 3rd party payment provider with Angular Typescript with or without NodeJS/ExpressJS with the redirect.

Flow:

enter image description here

The problem is that in some cases when I perform URL redirect successfully I don't receive any response from payment gateway. When a user clicks "Pay" - "Plati" he is redirected to the success page http://example.com/success and in case of error response to page http://example.com/cancel.

enter image description here

The expected scenario

The user comes to the website selects the products and clicks on the buy button. At that point, s/he is taken to another page where s/he makes the payment. After successful payment, the user is redirected back to the website and I get a response from the server and show the user a related message.

Option 1 - Form Action URL

If I do standard form submit and put payment gateway URL inside [action]="'https://test-wallet.example.com/checkout/'" then the user will be redirected directly to that URL and payment will be processed successfully. But in that case I don't receive a response that is needed for me to know what data to show to the user - success or error message.

<form [action]="'https://test-wallet.example.com/checkout/'" ngNoForm method="POST" target="_blank">
      <button type="submit">Pay with card</button>
      <input name='param1' value='param1'>
      <input name='param2' value='param2'>
      <input name='param3' value='param3'>
      <input name='param4' value='param4'>
      <input name='param5' value='param5'>
      <input name='param6' value='param6'>
      <input name='param7' value='param7'>
      <input name='param8' value='param8'>
      <input name='param9' value='param9'>
</form>

Option 2 - HttpClient through service

I've also tried making HttpClient POST request inside the Angular app and without NodeJS backend. In that case, I call the Payment Gateway URL directly but with CORS error.

payment.service.ts:

payFunction(parameters: any){
   return this._httpClient.post('https://test-wallet.example.com/checkout/'+ 
      'param1='+parameters.param1+ 
      '&param2='+parameters.param2+
      '&param3='+parameters.param3+ 
      '&param4='+parameters.param4+ 
      '&param5='+parameters.param5+
      '&param6='+parameters.param6+ 
      '&param7='+parameters.param7+
      '&param8='+parameters.param8+
      '&param9='+parameters.param9
      ,parameters
      ,this.httpOptions 
    )
   .catch(err => {
      console.log(err);
      return Observable.of(err)
   })
}

I call the previous service in component:

async test(form){
  await this._myPaymentService.payFunction(form.value).subscribe(res => {
        console.log(res);
})

In that case I received only CORS error.

enter image description here

Option 3 - jQuery AJAX

I'm calling this inside my Angular component with cross-domain contentType.

But I also received only CORS error as in the case above. I know that using jQuery in the Angular app is not by the book but I had to try.

 $.ajax({
   headers: { 
     'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
   },
   url : 'https://test-wallet.example.com/checkout/',
   type: "POST",
   beforeSend: function(xhrObj){
       xhrObj.setRequestHeader('Content-Type':  'application/x-www-form-urlencoded; charset=UTF-8');
   },
   dataType : "json",
   async:true,
   crossDomain:true,
   data: corvusDataObject,
   error: function () {
     alert('Ajax Error');
   },
   onFailure: function () {
     alert('Ajax Failure');
   },
   statusCode: {
     404: function() {
       alert("Ajax 404");
     }   
   },
   success : function (response) {
     alert("Success: " + JSON.stringify(response));
     }
   })
   .done(function( data ) {
   alert("Done: " + JSON.stringify(response));
});

Option 4 - NodeJS/ExpressJS backend

If I use this approach then I received a redirect in the same way as in the first case. But my backend doesn't receive any response from the payment gateway provider.

In Angular app I'm calling my API:

<form [action]="'http://localhost:8080/myPaymentAPI/'" ngNoForm method="POST" target="_blank">
      <button type="submit">Pay with card</button>
      <input name='param1' value='param1'>
      <input name='param2' value='param2'>
      <input name='param3' value='param3'>
      <input name='param4' value='param4'>
      <input name='param5' value='param5'>
      <input name='param6' value='param6'>
      <input name='param7' value='param7'>
      <input name='param8' value='param8'>
      <input name='param9' value='param9'>
</form>

In NodeJS/ExpressJS I've made myPaymentAPI API with 307 redirects (from this SO answer).

    var express = require('express');
    var app = express();
    var cors = require('cors')  // CORS
    var bodyParser = require('body-parser'); 

    app.use(bodyParser.urlencoded({ extended: true }));
    app.use(bodyParser.json());
    app.use(cors());

    var port = process.env.PORT || 8080;
    var apiRoutes = express.Router();

    apiRoutes.get('/', function(req, res) {
        res.json({ message: 'API works!' });
    });

    app.use('/api', apiRoutes);

    app.post('/myPaymentAPI', function(req, res, next) {

      let param1 = req.body.param1;
      let param2 = req.body.param2;
      let param3 = req.body.param3;
      let param4 = req.body.param4;
      let param5 = req.body.param5;
      let param6 = req.body.param6;
      let param7 = req.body.param7;
      let param8 = req.body.param8;
      let param9 = req.body.param9;

    res.status(200).redirect(307, 'https://test-wallet.example.com/checkout/?param1='+param1 +'&param2='+param2+...)
    //res.end();

    });

Above redirection transfers the user to URL (see the first image): https://test-wallet.example.com/#/checkout/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx and user on that URL make a payment but I, once again, don't receive any response.

Option 4.1

fetch returns HTML page but with blank <body>

app.post('/myPaymentAPI', function(req, res, next) {

    const url = 'https://test-wallet.example.com/checkout/?param1='+param1+'&param2='+param2+'&param3='+param3+'&param4='+param4+'&param5='+param5+'&param6='+param6+'&param7='+param7+'&param8='+param8+'&param9='+param9;
       fetch(url, {
           method : "POST",
           body: res.body
       }).then(
           response => response.text()
       ).then(
         html => res.send(html)
      ).catch((err) => {
         reject(err);
       });

});

Option 4.2

In this approach, I successfully get a short version of the URL (see the first image) and then I redirect the user to that URL.

    app.post('/myPaymentAPI', function(req, res, next) {

      let param1 = req.body.param1;
      let param2 = req.body.param2;
      let param3 = req.body.param3;
      ...

      try{
        var body = JSON.stringify(req.body);
        const url = 'https://test-wallet.example.com/checkout/?param1='+param1+'&param2='+param2+...;
        var newData = await fetch(url, {method: "POST", body: body})
        console.log(newData.url)
        res.redirect(307, newData.url);
      }catch(error){
        console.log(error)
      }

});

This page is opened after 307 redirects. The message says "Your request cannot be processed. We are sorry, an error occurred."

Do I need in this step once again append FormData before making that redirect?

enter image description here

Option 4.3

In this approach, I'm making a call to my API and create an object inside res.send which then I send to my frontend.

try{
     var body = JSON.stringify(req.body);
     const url = 'https://test-wallet.example.com/checkout/?param1='+param1+'&param2='+param2+'&param3='+param3+...;
       await fetch(url, {method: "POST", body: body}).then((response) => {
         const data = response;
         res.send({
           success: true,
           redirectURL: data.url,
           body: req.body
         })
      })
       .catch((error) => {
         console.error(error);
       })
   }catch(error){
     console.log(error)
}

On frontend I successfully receive redirectURL and body data and try to make a redirect.

this._myPaymentService.payFunction(form.value).subscribe(res => {
            console.log(res);
            console.log(res.redirectURL);
            window.location.replace(res.redirectURL);
})

The web browser then goes to the following page with blank content.

enter image description here

Because request has become GET. I know that it's not possible to send a POST request this way and I'm looking for way to do it.

enter image description here

Tomislav Stankovic
  • 3,080
  • 17
  • 35
  • 42
  • How does the payment service send the error or success response? It is just a redirection to the callbak url with the parameters in the query string? – David Mar 27 '20 at 17:48
  • @David I don't receive any response in the callback URL and that's my biggest problem. The documentation states that the success URL should be like this `http://example.com/success?order_number=1233` but I receive only `http://example.com/success`. I am waiting for a response from their support regarding parameters in the callback URL. – Tomislav Stankovic Mar 28 '20 at 16:14
  • @David Allegedly, POST parameters are in the body of the message. The URL remains the same and I need to expect POST on my side, they said. – Tomislav Stankovic Mar 30 '20 at 18:31
  • You said in your question that the user was redirected to 'example.com/success' after a successful payment (example.com being the 3rd party payment gateway). So from that success page the user has to press another button that will perform a post request to go back to your website? – David Mar 30 '20 at 20:07
  • @David `example.com/success` is my website and user is redirected to that page after he makes successful payment on `test-wallet.example.com` which is the address of the Payment Gateway. – Tomislav Stankovic Mar 30 '20 at 20:40
  • How about making a an api call to your server and creating a socket between your client and server right after the call.Through this way, your client will listen the changes which may be a success or an error response from your server. – Serdar Sayın Mar 31 '20 at 20:38
  • @Sardar Thank you for your answer. I'll try that too. – Tomislav Stankovic Apr 01 '20 at 11:26

2 Answers2

2

Wow, sounds like you are very eager to write code but are really lacking some fundamentals. Do you want to have an SPA or have an old school form POST? Of course you get an CORS error when you try to send an direct API request.

I am quite worried about the outcome of this since you are actually dealing with payments and dont seem to know much about architecture - maybe i'm wrong. Did you hear about OWASP or CSRF? Did you think about storing transactions just in case something bad happens? Did you protect against users sending bad requests with i.e. negative numbers? What about

Give yourself and the pockets of your users some comfort and read up first before writing code, go through at least some examples, i.e. Angular Tour of heroes.

Here is the basic flow of how it should look like.

The backend is the translator here. It provides an API, transforms data that the user sent (after validation) into a request that the payment provider needs. After getting the result it will transform the answer into a defined response to the Angular app - something which will be a success or error message. Then the Angular app can decide what to do: Show a ok or error message to the user.

And! You always get a message from the payment provider, if really not then you should implement a timeout and react with an error message to the user.

Good luck, i really pray that you learn about and implement some security measures.

enter image description here

hogan
  • 1,434
  • 1
  • 15
  • 32
  • 2
    Thank you for your detailed answer. I've implemented some of the steps from the above image and the solution with my backend as a translator has been most logical to me. I've published all this code to avoid answers telling me to try something I've already tried. Thanks once again. – Tomislav Stankovic Apr 02 '20 at 11:11
1

These 2 approach are seems correct:

  • Option 1
  • Option 4 (with nodejs server - before 4.1 where payment is successful)

However, there is a flow which seems missing. After the payment is made, the Payment API server does a post request to http://example.com/success or http://example.com/cancel and in the body you find the parameters. So, you can't directly use the url to show user the information on the screen (client side browser).


What you need to do is:

  • Have the node server (or your backend API server will also work), and use app.post handle the url at the server - the way you are doing for app.post('/myPaymentAPI',).
  • Update your database or get the relevant payment details or id from req.body etc.
  • Make a new url like https://yourwebsite.com/payment?status=SUCCESS&other-info or https://yourwebsite.com/payment/id
  • Redirect user to particular url on browser
  • That particular url will have the details or id. You can show the relevant details or get the id and make the API call as needed
app.post("http://example.com/success", function(req, res){
  //get the req.body/params here which Payment Server will post to success url
  //update your backend etc about payment status etc
  //redirect to your custom page from here https://yourwebsite.com/payment?status=success&id=id or similar
})

app.post("http://example.com/cancel", function(req, res){
  //get the req.body/params here which Payment Server will post to cancel url
  //update your backend etc about payment status etc
  //redirect to your custom page from here https://yourwebsite.com/payment?status=failure&id=id
})

Hope it helps. Revert for any doubts/clarifications

Sunil Chaudhary
  • 4,481
  • 3
  • 22
  • 41
  • Thank you for your answer. The problem is that I'm not sure in which step I need to read that `body` data. After the payment (the user has 15 minutes to complete the payment) is made I'm redirected to `http://example.com/success`. Do I need, in that Angular component, make a request to my API with the same address `app.post("http://example.com/success",...` and read `body` data? – Tomislav Stankovic Mar 31 '20 at 16:40
  • Currently, I'm testing a solution in which before payment is made I save all of the user data to my database (this is registration) and create a unique ID in localStorage and then waiting for `success` or `cancel` page to be activated on frontend Angular app. On `success` page I call my second API and update the user and on the `cancel` page I call my third API and delete that user so he can repeat payment. Comparison in the database I'm doing with a parameter from localStorage. Unless either of these two APIs is called I delete the user with that unique ID from the database after 15 minutes. – Tomislav Stankovic Mar 31 '20 at 16:50
  • 1
    After payment is made and user is redirected to `http://example.com/success`, that place you need to do the above thing. The redirection is generally a post request to `http://example.com/success` which needs to be intercepted in backend node server of yours. Then you have to redirect to new url for user on which he can see the details. You can't directly load `http://example.com/success` on screen. `http://example.com/success` is a url to which the payment server does a post call with details after payment is made. So, you need to handle that in server. – Sunil Chaudhary Apr 01 '20 at 10:52
  • 1
    As an example, try the following thing (Option 4 approach - before 4.1): I guess your node server is running on `http://localhost:8080/`. add `app.post("/unique-success-url", function(req, res){...` code from above with logs and change `http://example.com/success` to `http://localhost:8080/unique-success-url` in your config for success callback and same for cancel also. Try doing the payment, you should be able to see the params and other details in your node server. I will see, if I can find some flowchart diagram which will give you better clarity. – Sunil Chaudhary Apr 01 '20 at 10:57
  • Thank you man! I didn't see the forest from the tree. The whole time I was using the wrong callback URL-s. Instead of `https://example.com/api/success` (firstly created on my backend) I was using `https://example.com/success` (URL from website) and for that reason I was unable to see any response. – Tomislav Stankovic Apr 02 '20 at 13:33
  • Great that your problem is solved. I'm glad I was able to help. – Sunil Chaudhary Apr 02 '20 at 19:10