scaffold-app
npx skills add https://github.com/payram/payram-helper-mcp-server --skill scaffold-app
Agent 安装分布
Skill 文档
Scaffold Payram Application
Overview
This skill provides instructions for scaffolding complete starter applications with Payram integration. Instead of adding Payram to existing projects, you’ll generate new applications from scratch with payments, payouts, and webhooks pre-configured. Each scaffold includes a web console for testing functionality without writing frontend code.
When to Use This Skill
Use this skill when you need to:
- Create a new project with Payram integration from scratch
- Build proof-of-concept or demo applications quickly
- Learn Payram API patterns through working examples
- Generate reference implementations for your team
- Prototype new features before adding to production code
Prerequisites
Before starting, ensure you have:
- Completed the
setup-payramskill (understand environment configuration) - Chosen your target framework (Express, Next.js, FastAPI, Laravel, Gin, Spring Boot)
- Development tools installed (Node.js/Python/PHP/Go/Java)
- Basic familiarity with your chosen framework
Instructions
Part 1: Understanding Scaffold Structure
1.1 What’s Included
All scaffolds provide:
-
Environment Configuration
.env.examplewith Payram variables- Configuration loading/validation
-
Payment Endpoints
POST /api/payments/create– Create paymentGET /api/payments/:referenceId– Check status
-
Payout Endpoints
POST /api/payouts/create– Create payoutGET /api/payouts/:id– Check status
-
Webhook Handler (optional)
POST /api/payram/webhook– Receive events
-
Web Console (browser UI)
- Test payments visually
- Test payouts visually
- View API responses
-
Package Configuration
- Dependencies (Payram SDK, framework, etc.)
- Scripts for running/building
1.2 Directory Structures
Express:
payram-express-starter/
âââ package.json
âââ .env.example
âââ index.js
âââ public/
âââ index.html
Next.js:
payram-nextjs-starter/
âââ package.json
âââ .env.example
âââ next.config.js
âââ app/
â âââ page.tsx
â âââ api/
â âââ payments/
â â âââ create/route.ts
â â âââ [referenceId]/route.ts
â âââ payouts/
â â âââ create/route.ts
â â âââ [id]/route.ts
â âââ payram/
â âââ webhook/route.ts
âââ lib/
âââ payram.ts
FastAPI:
payram-fastapi-starter/
âââ requirements.txt
âââ .env.example
âââ main.py
âââ templates/
âââ index.html
Laravel:
payram-laravel-starter/
âââ composer.json
âââ .env.example
âââ routes/
â âââ api.php
âââ app/
â âââ Http/
â âââ Controllers/
â âââ PayramController.php
âââ resources/
âââ views/
âââ console.blade.php
Gin:
payram-gin-starter/
âââ go.mod
âââ .env.example
âââ main.go
âââ handlers/
â âââ payram.go
âââ static/
âââ index.html
Spring Boot:
payram-spring-boot-starter/
âââ pom.xml
âââ .env.example
âââ src/
â âââ main/
â âââ java/com/example/payram/
â â âââ PayramApplication.java
â â âââ controller/
â â âââ PayramController.java
â âââ resources/
â âââ application.properties
â âââ static/
â âââ index.html
Part 2: Framework-Specific Instructions
2.1 Express Scaffold
Step 1: Create Directory Structure
mkdir payram-express-starter
cd payram-express-starter
Step 2: Initialize Node Project
npm init -y
Step 3: Install Dependencies
npm install express cors dotenv payram
Step 4: Create .env.example
File: .env.example
PAYRAM_BASE_URL=https://your-merchant.payram.com
PAYRAM_API_KEY=pk_test_your_api_key_here
PORT=3000
Step 5: Create Main Server File
File: index.js
import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import { Payram } from 'payram';
dotenv.config();
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.static('public'));
const payram = new Payram({
apiKey: process.env.PAYRAM_API_KEY,
baseUrl: process.env.PAYRAM_BASE_URL,
});
// Payment endpoints
app.post('/api/payments/create', async (req, res) => {
try {
const {
amount = 1,
customerEmail = 'demo@example.com',
customerId = 'demo-customer',
} = req.body;
const checkout = await payram.payments.initiatePayment({
amountInUSD: Number(amount),
customerEmail,
customerId,
});
res.json(checkout);
} catch (error) {
console.error('Payment creation failed:', error);
res.status(500).json({
error: 'payment_create_failed',
details: error.message,
});
}
});
app.get('/api/payments/:referenceId', async (req, res) => {
try {
const payment = await payram.payments.getPaymentRequest(req.params.referenceId);
res.json(payment);
} catch (error) {
console.error('Payment status check failed:', error);
res.status(500).json({
error: 'payment_status_failed',
details: error.message,
});
}
});
// Payout endpoints
app.post('/api/payouts/create', async (req, res) => {
try {
const {
amount = '1',
currencyCode = 'USDT',
blockchainCode = 'ETH',
customerID = 'demo-customer',
email = 'merchant@example.com',
toAddress = '0xfeedfacecafebeefdeadbeefdeadbeefdeadbeef',
mobileNumber = '+15555555555',
residentialAddress = '123 Main St, City, Country',
} = req.body;
const payout = await payram.payouts.createPayout({
customerID,
email,
blockchainCode,
currencyCode,
amount: String(amount),
toAddress,
mobileNumber,
residentialAddress,
});
res.json(payout);
} catch (error) {
console.error('Payout creation failed:', error);
res.status(500).json({
error: 'payout_create_failed',
details: error.message,
});
}
});
app.get('/api/payouts/:id', async (req, res) => {
try {
const payout = await payram.payouts.getPayoutById(Number(req.params.id));
res.json(payout);
} catch (error) {
console.error('Payout status check failed:', error);
res.status(500).json({
error: 'payout_status_failed',
details: error.message,
});
}
});
// Webhook endpoint
app.post('/api/payram/webhook', (req, res) => {
console.log('Payram webhook event received:', req.body);
// TODO: Validate API-Key header
// TODO: Process event based on status
res.json({ message: 'Webhook received successfully' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Payram Express starter running on http://localhost:${PORT}`);
console.log(`Open http://localhost:${PORT} to access the test console`);
});
Step 6: Create Web Console
File: public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Payram Test Console</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 8px;
padding: 30px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
h1 {
color: #333;
margin-bottom: 30px;
}
.section {
margin-bottom: 40px;
padding: 20px;
background: #f9f9f9;
border-radius: 6px;
}
h2 {
color: #555;
margin-bottom: 15px;
font-size: 20px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #666;
}
input,
button {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
button {
background: #0066cc;
color: white;
border: none;
cursor: pointer;
font-weight: 600;
margin-top: 10px;
}
button:hover {
background: #0052a3;
}
.response {
margin-top: 15px;
padding: 15px;
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 4px;
font-family: monospace;
font-size: 12px;
white-space: pre-wrap;
word-break: break-all;
max-height: 300px;
overflow-y: auto;
}
</style>
</head>
<body>
<div class="container">
<h1>ð Payram Test Console</h1>
<!-- Payments Section -->
<div class="section">
<h2>ð³ Create Payment</h2>
<div class="form-group">
<label>Amount (USD)</label>
<input type="number" id="paymentAmount" value="10" step="0.01" />
</div>
<div class="form-group">
<label>Customer Email</label>
<input type="email" id="customerEmail" value="customer@example.com" />
</div>
<div class="form-group">
<label>Customer ID</label>
<input type="text" id="customerId" value="demo-customer-001" />
</div>
<button onclick="createPayment()">Create Payment</button>
<div id="paymentResponse" class="response" style="display:none;"></div>
</div>
<!-- Payouts Section -->
<div class="section">
<h2>ð° Create Payout</h2>
<div class="form-group">
<label>Amount</label>
<input type="text" id="payoutAmount" value="5" />
</div>
<div class="form-group">
<label>Currency Code</label>
<input type="text" id="currencyCode" value="USDT" />
</div>
<div class="form-group">
<label>Blockchain Code</label>
<input type="text" id="blockchainCode" value="ETH" />
</div>
<div class="form-group">
<label>To Address</label>
<input type="text" id="toAddress" value="0xfeedfacecafebeefdeadbeefdeadbeefdeadbeef" />
</div>
<button onclick="createPayout()">Create Payout</button>
<div id="payoutResponse" class="response" style="display:none;"></div>
</div>
<!-- Status Check Section -->
<div class="section">
<h2>ð Check Status</h2>
<div class="form-group">
<label>Payment Reference ID</label>
<input type="text" id="paymentRefId" placeholder="Enter payment reference ID" />
</div>
<button onclick="checkPaymentStatus()">Check Payment Status</button>
<div id="paymentStatusResponse" class="response" style="display:none;"></div>
<div class="form-group" style="margin-top: 20px;">
<label>Payout ID</label>
<input type="number" id="payoutId" placeholder="Enter payout ID" />
</div>
<button onclick="checkPayoutStatus()">Check Payout Status</button>
<div id="payoutStatusResponse" class="response" style="display:none;"></div>
</div>
</div>
<script>
async function createPayment() {
const amount = document.getElementById('paymentAmount').value;
const customerEmail = document.getElementById('customerEmail').value;
const customerId = document.getElementById('customerId').value;
const response = await fetch('/api/payments/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount, customerEmail, customerId }),
});
const data = await response.json();
displayResponse('paymentResponse', data);
if (data.reference_id) {
document.getElementById('paymentRefId').value = data.reference_id;
alert(`Payment created! Reference: ${data.reference_id}\n\nCheckout URL: ${data.url}`);
}
}
async function createPayout() {
const amount = document.getElementById('payoutAmount').value;
const currencyCode = document.getElementById('currencyCode').value;
const blockchainCode = document.getElementById('blockchainCode').value;
const toAddress = document.getElementById('toAddress').value;
const response = await fetch('/api/payouts/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount, currencyCode, blockchainCode, toAddress }),
});
const data = await response.json();
displayResponse('payoutResponse', data);
if (data.id) {
document.getElementById('payoutId').value = data.id;
alert(`Payout created! ID: ${data.id}\nStatus: ${data.status}`);
}
}
async function checkPaymentStatus() {
const referenceId = document.getElementById('paymentRefId').value;
if (!referenceId) {
alert('Please enter a payment reference ID');
return;
}
const response = await fetch(`/api/payments/${referenceId}`);
const data = await response.json();
displayResponse('paymentStatusResponse', data);
}
async function checkPayoutStatus() {
const payoutId = document.getElementById('payoutId').value;
if (!payoutId) {
alert('Please enter a payout ID');
return;
}
const response = await fetch(`/api/payouts/${payoutId}`);
const data = await response.json();
displayResponse('payoutStatusResponse', data);
}
function displayResponse(elementId, data) {
const element = document.getElementById(elementId);
element.style.display = 'block';
element.textContent = JSON.stringify(data, null, 2);
}
</script>
</body>
</html>
Step 7: Update package.json
{
"name": "payram-express-starter",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node index.js",
"dev": "node --watch index.js"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"payram": "latest"
}
}
Step 8: Run Application
# Copy .env.example to .env
cp .env.example .env
# Edit .env with real credentials
nano .env
# Start server
npm start
# Open browser
open http://localhost:3000
2.2 Next.js Scaffold
Quick Start:
# Create Next.js app
npx create-next-app@latest payram-nextjs-starter --typescript --tailwind --app --no-src-dir
cd payram-nextjs-starter
# Install Payram SDK
npm install payram
# Create .env.local
cat > .env.local << 'EOF'
PAYRAM_BASE_URL=https://your-merchant.payram.com
PAYRAM_API_KEY=pk_test_your_api_key_here
EOF
Create API Routes:
File: app/api/payments/create/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { Payram } from 'payram';
const payram = new Payram({
apiKey: process.env.PAYRAM_API_KEY!,
baseUrl: process.env.PAYRAM_BASE_URL!,
});
export async function POST(request: NextRequest) {
try {
const {
amount = 1,
customerEmail = 'demo@example.com',
customerId = 'demo',
} = await request.json();
const checkout = await payram.payments.initiatePayment({
amountInUSD: Number(amount),
customerEmail,
customerId,
});
return NextResponse.json(checkout);
} catch (error: any) {
return NextResponse.json(
{ error: 'payment_create_failed', details: error.message },
{ status: 500 },
);
}
}
File: app/api/payments/[referenceId]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { Payram } from 'payram';
const payram = new Payram({
apiKey: process.env.PAYRAM_API_KEY!,
baseUrl: process.env.PAYRAM_BASE_URL!,
});
export async function GET(request: NextRequest, { params }: { params: { referenceId: string } }) {
try {
const payment = await payram.payments.getPaymentRequest(params.referenceId);
return NextResponse.json(payment);
} catch (error: any) {
return NextResponse.json(
{ error: 'payment_status_failed', details: error.message },
{ status: 500 },
);
}
}
Similar patterns for payouts and webhooks.
2.3 FastAPI Scaffold
Quick Start:
mkdir payram-fastapi-starter
cd payram-fastapi-starter
# Create requirements.txt
cat > requirements.txt << 'EOF'
fastapi==0.104.1
uvicorn[standard]==0.24.0
python-dotenv==1.0.0
payram==1.0.0
EOF
# Install dependencies
pip install -r requirements.txt
# Create .env
cat > .env << 'EOF'
PAYRAM_BASE_URL=https://your-merchant.payram.com
PAYRAM_API_KEY=pk_test_your_api_key_here
EOF
File: main.py
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from pydantic import BaseModel
import os
from dotenv import load_dotenv
load_dotenv()
app = FastAPI(title="Payram FastAPI Starter")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# TODO: Initialize Payram client here
# from payram import Payram
# payram = Payram(
# api_key=os.getenv('PAYRAM_API_KEY'),
# base_url=os.getenv('PAYRAM_BASE_URL')
# )
class PaymentRequest(BaseModel):
amount: float = 1.0
customerEmail: str = "demo@example.com"
customerId: str = "demo-customer"
class PayoutRequest(BaseModel):
amount: str = "1"
currencyCode: str = "USDT"
blockchainCode: str = "ETH"
customerID: str = "demo-customer"
email: str = "merchant@example.com"
toAddress: str = "0xfeedfacecafebeefdeadbeefdeadbeefdeadbeef"
mobileNumber: str = "+15555555555"
residentialAddress: str = "123 Main St"
@app.post("/api/payments/create")
async def create_payment(req: PaymentRequest):
try:
# TODO: Call payram.payments.initiate_payment(...)
return {"message": "Payment endpoint - implement with Payram SDK"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/payments/{reference_id}")
async def get_payment_status(reference_id: str):
try:
# TODO: Call payram.payments.get_payment_request(reference_id)
return {"message": "Payment status endpoint - implement with Payram SDK"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/payouts/create")
async def create_payout(req: PayoutRequest):
try:
# TODO: Call payram.payouts.create_payout(...)
return {"message": "Payout endpoint - implement with Payram SDK"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/payouts/{payout_id}")
async def get_payout_status(payout_id: int):
try:
# TODO: Call payram.payouts.get_payout_by_id(payout_id)
return {"message": "Payout status endpoint - implement with Payram SDK"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/payram/webhook")
async def handle_webhook(payload: dict):
print("Webhook received:", payload)
return {"message": "Webhook received successfully"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Run:
python main.py
# or
uvicorn main:app --reload
Part 3: Common Patterns
3.1 Error Handling
All scaffolds should include:
try {
const result = await payram.payments.initiatePayment(...);
return success(result);
} catch (error) {
console.error('Payram API error:', error);
return error_response({
error: 'operation_failed',
details: error.message,
// Don't expose internal errors to clients
});
}
3.2 Environment Validation
Check configuration on startup:
if (!process.env.PAYRAM_BASE_URL || !process.env.PAYRAM_API_KEY) {
console.error('ERROR: Payram environment variables not configured');
console.error('Please copy .env.example to .env and fill in your credentials');
process.exit(1);
}
3.3 CORS Configuration
Allow frontend to call APIs:
app.use(
cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
methods: ['GET', 'POST'],
credentials: true,
}),
);
Best Practices
1. Never Commit Credentials
# Add to .gitignore
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
# Always provide .env.example
cp .env .env.example
# Replace values with placeholders in .env.example
2. Use Environment-Specific Configuration
.env.development # Local development
.env.test # Testing
.env.production # Production (managed via deployment)
3. Add Health Check Endpoint
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
payramConfigured: !!(process.env.PAYRAM_BASE_URL && process.env.PAYRAM_API_KEY),
});
});
4. Log API Interactions
console.log('[Payram] Creating payment:', {
amount,
customerId,
timestamp: new Date().toISOString(),
});
5. Validate Input
if (!amount || amount <= 0) {
return res.status(400).json({ error: 'Invalid amount' });
}
if (!customerId || customerId.trim().length === 0) {
return res.status(400).json({ error: 'Customer ID required' });
}
Troubleshooting
Port Already in Use
Error: EADDRINUSE: address already in use
Solution:
# Find process using port
lsof -ti:3000
# Kill process
kill -9 $(lsof -ti:3000)
# Or use different port
PORT=3001 npm start
Module Not Found
Error: Cannot find module 'payram'
Solution:
# Reinstall dependencies
rm -rf node_modules package-lock.json
npm install
Environment Variables Not Loading
Cause: .env file not in project root or not loaded.
Solution:
# Check file exists
ls -la .env
# Verify dotenv is loaded
# Add to top of your main file:
require('dotenv').config(); // CommonJS
# or
import 'dotenv/config'; // ESM
Related Skills
- setup-payram: Configure credentials after scaffolding
- integrate-payments: Understand payment patterns used in scaffolds
- integrate-payouts: Understand payout patterns
- handle-webhooks: Enhance webhook handlers in scaffolds
Summary
You now know how to scaffold complete Payram applications:
- Express: Node.js with simple file structure
- Next.js: React with App Router and API routes
- FastAPI: Python async web framework
- Laravel: PHP MVC framework (similar patterns)
- Gin: Go web framework (similar patterns)
- Spring Boot: Java enterprise framework (similar patterns)
All scaffolds include:
- Payment creation and status checking
- Payout creation and status checking
- Webhook handling (optional)
- Web console for testing
- Environment configuration
- Error handling
Next Steps:
- Choose your framework
- Follow framework-specific instructions
- Copy .env.example to .env and configure
- Run the application
- Test via web console
- Customize for your needs