mPAKT Compute Documentation

Taking card payments owner's guide

Let people buy mPAKT Compute credits with a card, through Stripe Checkout. Nobody enters a card number on this site; Stripe's page takes it, then tells the server, and the server adds the credits. About twenty minutes, most of it in Stripe's dashboard.

How it works

Someone picks an amount on /compute and presses Pay with card. The server asks Stripe for a Checkout page priced from the number of credits (1,000 credits = 1.00; the client never sends a price), and sends the browser there. When the payment succeeds Stripe calls POST /api/stripe/webhook, the server checks the signature and adds the credits, once, however many times Stripe repeats the call. A refund or a dispute takes the matching share of the credits back.

Bonus credits. People who volunteer their devices earn a contributor tier (Bronze, Silver or Gold; see compute/rewards.js), and a tier adds bonus credits to a card purchase: 5%, 10% or 20% on top. The price never changes: they pay for the credits they asked for and the bonus is extra. The bonus is worked out by the server when the Checkout Session is created, shown on Stripe's page as part of the product name (“… + 50 contributor bonus”), and carried in the session's metadata together with a signature only this server can make (an HMAC keyed with STRIPE_WEBHOOK_SECRET). The webhook credits a bonus only if the signature checks out and the amount is within the largest tier's rate; anything else is credited as an ordinary purchase without a bonus. You set nothing up for this, and it needs no extra Stripe permission or event. One consequence: if you change STRIPE_WEBHOOK_SECRET (for instance when going live) while a customer is paying, that one purchase is credited without its bonus; add the bonus by hand.

With the two secrets below unset nothing changes: the button is hidden and people ask you for credits by hand, as before.

Set-up

  1. Create a Stripe account

    Sign up at dashboard.stripe.com/register. You can do everything up to step 9 before Stripe has verified your business. Stay in test mode (the switch at the top of the dashboard; every key and webhook below exists once for test and once for live) until step 9.

  2. Make a restricted secret key

    Developers → API keys → Create restricted key. Name it mpakt-grid. Leave every permission at None except:

    • Checkout Sessions: Write (this is all the server uses; it creates a session and that is the only call it makes to Stripe)

    Refunds and disputes need no extra permission: the webhook event already carries everything the server needs. If a test purchase fails with a permissions error, open the key's View request logs (the ⋯ menu next to it): Stripe names the exact permission the failed request needed — add that one and nothing else. Copy the key (rk_test_…) — it is shown once. A full secret key (sk_test_…) also works but can do far more if it ever leaks; prefer the restricted one.

  3. Create the webhook endpoint

    Developers → Webhooks → Add endpoint.

    • Endpoint URL: https://YOUR-PUBLIC-HOST/api/stripe/webhook (the address the world reaches this server at, over https)
    • Events to send — exactly these four:
      • checkout.session.completed
      • checkout.session.async_payment_succeeded (bank debits and other delayed methods)
      • charge.refunded
      • charge.dispute.created

    After saving, reveal the Signing secret (whsec_…) and copy it. If you use Cloudflare, make sure no WAF or bot rule blocks /api/stripe/webhook; Stripe's calls are plain POSTs from the internet.

  4. Store the secrets, encrypted

    On the server, from /srv/mpakt_vtmu, as the service user. Each command asks for the value without echoing it and writes it to .env (mode 600) encrypted with AES-256-GCM; the first run creates the master key:

    npm run admin -- env set STRIPE_SECRET_KEY
    npm run admin -- env set STRIPE_WEBHOOK_SECRET
    npm run admin -- env list        # names and "encrypted", never values

    Plain settings go in the same .env (PUBLIC_URL=https://YOUR-PUBLIC-HOST: where people are sent back to after paying; set it if it is not already). Back up ~/.config/mpakt-grid/env.key, or use a systemd credential to bind the key to the machine; see the README, "Secrets". No systemd drop-in is needed.

    Optional, in .env:

    STRIPE_CURRENCYdefault usd. Any two-decimal currency Stripe supports; 1,000 credits cost 1.00 of it, so only change it if you want that.
    STRIPE_MIN_USDdefault 5: the smallest purchase (Stripe has its own minimum around 0.50; fees make tiny sales a loss).
    STRIPE_MAX_USDdefault 500: the largest purchase.

    (The names say USD; the amounts are in STRIPE_CURRENCY's units.)

  5. Restart the service

    sudo systemctl restart mpakt_vtmu.service
    journalctl -u mpakt_vtmu.service -n 20 --no-pager | grep stripe

    You should see [stripe] card payments on. Restarting drops volunteers' connections for a moment; they reconnect by themselves. Open /compute: the Credits box now has Buy credits, and the Owner box says card payments are on.

  6. Try it with a test card

    Buy the smallest amount. On Stripe's page use card number 4242 4242 4242 4242, any future expiry, any CVC and postcode. You land back on /compute, the credits appear within seconds, and the purchase is listed under Purchases (with its bonus, if the account has a tier). In the dashboard, Developers → Webhooks → your endpoint → Event deliveries should show the four event types answered with 200. Other cards worth trying: 4000 0000 0000 9995 (declined) and 4000 0025 0000 3155 (asks for 3-D Secure).

    Without a public address you can still test locally with the Stripe CLI: stripe listen --forward-to localhost:8008/api/stripe/webhook prints a temporary whsec_… to use as STRIPE_WEBHOOK_SECRET.

  7. Try a refund

    In the dashboard open the test payment and press Refund. The buyer's credits drop by the same share (a half refund takes back half of the credits, bonus included) and the Purchases list shows the amount refunded. A balance can go below zero; a box with no credits answers 402 until the buyer tops up. If a refund arrives for credits already spent, that is the cost of refunding.

  8. Decide your pricing

    The price of a credit is fixed at 1.00 per 1,000 credits; what credits buy is the price table on the Credits box (requests, GB-seconds, traffic), set in compute/service.js (PRICES). Stripe takes a fee from every payment (in the US around 2.9% + 30 cents), which is why the minimum purchase defaults to 5. Check the figures for your country on stripe.com/pricing. You may also owe tax or need terms of sale; that is outside this software.

  9. Go live

    Complete Stripe's business activation (Settings → Business). Then switch the dashboard to live mode and repeat steps 2 and 3 there: a new restricted key (rk_live_…) and a new webhook endpoint with the same URL and the same four events, which has a different signing secret. Put both into /etc/mpakt_vtmu/stripe.env, then sudo systemctl restart mpakt_vtmu.service. Make one small real purchase yourself and refund it.

    Test and live keys do not mix: a live key with the test webhook's secret (or the reverse) makes every payment arrive but never be credited. If someone paid and got nothing, look under Webhooks → Event deliveries for 400 answers first.

Day to day

Back to mPAKT Compute. Environment variables are also listed in the README.