Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vito Demo Bot

The official reference implementation for integrating the Vetox Vito API into a Discord bot.

Node discord.js License: MIT

A small, production-quality Discord bot that runs the Vito API purchase flow end-to-end — entirely inside Discord: it shows a store, takes a payment, waits for the buyer to confirm with their PIN on vetox.io, detects completion, and delivers a product. Clone it, drop in two secrets, and watch a real payment flow work — then lift the integration into your own bot.

It's a demonstration, not a storefront. The products and license keys are illustrative, but the Vito API calls, webhook handling, and signature verification are exactly what you'd ship. Where to issue your real product is marked in the code (completeOrder in src/index.js).


Table of contents


Features

  • 🛒 Full purchase flow/shop → product picker → Buy → PIN confirmation → delivery, all in Discord.
  • 🔌 Complete Vito API integration — key verification, POST /deduct, and transaction polling, in a tidy client module.
  • 📩 Signed webhooks — HMAC-SHA256 verification over the raw body, constant-time comparison, rotation-safe multi-signature support, and a replay-window (timestamp) check.
  • 🛡️ Purchase safety — server-authoritative pricing, duplicate/concurrent-buy protection, per-product availability, and idempotent, exactly-once delivery.
  • 💾 Durable orders — order state is persisted to disk with atomic writes, so deliveries survive restarts and a transaction is never fulfilled twice.
  • 🔁 Two completion paths — instant webhooks and a zero-setup polling fallback, so it works out of the box.
  • 🗂️ Categorised catalogue — products are grouped by category in the storefront, each with an availability flag.
  • 🧾 Live audit log — every step is posted to an auto-created #vito-demo-log channel and the console.
  • ⚙️ Configurable without touching code — secrets via .env, catalogue via products.json.
  • 🪶 Minimal & readable — three runtime dependencies, small single-purpose modules, heavily commented for learning.
  • Tested — a dependency-free node --test suite covers the catalogue, order store, webhook signatures, and UI helpers.
  • 🔐 Runs with just two secrets — a Discord bot token and a Vito API key.

How the purchase flow works

  1. Display a shop/shop posts an embed with a product picker.
  2. Initiate a purchase — clicking Buy calls POST /public/vito/v1/deduct, creating a payment request (no Vito moves yet).
  3. Payment-request DM — because the call includes your guildId, the Vito API DMs the buyer from your server's own bot with the product details and a secure link.
  4. PIN confirmation — the buyer enters their PIN on vetox.io (never inside Discord).
  5. Completion — the API debits the buyer, settles to the project owner, and fires a signed webhook / records the transaction.
  6. Delivery — the bot delivers a license key by DM and in the store reply, and logs every step.
