Guide

Take payments: products, memberships and checkout

Create what you sell in My Account, add it to someone's cart from your project, and they pay on one checkout page that shows exactly who gets what.

The old payment system retires on December 3rd. /currency/transfer, payment popups (/currency/transfer-request and the old transfer block), /store/game/:id/buy, /currency/subscribe and bot payment requests keep working until then. Move to products and the cart before that.

Everything is in credits

Checkout spends Nookara credits, the same balance as everything else. The credits go straight to the seller.

Create a product

Go to My Account > Products. A product has a name, a price, an optional icon and description, and one of three types:

Link it to one of your apps and checkout shows that app's logo as the one managing the payment. A linked product, its members and its sales then live inside the app: My Account > Apps, then Payments. The type can't change after it's created. A new price applies to new buyers: members keep the price they signed up at, and anyone with it in their cart sees the change before paying.

Free trials and discounts

Each person gets a product's trial and first-payment discount once, and a discount always leaves at least 1 credit to pay.

Sell it

The simplest way is a link. Copy it from your product: https://nookara.cc/cart/add/prod_... adds it and opens checkout. Add ?plan=yearly or ?quantity=3 if you want.

From a PenguinMod or TurboWarp project

add 1 of product prod_... to cart add membership prod_... to cart on yearly plan add payment of 10 credits to username to cart with note open checkout and wait has product prod_...? times bought of product prod_... membership status of product prod_... info of product prod_...

From your own code

POST /cart/items   Authorization: Bearer TOKEN
{"product_id":"prod_...", "plan":"yearly", "quantity":1}
  or {"to":"username", "amount":10, "note":"thanks"}
  or {"game_id":"c-abcd1234"}
-> {"id":12, "count":2, "checkout_url":"https://nookara.cc/checkout"}

GET /products/prod_.../access   Authorization: Bearer TOKEN
-> {"product_id":"prod_...", "type":"membership", "owned":true, "count":0,
    "membership":{"status":"trialing", "plan":"period", "period":"monthly",
                  "current_period_end":"...", "cancel_at_period_end":false}}

GET /products/prod_...     public details
GET /products?seller=name  what someone sells
from velora_api import Client

api = Client(token, origin=origin)
print(api.add_to_cart("prod_...", plan="yearly")["checkout_url"])
if api.product_access("prod_...")["owned"]:
    print("unlocked")

Adding to the cart needs no credits scope: it moves no money, and the person sees everything at checkout. Asking /access needs View Credits, unless your app is the one linked to the product. Give access based on /access, not on anything a page tells you.

From a bot

A bot never needs a user token or a sign-in. It makes a cart with its bot token and gets a link; whoever opens the link signs in on Nookara and pays on the checkout page. The buyer doesn't have to be known up front.

POST /bot/carts   Authorization: Bot BOT_TOKEN
{"order_id":"ord-1001",
 "items":[{"product_id":"prod_...","plan":"yearly"}, {"amount":20,"description":"Premium pack"}],
 "buyer":"alice", "expires_in":3600, "return_url":"https://shop.example/done", "metadata":"yours"}
-> 201 {"id":"cart_...","url":"https://nookara.cc/checkout/cart_...","status":"open", ...}

GET  /bot/carts/cart_...          or  GET /bot/carts?order_id=ord-1001
-> {"status":"paid","buyer":{"id":"260","username":"alice"},"total_paid":20,"payment_id":"ord_...","paid_at":"...", ...}

POST /bot/carts/cart_.../cancel
from velora_api import BotClient

bot = BotClient(BOT_TOKEN)
cart = bot.create_cart([{"amount": 20, "description": "Premium pack"}], "ord-1001")
print(cart["url"])                     # send this to the buyer
cart = bot.wait_for_cart(cart["id"])
if cart["status"] == "paid":
    print("paid by", cart["buyer"]["username"])

Keep a bot token on a server you control, never in a project or a web page.

Paying without the checkout page

A project the person gave Transfer Credits can pay by itself, with no page and no captcha. It's for what you name in the request, never for what's in their cart, and the person gets a notification every time.

POST /checkout/direct/quote   {"items":[{"product_id":"prod_...","quantity":2}]}
-> {"total":20, "lines":[{"name":"Coins","amount":20,"pay_to":"seller","problem":null,"recurring":false}]}

POST /checkout/direct   {"items":[...], "expected_total":20, "idempotency_key":"16 to 64 of A-Z a-z 0-9 _ -"}
-> {"order":{"id":"ord_...","kind":"direct","total":20,"paid_by":"Your App","lines":[...]}}
buy 1 of product prod_... without checkout pay 10 credits to username with note without checkout

expected_total has to match or nothing is charged (price_changed). Send the same key on a retry: it never charges twice. Everything in one request goes through together or not at all. Anything that bills later, a membership that renews or a trial that starts billing, gets checkout_required: the person has to agree to that on the checkout page. A website session gets use_checkout. Only ask for Transfer Credits if you need this: without it you can still sell everything through checkout.

What a project can't do

Your token can't read the cart or pay for it. Prices always come from the server: anything else you send with a product is ignored.

What the buyer sees

The checkout page lists every item with its price, who the money goes to, and the app managing it. Apps are marked like on the sign-in screen: unverified is marked, a verified app shows nothing extra, official is marked. A legacy project that added something shows its name, marked unverified. Items from different sellers can share a cart, and either everything goes through or nothing does.

Memberships over time

Refunds

You refund a whole sale from Products, out of your balance. A refund takes a one-time item away, and refunding the period someone is in ends their membership. Payments to a person can't be undone. Hyperfocus games keep their own refund button while they've been played under 30 minutes.

Errors

product_not_foundIt doesn't exist or isn't for sale.
self_purchase, self_transferYou can't buy from or pay yourself.
already_ownedThey already have this one-time item or game.
invalid_amountA payment needs a whole number of credits, 1 or more.
cart_fullA cart holds 50 lines.
insufficient_scopeThe token doesn't have the permission for this.
order_id_conflictA bot used that order_id for a different cart.
checkout_requiredThis bills later, so the person has to agree on the checkout page.
item_problemSomething in a direct payment can't go through; lines[].problem says what. Nothing was charged.
website_session_requiredOnly a website sign-in can do this: reading the cart, paying, managing products.

More guides