284

I did some research on this topic, and there are some experts who have said that it is not possible, so I would like to ask for an alternative solution.

My situation:

Page A: [checkout.php] Customer fills in their billing details.

Page B: [process.php] Generate an invoice number and store customer details in database.

Page C: [thirdparty.com] Third Payment Gateway (ONLY ACCEPT POST DATA).

Customer fills in their details and sets up their cart in Page A, then POSTs to Page B. Inside process.php, store the POSTed data inside the database and generate an invoice number. After that, POST the customer data and invoice number to thirdparty.com payment gateway. The problem is doing POST in page B. cURL is able to POST the data to Page C, but the problem is the page didn't redirect to page C. The customer needs to fill in Credit Card details on Page C.

The third party payment gateway did give us the API sample, the sample is POST the invoice number together with customer detail. We don't want the system to generate an excess of unwanted invoice numbers.

Is there any solution for this? Our current solution is for the customer to fill detail in Page A, then in Page B we create another page showing all the customer details there, where the user can click a CONFIRM button to POST to Page C.

Our goal is for customers to only have to click once.

Hope my question is clear :)

Quelklef
  • 1,999
  • 2
  • 22
  • 37
Shiro
  • 7,344
  • 8
  • 46
  • 80
  • i don't think is duplicate for this, it is because my goal is inside a PHP page, pass the POST data and redirect it. cURL I don't think is possible to do it. Just seek for expert any alternative for it – Shiro Apr 07 '11 at 09:40
  • ʜᴛᴛᴘ 308 redirection code ? – user2284570 Feb 22 '19 at 11:05

13 Answers13

244

Generate a form on Page B with all the required data and action set to Page C and submit it with JavaScript on page load. Your data will be sent to Page C without much hassle to the user.

This is the only way to do it. A redirect is a 303 HTTP header that you can read up on http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html, but I'll quote some of it:

The response to the request can be found under a different URI and SHOULD be retrieved using a GET method on that resource. This method exists primarily to allow the output of a POST-activated script to redirect the user agent to a selected resource. The new URI is not a substitute reference for the originally requested resource. The 303 response MUST NOT be cached, but the response to the second (redirected) request might be cacheable.

The only way to achieve what you're doing is with a intermediate page that sends the user to Page C. Here's a small/simple snippet on how you can achieve that:

<form id="myForm" action="Page_C.php" method="post">
<?php
    foreach ($_POST as $a => $b) {
        echo '<input type="hidden" name="'.htmlentities($a).'" value="'.htmlentities($b).'">';
    }
?>
</form>
<script type="text/javascript">
    document.getElementById('myForm').submit();
</script>

You should also have a simple "confirm" form inside a noscript tag to make sure users without Javascript will be able to use your service.

emotality
  • 12,795
  • 4
  • 39
  • 60
Peeter
  • 9,282
  • 5
  • 36
  • 53
  • 2
    my goal is in Page A -> Page C, Page B is the controller (business logic) generate the invoice number. From system view is Page A-> Page B-> Page C, but from user view should be Page A->Page B, they should never release Page B exist. – Shiro Apr 07 '11 at 09:42
  • ok, thanks for your answer. It's help! We had change our logic by using javascript to auto submit it. – Shiro Apr 08 '11 at 00:59
  • 3
    @Peeter: smart suggestion +1. The only issue is that when user on Page C push back button is resubmitted to Page C. Moreover you forgot to encode the `$a` and `$b` by using `htmlentities/htmlspecialchars`, see http://stackoverflow.com/questions/6180072/php-forward-data-post/6180337#6180337 – Marco Demaio Oct 16 '11 at 14:44
  • 9
    question here is what if Javascript is disabled on the client? Page B will not redirect to page C then, will it? – Imran Omar Bukhsh Dec 09 '11 at 13:00
  • 1
    Updated my answer with a solution to have it work for users without Javascript and added htmlentities. – Peeter Dec 12 '12 at 08:35
  • 28
    `` inside the `
    `
    – nullability Mar 27 '13 at 17:49
  • 4
    the `language` attribute is deprecated, it should be `` – Alex W Aug 27 '13 at 21:17
  • 2
    I'm surprised no one else has said this, this answer is very bad practice and should be avoided. By having this form on page B - after the data has been saved in to the database you are making it very possible that someone could manipulate this data (even if hidden). So the price or order number, or anything else could be modified by the end user - something you really want to try and avoid. See any of the options below which use $SESSION - this is a much better way to do this. – tim.baker Mar 30 '17 at 09:48
  • with Page B should not be in the history. So you don't get in trouble if the user hit the back button. – Alexander Behling Jun 15 '20 at 08:13
