Help with integrating paypal payment in Django (e-commerce site)

I am developing an e-commerce site for selling cakes online. I have done 90% of the groundwork, including setting up a paypal payment system. I seem to be stuck on a single error I am not being able to resolve. The payment falls apart towards the end of the process. Let me upload some parts of the code for inspection. Please could you help? checkout template (JavaScript)

<script>
// Getting cart total from the server (Django)
var total = ‘{{order.get_cart_total}}’
// Render the PayPal button into #paypal-button-container
paypal.Buttons({
// Set up the transaction (create order)
//Tells PayPal: “Create an order for £X.XX”
// PayPal stores this order internally
// Returns a PayPal order ID
// No  money taken, no backend call, only an intent to pay
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: parseFloat(total).toFixed(2)
}
}]
});
},
// Finalise the transaction
//This fires when a user logs into PayPal, user clicks approve/pay
//actions.order.capture():  actually captures the money finalises the transaction on payPal’s side
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
// Show a success message to the buyer
alert(‘Transaction completed by ’ + details.payer.name.given_name + ‘!’);
//send PayPal order ID to backend - bridge between PayPal and Django
submitFormData(data.orderID)
});
}
}).render(’#paypal-button-container’);
ipt>

// Backend verification and s<script>
// Backend verification and sanity checks (the value is not trusted, backend re-checks it)
var total = parseFloat('{{ order.get_cart_total }}').toFixed(2); // Ensure proper float format
var deliverorder = '{{ order.deliver }}'

//For logged in users, data is already stored in the database- so no need to collect userinfo
if (user != 'AnonymousUser'){
    document.getElementById('userinfo').innerHTML = '';
}
//Depending on anonymous user choice - deliver or not not, option are displayed 
document.getElementById('deliver').addEventListener('change', function(){
    var deliveryinfo = document.getElementById('delivery-info');
    if (this.checked){
        deliveryinfo.style.display = 'block';
        deliverorder = 'True';
        
    }
    else {
        deliveryinfo.style.display = 'none';
        deliverorder = 'False';
    }
});

// stops browser from reloading, submitting traditionally - instead you show the PayPal button


var form = document.getElementById('form')
//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");
//})
document.getElementById('form-button').addEventListener('click', function () {
    console.log('Continue clicked');
    this.classList.add("hidden");
    document.getElementById('payment-info').classList.remove("hidden");
});
function submitFormData(paypalOrderId) {
    console.log('Payment Button clicked');
    var userFormData = {
        'name': null,
        'email': null,
        'total': total,
        'deliverorder': deliverorder,
        'paypal_order_id':paypalOrderId
    }
    var deliveryInfo = {
        'address': null,
        'city': null,
        'postcode': null,
    };        
    

    if (deliverorder != 'False'){
        deliveryInfo.address = form.address.value
        deliveryInfo.city = form.city.value
        deliveryInfo.postcode = form.postcode.value
    }
    if (user == 'AnonymousUser'){
        userFormData.name = form.name.value
        userFormData.email = form.email.value
    }
    var url = "/process_order/";
    fetch(url, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-CSRFToken': csrftoken,
        },
        body: JSON.stringify({'form': userFormData, 'deliveryInfo': deliveryInfo}),
    })
    .then(response => {
        if (!response.ok) {
            return response.json().then(err => {
                throw new Error(err.details || 'Something went wrong');
            });
        }
        return response.json();
    })
    .then(data => {
        console.log('Success:', data);
        alert('Transaction completed');
        document.cookie = 'cart=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
        cart = {};
        window.location.href = "{% url 'home' %}";
    })
    .catch(error => {
        alert('Checkout failed: ' + error.message);
    });
}
                                                                                                                                               process_order (views)                                                                                                                        
def process_order(request):a = json.loads(request.body)

# -------------------------------

# Get customer + order

# -------------------------------

if request.user.is_authenticated:

customer = request.user.customer

order, _ = Order.objects.get_or_create(

customer=customer,

complete=False

)

else:

customer, order = guestOrder(request, data)

print(“Order ID:”, order.id)                                                                                                            # -------------------------------

# Verify PayPal payment

# -------------------------------

form_data = data.get(‘form’, {})

paypal_order_id = form_data.get(‘paypal_order_id’)

if not paypal_order_id:

return JsonResponse({‘error’: ‘Missing PayPal order ID’}, status=400)

