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.
/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:
- Membership. A price and a billing frequency: weekly, monthly, every 3 months or yearly. You can offer a yearly plan with a percentage off.
- Single item. Can be bought any number of times, like in-game coins.
- One-time item. Can only be bought once, like DLC.
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
- Free trial (memberships): pick the length in days, and whether it just ends or starts billing. If it starts billing, the person has to agree to that at checkout, which tells them the date and the amount, and that it keeps billing until they cancel in My Account > Payments. They're reminded before it starts.
- Discount: some credits off, with an optional end date. On a membership it's the first payment only, like 10 credits off your first month. On an item it's off each one while the offer runs.
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
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
- What it can hold: up to 20 items, each a product your bot's owner sells or an amount of credits. The money always goes to the owner.
- Who can pay: anyone signed in, once. Set
buyer(username or id) and only they can. - Status:
open,paid,cancelledorexpired. Give access only onpaid, and usebuyerto see who it was. There are no webhooks: poll.expires_inis 60 seconds to 7 days, default 1 hour. - Retries: the same
order_idwith the same items returns the same cart. Different items getorder_id_conflict. - Memberships: fine in a cart, the person agrees to the billing at checkout. The status tells you it was paid; use
GET /bot/cartsfor the record.
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":[...]}}
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
- Renewing: charged at the end of each period, at the price they signed up at.
- Can't pay: you choose. By default it pauses (no access) until they pay or cancel, and paused memberships are never charged on their own. Or it can just end.
- Cancelling: the buyer cancels in My Account > Payments and keeps it until the period ends, with no partial refund. You can end someone's membership the same way from Products.
- Archiving a product stops new sales. Existing members keep renewing until they or you end it.
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_found | It doesn't exist or isn't for sale. |
self_purchase, self_transfer | You can't buy from or pay yourself. |
already_owned | They already have this one-time item or game. |
invalid_amount | A payment needs a whole number of credits, 1 or more. |
cart_full | A cart holds 50 lines. |
insufficient_scope | The token doesn't have the permission for this. |
order_id_conflict | A bot used that order_id for a different cart. |
checkout_required | This bills later, so the person has to agree on the checkout page. |
item_problem | Something in a direct payment can't go through; lines[].problem says what. Nothing was charged. |
website_session_required | Only a website sign-in can do this: reading the cart, paying, managing products. |