Skip to main content

Payments

This page is the reference for integrating Pi payments. Use the Pi SDK (window.Pi from https://sdk.minepi.com/pi-sdk.js) on the frontend and the Platform API on the backend.

For the conceptual three-phase flow, see Developer Payment Flow. For method signatures, see the Client SDK reference.

Implementation Workflow

Initialize the Pi SDK

Every Pi App must include the SDK script and call Pi.init() before any other SDK methods.

<head>
<!-- ... other <head> content (meta, title, styles, etc.) ... -->
<script src="https://sdk.minepi.com/pi-sdk.js"></script>
</head>
Pi.init({ version: "2.0", sandbox: true }); // use sandbox: false in production

Authenticate, then create a payment

Authenticate first (the payments scope is required). Then call Pi.createPayment with callbacks that forward paymentId and txid to your backend.

async function handleAuth() {
try {
const auth = await window.Pi.authenticate(
['username', 'payments'],
onIncompletePayment
);
console.log(`User ${auth.user.username} is logged in.`);
return auth;
} catch (err) {
console.error("Auth failed", err);
}
}

function onIncompletePayment(payment) {
// Send payment.identifier to your backend so it can complete the interrupted payment
}

function buyItem(accessToken) {
window.Pi.createPayment(
{ amount: 1, memo: 'Unlock Premium', metadata: { feature: 'premium' } },
{
onReadyForServerApproval: async (paymentId) => {
await fetch('/api/payments/approve', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
},
body: JSON.stringify({ paymentId }),
});
},
onReadyForServerCompletion: async (paymentId, txid) => {
const res = await fetch('/api/payments/complete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
},
body: JSON.stringify({ paymentId, txid }),
});
if (res.ok) {
// Deliver the purchased feature only after the backend confirms completion
}
},
onCancel: (paymentId) => {
console.log('Payment cancelled:', paymentId);
},
onError: (error, payment) => {
console.error('Payment error:', error, payment);
},
}
);
}

Handling Payments on the Backend

Your backend must call the Pi Platform API with your Server API Key. Never expose that key in client-side code.

const express = require('express');
const app = express();
app.use(express.json());

const PI_API_BASE = 'https://api.minepi.com/v2';
const PI_API_KEY = process.env.PI_API_KEY;

app.post('/api/payments/approve', async (req, res) => {
const { paymentId } = req.body;
try {
const approval = await fetch(`${PI_API_BASE}/payments/${paymentId}/approve`, {
method: 'POST',
headers: { Authorization: `Key ${PI_API_KEY}` },
});
res.status(approval.status).json(await approval.json());
} catch (err) {
res.status(500).send(err.message);
}
});

app.post('/api/payments/complete', async (req, res) => {
const { paymentId, txid } = req.body;
try {
const completion = await fetch(`${PI_API_BASE}/payments/${paymentId}/complete`, {
method: 'POST',
headers: {
Authorization: `Key ${PI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ txid }),
});
const data = await completion.json();
if (completion.ok) {
// Deliver the purchased feature here, using verified payment data
}
res.status(completion.status).json(data);
} catch (err) {
res.status(500).send(err.message);
}
});

The same handshake applies in any backend language: POST /v2/payments/{paymentId}/approve then POST /v2/payments/{paymentId}/complete with { "txid": "<txid>" }. See the Platform API reference.

The frontend–backend handshake

A payment only settles if the frontend trigger and the backend validator stay in step:

  1. Frontend (Pi.createPayment): Starts the payment. The SDK provides a paymentId via onReadyForServerApproval.
  2. Handshake: The frontend sends the paymentId to your backend.
  3. Backend (Platform API): Approves the payment with Pi Servers (POST /v2/payments/{paymentId}/approve).
  4. Blockchain: The user signs the transaction; the frontend receives a txid via onReadyForServerCompletion.
  5. Finalize: The backend completes the transaction (POST /v2/payments/{paymentId}/complete) and then delivers the purchased feature.

Implementation checklist

When adding Pi payments to an app:

  1. Load https://sdk.minepi.com/pi-sdk.js and call Pi.init() before any other SDK methods.
  2. Authenticate with Pi.authenticate(['username', 'payments'], onIncompletePaymentFound).
  3. Create payments with Pi.createPayment(paymentData, callbacks). Wire onReadyForServerApproval and onReadyForServerCompletion to your backend.
  4. Implement backend endpoints that call the Platform API with Authorization: Key <PI_API_KEY>. Store the key in environment variables; never hardcode credentials.
  5. Always include a check for incomplete payments during the initial auth phase.
  6. Deliver purchased features only after the backend receives a successful /complete response from Pi.
  7. Include the CDN script in every environment, including production. The Pi Browser does not inject window.Pi for you.

Next

For App-to-User (A2U) payouts from your app to a Pioneer, see Advanced Payments.