paypal_data = verify_paypal_order(paypal_order_id)

#if paypal_data[‘status’] != ‘COMPLETED’:

#return JsonResponse({‘error’: ‘Payment not completed’}, status=400)

if paypal_data[‘status’] not in [‘COMPLETED’, ‘APPROVED’]:

return JsonResponse({‘error’: ‘Payment not completed’}, status=400)

#paid_amount = Decimal(

#paypal_data[‘purchase_units’][0][‘amount’][‘value’]

#)

#cart_total = Decimal(order.get_cart_total)

paid_amount = Decimal(

paypal_data[‘purchase_units’][0][‘amount’][‘value’]

).quantize(Decimal(‘0.00’))

#cart_total = Decimal(order.get_cart_total).quantize(Decimal(‘0.00’))

cart_total = Decimal(str(order.get_cart_total)).quantize(Decimal(‘0.00’))

if paid_amount != cart_total:

print(“ Paid:”, paid_amount)

print(“ Cart:”, cart_total)

return JsonResponse({‘error’: ‘Payment amount mismatch’}, status=400)

# -------------------------------

# Update order

# -------------------------------

deliver = form_data.get(‘deliverorder’) == ‘True’

order.deliver = deliver

order.transaction_id = paypal_order_id

order.complete = True

order.status = ‘DEBUG_Saved’

order.save()

print(“Order sabved”)

# -------------------------------

# Delivery address

# -------------------------------

if deliver:

DeliveryAddress.objects.create(

customer=customer,

order=order,

address=data[‘deliveryInfo’][‘address’],

city=data[‘deliveryInfo’][‘city’],

postcode=data[‘deliveryInfo’][‘postcode’],

)

# -------------------------------

# Clear guest cart

# -------------------------------

response = JsonResponse(‘Payment submitted..’, safe=False)

if not request.user.is_authenticated:

response.delete_cookie(‘cart’, path=‘/’)

request.session.pop(‘cart’, None)

return response

except Exception as e:

print(traceback.format_exc())

return JsonResponse({‘error’: str(e)}, status=500)

def get_paypal_access_token():

response = requests.post(

“https://api-m.sandbox.paypal.com/v1/oauth2/token”,

auth=(settings.PAYPAL_CLIENT_ID, settings.PAYPAL_SECRET),

data={“grant_type”: “client_credentials”},

)

response.raise_for_status()

return response.json()[“access_token”]

def verify_paypal_order(paypal_order_id):

access_token = get_paypal_access_token()

headers = {

“Content-Type”: “application/json”,

“Authorization”: f"Bearer {access_token}",

}

#Live change later Replace api-m.sandbox.paypal.com → api-m.paypal.com

response = requests.get(

f"https://api-m.sandbox.paypal.com/v2/checkout/orders/{paypal_order_id}",

headers=headers,

)

response.raise_for_status()

return response.json()

Thanks.

P

Something has happened to the code you’ve posted here - it’s completely unformatted. You’ve lost all leading spaces, and you’ve got excessive blank lines.

I would suggest that you try copy/pasting your code again into a post to ensure that it remains formatted correctly.

Thanks Ken for your usual support. Let me post it again.
checkout.html

{% extends ‘main.html’ %}
{% load static %}
{% block content %}

<div class="row" style="margin-left: 175px; width:1100px; margin-top:30px;">
    <div class="col-lg-6">
        <div class="box-element" id="form-wrapper" style="background-color: #F5F5F5; 		    padding:10px;">
            <form id="form">
                <div class="user-info" id="userinfo">
                    <div class="form-field">
                        <input required class="form-control" type="text" name="name" placeholder="Name..">
                    </div>
                    <div class="form-field">
                        <input required class="form-control" type="email" name="email" placeholder="Email..">
                    </div>
                </div>
                <hr>
                <div class="form-field">
                    <input type="checkbox" id="deliver" name="deliver" value="deliver">
                    <label for="deliver">Add delivery details</label>
                </div>
                <hr>
                <div id="delivery-info" style="display:none">
                    <p><h6><strong>Delivery Information:</strong></h6></p>
                    <hr>
                    <div class="form-field">
                        <input required class="form-control" type="text" name="address" placeholder="Address..">
                    </div>
                    <div class="form-field">
                        <input required class="form-control" type="text" name="city" placeholder="City..">
                    </div>
                    <div class="form-field">
                        <input required class="form-control" type="text" name="postcode" placeholder="Postcode..">
                    </div>
                </div>
                <hr>
                <!--<input id="form-button" class="btn btn-success btn-block" type="submit" value="Continue">-->
                <button id="form-button" class="btn btn-success btn-block" type="button">Continue</button>
            </form>
        </div>

    <br>
    <div class="box-element hidden" id="payment-info">
        <small>Paypal Options</small>
        <!-- <button id="make-payment">Make Payment</button> -->
        <div id="paypal-button-container"></div>
    </div>
    
