I’m trying to get PayPal’s transaction ID after the payment was approved on the client-side. I’m doing client-side integration of PayPal and Django. I can totally get the Payment ID and order ID and so forth, but these will be discarded by PayPal after the payment got approved. PayPal only recorded Transaction ID which can be used to track the payment with PayPal. When I’m trying to serialize the return actions which capture the Transaction ID – somehow I got a status code of 500 – Internal Server Error. The funny thing is that I can totally do console.log(transaction.id) and get the transaction ID in the console. Anyway, my error prone code is below:
In payment.html I got huge portion of html stuff, but I don’t post it here. I only post where the JavaScript begins:
<script> // Generating csrf_token on the fly function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } let amount = "{{ grand_total }}" const url = "{% url 'payment' %}" let csrftoken = getCookie('csrftoken'); let orderID = "{{ order.order_number }}" const payment_method = 'PayPal' const redirect_url = "{% url 'order_complete' %}" // Render the PayPal button into #paypal-button-container const paypalButtonsComponent = paypal.Buttons({ // optional styling for buttons // https://developer.paypal.com/docs/checkout/standard/customize/buttons-style-guide/ style: { color: "gold", shape: "pill", layout: "vertical" }, // set up the transaction createOrder: (data, actions) => { // pass in any options from the v2 orders create call: // https://developer.paypal.com/api/orders/v2/#orders-create-request-body const createOrderPayload = { purchase_units: [ { amount: { value: amount } } ] }; return actions.order.create(createOrderPayload); }, // finalize the transaction onApprove: (data, actions) => { const captureOrderHandler = (details) => { const payerName = details.payer.name.given_name; console.log(details); console.log('Transaction completed'); sendData(); function sendData() { fetch(url, { method: "POST", headers: { "Content-type": "application/json", "X-CSRFToken": csrftoken, }, body: JSON.stringify({ orderID: orderID, transID: details.id, payment_method: payment_method, status: details.status, }), }) .then((response) => response.json()) .then((data) => { window.location.href = redirect_url + '?order_number=' + data.order_number + '&payment_id=' + data.transID; }); } }; //return actions.order.capture().then(captureOrderHandler); return actions.order.capture().then(function(orderData) { // Successful capture! For dev/demo purposes: const transaction = orderData.purchase_units[0].payments.captures[0]; sendTransactionID(); function sendTransactionID() { fetch(url, { method: "POST", headers: { "Content-type": "application/json", "X-CSRFToken": csrftoken, }, body: JSON.stringify({ actualTransID: transaction.id, }), }) } }); }, // handle unrecoverable errors onError: (err) => { console.error('An error prevented the buyer from checking out with PayPal'); } }); paypalButtonsComponent .render("#paypal-button-container") .catch((err) => { console.error('PayPal Buttons failed to render'); }); </script>
In my order’s view I got this:
def payment(request): body = json.loads(request.body) order = Order.objects.get(user=request.user, is_ordered=False, order_number=body['orderID']) # Store transaction details inside Payment model processed_payment = Payment( user=request.user, payment_id=body['transID'], payment_method=body['payment_method'], amount_paid=order.order_total, status=body['status'], ) processed_payment.save() order.payment = processed_payment order.is_ordered = True order.save() # Move the cart items to Ordered Product table cart_items = CartItem.objects.filter(user=request.user) for item in cart_items: ordered_product = OrderProduct() ordered_product.order_id = order.id ordered_product.payment = processed_payment ordered_product.user_id = request.user.id ordered_product.product_id = item.product_id ordered_product.quantity = item.quantity ordered_product.product_price = item.product.price ordered_product.ordered = True ordered_product.save() cart_item = CartItem.objects.get(id=item.id) product_variation = cart_item.variations.all() ordered_product = OrderProduct.objects.get(id=ordered_product.id) ordered_product.variation.set(product_variation) ordered_product.save() # Reduce the quantity of the sold products product = Product.objects.get(id=item.product_id) product.stock -= item.quantity product.save() # Clear the cart of cart items CartItem.objects.filter(user=request.user).delete() # Send order received email to customer mail_subject = 'Thank you for your order!' message = render_to_string('order_received_email.html', { 'user': request.user, 'order': order, }) to_email = order.email send_email = EmailMessage(mail_subject, message, to=[to_email]) send_email.send() # Send order number and transaction id back to sendData method via JsonResponse data = { 'order_number': order.order_number, 'transID': processed_payment.payment_id, } return JsonResponse(data)
If I take out this in payment.html:
return actions.order.capture().then(function(orderData) { // Successful capture! For dev/demo purposes: const transaction = orderData.purchase_units[0].payments.captures[0]; sendTransactionID(); function sendTransactionID() { fetch(url, { method: "POST", headers: { "Content-type": "application/json", "X-CSRFToken": csrftoken, }, body: JSON.stringify({ actualTransID: transaction.id, }), }) } });
Which I will be left with:
<script> // Generating csrf_token on the fly function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } let amount = "{{ grand_total }}" const url = "{% url 'payment' %}" let csrftoken = getCookie('csrftoken'); let orderID = "{{ order.order_number }}" const payment_method = 'PayPal' const redirect_url = "{% url 'order_complete' %}" // Render the PayPal button into #paypal-button-container const paypalButtonsComponent = paypal.Buttons({ // optional styling for buttons // https://developer.paypal.com/docs/checkout/standard/customize/buttons-style-guide/ style: { color: "gold", shape: "pill", layout: "vertical" }, // set up the transaction createOrder: (data, actions) => { // pass in any options from the v2 orders create call: // https://developer.paypal.com/api/orders/v2/#orders-create-request-body const createOrderPayload = { purchase_units: [ { amount: { value: amount } } ] }; return actions.order.create(createOrderPayload); }, // finalize the transaction onApprove: (data, actions) => { const captureOrderHandler = (details) => { const payerName = details.payer.name.given_name; console.log(details); console.log('Transaction completed'); sendData(); function sendData() { fetch(url, { method: "POST", headers: { "Content-type": "application/json", "X-CSRFToken": csrftoken, }, body: JSON.stringify({ orderID: orderID, transID: details.id, payment_method: payment_method, status: details.status, }), }) .then((response) => response.json()) .then((data) => { window.location.href = redirect_url + '?order_number=' + data.order_number + '&payment_id=' + data.transID; }); } }; return actions.order.capture().then(captureOrderHandler); }, // handle unrecoverable errors onError: (err) => { console.error('An error prevented the buyer from checking out with PayPal'); } }); paypalButtonsComponent .render("#paypal-button-container") .catch((err) => { console.error('PayPal Buttons failed to render'); }); </script>
This would totally work – and in my Payment model I could only record Payment ID and Order ID and so forth – but these are useless after payment went through with PayPal – because PayPal only retains the Transaction ID – and I can’t get Transaction ID to be sent to the backend – but I can only print to the console using console.log – and this is frustrating.
If I can get transaction ID to be sent to the backend using fetch – then I can do something like this:
completed_payment = Payment( paypal_transaction_id=body['actualTransID'] ) completed_payment.save()
But can this be done even though the first redirection already happened with this code:
.then((data) => { window.location.href = redirect_url + '?order_number=' + data.order_number + '&payment_id=' + data.transID;
So, is it then I need to fetch the redirect_url (such as payment_complete view) and not the previous url (such as payment view)? Basically, the JavaScript stuff really got me confused. Something is wrong with my code? Any help? Thanks…
Advertisement
Answer
An instructor of mine on Udemy solved this issue. The answer is to do this in the onApprove function using the code below:
transaction_id = details['purchase_units'][0]['payments']['captures'][0].id // console.log(transaction_id)
Here is the completed working code for PayPal Client Side Integration with the ability to record PayPal Transaction ID into the database.
<script> // Generating csrf_token on the fly function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } let amount = "{{ grand_total }}" const url = "{% url 'payment' %}" let csrftoken = getCookie('csrftoken'); let orderID = "{{ order.order_number }}" const payment_method = 'PayPal' const redirect_url = "{% url 'order_complete' %}" const order_errors_url = "{% url 'order_errors' %}" // Render the PayPal button into #paypal-button-container const paypalButtonsComponent = paypal.Buttons({ // optional styling for buttons // https://developer.paypal.com/docs/checkout/standard/customize/buttons-style-guide/ style: { color: "gold", shape: "pill", layout: "vertical" }, // set up the transaction createOrder: (data, actions) => { // pass in any options from the v2 orders create call: // https://developer.paypal.com/api/orders/v2/#orders-create-request-body const createOrderPayload = { purchase_units: [ { amount: { value: amount } } ] }; return actions.order.create(createOrderPayload); }, // finalize the transaction onApprove: (data, actions) => { const captureOrderHandler = (details) => { const payerName = details.payer.name.given_name; transaction_id = details['purchase_units'][0]['payments']['captures'][0].id //console.log(transaction_id) sendData(); function sendData() { fetch(url, { method: "POST", headers: { "Content-type": "application/json", "X-CSRFToken": csrftoken, }, body: JSON.stringify({ orderID: orderID, transID: details.id, paypal_transaction_id: transaction_id, payment_method: payment_method, status: details.status, }), }) .then((response) => response.json()) .then((data) => { window.location.href = redirect_url + '?order_number=' + data.order_number + '&payment_id=' + data.transID; }); } }; return actions.order.capture().then(captureOrderHandler); }, // handle unrecoverable errors onError: (err) => { // console.error('An error prevented the buyer from checking out with PayPal'); window.location.href = order_errors_url } }); paypalButtonsComponent .render("#paypal-button-container") .catch((err) => { console.error('PayPal Buttons failed to render'); }); </script>
IN the payment view, you can always do something like this:
def payment(request): body = json.loads(request.body) order = Order.objects.get(user=request.user, is_ordered=False, order_number=body['orderID']) # Store transaction details inside Payment model processed_payment = Payment( user=request.user, payment_id=body['transID'], paypal_transaction_id=body['paypal_transaction_id'], payment_method=body['payment_method'], amount_paid=order.order_total, status=body['status'], ) processed_payment.save() order.payment = processed_payment order.is_ordered = True order.save() # Send order number and transaction id back to sendData method via # JsonResponse data = { 'order_number': order.order_number, 'transID': processed_payment.payment_id, } return JsonResponse(data)