43

$_SESSION is your friend if you don't want to mess with Javascript

Let's say you're trying to pass an email:

On page A:

// Start the session
session_start();

// Set session variables
$_SESSION["email"] = "awesome@email.com";

header('Location: page_b.php');

And on Page B:

// Start the session
session_start();

// Show me the session!  
echo "<pre>";
print_r($_SESSION);
echo "</pre>";

To destroy the session

unset($_SESSION['email']);
session_destroy();
Robert Sinclair
  • 4,550
  • 2
  • 44
  • 46
42
/**
  * Redirect with POST data.
  *
  * @param string $url URL.
  * @param array $post_data POST data. Example: ['foo' => 'var', 'id' => 123]
  * @param array $headers Optional. Extra headers to send.
  */
public function redirect_post($url, array $data, array $headers = null) {
  $params = [
    'http' => [
      'method' => 'POST',
      'content' => http_build_query($data)
    ]
  ];

  if (!is_null($headers)) {
    $params['http']['header'] = '';
    foreach ($headers as $k => $v) {
      $params['http']['header'] .= "$k: $v\n";
    }
  }

  $ctx = stream_context_create($params);
  $fp = @fopen($url, 'rb', false, $ctx);

  if ($fp) {
    echo @stream_get_contents($fp);
    die();
  } else {
    // Error
    throw new Exception("Error loading '$url', $php_errormsg");
  }
}
Eduardo Cuomo
  • 17,828
  • 6
  • 117
  • 94
  • 19
    While at first this also looks better than the accepted answer, I note that it does not redirect to the supplied `$url`--It just replaces the contents of the existing page with the contents of the `$url` page. **Importantly**, php code in the `$url` page is not evaluated. – iPadDeveloper2011 Oct 17 '13 at 04:25
  • @iPadDeveloper2011, in the URL, you not have a PHP code! The PHP code is executed in the SERVER, not in the client. – Eduardo Cuomo Oct 17 '13 at 19:07
  • 1
    `redirect_post()`, which is server side, php code, sends a request to the SERVER for a `$url`. If the `$url` is a `.php` page, I note that the php is not evaluated--html is returned with php tags still in it. Isn't the whole point to send POST data to a server side script? – iPadDeveloper2011 Oct 18 '13 at 01:46
  • 2
    @EduardoCuomo this method only works with absoulte urls, because of how fopen() works: http://php.net/manual/en/function.fopen.php It's still a pretty nifty answer. – Omn Oct 23 '13 at 23:03
  • @Omn you're right! "This doesn't work with relative URL?" is not a question, is a statement. Sorry for the confusion – Eduardo Cuomo Dec 02 '13 at 17:42
  • @iPadDeveloper2011 You should pass the absolute URL. Examples: `redirect_post('/not/working/example.php');` and correct is `redirect_post('http://www.example.com/working/example.php');` – Eduardo Cuomo Oct 29 '15 at 12:01
  • @luky you can call it as you want, it's just an example – Eduardo Cuomo Jun 10 '17 at 15:52
28

I have another solution that makes this possible. It requires the client be running Javascript (which I think is a fair requirement these days).

Simply use an AJAX request on Page A to go and generate your invoice number and customer details in the background (your previous Page B), then once the request gets returned successfully with the correct information - simply complete the form submission over to your payment gateway (Page C).

This will achieve your result of the user only clicking one button and proceeding to the payment gateway. Below is some pseudocode

HTML:

<form id="paymentForm" method="post" action="https://example.com">
  <input type="hidden" id="customInvoiceId" .... />
  <input type="hidden" .... />

  <input type="submit" id="submitButton" />
</form>

JS (using jQuery for convenience but trivial to make pure Javascript):

