PayPal API Cross Site Request Cookie Issue (Django, JavaScript Issue)

Viewed 66

I'm creating a site for my senior project and have run into some trouble creating a payment portal for my site. The site was working correctly the other night, and without making any changes, the buttons now fail to render and I am being spammed with errors regarding indicating a cookie in a cross site address. Attached is the checkout.html file which the PayPal js is included within, along with the error codes I am receiving from the console. Any help would be much appreciated!

I have tried to edit the global config and played around with the SameSite attribute, but to no avail :(

{% extends 'main.html' %}
{% load static %}
<!DOCTYPE html>
<head>
    <link rel="stylesheet" type="text/css" href="{% static 'css/checkout.css' %}">
</head>
<body>
{% block content %}
<div class="row">
     <div class="col-sm-6 mt-4 mb-4">
          <div class="box-element" id="form-wrapper">
               <h2>Recipient Information</h2>
               <form id="form">
                    <div id="recipient-info">
                         <div class="form-field">
                              <input required class="form-control" type="text" name="recipient_first_name" placeholder="Recipient First Name..">
                         </div>
                         <div class="form-field">
                              <input required class="form-control" type="text" name="recipient_last_name" placeholder="Recipient Last Name..">
                         </div>
                         <br>
                         <div class="form-field">
                              <input required class="form-control" type="email" name="recipient_email" placeholder="Recipient Email..">
                         </div>
                         <div class="row ml-auto">
                         <label class = "mt-1" for="pickup_location">Select a pickup location: </label>
                         <select class="mt-2 ml-2" name="pickup_location" size="4" multiple>
                           <option value="nabatieh">Nabatieh</option>
                           <option value="tyre">Tyre</option>
                           <option value="saida">Saida</option>
                           <option value="beirut">Beirut</option>
                         </select><br><br>
                    </div>
                    </div>

                    <hr>
                    <input id="form-button" class="btn btn-success btn-block" type="submit" value="Continue">
               </form>
          </div>

          <br>
          <div class="box-element hidden" id="payment-info">
               <h2>PayPal Options</h2>
               <div id="paypal-button-container"></div>
          </div>
          
     </div>

     <div class="col-sm-6 mt-4 mb-4">
          <div class="box-element">
               <a  class="btn btn-outline-dark" href="{% url 'cart' %}">&#x2190; Back to Cart</a>
               <hr>
               <h3>Order Summary</h3>
               <hr>
               {% for item in items %}
               <div class="cart-row">
                    <div style="flex:2"><img class="row-image" src="{{item.product.imageURL}}"></div>
                    <div style="flex:2"><p>{{item.product.name}}</p></div>
                    <div style="flex:1"><p>{{item.product.price|floatformat:2}}</p></div>
                    <div style="flex:1"><p>{{item.quantity}}</p></div>
               </div>
               {% endfor %}
               <h5>Items:   {{order.get_cart_items}}</h5>
               <h5>Total:   ${{order.get_cart_total|floatformat:2}}</h5>
          </div>
     </div>
</div>
<script src="https://www.paypal.com/sdk/js?client-id=ASbTo6tPEAlA-TQ3zYFDMuuDgSWzmybQ3E3LeE3xi2s3hN6UmiREE30_atIal3f9ui2r3Eh6oGpxmil-&currency=USD"></script>

<script>
     var total = '{{order.get_cart_total}}'

     paypal.Buttons({

         // Set up the transaction
         createOrder: function(data, actions) {
             return actions.order.create({
                 purchase_units: [{
                     amount: {
                         value: parseFloat(total).toFixed(2)
                     }
                 }]
             });
         },

         // Finalize the transaction
         onApprove: function(data, actions) {
             return actions.order.capture().then(function(orderData) {
                 submitFormData()
                 console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
                 var transaction = orderData.purchase_units[0].payments.captures[0];
                 alert('Transaction '+ transaction.status + ': ' + transaction.id + '\n\nSee console for all available details');

                 // Replace the above to 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>';
                 actions.redirect('home.html');
             });
         }


     }).render('#paypal-button-container');
 </script>

<script type="text/javascript">
     var form = document.getElementById('form')
     var total = '{{order.get_cart_total|floatformat:2}}'
     form.addEventListener('submit', function(e){
               e.preventDefault()
               console.log('Form submitted...')
               document.getElementById('form-button').classList.add('hidden')
               document.getElementById('payment-info').classList.remove('hidden')
     })

     function submitFormData(){
          console.log('Payment button has been clicked...')

          var orderData = {
               'total':total,
          }

          var recipientInformation = {
               'recipient_first_name':null,
               'recipient_last_name':null,
               'email':null,
               'pickup_location':null,
          }

          recipientInformation.recipient_first_name = form.recipient_first_name.value
          recipientInformation.recipient_last_name = form.recipient_last_name.value
          recipientInformation.recipient_email = form.recipient_email.value
          recipientInformation.pickup_location = form.pickup_location.value

          var url = "/process_order/"
            fetch(url, {
                method:'POST',
                headers:{
                    'Content-Type':'applicaiton/json',
                    'X-CSRFToken':csrftoken,
                }, 
                body:JSON.stringify({'form':orderData, 'recipient-information':recipientInformation}),
                
            })
          .then((response) => response.json())
            .then((data) => {
                console.log('Success:', data);
                alert('Transaction completed');  
                window.location.href = "{% url 'store' %}"

                })
     }
</script>

{% endblock content %}
</body>
</html>

Error from console

Error information expanded

Here is how the portal was working the other night, note I made no changes on my end from then until now.

https://gyazo.com/7cfcc6a9d226af3604a7a597d39e7316

1 Answers

I don't know what your issue is, but it is wrong and bad design to use actions.order.create / capture on the client side and then perform server operations after the fact. This should never be done.

If server operations are to take place (such as saving form data or writing ot a database), the payment creation/capture should be done on the server, via API. With any luck, the problem in your question can be resolved as a byproduct of starting over and implementing things correctly:

Follow the PayPal Checkout integration guide and make 2 routes (url paths) on your django server, one for 'Create Order' and one for 'Capture Order'. You can use the Checkout-Python-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 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 sending confirmation emails or reserving product) immediately before forwarding your 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 or form data in your case, add a body parameter to the fetch with a value that is a JSON string or object of your serialized data)

Related