@pok-pay/server-sdk
v0.1.0
Published
POK Pay official Node.js/TypeScript server SDK
Downloads
112
Readme
@pok-pay/server-sdk
Official POK Pay Node.js/TypeScript server SDK — full integration kit with API client, webhook handler, NestJS module, and Prisma/TypeORM schemas.
Installation
npm install @pok-pay/server-sdkRequires Node.js 18+.
Environment Variables
POKPAY_ENV=staging # 'staging', 'production', or 'live' (alias for production)
POKPAY_KEY_ID=your_key_id
POKPAY_KEY_SECRET=your_key_secret
POKPAY_MERCHANT_ID=your_merchant_id_uuid| Env | API URL | App URL |
|---|---|---|
| staging | https://api-staging.pokpay.io | https://app-staging.pokpay.io |
| production | https://api.pokpay.io | https://app.pokpay.io |
| live | https://api.pokpay.io | https://app.pokpay.io |
Quick Start — Express
import express from 'express';
import { PokPayClient, handlePokWebhook } from '@pok-pay/server-sdk';
const pokConfig = {
env: process.env.POKPAY_ENV as 'staging' | 'production',
keyId: process.env.POKPAY_KEY_ID!,
keySecret: process.env.POKPAY_KEY_SECRET!,
merchantId: process.env.POKPAY_MERCHANT_ID!,
};
const client = new PokPayClient(pokConfig);
const app = express();
app.use(express.json());
// 1. Create a payment order (requires your app's auth)
app.post('/payments/create-order', authMiddleware, async (req, res) => {
const { amount, currencyCode, orderId } = req.body;
const sdkOrderId = await client.createOrder({
amount,
currencyCode,
webhookUrl: `${process.env.API_URL}/webhooks/pok?orderId=${orderId}`,
redirectUrl: `${process.env.APP_URL}/payment/success`,
});
// Save sdkOrderId to your DB order record here
res.json({ sdkOrderId });
});
// 2. Setup tokenized 3DS for saved cards (user MUST be authenticated)
app.post('/payments/setup-tokenized-3ds', authMiddleware, async (req, res) => {
const { cardId, sdkOrderId } = req.body;
const payerAuthentication = await client.setupTokenized3DS(cardId, sdkOrderId);
res.json({ payerAuthentication });
});
// 3. Webhook handler (public — no auth)
// POK does not provide webhook signatures. This handler cross-verifies
// with the POK API before marking orders complete.
app.post('/webhooks/pok', async (req, res) => {
const result = await handlePokWebhook(pokConfig, req.body, {
async getOrder(orderId) {
// Return your order from DB (must include pokOrderId and userId)
return db.orders.findUnique({ where: { id: orderId } });
},
async markOrderCompleted(orderId) {
await db.orders.update({ where: { id: orderId }, data: { status: 'COMPLETED' } });
},
async saveCardToken(userId, pokCardId) {
// Upsert — do not throw, card saving must not break payment confirmation
await db.savedCard.upsert({
where: { userId_pokCardId: { userId, pokCardId } },
create: { userId, pokCardId },
update: {},
});
},
});
res.status(200).json(result); // Always return 200 to POK
});Quick Start — NestJS
// app.module.ts
import { PokPayModule } from '@pok-pay/server-sdk/nestjs';
@Module({
imports: [
PokPayModule.forRoot({
env: process.env.POKPAY_ENV as 'staging' | 'production',
keyId: process.env.POKPAY_KEY_ID!,
keySecret: process.env.POKPAY_KEY_SECRET!,
merchantId: process.env.POKPAY_MERCHANT_ID!,
}),
],
})
export class AppModule {}
// payment.controller.ts
import { PokPayService } from '@pok-pay/server-sdk/nestjs';
@Controller('payments')
export class PaymentController {
constructor(private readonly pokPay: PokPayService) {}
@Post('create-order')
@UseGuards(JwtAuthGuard)
async createOrder(@Body() body: CreateOrderDto) {
const sdkOrderId = await this.pokPay.createOrder({
amount: body.amount,
currencyCode: body.currencyCode,
webhookUrl: `${process.env.API_URL}/webhooks/pok?orderId=${body.orderId}`,
redirectUrl: `${process.env.APP_URL}/payment/success`,
});
return { sdkOrderId };
}
@Post('setup-tokenized-3ds')
@UseGuards(JwtAuthGuard) // Must be authenticated
async setup3DS(@Body() body: Setup3DSDto) {
const payerAuthentication = await this.pokPay.setupTokenized3DS(
body.cardId,
body.sdkOrderId,
);
return { payerAuthentication };
}
}Database Schema
Prisma
Copy node_modules/@pok-pay/server-sdk/src/schema/saved-card.prisma into your schema.prisma.
TypeORM
Copy node_modules/@pok-pay/server-sdk/src/schema/saved-card.typeorm.ts into your entities directory and adjust the User relation import.
Payment Flow
1. Backend: createOrder() → sdkOrderId
2a. Card: Frontend renders POK card form with sdkOrderId
2b. Wallet: Frontend displays QR code at {APP_URL}/sdk-orders/{sdkOrderId}
(user scans with POK app)
2c. Saved card: Frontend calls POST /payments/setup-tokenized-3ds
→ payerAuthentication → frontend completes 3DS
3. User pays
4. POK → POST /webhooks/pok?orderId={your-internal-id}
5. handlePokWebhook() cross-verifies with POK API, marks order COMPLETED,
saves card token (if applicable and user is logged in)API Reference
new PokPayClient(config)
| Param | Type | Description |
|---|---|---|
| env | 'staging' \| 'production' \| 'live' | Resolves API URLs automatically ('live' is an alias for 'production') |
| keyId | string | POK API key ID |
| keySecret | string | POK API key secret |
| merchantId | string | POK merchant UUID |
client.createOrder(params) → Promise<string>
Returns sdkOrderId. Obtains a fresh access token on each call (tokens are short-lived).
client.getOrderStatus(sdkOrderId) → Promise<{ isCompleted: boolean, status: string, canBeCaptured: boolean }>
Cross-verify order completion with POK's API. canBeCaptured is true only for orders
created with autoCapture: false where payment has been frozen but not yet captured.
client.getCardDetails(cardId) → Promise<PokCardDetails>
Always call once per card. POK does not guarantee response ordering for multi-card requests.
client.setupTokenized3DS(cardId, sdkOrderId) → Promise<unknown>
Returns payerAuthentication blob. Pass it unmodified to the frontend setUpCardTokenPayment() call.
handlePokWebhook(config, body, deps) → Promise<{ ok: boolean }>
Idempotent webhook handler. Cross-verifies with POK API before confirming. Card saving is best-effort (errors are swallowed).
client.captureOrder(sdkOrderId) → Promise<Record<string, unknown>>
Capture a frozen payment. Only valid when canBeCaptured is true (orders created
with autoCapture: false).
Endpoint: POST /merchants/{merchantId}/sdk-orders/{sdkOrderId}/capture.
client.cancelOrder(sdkOrderId) → Promise<Record<string, unknown>>
Release frozen funds without capturing. Use to cancel an autoCapture: false order.
Endpoint: POST /merchants/{merchantId}/sdk-orders/{sdkOrderId}/cancel.
client.refundOrder(sdkOrderId, amount?) → Promise<Record<string, unknown>>
Refund a captured order. Omit amount for a full refund; supply it for a partial
refund. Only valid when isRefundable is true on the order.
Endpoint: POST /merchants/{merchantId}/sdk-orders/{sdkOrderId}/refund.
handleCaptureOrder(config, input) / handleCancelOrder / handleRefundOrder
Framework-agnostic route handlers — same pattern as handleCreateOrder. Wire into
Express/NestJS/etc. with your own auth middleware.
Security Notes
- Never expose
keyId,keySecret, ormerchantIdto the browser or mobile app - Access tokens are obtained server-side only and never returned to the client
- POK does not provide webhook HMAC signatures; this SDK uses API cross-verification instead
- The
setup-tokenized-3dsendpoint must be protected by your authentication middleware - Card token saving (
saveCardToken) must never throw — wrap your DB call in try/catch
