How to set up PayPal Smart Buttons with Vue.js class components

Viewed 532

I'm trying to add PayPal buttons to an existing checkout page. However, I've had quite a bit of trouble because our project uses Vue.js class components, while examples that I've come across don't (the official PayPal documentation does not use class components: https://developer.paypal.com/docs/business/checkout/configure-payments/single-page-app/).

I have come across the workaround of using the mounted() hook to inject the PayPal SDK script into the DOM, and I have gotten success in making the buttons appear, but there is the problem where I can't make the payment details dynamic (order info like total amount, item description, etc. are stored in the Vue component's state, and I have not found a way to pass that state to a static JS script in the DOM).

I am currently trying to adapt the official PayPal documentation to work with class components, and my code is as follows (with non-PayPal-related parts cut out):

Index.html:

<!DOCTYPE html>
<html class="no-js" lang="en" dir="ltr">
    <head>
        <script defer src="https://www.paypal.com/sdk/js?client-id=abc123&disable-funding=credit,card"></script>
    </head>
    <body>
        <div id="app">
        <!-- The shop component is put inside here -->
        </div>
    </body>
</html>

shop.vue:

<template>
    <paypal-buttons :on-approve="onApprove" :create-order="createOrder" />
</template>
<script lang="ts" src="./shop.component.ts"></script>

shop.component.ts:

const PayPalButton = paypal.Buttons.driver("vue", window.Vue);

@Component({
  components: {
    PayPalButton,
  },
})
export default class Shop extends Vue {

  public total = '10.0'; // Will be dynamic

  public mounted(): void {
    ...
  }

  public createOrder(data, actions) {
    return actions.order.create({
        purchase_units : [{
            amount: {
              value: total
            }
        }]
    });
  }
  public onApprove(data, actions) {
    return actions.order.capture().then(function (details) {
        console.log(details)
    })
  }
}

This code will build successfully, but I am not able to actually open the page as there is an error. In the browser console, I see the following error:

TypeError: Cannot read property 'component' of undefined

Upon further debugging, I have found that the line paypal.Buttons.driver("vue", window.Vue); causes the error, and this is because paypal is undefined. I am fairly certain that the PayPal script in index.html loaded properly, and I also do not think this is due to a missing npm package or import. One of the few resources I found online was this: Vue PayPal implementation with vue-head, paypal not defined

Unfortunately, this link's solution uses the mounted() hook, which is what I have tried before, and which does not fix the problem of offering the dynamic total that I want.

Does anybody have experience with PayPal SDK & Vue.js class components? Any help would be greatly appreciated!

1 Answers

After more testing, I have arrived at this conclusion: so apparently it is possible to have a dynamic order sent to PayPal, even when you use the mounted() hook to inject the SDK script into the DOM. It just turns out that when I first tried to do this, I had a flawed understanding of Vue class components and thus referenced the component state incorrectly.

In my final code, I took out the PayPal SDK <script> tag from index.html. My code from the other 2 files are revised as follows:

shop.vue:

<template>
    <div id="paypal-button"></div>
</template>
<script lang="ts" src="./shop.component.ts"></script>

shop.component.ts:

export default class Shop extends Vue {

  public total = '10.0'; // Will be dynamic

  public mounted(): void {
    const script = document.createElement('script');
    const clientId = 'abc123';
    script.src = `https://www.paypal.com/sdk/js?client-id=${clientId}&disable-funding=credit,card`;
    script.addEventListener('load', this.paypalSetLoaded);
    document.body.appendChild(script);
  }

  public paypalSetLoaded() {
    window.paypal
      .Buttons({
        style: {
          color: 'blue',
          shape: 'pill',
        },
        createOrder: this.paypalCreateOrder,
        onApprove: this.paypalOnApprove,
      })
      .render('#paypal-button');
  }

  public paypalCreateOrder(data, actions) {
    return actions.order.create({
      purchase_units: [{
        amount: {
          currency_code: 'USD',
          value: this.total,
        }
      }],
    });
  }

  public paypalOnApprove(data, actions) {
    return actions.order.capture().then(details => {
      console.log(details);
    });
  }
}

Even if the value of total changes during execution of the program (due to user interaction), an order for the correct amount of money will always be sent to PayPal.

I am not sure whether this is best practice, but the code works and is both efficient & scalable based on what I can forsee. Hope this helps anybody else who encountered a similar problem

Related