sequenceDiagram
    participant B as Buyer (Discord)
    participant Bot as Vito Demo Bot
    participant API as Vito API
    participant Pay as vetox.io (PIN)

    B->>Bot: /shop → pick product → Buy
    Bot->>API: POST /deduct (discordId, amount, guildId, merchantRef)
    API-->>Bot: { confirmationId, confirmUrl, expiresAt }
    API-->>B: DM with secure confirm link (from your server's bot)
    Bot-->>B: "Payment requested" + Complete Payment button
    B->>Pay: Sign in, enter PIN
    Pay->>API: Confirm
    API-->>Bot: webhook confirmation.completed (signed)  ⟍
    API-->>Bot: GET /transactions shows it (poll)         ⟋ either path
    Bot-->>B: 🎉 License key (DM + store reply + log)
Loading

Architecture

A thin Discord layer over a small Vito API client. Each module has one job:

Module Responsibility
src/index.js Entry point: Discord client, /shop, the buy flow, completion detection, delivery, logging, boot & graceful shutdown.
src/config.js Loads and validates environment configuration.
src/vito.js Vito API client (auth, deduct, transactions) + webhook signature verification.
src/webhook.js Express receiver for signed Vito webhooks.
src/store.js Durable order lifecycle — file-backed (atomic writes), duplicate-buy guard, consumed-transaction set, and pruning. Single-instance; swap for a database if you run multiple instances.
src/ui.js All Discord embeds/components + license-key generation.
src/products.js Loads & validates the catalogue from products.json.

Data flow: index.js orchestrates everything. It calls vito.js to talk to the API, renders with ui.js, tracks orders in store.js, and receives webhooks through webhook.js. Completion arrives via either the webhook handler or the polling loop — both funnel into a single idempotent completeOrder.


Prerequisites


Quick start

# 1. Clone
git clone https://github.com/iRyoDev/vito-demo-bot.git
cd vito-demo-bot

# 2. Install
npm install

# 3. Configure — copy the template and fill in the two required values
cp .env.example .env
#   VITO_API_KEY=vito_live_…
#   DISCORD_BOT_TOKEN=…

# 4. Run
npm start

You should see a startup banner, /shop registered, and Vito API key: valid ✅. Run /shop in your server and buy something to exercise the full flow.

Only VITO_API_KEY and DISCORD_BOT_TOKEN are required. With no VITO_GUILD_ID set, /shop is registered globally (it can take up to ~1h to appear the first time). Set VITO_GUILD_ID for instant registration in your server.


Configuration

All configuration is environment-based — copy .env.example to .env and edit. Nothing in src/ needs changing to run.

Required

Variable Description
VITO_API_KEY Your Vito project key (vito_live_… / vito_test_…).
DISCORD_BOT_TOKEN Token of the bot that hosts this store (must be in your server).

Recommended

Variable Description
VITO_GUILD_ID Your server ID. Set it for instant /shop registration and as the originating guild for the payment-request DM. Leave blank to register globally and use each interaction's own guild.
VITO_WEBHOOK_KEY Webhook signing secret (whsec_…). Enables HMAC verification on the webhook path.

Optional

Variable Default Description
VITO_API_BASE_URL https://api.vetox.io Override only if you run your own Vito stack.
PORT 4000 Port for the local webhook receiver.
PUBLIC_WEBHOOK_URL Public HTTPS URL of this receiver (display only, e.g. your tunnel URL).
LOG_CHANNEL_ID Pin the log channel by ID. If unset, a #vito-demo-log channel is auto-created.
VITO_DATA_FILE data/store.json Where the durable order store is written (atomic JSON writes).
VITO_WEBHOOK_TOLERANCE_SEC 300 Reject a signed webhook whose timestamp is older/newer than this (replay protection). 0 disables the check.
SHOP_POST_ACCESS ADMINISTRATOR_ONLY Who may post the store into a channel — ADMINISTRATOR_ONLY or EVERYONE (see below).
SHOP_POST_PREFIX !shop Trigger word for the text (prefix) post command.
SHOP_POST_PREFIX_ENABLED true false disables the !shop prefix and drops the privileged Message Content intent (slash-only).

Posting the store to a channel

Besides the /shop slash command (which replies to you), there's a second way to drop the storefront into a channel as a normal message (sent via send, so it persists and everyone can browse/buy from it). Two triggers, same behaviour:

  • !shop — a prefix text command (SHOP_POST_PREFIX, default !shop).
  • /shop-post — a slash command.

Who can use it is controlled server-side by SHOP_POST_ACCESS:

Value Meaning
ADMINISTRATOR_ONLY (default) Only members with the Administrator permission.
EVERYONE Any member.

⚠️ The !shop prefix requires the privileged "Message Content" intent. Enable it in the Discord Developer Portal → your App → BotPrivileged Gateway IntentsMessage Content, or the bot will fail to log in (it prints a clear hint if this happens). Don't want the privileged intent? Set SHOP_POST_PREFIX_ENABLED=false — the prefix is disabled, the intent is not requested, and the bot runs on non-privileged intents with only the /shop-post slash command (which never needs the intent).


Customizing the store

The catalogue lives in products.json at the repo root — edit it to change the storefront without touching any code. Each entry:

{
  "id": "game-pro-1y",
  "name": "GamePro License — 1 Year",
  "description": "Unlock all premium features in GamePro for 12 months.",
  "price": 25,
  "type": "digital_key",
  "category": "Apps & Software",
  "available": true,
  "imageUrl": "https://picsum.photos/seed/gamepro/400/240"
}
Field Notes
id Unique string. Duplicate ids are dropped with a warning.
name / description Shown in embeds; also sent to the Vito API. name may contain Discord custom-emoji markup (e.g. <a:Nitro:123…>) — it renders in embeds, and the picker falls back to the emoji's name so labels never show raw markup.
price Positive integer, in Vito. This is the only source of truth for what a buyer is charged — read server-side, never from the client. Keep it small so a test wallet can cover it.
type Product type sent to the API (e.g. digital_key).
category (optional) Grouping label for the storefront. Defaults to Other.
available (optional) false hides the product from the picker and blocks purchase. Defaults to true.
imageUrl Must be a direct https:// image URL — a page URL (e.g. an image-host landing page) won't render as a Discord thumbnail and the Vito API may reject it.

Invalid entries are skipped with a warning; if the file is missing or empty, a built-in demo catalogue is used so the bot always runs.

The /shop banner is assets/vito-banner.png — replace that file to rebrand the storefront (it's attached to the embed, so no image host is needed). If the file is absent, the embed simply renders without a banner.


Completion detection: webhook vs polling

The bot confirms a purchase two ways, and the log tells you which one fired:

  1. Polling (default, zero setup). Polls GET /public/vito/v1/transactions every few seconds and delivers as soon as the deduction completes. Needs the transactions:read scope; if it's missing, the bot logs a one-time notice.

  2. Webhook (instant, signature-verified). When you point your project's callback URL at this server, deliveries arrive immediately and are HMAC-verified.

Both routes converge on one idempotent delivery, so even if a webhook and a poll race, the key is delivered exactly once.


Setting up webhooks

  1. Expose the bot over HTTPS — for local dev, use a tunnel:
    ngrok http 4000
    # or:  cloudflared tunnel --url http://localhost:4000
  2. In the Vetox dashboard, set your project's callback URL to https://<your-tunnel>/webhook/vito (callback URLs must be https://).
  3. Set VITO_WEBHOOK_KEY (whsec_…) so the bot verifies the X-Vito-Signature HMAC and labels deliveries "signature verified".

Full event list, payload fields, and the signature scheme are in docs/VITO_API.md §5.


Discord bot setup

Invite the bot with the bot and applications.commands scopes.

Permission Why
Send Messages Post the storefront and replies.
Attach Files Attach the /shop banner image.
Manage Channels (optional) Auto-create the #vito-demo-log channel. Without it, logging falls back to the console.

Gateway intents — the core flow uses only the non-privileged Guilds intent. The !shop prefix command additionally needs Guild Messages + the privileged Message Content intent: enable Message Content in the Developer Portal → your App → BotPrivileged Gateway Intents, or the bot won't log in. If you don't want the privileged intent, use the /shop-post slash command instead (no extra intent needed) — see Posting the store to a channel.

The buyer should share a server with the bot so the payment-request and license DMs can reach them — the confirmUrl works regardless.


Production deployment

For a complete, command-by-command guide to running this as a real service — process manager (pm2 / systemd) with auto-restart, exposing the webhook, verifying the deployment, updating, backups & recovery, managing products safely, and the digital-key delivery / fulfilment flow — see docs/DEPLOYMENT.md.

Quick production install:

npm ci --omit=dev
npm test                              # optional; no secrets needed
npm i -g pm2
pm2 start src/index.js --name vito-store --time
pm2 save && pm2 startup               # restart on boot

Fulfilment is a placeholder. newLicenseKey() mints a random key, not a real product key. Wire your own fulfilment into completeOrder (src/index.js) — the single, idempotent delivery point — before selling real goods. See docs/DEPLOYMENT.md.


Vito API reference

Every endpoint, webhook event, scope, signature detail, and Discord permission is documented in docs/VITO_API.md — the canonical reference for reproducing this integration. Endpoints used:

Method Path Purpose
GET /public/vito/v1/auth/verify Validate the API key (startup).
POST /public/vito/v1/deduct Create a payment request.
GET /public/vito/v1/transactions Poll for completion (fallback).

Inbound: POST /webhook/vito receives confirmation.completed / .failed / .cancelled / .expired events.


Project structure

vito-demo-bot/
├─ src/
│  ├─ index.js        # entry: Discord client, buy flow, completion, delivery, boot
│  ├─ config.js       # env loading + validation
│  ├─ vito.js         # Vito API client + webhook signature verification
│  ├─ webhook.js      # Express webhook receiver
│  ├─ store.js        # durable, file-backed order lifecycle + dedup + pruning
│  ├─ ui.js           # Discord embeds/components + license-key generation
│  └─ products.js     # catalogue loader/validator
├─ test/              # node --test suite (products, store, vito, ui)
├─ assets/            # storefront banner (vito-banner.png)
├─ docs/
│  ├─ VITO_API.md     # endpoint, webhook, scope & permission reference
│  └─ DEPLOYMENT.md   # production deploy, operations, backup & recovery guide
├─ data/              # persisted order store (gitignored, auto-created)
├─ products.json      # editable product catalogue
├─ .env.example       # configuration template
├─ package.json
└─ README.md

Testing

A dependency-free test suite runs on the built-in Node test runner:

npm test        # node --test

It covers the catalogue loader/validation, the durable order store (duplicate-buy protection, persistence round-trip, pruning), webhook signature + replay-window verification, and the UI helpers (emoji-safe picker labels, availability). No Discord token or Vito key is needed — the suite exercises the pure logic paths.


Troubleshooting

Symptom Likely cause / fix
Missing required config: … Set VITO_API_KEY and DISCORD_BOT_TOKEN in .env.
Vito API key check failed Wrong/expired key, or it lacks access. Check the dashboard.
/deduct rejected Missing deduct:create scope, TOS not accepted, inactive membership, insufficient balance, or a non-https product image. The exact reason is shown in Discord.
/shop doesn't appear Global registration can take ~1h. Set VITO_GUILD_ID for instant registration.
Completion never detected Add the transactions:read scope or configure a webhook tunnel.
Webhook returns 401 The signature didn't verify or its timestamp is outside the replay window — confirm VITO_WEBHOOK_KEY matches the dashboard secret and the server clock is roughly correct (or raise/disable VITO_WEBHOOK_TOLERANCE_SEC).
No #vito-demo-log channel Grant Manage Channels, or set LOG_CHANNEL_ID. Logs still go to the console.

Security notes

  • Secrets stay in .env (gitignored). Never commit .env; share only .env.example.
  • Webhooks are secure by default: with no VITO_WEBHOOK_KEY set, inbound webhooks are ignored (acked but not acted on) so a forged delivery can't hand out a key — set the secret to enable the webhook completion path (polling still delivers meanwhile). When set, the HMAC is computed over the raw request body with a constant-time comparison, and deliveries outside VITO_WEBHOOK_TOLERANCE_SEC are rejected as replays.
  • Prices are server-authoritative — the client only ever sends a product id; the amount charged is read from products.json, never from the client.
  • Use a stable idempotency key per purchase (this bot uses the order id) so retries can't double-charge. A duplicate/concurrent Buy for the same product reuses the in-flight order instead of opening a second charge.
  • Orders are persisted to data/store.json with atomic writes, so deliveries survive a restart and a transaction is never fulfilled twice. This is a single-instance store — for multiple instances, move orders and the fulfilled-transaction set into a shared database with unique constraints.

License

MIT © Ryo (iRyoDev). The storefront, products, and license keys are illustrative; this project demonstrates the Vito payment flow and is not a real store.

About

Standalone Discord bot demoing the Vetox Vito API purchase flow end-to-end — shop, buy, PIN confirmation, and license delivery, all inside Discord.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages