skip to Main Content

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…

2

Answers


  1. Chosen as BEST 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)
    

  2. Don’t capture things client-side and then send the information to a server after the fact. That’s a fundamentally bad design for ecommerce payments.

    Instead, implement server-side API calls to create and capture a PayPal order. You can use the Checkout-Python-SDK for this, or direct HTTPS API calls after first obtaining an access_token with your client_id and secret. You’ll need two django routes that only output JSON data, one for the create order API operation and one for the capture that takes an id as input (path parameter, query string, or w/e) and captures it. The capture route can then record the successful transaction ID in your database, as well as trigger any other logic you need it to do (such as sending a confirmation email or reserving product) — immediately before forwarding the JSON result to the frontend caller (regardless of success or failure, always forward the result in a format the frontend recognizes to handle any relevant errors on the client side)

    Once you have your two routes, use this for the frontend approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search