$('#submitButton').click(function(e) {
  e.preventDefault(); //This will prevent form from submitting

  //Do some stuff like build a list of things being purchased and customer details

  $.getJSON('setupOrder.php', {listOfProducts: products, customerDetails: details }, function(data) {
  if (!data.error) {
    $('#paymentForm #customInvoiceID').val(data.id);
    $('#paymentForm').submit();   //Send client to the payment processor
  }
});
MikeMurko
  • 2,214
  • 1
  • 27
  • 56
  • 1
    This solution have a issue, if someone turn off the JavaScript in this page, do refresh, and submit, it will go payment page without save anything. this could be loophole. – caoglish Apr 08 '14 at 02:27
  • 2
    Then only set the paymentForm action with Javascript? If JS disabled, the form won't submit. – MikeMurko Apr 08 '14 at 03:33
  • The user turning off JS should not cause us an issue - the reason we have to go to Page B is probably to generate a fingerprint or add an order code, etc. Without these then the 3rd party page C should fail, or at worst, when the result comes back to us from the 3rd party without an order code, we should not accept the order. This is overall a good solution if JS is acceptable. – Coder Oct 14 '17 at 10:45
7

I know this is an old question, but I have yet another alternative solution with jQuery:

var actionForm = $('<form>', {'action': 'nextpage.php', 'method': 'post'}).append($('<input>', {'name': 'action', 'value': 'delete', 'type': 'hidden'}), $('<input>', {'name': 'id', 'value': 'some_id', 'type': 'hidden'}));
actionForm.submit();

The above code uses jQuery to create a form tag, appending hidden fields as post fields, and submit it at last. The page will forward to the form target page with the POST data attached.

p.s. JavaScript & jQuery are required for this case. As suggested by the comments of the other answers, you can make use of <noscript> tag to create a standard HTML form in case JS is disabled.

Raptor
  • 53,206
  • 45
  • 230
  • 366
7

I'm aware the question is php oriented, but the best way to redirect a POST request is probably using .htaccess, ie:

RewriteEngine on
RewriteCond %{REQUEST_URI} string_to_match_in_url
RewriteCond %{REQUEST_METHOD} POST
RewriteRule ^(.*)$ https://domain.tld/$1 [L,R=307]

Explanation:

By default, if you want to redirect request with POST data, browser redirects it via GET with 302 redirect. This also drops all the POST data associated with the request. Browser does this as a precaution to prevent any unintentional re-submitting of POST transaction.

But what if you want to redirect anyway POST request with it’s data? In HTTP 1.1, there is a status code for this. Status code 307 indicates that the request should be repeated with the same HTTP method and data. So your POST request will be repeated along with it’s data if you use this status code.

SRC

Pedro Lobito
  • 94,083
  • 31
  • 258
  • 268
6

You can let PHP do a POST, but then your php will get the return, with all sorts of complications. I think the simplest would be to actually let the user do the POST.

So, kind-of what you suggested, you'll get indeed this part:

Customer fill detail in Page A, then in Page B we create another page show all the customer detail there, click a CONFIRM button then POST to Page C.

But you can actually do a javascript submit on page B, so there is no need for a click. Make it a "redirecting" page with a loading animation, and you're set.

Nanne
  • 64,065
  • 16
  • 119
  • 163
  • This is what we do now. I am thinking is there any PHP better logic to solve it. Thanks. ;) – Shiro Apr 07 '11 at 09:44
  • ya, more toward on HTTP, but I work in PHP environment, so i think is both tag. Thanks! Col. Shrapnel. – Shiro Apr 08 '11 at 00:58
5

There is a simple hack, use $_SESSION and create an array of the posted values, and once you go to the File_C.php you can use it then do you process after that destroy it.

Naman
  • 1,519
  • 18
  • 32
5

I faced similar issues with POST Request where GET Request was working fine on my backend which i am passing my variables etc. The problem lies in there that the backend does a lot of redirects, which didnt work with fopen or the php header methods.

So the only way i got it working was to put a hidden form and push over the values with a POST submit when the page is loaded.

echo
'<body onload="document.redirectform.submit()">   
    <form method="POST" action="http://someurl/todo.php" name="redirectform" style="display:none">
    <input name="var1" value=' . $var1. '>
    <input name="var2" value=' . $var2. '>
    <input name="var3" value=' . $var3. '>
    </form>
</body>';
kaya
  • 1,626
  • 1
  • 21
  • 27
  • Hey @kaya, this looks great, does this work as an intermediate page between the original page and the target? – Daniel Jan 19 '21 at 15:37
  • @Daniel, correct - i used this to perform an autologin to a website from a dashboard via hashValue. The dashboard contained only a link without user data like username/password, so i passed the hashValue to the LoginHook.php via GET Request, from there i obtained logins from DB and redirect user to the destination via POST Request. – kaya Jan 20 '21 at 16:25
  • didn't you forget to add input type="text" or "hidden" ? – asela daskon Mar 26 '21 at 08:38
  • I dont know to be honest, this worked for me and the whole form is hidden if you look at the style Attribute. – kaya Mar 27 '21 at 13:03
4

You can use sessions to save $_POST data, then retrieve that data and set it to $_POST on the subsequent request.

User submits request to /dirty-submission-url.php
Do:

if (session_status()!==PHP_SESSION_ACTIVE)session_start();
     $_SESSION['POST'] = $_POST;
}
header("Location: /clean-url");
exit;

