Whop Functions

Now that we have built our entry points lets work on creating our Whop Functions for the panel.

First let's require our node-fetch module.

const fetch = require('node-fetch')

Promises

The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

Description

A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action's eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.

A Promise is in one of these states:

  • pending: initial state, neither fulfilled nor rejected.
  • fulfilled: meaning that the operation was completed successfully.
  • rejected: meaning that the operation failed.

The eventual state of a pending promise can either be fulfilled with a value or rejected with a reason (error). When either of these options occur, the associated handlers queued up by a promise's then method are called. If the promise has already been fulfilled or rejected when a corresponding handler is attached, the handler will be called, so there is no race condition between an asynchronous operation completing and its handlers being attached.

A promise is said to be settled if it is either fulfilled or rejected, but not pending.

801801

You will also hear the term resolved used with promises — this means that the promise is settled or "locked-in" to match the eventual state of another promise, and further resolving or rejecting it has no effect. The States and fates document from the original Promise proposal contains more details about promise terminology. Colloquially, "resolved" promises are often equivalent to "fulfilled" promises, but as illustrated in "States and fates", resolved promises can be pending or rejected as well. For example:

new Promise((resolveOuter) => {
  resolveOuter(
    new Promise((resolveInner) => {
      setTimeout(resolveInner, 1000);
    })
  );
});

This promise is already resolved at the time when it's created (because the resolveOuter is called synchronously), but it is resolved with another promise, and therefore won't be fulfilled until 1 second later, when the inner promise fulfills. In practice, the "resolution" is often done behind the scenes and not observable, and only its fulfillment or rejection are.

📘

Note:

Several other languages have mechanisms for lazy evaluation and deferring a computation, which they also call "promises", e.g. Scheme. Promises in JavaScript represent processes that are already happening, which can be chained with callback functions. If you are looking to lazily evaluate an expression, consider using a function with no arguments e.g. f = () => expression to create the lazily-evaluated expression, and f() to evaluate the expression immediately.

Chained Promises

The methods Promise.prototype.then(), Promise.prototype.catch(), and Promise.prototype.finally() are used to associate further action with a promise that becomes settled. As the Promise.prototype.then() and Promise.prototype.catch() methods return promises, they can be chained.

The .then() method takes up to two arguments; the first argument is a callback function for the fulfilled case of the promise, and the second argument is a callback function for the rejected case. Each .then() returns a newly generated promise object, which can optionally be used for chaining; for example:

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("foo");
  }, 300);
});

myPromise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB, handleRejectedB)
  .then(handleFulfilledC, handleRejectedC);

Processing continues to the next link of the chain even when a .then() lacks a callback function that returns a Promise object. Therefore, a chain can safely omit every rejection callback function until the final .catch().

Handling a rejected promise in each .then() has consequences further down the promise chain. Sometimes there is no choice, because an error must be handled immediately. In such cases we must throw an error of some type to maintain error state down the chain. On the other hand, in the absence of an immediate need, it is simpler to leave out error handling until a final .catch() statement. A .catch() is really just a .then() without a slot for a callback function for the case when the promise is fulfilled.

myPromise
  .then(handleFulfilledA)
  .then(handleFulfilledB)
  .then(handleFulfilledC)
  .catch(handleRejectedAny);

Using Arrow Function Expressions for the callback functions, implementation of the promise chain might look something like this:

myPromise
  .then((value) => `${value} and bar`)
  .then((value) => `${value} and bar again`)
  .then((value) => `${value} and again`)
  .then((value) => `${value} and again`)
  .then((value) => {
    console.log(value);
  })
  .catch((err) => {
    console.error(err);
  });

📘

Note:

For faster execution, all synchronous actions should preferably be done within one handler, otherwise it would take several ticks to execute all handlers in sequence.

The termination condition of a promise determines the "settled" state of the next promise in the chain. A "fulfilled" state indicates a successful completion of the promise, while a "rejected" state indicates a lack of success. The return value of each fulfilled promise in the chain is passed along to the next .then(), while the reason for rejection is passed along to the next rejection-handler function in the chain.

The promises of a chain are nested like Russian dolls, but get popped like the top of a stack. The first promise in the chain is most deeply nested and is the first to pop.

(promise D, (promise C, (promise B, (promise A) ) ) )

When a nextValue is a promise, the effect is a dynamic replacement. The return causes a promise to be popped, but the nextValue promise is pushed into its place. For the nesting shown above, suppose the .then() associated with "promise B" returns a nextValue of "promise X". The resulting nesting would look like this:

(promise D, (promise C, (promise X) ) )

A promise can participate in more than one nesting. For the following code, the transition of promiseA into a "settled" state will cause both instances of .then() to be invoked.

const promiseA = new Promise(myExecutorFunc);
const promiseB = promiseA.then(handleFulfilled1, handleRejected1);
const promiseC = promiseA.then(handleFulfilled2, handleRejected2);

An action can be assigned to an already "settled" promise. In that case, the action (if appropriate) will be performed at the first asynchronous opportunity. Note that promises are guaranteed to be asynchronous. Therefore, an action for an already "settled" promise will occur only after the stack has cleared and a clock-tick has passed. The effect is much like that of setTimeout(action,10).

const promiseA = new Promise((resolutionFunc, rejectionFunc) => {
  resolutionFunc(777);
});
// At this point, "promiseA" is already settled.
promiseA.then((val) => console.log("asynchronous logging has val:", val));
console.log("immediate logging");

