We will assume you have a general understanding of programming. This guide is intended for developers, and we expect you to have a good understanding of REST APIs in general. If you’re not a developer, please skip this section.

We’ll make practical use of dynamic payments so that you can use them effectively in your applications. There are 8 steps required to make this work. We will use TypeScript and Node.js here, but the implementation can be ported to any language.

A practical use case for dynamic payments is, for example, if you have a Discord server and you want to grant a user a role. In this case, you can pass in:

fields[{input: "discord_username", display_value: "Discord Username"}]

during the payment creation. This will prompt for the Discord username in the checkout process. Once the payment is made, a payment.succeeded webhook event is triggered. You can then receive the Discord username as discord_username or fields[0].input in the request body of the webhook and grant the user their role.

This has many practical applications, for example, a site selling dynamic courses or other customizable products or services.

1

Grab the secrets

Log in to your account and navigate to the “API Tokens” section. Click the “Create New Token” button. The generated API token will look similar to this: api_tok_A7eUMjyjyMfV9G0u7YH18SozCpr1hEiU. Copy and securely store the API token.

Next in your shop, create a new webhook. The webhook secret will look like this: whsk_GseIBNijOmcUFeI3G5OEkgfus5ZfRMZ5. Copy and securely store the webhook secret.

We are storing these two secrets as

.env
PANDABASE_API_TOKEN="api_tok_A7eUMjyjyMfV9G0u7YH18SozCpr1hEiU"
PANDABASE_WEBHOOK_SECRET="whsk_GseIBNijOmcUFeI3G5OEkgfus5ZfRMZ5"

in our .env file.

2

Prepare backend

Set up an Express server with TypeScript. First, initialize your project and install the necessary dependencies:

npm init
npm add express dotenv
npm add -D typescript @types/express @types/node ts-node
npx tsc --init

Create a src folder and add an index.ts file:

import express from "express";
import dotenv from "dotenv";

dotenv.config();

const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});
3

Build webhook receiver

Add a route to receive webhooks and verify the signature:

import crypto from "crypto";

// ... prev code

function validateSignature(
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
) {
  const signature = crypto
    .createHmac("sha256", process.env.PANDABASE_WEBHOOK_SECRET!)
    .update(JSON.stringify(req.body))
    .digest("hex");

  if (req.headers["x-pandabase-signature"] === signature) {
    next();
  } else {
    res.status(401).send("Invalid signature");
  }
}

app.post("/webhook", validateSignature, (req, res) => {
  const event = req.body;

  if (event.type === "payment.succeeded") {
    // handle successful payment
    console.log("Payment succeeded:", event.data);
    // implement your logic here (e.g., granting access)
  }

  res.sendStatus(200);
});
4

Generate payment session

Create a function to generate a payment session:

async function createPaymentSession(
  amount: number,
  username: string
): Promise<string> {
  try {
    const response = await fetch(
      `https://api.pandabase.io/${process.env.PANDABASE_SHOP_ID}/shop_id/payments`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.PANDABASE_API_TOKEN}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          dynamic: true,
          amount,
          currency: "USD",
          description: "Premium access",
          extra_params: {
            default_values: {
              full_name: "",
              email: "",
            },
            fields: [
              {
                type: "input",
                name: "username",
                display_name: "Your Username",
              },
            ],
          },
          return_url: "https://your-site.com/success",
          cancel_url: "https://your-site.com/cancel",
          tax_enabled: true,
        }),
      }
    );

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    return data.payload.checkout_url;
  } catch (error) {
    console.error("Error creating payment session:", error);
    throw error;
  }
}
5

Send checkout link

Create an endpoint to initiate the payment process:

app.post("/create-payment", async (req, res) => {
  try {
    const { amount, username } = req.body;
    const checkoutUrl = await createPaymentSession(amount, username);
    res.json({ checkoutUrl });
  } catch (error) {
    res.status(500).json({ error: "Failed to create payment session" });
  }
});
6

Monitor payment.success event

The webhook receiver we set up in Step 3 will handle the payment.succeeded event. Make sure to implement the necessary logic in the webhook handler.

7

Grant app access

In the webhook handler, implement the logic to grant access based on the username:

app.post("/webhook", validateSignature, (req, res) => {
  const event = req.body;

  if (event.type === "payment.succeeded") {
    const username = event.data.payment.fields[0].input; // username
    const id = event.data.payment.id; // payment id

    grantAccess(username, id);
  }

  res.sendStatus(200);
});

function grantAccess(username: string, id: string) {
  // Implement your logic
  console.log(`Granting access to user: ${username} | Payment ID: ${id}`);
}
8

You've finished!