Then the browser redirects to and requests /clean-submission-url from your server. You will have some internal routing to figure out what to do with this.
At the beginning of the request, you will do:

if (session_status()!==PHP_SESSION_ACTIVE)session_start();
if (isset($_SESSION['POST'])){
    $_POST = $_SESSION['POST'];
    unset($_SESSION['POST']);
}

Now, through the rest of your request, you can access $_POST as you could upon the first request.

Reed
  • 14,703
  • 8
  • 66
  • 110
  • You could alternatively process the `$_POST` data into a query string and pass it to the subsequent page that way. But then the data can't be too terribly large. And this probably does not work if file uploads are involved, but I'm not sure. – Reed Apr 25 '19 at 15:24
  • 1
    You made my day Thank You @Reed – Gursewak Dhindsa Jul 05 '20 at 14:03
  • 1
    there seem to be a sense here. that $_SESSION['POST'] = $_POST; seem to hold the magic – webs Dec 13 '20 at 08:39
-2

Try this:

Send data and request with http header in page B to redirect to Gateway

<?php
$host = "www.example.com";
$path = "/path/to/script.php";
$data = "data1=value1&data2=value2";
$data = urlencode($data);

header("POST $path HTTP/1.1\\r\
" );
header("Host: $host\\r\
" );
header("Content-type: application/x-www-form-urlencoded\\r\
" );
header("Content-length: " . strlen($data) . "\\r\
" );
header("Connection: close\\r\
\\r\
" );
header($data);
?>

Additional Headers :

Accept: \*/\*
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like 
Gecko) Chrome/74.0.3729.169 Safari/537.36
user3754884
  • 87
  • 11
-3
function post(path, params, method) {
    method = method || "post"; // Set method to post by default if not specified.



    var form = document.createElement("form");
    form.setAttribute("method", method);
    form.setAttribute("action", path);

    for(var key in params) {
        if(params.hasOwnProperty(key)) {
            var hiddenField = document.createElement("input");
            hiddenField.setAttribute("type", "hidden");
            hiddenField.setAttribute("name", key);
            hiddenField.setAttribute("value", params[key]);

            form.appendChild(hiddenField);
         }
    }

    document.body.appendChild(form);
    form.submit();
}

Example:

 post('url', {name: 'Johnny Bravo'});
Chris Wesseling
  • 6,226
  • 2
  • 36
  • 72
Stephen Ngethe
  • 1,034
  • 13
  • 24
-3

Here there is another approach that works for me:

if you need to redirect to another web page (user.php) and includes a PHP variable ($user[0]):

header('Location:./user.php?u_id='.$user[0]);

or

header("Location:./user.php?u_id=$user[0]");
Lucho
  • 221
  • 1
  • 6
  • 2
    Those alternatives are meant to use GET protocol. It exposes variables on client's browser. OP wanted the same behaviour with POST instead. – Sergio A. Jan 17 '20 at 12:46