This package, pi-sdk-express, helps you quickly scaffold, configure, and integrate all
necessary components for using Pi Network payments, authentication,
and user flows with a Next.js project. It is designed for modern
Express apps that want a working,
idiomatic Pi payment and authentication experience with minimal
boilerplate.
The pi-sdk-express
package
is part of the “Ten Minutes to Transactions” effort described in this
video.
If you are planning to use the Express framework for your app, it is highly suggested that you use this package rather than implement transaction processing by hand with the core Pi SDK. The three way handshake between client, server, and the Pi servers required is provded for you.
While this process is covered in the Getting Started Guide, here is a brief reminder of the steps you need to take. Application registration is also discussed in the video.
http://localhost:3000. The actual port is between you and your development server.pi-sdk-express as a dependency in your Express projectnpm install pi-sdk-expressyarn add pi-sdk-expressnpx pi-sdk-express-installyarn pi-sdk-express-installThis will generate:
routes/pi_payment/ directory with individual route handlersroutes/pi_payment/index.ts - Router that exports all routesapp.example.ts - Example Express app setupOption A: Use the Router Factory (Quick Start)
import express from 'express';
import { createPiPaymentRouter } from 'pi-sdk-express';
const app = express();
// Required: JSON body parser middleware
app.use(express.json());
// Mount Pi payment routes
app.use('/pi_payment', createPiPaymentRouter());
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});Option B: Use Generated Routes (More Control)
import express from 'express';
import piPaymentRoutes from './routes/pi_payment';
const app = express();
app.use(express.json());
app.use('/pi_payment', piPaymentRoutes);
app.listen(3000);Use pi-sdk-js on your frontend to initiate payments. The frontend SDK will automatically call these endpoints.
For package docs and examples, see:
pi-sdk-js (vanilla JS/TS apps)pi-sdk-react (React apps)export PI_API_KEY="your-api-key-here"
export PI_API_URL_BASE="https://api.minepi.com" # Optional, defaults to this
export PI_API_VERSION="v2" # Optional, defaults to v2
export PI_API_CONTROLLER="payments" # Optional, defaults to paymentsThe router creates the following endpoints:
POST /pi_payment/approve - Approve a paymentPOST /pi_payment/complete - Complete a paymentPOST /pi_payment/cancel - Cancel a paymentPOST /pi_payment/error - Handle payment errorsPOST /pi_payment/incomplete - Handle incomplete paymentsThe process is similar to the one described above with the following differences.
Since there are two servers for providing Express and React functionality, we
will need to create a frontend directory for the React service and a backend directory for Express. Create an empty directory for your app. We will
fill it with these two directories.
First, lets create the frontend directory.
yarn create vite frontend -- --template react-ts --no-interactivenpm create vite frontend -- --template react-ts --no-interactiveNext, cd frontend so we can start modifying the basic React app.
yarn add pi-sdk-react
yarn pi-sdk-react-install --dest componentsnpm add pi-sdk-react
npx pi-sdk-react-install --dest componentsWe will need to let React know how to proxy Express requests. Add the following
to vite.config.ts. It will also eliminate React version issues between
the app and it’s packages.
// Place inside defineConfig. The "server" key should be at
// the same level as the existing "plugins" key.
server: {
port: 3000,
proxy: {
// Forward /pi_payment to Express so requests don't stay on Vite's port
'/pi_payment': {
target: backendTarget,
changeOrigin: true,
configure(proxy) {
proxy.on('error', (err, _req, _res) => {
console.error(
`[vite] pi_payment proxy error: backend not reachable at ${backendTarget}. Is the Express server running? (e.g. PORT=${backendPort} yarn start)`
);
console.error(err.message);
});
},
},
},
},
resolve: {
// Single React instance so pi-sdk-react hooks use the apps React (avoids "useState of null")
dedupe: ["react", "react-dom"],
},In the same file, add these lines near the begining of the file.
const backendPort = process.env.BACKEND_PORT || 3001
const backendTarget = `http://127.0.0.1:${backendPort}Now we are ready to add the Pi Button to the default app. In the
src/App.tsx file add the import near the top and the tag where
you wish.
import { PiButton } from "../components/PiButton"
// ...
<PiButton/>Don’t forget to add the pi-sdk.js library to the page header in index.html.
<head>
<!-- ... other <head> content (meta, title, styles, etc.) ... -->
<script src="https://sdk.minepi.com/pi-sdk.js"></script>
</head>Now, let’s create the backend directory.
npx express-generator backend --view=reactNext, cd backend so we can start modifying the basic Express app.
yarn add pi-sdk-express
yarn pi-sdk-react-express-installnpm add pi-sdk-react
npx pi-sdk-react-express-installFinally, Express needs to know about the payment paths. Add these two lines
to the app.js file.
// This should be near the top
var { createPiPaymentRouter } = require("pi-sdk-express");\
// This one should be placed with similar ```app.use``` lines
app.use("/pi_payment", createPiPaymentRouter());These two servers need to be running in order to process transaction requests. You will need two terminal windows. Running one or both in the background of a terminal will mixup the server logs.
yarn dev // in the app/frontend directory
PORT=3001 dev server // in the app/backend directoryBy default, incomplete payments are automatically completed. You can customize this behavior:
import { createPiPaymentRouter, IncompletePaymentDecision } from 'pi-sdk-express';
const router = createPiPaymentRouter({
incompleteCallback: async (paymentId: string, transactionId: string): Promise<IncompletePaymentDecision> => {
// Your custom logic here
// Check database, review payment, etc.
if (shouldComplete(paymentId)) {
return 'complete';
} else {
return 'cancel';
}
}
});Add authentication, logging, or other middleware:
import { createPiPaymentRouter } from 'pi-sdk-express';
const router = createPiPaymentRouter({
middleware: [
// Custom authentication middleware
(req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
},
// Custom logging middleware
(req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
next();
}
]
});Mount the router at a different path:
app.use('/api/payments/pi', createPiPaymentRouter());Note: If you change the mount path, you’ll need to configure the frontend SDK accordingly.
The complete payment flow involves three parties:
pi-sdk-js or pi-sdk-reactPi.createPayment()POST /pi_payment/approve → Your server → Pi APIPOST /pi_payment/complete → Your server → Pi APIAll endpoints return appropriate HTTP status codes:
200 - Success400 - Bad Request (missing/invalid parameters)500 - Internal Server ErrorErrors are logged to the console with [PiSDK] prefix for easy filtering.
The SDK provides hooks for database integration. Example:
import { approveHandler } from 'pi-sdk-express';
// Custom approve handler with database
async function customApproveHandler(req: Request, res: Response) {
const { accessToken, paymentId } = req.body;
// Find or create user
const user = await findOrCreateUser(accessToken);
// Create transaction record
const transaction = await Transaction.create({
paymentId,
userId: user.id,
state: 'approval_pending'
});
// Call default handler (or implement your own Pi API call)
// ... your implementation
}pi-sdk-js - Core JavaScript SDK (frontend)pi-sdk-react - React hooks and componentspi-sdk-nextjs - Next.js integrationPiOS License - See LICENSE file for details.