1

I'm hoping some knowledgeable soul may be able to explain where I'm going wrong with the code below.

I am trying to upgrade from PayPal Encrypted Payment Settings (EPS) to a more current PayPal checkout solution. I've written my shopping cart in PHP and essentially have a final page of checkout where I have everything ready to go, e.g.

$seid \\ session id to uniquely identify session / items in basket and used on the return call to write order to database, update stock levels etc
$txn_value_excl_tax
$shipping
$tax
$grand_total

The relevant part of the subsequent PHP code looks like this:

<?php
include 'createClientToken.php';
?>

<script src="https://www.paypal.com/sdk/js?components=hosted-fields,buttons&client-id=MyClientID&currency=GBP&disable-funding=credit" data-client-token='<?=$clientToken?>'></script>

<DIV id="paypal-button-container" style="width:120px;"></DIV>

<SCRIPT>
paypal.Buttons({
    createOrder:function(data,actions) {
        return actions.order.create({
            purchase_units:[{
                custom_id:"<? echo $seid;?>",
                amount:{
                    ...
                },
            }]
        });
    },

    onApprove: function(data, actions) {
        return actions.order.capture().then(function(details) {
            // only invokes code below if order is successful?

            var txn_status  = details.status;
            var custom_id   = details.purchase_units[0].custom_id;
            var given_name  = details.payer.name.given_name;

alert("Status: "+txn_status);

                // need to write order to database & display order confirmation
            return fetch('completed.php', {
                method: 'post',
                headers: {
                    'content-type': 'application/json'
                },
                body: JSON.stringify({
                    orderID: data.orderID
                })
            });
        });
    },

    onCancel: function (data) {
        alert("USER CANCELLED");
    }

}).render('#paypal-button-container');

</SCRIPT>

I believe all is well with the above code, as it successfully alerts out the transaction status (txn_status) and doesn't present any errors in the console log as far as I can see.

completed.php looks like this:

<?php

$orderID = json_decode($_POST['orderID']);
$custom_ID = json_decode($_POST['custom_ID']);

    session_start();
    include_once("myroutines.php");  // connect to db within here and assign to $conn
    connect();

    $write_txn = "update transactions set checked_out='true' where custom_ID='$custom_ID';";
    mysql_query($write_txn, $conn) or die(mysql_error());

?>

But the code in completed.php writes a blank custom_ID to the database. As far as I can tell, $orderID is also blank as I've tested updating an existing row in the table with $orderID, so it looks like I'm failing in how I retrieve the JSON info. Does somebody have a working example of what that specific piece of code should look like, rather than just point me to a generic "parsing JSON for PHP how-to"?

I've spent literally several days now attempting to work through PP docs to implement this, though it would appear one how-to links to another equally abstract how-to which references a PP 404 page. I've also looked at other similar questions here on Stack which has helped me along further, but I'm still missing this critical piece of the jigsaw. The horrendously over-complicated integration (imo) of PP and lack of clear how-to is inspiring me to offer a second checkout option with Square, which I'm hoping will be more intuitive, but I'd like to get this up and running first.

Mahmoud Abdelsattar
  • 1,299
  • 1
  • 15
  • 31
Steve
  • 21
  • 3

1 Answers1

0

2023 update: actions.order.create and actions.order.capture are also deprecated now and should not be used for any new integrations, period

The current standard integration guide only has an example for creating and capturing an order on a server. The index.js given in that example is in node.js, but its two backend routes can of course be implemented in any language, including PHP


(2022 answer follows)

If you are going to write to a database, do not use actions.order.create and do not use actions.order.capture. Those are for client-side operations, and communicating with your server from the client after a payment completes is entirely bad design.

Instead, use the v2/checkout/orders API and make two routes (url paths) on your server, one for 'Create Order' and one for 'Capture Order'. You could use the (recently deprecated) Checkout-PHP-SDK for the routes' API calls to PayPal, or your own HTTPS implementation of first getting an access token and then doing the call. Both of these routes should return/output only JSON data (no HTML or text). Inside the 2nd route, when the capture API is successful you should verify the amount was correct and store its resulting payment details in your database (particularly purchase_units[0].payments.captures[0].id, which is the PayPal transaction ID) and perform any necessary business logic (such as reserving product or sending an email) immediately before forwarding return JSON to the frontend caller. In the event of an error forward the JSON details of it as well, since the frontend must handle such cases.

Pair those 2 routes with this frontend approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server . (If you need to send any additional data from the client to the server, such as an items array or selected options, add a body parameter to the fetch with a value that is a JSON string or object)


Note: your above code should be discarded since you should always capture on the server when writing to a database, but another thing it's doing wrong is storing the order ID instead of the capture ID. The order ID is only using during approval and of no value once it is captured, since it is not the transaction ID that will show in the PayPal account/reports and gets used for accounting.

Preston PHX
  • 27,642
  • 4
  • 24
  • 44
  • Thanks for the response. This code is based on an example from a PP developer, and I'm fairly sure they know that I need to store the transaction in the database (though I will check). I'm getting an access token from createClientToken.php, so against that backdrop does what you recommend still hold true? I've seen the PP page you kindly reference and many others. However I find they are not very helpful and only give a generic part of the solution. They strike me as being useful as reference material if one has already written a dozen PP checkouts. They send me round in circles. – Steve Aug 29 '22 at 18:04
  • What I and I suspect others are very sorely missing is an actual working example, i.e. checkout.php yourbackend.php ...if you know of one? – Steve Aug 29 '22 at 18:04
  • The examples that use actions.order.create/capture are for simple use cases, not writing to a database, that requires a backend integration. There's a full stack example in node.js at https://developer.paypal.com/docs/checkout/standard/integrate/ , I don't know of a PHP one but basically you have to implement the two routes to call the PayPal API and there's various ways to do it including PHP's curl – Preston PHX Aug 29 '22 at 18:12
  • Ahh thanks Preston. Does anyone therefore know of a real world working PHP example where a simple PHP page containing all the relevant variables ready to go, and an integration of a PayPal button with the transaction being written to a database on the backend? Surely I can't be the only one. The PP docs imo are vague and generic, failing to explicitly illustrate what the handful of relevant scripts should look like and how they should pass variables to each other. Any real world examples rather than links to PP how-to (which I've exhausted) gratefully received. – Steve Aug 29 '22 at 18:22
  • Preston would you agree the following would be the way to go? (Doesn't seem to be possible to maintain formatting so apologies if it's unclear)... paypal.Buttons({ // Set up the transaction createOrder: function(data, actions) { //Setup path to create order file return fetch('createOrder.php', { method: 'post' }).then(function(res) { return res.json(); }).then(function(data) { return data.id; }); }, – Steve Aug 29 '22 at 18:27
  • Yes, the example at https://developer.paypal.com/demo/checkout/#/pattern/server basically already gives you that as well as everything else you need on the client side for nice error handling, all you are doing differently is naming your first route 'createOrder.php' instead of the sample path of that demo – Preston PHX Aug 29 '22 at 18:29