</div>
<div class="col-lg-6">
    <div class="box-element" style="background-color: #F5F5F5; padding: 10px;">
        <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">
                {% if item.imageURL %}
                    <img style="width:100%; height:auto" src="{{ item.imageURL }}">
                {% elif item.cake.imageURL %}
                    <img style="width:100%; height:auto" src="{{ item.cake.imageURL }}">
                {% endif %}
            </div>
            &nbsp;&nbsp;
            <div style="flex:2">
                <p><strong>
                    {% if item.name %}
                        {{item.name}}
                    {% elif item.cake.name %}
                        {{item.cake.name}}
                    {% endif %}
                    </strong>
                </p>
            </div>
            &nbsp;&nbsp;
            <div style="flex:1"><p>£{{item.price|floatformat:2}}</p></div>
            <div style="flex:1"><p>x {{item.quantity}}</p></div>
        </div>
        {% endfor %}
        <h5>Item: {{order.get_cart_items}}</h5>
        <h5>Total: £{{order.get_cart_total|floatformat:2}}</h5>
    </div>
    <hr>
    Your payment transaction is secure! Click <a href="javascript:void(0);" onclick="showAlert()">here</a> for more info.
    <hr>

</div>

</div>
<!--<script src="https://www.sandbox.paypal.com/sdk/js?client-id=***************************************************************&currency=GBP"></script>-->
<script src="https://www.paypal.com/sdk/js?client-id=ARnDiBUiWFPg8zSCbJ9MD6ESBe0DP9lXCBH2OooHpUGjl6zAWZqGh6JaZqDOCCL32fTAuyjfodtwJWci&currency=GBP"></script>
<script>
    function showAlert() {
        alert("We prioritise your security and confidentiality. When you shop with us, rest assured that your personal information and payment details are fully protected. Here’s how we ensure your security:"+
            "\n* Encrypted Payment Processing \n* Payment Gateway Security \n* No sharing of Personal Information to a Third Party \n* No Hidden Fees \n* And more than anything else, you can pay through PayPal!"
        );
    }
</script>
<script>
        // Getting cart total from the server (Django)
        var total = '{{order.get_cart_total}}'
        // Render the PayPal button into #paypal-button-container
        paypal.Buttons({

        // Set up the transaction (create order)
        //Tells PayPal: “Create an order for £X.XX”
        // PayPal stores this order internally
        // Returns a PayPal order ID
        // No  money taken, no backend call, only an intent to pay
        createOrder: function(data, actions) {
            return actions.order.create({
                purchase_units: [{
                    amount: {
                    value: parseFloat(total).toFixed(2)
                    }
                }]
            });
        },

        // Finalise the transaction
        //This fires when a user logs into PayPal, user clicks approve/pay 
        //actions.order.capture():  actually captures the money finalises the transaction on payPal's side
        onApprove: function(data, actions) {
            return actions.order.capture().then(function(details) {
                // Show a success message to the buyer
                alert('Transaction completed by ' + details.payer.name.given_name + '!');
                //send PayPal order ID to backend - bridge between PayPal and Django
                submitFormData(data.orderID)
            });
        }

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

</script>

<script>
    // Backend verification and sanity checks (the value is not trusted, backend re-checks it)
    var total = parseFloat('{{ order.get_cart_total }}').toFixed(2); // Ensure proper float format
    var deliverorder = '{{ order.deliver }}'
    
    //For logged in users, data is already stored in the database- so no need to collect userinfo
    if (user != 'AnonymousUser'){
        document.getElementById('userinfo').innerHTML = '';
    }
    //Depending on anonymous user choice - deliver or not not, option are displayed 
    document.getElementById('deliver').addEventListener('change', function(){
        var deliveryinfo = document.getElementById('delivery-info');
        if (this.checked){
            deliveryinfo.style.display = 'block';
            deliverorder = 'True';
            
        }
        else {
            deliveryinfo.style.display = 'none';
            deliverorder = 'False';
        }
    });
 
    // stops browser from reloading, submitting traditionally - instead you show the PayPal button
    var form = document.getElementById('form')
    //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");
    //})
    document.getElementById('form-button').addEventListener('click', function () {
        console.log('Continue clicked');
        this.classList.add("hidden");
        document.getElementById('payment-info').classList.remove("hidden");
    });
    function submitFormData(paypalOrderId) {
        console.log('Payment Button clicked');
        var userFormData = {
            'name': null,
            'email': null,
            'total': total,
            'deliverorder': deliverorder,
            'paypal_order_id':paypalOrderId
        }
        var deliveryInfo = {
            'address': null,
            'city': null,
            'postcode': null,
        };        
        
        if (deliverorder != 'False'){
	    	deliveryInfo.address = form.address.value
		    deliveryInfo.city = form.city.value
		    deliveryInfo.postcode = form.postcode.value
        }
		if (user == 'AnonymousUser'){
	    	userFormData.name = form.name.value
	    	userFormData.email = form.email.value
	    }
        var url = "/process_order/";
        fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': csrftoken,
            },
            body: JSON.stringify({'form': userFormData, 'deliveryInfo': deliveryInfo}),
        })
        .then(response => {
            if (!response.ok) {
                return response.json().then(err => {
                    throw new Error(err.details || 'Something went wrong');
                });
            }
            return response.json();
        })
        .then(data => {
            console.log('Success:', data);
            alert('Transaction completed');
            document.cookie = 'cart=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
            cart = {};
            window.location.href = "{% url 'home' %}";
        })
        .catch(error => {
            alert('Checkout failed: ' + error.message);
        });
    }
