Skip to Content
DocsVXShop2. Implement Shop

2. Implement Shop

This guide walks you through implementing VXShop in your game.

Three Steps

  1. Create Purchase UI - Design shop buttons in your game
  2. Integrate VXShop - Connect buttons to VXShop payment dialogs ⭐
  3. Handle Purchase - Update user state on server when purchase completes

Step 2 is the key integration point. Steps 1 and 3 are standard game development.

Step 1: Create Purchase UI

Design your shop interface however you like:

  • Dedicated shop screen
  • In-game popup menus
  • Purchase buttons within gameplay

VXShop doesn’t impose any UI requirements. Match your game’s style.

Step 2: Integrate VXShop

Option A: Vibe Coding

Tell Agent8 to connect your button to VXShop:

"Connect the Remove Ads button to VX Shop. The product ID is remove-ads."

Product ID Required

Always provide the exact Product ID from your Verse8 VX Shop dashboard.

Option B: Manual Implementation

Review Your Code

Payment logic must always be reviewed manually. Incorrect flows can lead to CPP removal.

Installation

npm install @verse8/platform

Basic Usage

import { useVXShop } from '@verse8/platform'; import { useEffect } from 'react'; function Shop() { const { getItem, buyItem, refresh, onClose } = useVXShop(); // Refresh when purchase dialog closes useEffect(() => { const cancel = onClose(() => refresh()); return cancel; }, [onClose, refresh]); return ( <button onClick={() => buyItem('remove-ads')}> Remove Ads </button> ); }

That’s it! buyItem() opens the VXShop payment dialog.

Advanced: Handling Purchase Limits

Show different button states based on purchase availability:

function Shop() { const { getItem, buyItem, refresh, onClose } = useVXShop(); useEffect(() => { const cancel = onClose(() => refresh()); return cancel; }, [onClose, refresh]); const item = getItem('remove-ads'); return ( <div> {/* Already purchased */} {item?.purchaseLimitReached && ( <button disabled>Purchased</button> )} {/* Can purchase */} {item?.purchasable && !item.purchaseLimitReached && ( <button onClick={() => buyItem(item.productId)}> {item.price} VX </button> )} {/* Cannot purchase */} {!item?.purchasable && ( <button disabled> {item?.purchaseBlockReason || 'Cannot Buy'} </button> )} </div> ); }

Advanced: Showing Remaining Purchases

For items with purchase limits, show remaining count:

function Shop() { const { getItem, buyItem, refresh, onClose } = useVXShop(); useEffect(() => { const cancel = onClose(() => refresh()); return cancel; }, [onClose, refresh]); const item = getItem('event-pack'); // Item with purchase limit (e.g., 3 times max) if (item?.purchaseLimit) { const remaining = item.remainingPurchaseQuantity; const limit = item.purchaseLimit; return ( <button onClick={() => buyItem(item.productId)}> Buy {item.price} VX ({remaining}/{limit}) </button> ); } // Unlimited purchase item return ( <button onClick={() => buyItem('event-pack')}> Buy for 500 VX </button> ); }

Step 3: Handle Purchase on Server

When a purchase completes, update user state on your game server.

Server-Side Required

All purchase state changes must happen server-side for security.

Basic Handler

In server.js, implement $onItemPurchased:

// server.js async $onItemPurchased({ account, purchaseId, productId, quantity, metadata }) { switch (productId) { case "remove-ads": await $global.updateUserState(account, { adsRemoved: true }); break; case "gold-pack-1000": const userState = await $global.getUserState(account); await $global.updateUserState(account, { gold: (userState.gold || 0) + 1000 }); break; } return { success: true }; }

Parameters:

  • account - User’s account ID
  • purchaseId - Unique transaction ID
  • productId - Product ID from VX Shop
  • quantity - Number purchased
  • metadata - Custom data (if configured)

The game server automatically syncs state to the client in real-time.

👉 Learn more about state management

Using Metadata

If you configured metadata for your product:

// server.js async $onItemPurchased({ account, purchaseId, productId, quantity, metadata }) { if (productId === 'item-pack') { const data = JSON.parse(metadata); const userState = await $global.getUserState(account); await $global.updateUserState(account, { items: { ...userState.items, [data.itemType]: (userState.items?.[data.itemType] || 0) + data.quantity }, gold: (userState.gold || 0) + data.bonusGold }); } return { success: true }; }

Testing Checklist

Before going live:

  • ✅ Purchase buttons open correct products
  • ✅ Payment dialog displays correctly
  • ✅ Purchases update user state
  • ✅ State syncs to client immediately
  • ✅ Purchase limits work correctly
  • ✅ Error messages display properly

Test Thoroughly

Incorrect payment flows can harm user experience and lead to CPP removal.

API Reference

useVXShop() Hook

interface UseVXShopResult { items: VXShopItem[]; // All available items isLoading: boolean; // Loading state error: string | null; // Error if any getItem: (productId: string) => VXShopItem | undefined; buyItem: (productId: string) => void; // Opens payment dialog refresh: () => Promise<void>; // Refresh items onClose: (callback: (payload: VXShopDialogClosedPayload) => void) => () => void; }

VXShopItem Interface

interface VXShopItem { id: number; verseId: string; productId: string; // Your Product ID name: string; price: number; // Price in VX stock: number; imageUrl: string; description: string; metadata: string | null; // JSON string purchasable: boolean; // Can user buy this? purchaseBlockReason?: string; // Why blocked remainingPurchaseQuantity: number | null; purchaseLimit: number | null; purchaseConstraints: PurchaseConstraints | null; purchasedCount: number; purchaseLimitReached: boolean; }

VXShopDialogClosedPayload Interface

interface VXShopDialogClosedPayload { purchased: boolean; // Was purchase completed? productId: string; action: "purchased" | "closed"; }

Next Steps

Your VXShop integration is complete!

Want to see complete implementation examples?

Additional resources:

Last updated on