Mark Roworth
Mark Roworth

Reputation: 566

PayPal JavaScript SDK with a PHP server?

I'm working with PayPal Standard Payments for the first time, using the code from their button generator on my "payNow" PHP page. I'm fairly new to PHP and JavaScript, but well-versed in C#/MSSQL. The code provided by PayPal is (with the addition of a little PHP to modify the URL):

<!-- paypal start -->

<div id="smart-button-container">
      <div style="text-align: center;">
        <div style="margin-bottom: 1.25rem;">
          <p>Appster - including updates</p>
          <select id="item-options"><option value="Annual" price="360.00">Annual - 360.00 GBP</option><option value="Monthly" price="30.00">Monthly - 30.00 GBP</option></select>
          <select style="visibility: hidden" id="quantitySelect"></select>
        </div>
      <div id="paypal-button-container"></div>
      </div>
    </div>
    <?php
        $url = "https://www.paypal.com/sdk/js";
        $url .= "?client-id=" . PAYPAL_CLIENT_ID;
        $url .= "&debug=true";
        $url .= "&commit=true";
        $url .= "&currency=GBP";
        $url .= "&locale=en_GB";
        echo '<script src="' . $url . '" data-sdk-integration-source="button-factory"></script>' . PHP_EOL;
    ?>
    <!-- <script src="https://www.paypal.com/sdk/js?client-id=sb&enable-funding=venmo&currency=GBP" data-sdk-integration-source="button-factory"></script> -->
    <script>
        function initPayPalButton() {
            var shipping = 0;
            var itemOptions = document.querySelector("#smart-button-container #item-options");
            var quantity = parseInt();
            var quantitySelect = document.querySelector("#smart-button-container #quantitySelect");
            
            if (!isNaN(quantity)) {
                quantitySelect.style.visibility = "visible";
            }
            
            var orderDescription = 'Appster - including upgrades';
            if(orderDescription === '') {
                orderDescription = 'Item';
            }
            
    paypal.Buttons({
      style: {
        shape: 'rect',
        color: 'gold',
        layout: 'vertical',
        label: 'paypal',
        
      },
      createOrder:
      function(data, actions) {
        var selectedItemDescription = itemOptions.options[itemOptions.selectedIndex].value;
        var selectedItemPrice = parseFloat(itemOptions.options[itemOptions.selectedIndex].getAttribute("price"));
        var tax = (20 === 0 || false) ? 0 : (selectedItemPrice * (parseFloat(20)/100));
        if(quantitySelect.options.length > 0) {
          quantity = parseInt(quantitySelect.options[quantitySelect.selectedIndex].value);
        } else {
          quantity = 1;
        }

        tax *= quantity;
        tax = Math.round(tax * 100) / 100;
        var priceTotal = quantity * selectedItemPrice + parseFloat(shipping) + tax;
        priceTotal = Math.round(priceTotal * 100) / 100;
        var itemTotalValue = Math.round((selectedItemPrice * quantity) * 100) / 100;

        return actions.order.create({
          purchase_units: [{
            description: orderDescription,
            amount: {
              currency_code: 'GBP',
              value: priceTotal,
              breakdown: {
                item_total: {
                  currency_code: 'GBP',
                  value: itemTotalValue,
                },
                shipping: {
                  currency_code: 'GBP',
                  value: shipping,
                },
                tax_total: {
                  currency_code: 'GBP',
                  value: tax,
                }
              }
            },
            items: [{
              name: selectedItemDescription,
              unit_amount: {
                currency_code: 'GBP',
                value: selectedItemPrice,
              },
              quantity: quantity
            }]
          }]
        });
      },
    onApprove:
        function(data, actions)
        {
            return actions.order.capture().then
            (
                function(orderData)
                {
                    // Full available details
                    console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));

                    // Show a success message within this page, e.g.
                    const element = document.getElementById('paypal-button-container');
                    element.innerHTML = '';
                    element.innerHTML = '<h3>Thank you for your payment!</h3>';

                    // Or go to another URL:  actions.redirect('thank_you.html');
                    actions.redirect('paymentReceived.php');
                    //var successfulPurchase = document.getElementById('successfulPurchase');
                    //successfulPurchase.style.display = 'table-row';
                }
            );
        },
    onError: 
        function(err)
        {
            alert("Payment Failed");
            console.log(err);
        },
    }).render('#paypal-button-container');
  }
  initPayPalButton();
    </script>
    
<!-- paypal end -->

On successful payment, I want to use JavaScript to use another PHP page (yet to be written) to update my back-end database with the fact that the user has bought something, by writing a row to a table. My guess is that the right way to do this is to write a RESTful API page to write the data, using a POST function. My questions are, do I call this in the onApprove function just before the redirect statement? What happens in the API fails? How would you recommend handling that?

Upvotes: 0

Views: 1470

Answers (1)

Preston PHX
Preston PHX

Reputation: 30402

Do not use actions.order.create() / .capture() to create and capture an order on the client side and only later send information to a server. Change to a proper server side integration.

Follow the Set up standard payments guide and make 2 routes on your server, one for 'Create Order' and one for 'Capture Order', documented here. Both routes should return only JSON data (no HTML or text). Inside the 2nd route, when the capture API is successful you should 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 sending confirmation emails or reserving product) immediately before forwarding your return JSON to the frontend caller.

  • This point about only returning JSON data is important since sloppy php code might output other things, or you may be using the Checkout-PHP-SDK sample code which has extra print statements. Remove anything like that, and only output JSON! You can review the response body in your browser's Network tab.

Pair your 2 routes with the frontend approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server

Upvotes: 1

Related Questions