</script>

{% endblock content %}                                                         

def process_order(request):

    print("I am in process order")

    try:

        data = json.loads(request.body)

        # -------------------------------

        # Get customer + order

        # -------------------------------

        if request.user.is_authenticated:

            customer = request.user.customer

            order, _ = Order.objects.get_or_create(

                customer=customer,

                complete=False

            )

        else:

            customer, order = guestOrder(request, data)

            print("Order ID:", order.id)                                                 
   
        # -------------------------------

        # Verify PayPal payment

        # -------------------------------

        form_data = data.get('form', {})

        paypal_order_id = form_data.get('paypal_order_id')


        if not paypal_order_id:

            return JsonResponse({'error': 'Missing PayPal order ID'}, 
                   status=400)

        paypal_data = verify_paypal_order(paypal_order_id)

        #if paypal_data['status'] != 'COMPLETED':

            #return JsonResponse({'error': 'Payment not completed'}, 
                    status=400)

        if paypal_data['status'] not in ['COMPLETED', 'APPROVED']:

            return JsonResponse({'error': 'Payment not completed'}, status=400)


       paid_amount = Decimal(

            paypal_data['purchase_units'][0]['amount']['value']

        ).quantize(Decimal('0.00'))


        cart_total =   
                   Decimal(str(order.get_cart_total)).quantize(Decimal('0.00'))


        if paid_amount != cart_total:

            return JsonResponse({'error': 'Payment amount mismatch'}, 
                   status=400)

        # -------------------------------

        # Update order

        # -------------------------------

        deliver = form_data.get('deliverorder') == 'True'


        order.deliver = deliver

        order.transaction_id = paypal_order_id

        order.complete = True

        order.status = 'DEBUG_Saved'

        order.save()

        print("Order sabved")

        # -------------------------------

        # Delivery address

        # -------------------------------

        if deliver:

            DeliveryAddress.objects.create(

                customer=customer,

                order=order,

                address=data['deliveryInfo']['address'],

                city=data['deliveryInfo']['city'],

                postcode=data['deliveryInfo']['postcode'],

            )

        # -------------------------------

        # Clear guest cart

        # -------------------------------

        response = JsonResponse('Payment submitted..', safe=False)

        if not request.user.is_authenticated:

            response.delete_cookie('cart', path='/')

            request.session.pop('cart', None)

        return response

    except Exception as e:

        print(traceback.format_exc())

        return JsonResponse({'error': str(e)}, status=500)

Side note: Your python code still is double-spaced. There shouldn’t be blank lines between every other line.

