Payments and webhooks
Every payment is a Rotur payment request that the person approves on rotur.dev. Rotur tells mistwarp-api about payments, account changes and privacy requests through a signed webhook.
Payments
Buying a game product, donating with a comment and refunding a game product are Rotur payment requests (POST /v2/apps/<app>/payment-requests). mistwarp-api never holds a permission to spend anyone's credits.
- The editor asks for an intent. mistwarp-api asks Rotur for the payment, with a purchase key (
mwbuy_,mwgame_,mwdonate_ormwrefund_) as itsreference, and answers with the key and Rotur'sapproveUrl. - The person approves it on rotur.dev with their password. Rotur moves the credits, shared between everyone the sale pays: the creator, collaborators, the remixed project's creator and MistWarp's fee.
- Rotur's
payment.completedwebhook marks the key paid and delivers game products. The editor's confirm call does the same if it gets there first, asking Rotur whether the request was paid. Whichever comes second finds it done.
Donations are delivered when their comment is posted, and refunds when the product is revoked. Paid keys waiting for that are kept for 30 days, and unpaid ones for an hour (Rotur's requests expire after 15 minutes).
Project sales have ended
Projects can no longer be sold. POST /v1/projects/:id/purchase/intent and /purchase/confirm answer 410 with the code project_sales_ended, and a PUT /v1/projects/:id that sets a price above 0 answers 400 with the same code. A project that had a price plays, and can be remixed and seen inside, the same as a free one with the same settings. Remixes of paid projects are no longer kept private. The webhook still records a mwbuy_ payment that was started before sales ended, so it can be refunded.
What was sold stays readable: buyers keep their records in GET /v1/me/purchases, and creators keep the buyers, revenue and sales history in their project analytics and GET /v1/me/stats.
scripts/refund_project_sales.py pays every buyer back the full price of every project they bought, from one Rotur account, with one transfer per buyer (POST /v2/me/transfers). It sends nothing without --apply. Each transfer is written to data/project-sale-refunds.json before it is sent and marked when Rotur answers, so running it again never pays a purchase twice. It stops if a project purchase can still be paid on Rotur, if the token doesn't belong to --payer, if the payer doesn't have enough credits, or if an earlier transfer's outcome is unknown (check the payer's Rotur transactions, then --resolve <id>=sent or =failed).
python3 scripts/refund_project_sales.py
ROTUR_REFUND_TOKEN=<token of the paying account> python3 scripts/refund_project_sales.py --apply --payer <that account>
Tests: python3 -m unittest scripts/test_refund_project_sales.py, and with a compiled binary MWAPI_BIN=<path> python3 -m unittest scripts/test_project_sales_api.py.
Rotur webhooks
Set the MistWarp Rotur App's webhook to https://api.mistwarp.org/v1/rotur/webhook on rotur.dev/me/developer, with privacy requests on, and put its signing secret in ROTUR_WEBHOOK_SECRET.
user.deleted,user.leftanduser.bannederase everything MistWarp holds about that person, the same as deleting their data from Settings.- A
privacy.requestforerasuredoes the same. One foraccesssends them a MistWarp notification pointing to the download in Settings. payment.completedfinishes the purchase it names (see Payments).
Deliveries are checked against Rotur-Signature and refused if their timestamp is more than five minutes out. Each is saved to data/rotur-webhooks.json, answered, then handled in the background, and a retried delivery is only handled once. Anything not yet handled when the server stops is handled when it starts.
Rotur only sends these for people who have used MistWarp through Sign in with Rotur, or whose account was made on MistWarp. The five-minute check of /accounts/deleted_check stays for everyone else.