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.