Please be more specific and provide more details.

  • What is the error you are receiving?
  • Is it happening in the JavaScript or in your view?
  • If it’s failing in the JavaScript, what error shows up in the browser’s console?
  • If it’s failing in your view, what is the error)
  • In both cases, what is the traceback?
  • Do you have any data that you have printed that might explain the issue?

The message I get after logging in to paypal and processing the order is - “Checkout failed. Somerhing went wrong’. When I check the database saved the order but the complete field isnot cheked, transaction_id doesn’t save the paypay_id and deliver is not checked as well. Error I get is as follows:

[Intervention] Images loaded lazily and replaced with placeholders. Load events are deferred. See https://go.microsoft.com/fwlink/?linkid=2048113
checkout?sessionID=uid_2caf8e7227_mtk6mtg6mjq&buttonSessionID=uid_498b099052_mtk6mtg6mjq&stickiness…:1 [Intervention] Images loaded lazily and replaced with placeholders. Load events are deferred. See https://go.microsoft.com/fwlink/?linkid=2048113
www.paypalobjects.com/pay/_next/static/media/2b824d9cab850069-s.p.woff2:1 Failed to load resource: the server responded with a status of 404 ()
2b824d9cab850069-s.p.woff2:1 Failed to load resource: the server responded with a status of 404 ()
18The resource was preloaded using link preload but not used within a few seconds from the window’s load event. Please make sure it has an appropriate as value and it is preloaded intentionally.

Apologies if I was not as clear as I should be.

Basically, my transaction_id used to be generated using this line of code:

transaction_id = datetime.datetime.now().timestamp()

But then if two customers order at the same second they will have the same transaction_id. To avoid this conflict, I decided to make it unique by getting paypal_order_id after successful transaction. However, this id doen’t seem to be captured, hence it failed to save trasaction_id into the database. Also complete and deliver fields are not checked even though the transaction is going through. My model for this looks like this if it helps:

class Order(models.Model):

    customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True, blank=True)

    date_ordered = models.DateField(auto_now_add=True, db_index=True)  # Adding an index

    complete = models.BooleanField(default=False)

    deliver = models.BooleanField(default=False)

    transaction_id = models.CharField(max_length=100, null=True, blank=True, unique=True)

    status = models.CharField(max_length=100, default='Not Paid')

    

    def __str__(self):

        return str(self.id)

    

    @property

    def get_cart_total(self):


   class Meta:

        unique_together = ('order', 'cake', 'size', 'sponge', 'message')  # To prevent duplicate cakes in the same order

        orderitems = self.orderitem_set.all()

        total = sum([item.get_total for item in orderitems])

        return total

    

    @property

    def get_cart_items(self):

        orderitems = self.orderitem_set.all()

        total = sum([item.quantity for item in orderitems])

        return total

    

    @property

    def get_cart_items(self):

        orderitems = self.orderitem_set.all()

        total = sum([item.quantity for item in orderitems])

        return total

        

class OrderItem(models.Model):

    cake = models.ForeignKey(BirthdayCake, on_delete=models.SET_NULL, null=True)

    order = models.ForeignKey(Order, on_delete=models.SET_NULL, null=True, blank=True)

    quantity = models.IntegerField(default=1, null=True, blank=True)

    date_added = models.DateField(auto_now_add=True)

    

    # Fields for size and sponge type

    size = models.CharField(max_length=40, blank=True, null=True)  # For BirthdayCake size

    sponge = models.CharField(max_length=20, blank=True, null=True)  # For BirthdayCake sponge type

    message = models.TextField(null=True, blank=True)

    price = models.DecimalField(max_digits=6, decimal_places=2, default=0.00)  # Added price field

    

    @property

    def get_total(self):

        # Ensure dynamic price calculation

        return self.price * self.quantity


Great! This all should be good enough that someone who knows how to use PayPal payments should be able to step in and help you out here.

Beyond that, it might be helpful if you added some more logging / print statements to see where the data is at different points to identify where things are getting lost.

For example, you could log the data in the JSON.stringify call in your submitFormData function to ensure that you’ve got the expected data at that point. You could then print the data variable in process_order after the json.loads to ensure you’re getting what you think you’re getting - and that it matches what the browser is sending. And then you can continue on from there. Expand what you print or log until you figure out exactly where this is failing.

Thanks for your support. I have finally resolved the problem.

I had incorrect PAYPAL_CLIENT_ID and PAYPAL_SECRET in the settings.

It is now all working perfectly.