// produces output in this order:
// immediate logging
// asynchronous logging has val: 777

Thenables

The JavaScript ecosystem had made multiple Promise implementations long before it became part of the language. Despite being represented differently internally, at the minimum, all Promise-like objects implement the Thenable interface. A thenable implements the .then() method, which is called with two callbacks: one for when the promise is fulfilled, one for when it's rejected. Promises are thenables as well.

To interoperate with the existing Promise implementations, the language allows using thenables in place of promises. For example, Promise.resolve will not only resolve promises, but also trace thenables.

const aThenable = {
  then(onFulfilled, onRejected) {
    onFulfilled({
      // The thenable is fulfilled with another thenable
      then(onFulfilled, onRejected) {
        onFulfilled(42);
      },
    });
  },
};

Promise.resolve(aThenable); // A promise fulfilled with 42

If you would like to know more about the constructor and the static methods you may Go Here to fully understand the Promises.

Functions

Awesome! Now we will need to create 6 different promises which will interact with our Whop API.

getLicensesByDiscordId(discord_id)

Let's create our licenses function which will get all the licenses by the user Discord ID.

...
const getLicensesByDiscordId = (discord_id) => new Promise(async (resolve, reject) => {
    try {
        const response = await fetch(`https://api.whop.com/api/v1/licenses?discord_account_id=${discord_id}`, {
            headers: {
                'Authorization': 'Bearer ' + process.env.WHOP_TOKEN
            }
        })
        const body = await response.json()
        var arr = []
        var arr = body.users
        resolve(arr)
    } catch (error) {
        console.log(error)
        resolve([])
    }
})

Let's explain what is happening here.

We are using the Whop API to grab all the licenses of the user by its Discord ID, we are doing so by creating a Promise with a try catch to handle any error that occurs.

getLicenses()

Let's create our licenses function to grab all licenses.

This function will allow us to show all the licenses in the licenses table.

...
const getLicenses = () => new Promise(async (resolve, reject) => {
    try {
        const response = await fetch(`https://api.whop.com/api/v1/licenses`, {
            headers: {
                'Authorization': 'Bearer ' + process.env.WHOP_TOKEN
            }
        })
        const body = await response.json()
        var arr = body.users
        resolve(arr)
    } catch (error) {
        console.log(error)
        resolve([])
    }
})

resetLicense(license)

Let's create our reset license function.

Your users will want to reset their license if they changed their PC if you have metadata setup on the license or they rented the license or simply wanna give it to someone to try your system before buying it.

...
const resetLicense = (license) => new Promise(async (resolve, reject) => {
    try {
        const response = await fetch(`https://api.whop.com/api/v1//licenses/${license}/reset`, {
            method: 'POST',
            headers: {
                'Authorization': 'Bearer ' + process.env.WHOP_TOKEN
            },
            json: {
                metadata: {}
            }
        })
        const body = await response.json()
        if(body.success) {
            resolve(true)
        } else {
            resolve(false)
        }
    } catch (error) {
        console.log(error)
        resolve([])
    }
})

The reset function uses POST method with the license in the parameters and the metadata as an empty object to reset the license HWID.

getPlans()

Let's create our plans function to return our made plans.

...
const getPlans = () => new Promise(async (resolve, reject) => {
    try {
        const response = await fetch('https://api.whop.com/api/v1/plans', {
            headers: {
                'Authorization': 'Bearer ' + process.env.WHOP_TOKEN
            }
        })
        const body = await response.json()
        var arr = body.plans
        resolve(arr)
    } catch (error) {
        console.log(error)
        resolve([])
    }
})

getAccessPasses()

Let's create our access passes function to return our made passes.

...
const getAccessPasses = () => new Promise(async (resolve, reject) => {
    try {
        const response = await fetch('https://api.whop.com/api/v1/access_passes', {
            headers: {
                'Authorization': 'Bearer ' + process.env.WHOP_TOKEN
            }
        })
        const body = await response.json()
        var arr = body.access_passes
        resolve(arr)
    } catch (error) {
        console.log(error)
        resolve([])
    }
})

createPurchaseLink(plan, stock, password)

Let's create our purchase link creation function which takes in the Plan ID, Stock, and Password.

the createPurchaseLink() function is useful when you need to create a restock but you can't login to the Whop Panel or simply you have your own panel online 24/7 and that is all you will ever need..

...
const createPurchaseLink = (plan, stock, password) => new Promise(async (resolve, reject) => {
    try {
        const response = await fetch(`https://api.whop.com/api/v1/plans/${plan}/create_link?stock=${stock}${password.length > 0 ? `&password=${password}` : ''}`, {
            method: 'POST',
            headers: {
                'Authorization': 'Bearer ' + process.env.WHOP_TOKEN
            }
        })
        const body = await response.json()
        if(body.success) {
            resolve(body.release_link.direct_link)
        } else {
            resolve(body.message)
        }
    } catch (error) {
        console.log(error)
        resolve('Invalid Request Body')
    }
})

The create purchase link functions uses POST method and passes the Plan ID, Stock, and Password in the paramenets and returns the release link.

Exporting Functions

Now to make the functions accessible we will need to export them

...
module.exports = {
    getLicensesByDiscordId,
    getLicenses,
    resetLicense,
    getPlans,
    getAccessPasses,
    createPurchaseLink
}

Awesome! You have successfully setup our Whop Functions which will work alongside the panel.