Building a Storefront
A custom storefront is your own website selling your PayNow products. PayNow handles pricing, tax, payment, delivery and subscriptions. You handle the pages. The pages that follow walk the build in order, and this one is the map.
If you would rather not write code, the hosted webstore does all of this for you. Come here when you want full control over the design or want to embed the store in an existing site.
What you need
| Thing | Where it comes from | Where it lives |
|---|---|---|
| Store ID | Dashboard → Store Settings | Server and browser, it is not secret |
| API key | Dashboard → API Keys | Server only. It controls the store. |
| Customer tokens | Minted per signed-in customer, see Signing Customers In | Server, or an httpOnly cookie |
@paynow-gg/typescript-sdk | npm | Typed clients for both APIs, see TypeScript SDK |
@paynow-gg/paynow.js | npm or CDN | Opens the checkout in an overlay on your page, see PayNow.js |
Products, tags and game servers are configured in the dashboard first. The storefront reads them - it does not define them. API Behaviour Notes covers that and a few other rules the endpoint reference leaves implicit.
The two APIs, and which side calls them
The Storefront API is the customer's view - products with final prices, their cart, their orders and subscriptions, and checkout creation. It authenticates with a customer token and is safe to call from a browser, because PayNow validates prices and products server-side.
The Management API is yours - it authenticates with the API key and can change anything in the store. It never leaves your server.
In a storefront it does exactly two jobs:
- Looking up or creating the customer record for someone who just signed in.
- Minting their customer token.
Catalog reads (products, tags, store details) accept either credential. A server can fetch them with the API key and cache the result. A browser can fetch them with a customer token.
Never ship the API key to a browser, a mobile app or a game client, and never put it in a public repository. If it leaks, rotate it in the dashboard immediately.
Build order
- Catalog pages. List products with
GET /v1/store/products, optionally filtered by tag slug, and render each product'spricing.price_final. Read Carts and Checkout for what the pricing fields mean. - Sign-in. Establish who the customer is and get them a customer token. The flow depends on the store's platform. Signing Customers In outlines this.
- Cart or direct checkout. Either keep a server-side cart with the cart endpoints, or build a checkout session straight from a list of lines. Carts and Checkout.
- Open the checkout. Redirect to the session's
url, or open it in place with PayNow.js and react to thecompletedevent. Also in Carts and Checkout. - Gifting, if you want it. Gifting covers the parameters and the rules.
- Account pages. Orders, subscriptions with cancel, and what the customer currently owns. Account Pages.
- Delivery. Nothing on the website side. When an order completes, PayNow delivers it the way your store is configured: a game server store runs the product's commands through the PayNow plugin or the Gameserver API, and any other store receives a delivery webhook that your own backend fulfils. Confirm one of those is in place with Delivery Methods before you test a purchase.
A minimal server-side client
The SDK's storefront client takes the store ID and an optional customer token. Create one per request with whichever token the signed-in customer has.
import { createManagementClient, createStorefrontClient } from "@paynow-gg/typescript-sdk";
const management = createManagementClient({
apiKey: process.env.PAYNOW_API_KEY!,
storeId: process.env.PAYNOW_STORE_ID!,
});
function storefrontFor(customerToken?: string) {
return createStorefrontClient({
storeId: process.env.PAYNOW_STORE_ID!,
customerToken,
});
}
const products = await storefrontFor().products.getStorefrontProducts();Both clients accept baseUrl, headers and a custom fetch in their options. Use headers to forward the customer's IP on storefront calls, and fetch if your framework caches or instruments requests.
Forwarding the customer's IP and Country Code
PayNow resolves regional pricing, tax and currency from the customer's location. When your server makes storefront calls on the customer's behalf, PayNow sees your server's address, not theirs.
Pass x-paynow-customer-ip on those calls so prices match what the customer will pay at checkout. If you know the customer's country, pass x-paynow-customer-countrycode as well, as an ISO 3166-1 alpha-2 code such as GB.
If your site sits behind Cloudflare, both values are already on the request. CF-Connecting-IP is the visitor's real IP address. CF-IPCountry is their country code, added when IP Geolocation is enabled under Network in the Cloudflare dashboard.
await storefrontFor(token).cart.addLine(
{ product_id: productId, quantity: 1 },
{
headers: {
"x-paynow-customer-ip": request.headers.get("cf-connecting-ip"),
"x-paynow-customer-countrycode": request.headers.get("cf-ipcountry"),
},
},
);Without Cloudflare, use the first address in X-Forwarded-For from a proxy you control, or the socket address if nothing sits in front of your server. Calls made directly from the browser do not require any of this.
You should only trust these headers when the request genuinely came through Cloudflare or your own proxy. Anyone who can reach your origin directly can set them to whatever they like.