Payment API Reference
The Payment API handles all financial transactions on the ZhenRent platform, including task payments, refunds, and balance management.
Overview
ZhenRent uses an escrow-based payment system. When you dispatch a task, funds are held in escrow until task completion. Workers receive payment only after you approve their work.
Payment Flow
1. Dispatch Task → 2. Create Payment → 3. Hold in Escrow → 4. Task Completed → 5. Release to Worker
↓
(Auto-refund if failed)
Key Features:
- Automatic escrow for all tasks
- Instant refunds for failed tasks
- Multiple payment methods (Alipay, WeChat, Cards)
- 20% platform fee on all transactions
- Full transaction history and invoicing
Base URL
https://www.zhenrent.com/api/v1
Authentication
All endpoints require API Key authentication:
Authorization: Bearer zr_live_your_api_key
X-API-Secret: your_api_secret
Supported Payment Methods
Alipay (支付宝) - Recommended for Chinese Users
Fast, secure, and most popular in China.
Features:
- Instant settlement
- QR code or redirect flow
- Widely accepted across China
- Mobile and desktop support
Processing Time: Instant
Refund Time: 1-3 business days
WeChat Pay (微信支付) - Popular Alternative
Integrated with WeChat ecosystem.
Features:
- In-app or QR code payment
- High adoption rate in China
- Mobile-first experience
- Instant confirmation
Processing Time: Instant
Refund Time: 1-3 business days
Credit/Debit Cards - International Support
Standard card processing for global users.
Accepted Cards:
- Visa
- Mastercard
- UnionPay (银联)
- American Express (selected regions)
Processing Time: Instant
Refund Time: 5-10 business days
Bank Transfer - Enterprise Only
Manual processing for high-volume customers.
Requirements:
- Enterprise account verification
- Minimum transfer: 1,000 RMB
- Business license documentation
Processing Time: 1-3 business days
Refund Time: 3-5 business days
Contact: enterprise@zhenrent.com for bank transfer setup.
Create Payment Order
Create a payment order for a task. This endpoint is automatically called when you dispatch a task via the Task API, but you can also call it directly for manual payment flows.
Endpoint
POST /api/v1/payment/create
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| task_id | string | Yes | UUID of the task to pay for |
| amount | integer | Yes | Amount in cents (must match task cost) |
| currency | string | Yes | Currency code: CNY or USD |
| payment_method | string | Yes | Method: alipay, wechat, card, bank_transfer |
| return_url | string | Yes | URL to redirect user after payment |
| callback_url | string | No | Webhook URL for payment notifications |
| metadata | object | No | Custom metadata (max 1KB JSON) |
Example Request
curl -X POST "https://www.zhenrent.com/api/v1/payment/create" \
-H "Authorization: Bearer zr_live_your_api_key" \
-H "X-API-Secret: your_api_secret" \
-H "Content-Type: application/json" \
-d '{
"task_id": "550e8400-e29b-41d4-a716-446655440000",
"amount": 5000,
"currency": "CNY",
"payment_method": "alipay",
"return_url": "https://your-app.com/payment/success",
"callback_url": "https://your-app.com/webhooks/payment"
}'
Python Example
import requests
response = requests.post(
"https://www.zhenrent.com/api/v1/payment/create",
headers={
"Authorization": f"Bearer {API_KEY}",
"X-API-Secret": API_SECRET,
"Content-Type": "application/json"
},
json={
"task_id": "550e8400-e29b-41d4-a716-446655440000",
"amount": 5000,
"currency": "CNY",
"payment_method": "alipay",
"return_url": "https://your-app.com/payment/success",
"callback_url": "https://your-app.com/webhooks/payment"
}
)
payment_data = response.json()
print(f"Redirect user to: {payment_data['payment_url']}")
JavaScript/Node.js Example
const axios = require('axios');
const createPayment = async (taskId, amount) => {
const response = await axios.post(
'https://www.zhenrent.com/api/v1/payment/create',
{
task_id: taskId,
amount: amount,
currency: 'CNY',
payment_method: 'alipay',
return_url: 'https://your-app.com/payment/success',
callback_url: 'https://your-app.com/webhooks/payment'
},
{
headers: {
'Authorization': `Bearer ${process.env.ZHENRENT_API_KEY}`,
'X-API-Secret': process.env.ZHENRENT_API_SECRET,
'Content-Type': 'application/json'
}
}
);
return response.data.payment_url;
};
// Usage
const paymentUrl = await createPayment('550e8400-e29b-41d4-a716-446655440000', 5000);
console.log(`Redirect user to: ${paymentUrl}`);
Success Response (201 Created)
{
"order_id": "ord_abc123def456",
"task_id": "550e8400-e29b-41d4-a716-446655440000",
"payment_url": "https://pay.zhenrent.com/checkout/ord_abc123def456",
"qr_code_url": "https://pay.zhenrent.com/qr/ord_abc123def456.png",
"amount": 5000,
"currency": "CNY",
"payment_method": "alipay",
"status": "pending",
"expires_at": "2026-04-01T11:15:00Z",
"created_at": "2026-04-01T11:00:00Z"
}
Response Fields:
payment_url- Redirect user to this URL to complete paymentqr_code_url- QR code image for mobile payments (Alipay/WeChat)expires_at- Payment link expiration (default: 15 minutes)
Error Responses
| Status Code | Error Code | Description |
|---|---|---|
| 400 | validation_error | Invalid request parameters |
| 400 | amount_mismatch | Amount doesn't match task cost |
| 401 | invalid_credentials | Invalid API key or secret |
| 404 | task_not_found | Task doesn't exist |
| 409 | duplicate_order | Payment order already exists for task |
| 429 | rate_limit_exceeded | Too many requests |
400 Example (Amount Mismatch)
{
"error": {
"code": "amount_mismatch",
"message": "Payment amount (3000 cents) doesn't match task cost (5000 cents)",
"expected_amount": 5000,
"provided_amount": 3000
}
}
Query Payment Status
Retrieve the current status of a payment order.
Endpoint
GET /api/v1/payment/query?order_id={order_id}
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| order_id | string | Yes | Payment order ID |
Example Request
curl "https://www.zhenrent.com/api/v1/payment/query?order_id=ord_abc123def456" \
-H "Authorization: Bearer zr_live_your_api_key" \
-H "X-API-Secret: your_api_secret"
Python Example
def check_payment_status(order_id):
response = requests.get(
f"https://www.zhenrent.com/api/v1/payment/query",
headers={
"Authorization": f"Bearer {API_KEY}",
"X-API-Secret": API_SECRET
},
params={"order_id": order_id}
)
return response.json()
status = check_payment_status("ord_abc123def456")
print(f"Payment status: {status['status']}")
Success Response (200 OK)
{
"order_id": "ord_abc123def456",
"task_id": "550e8400-e29b-41d4-a716-446655440000",
"amount": 5000,
"currency": "CNY",
"payment_method": "alipay",
"status": "completed",
"transaction_id": "txn_xyz789",
"paid_at": "2026-04-01T11:05:23Z",
"created_at": "2026-04-01T11:00:00Z",
"cost_breakdown": {
"worker_payout_cents": 4000,
"platform_fee_cents": 1000,
"total_cents": 5000
}
}
Payment Status Values:
pending- Payment created, awaiting user actionprocessing- Payment submitted, processingcompleted- Payment successful, funds in escrowfailed- Payment failedexpired- Payment link expired (15 min timeout)refunded- Payment refunded to user
Error Responses
| Status Code | Error Code | Description |
|---|---|---|
| 401 | invalid_credentials | Invalid authentication |
| 404 | order_not_found | Order doesn't exist |
Request Refund
Request a refund for a completed payment. Refunds are automatically processed for failed or cancelled tasks, but you can also request manual refunds.
Endpoint
POST /api/v1/payment/refund
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| order_id | string | Yes | Payment order ID to refund |
| amount | integer | No | Partial refund amount in cents (default: full) |
| reason | string | Yes | Refund reason (max 500 chars) |
| refund_method | string | No | Method: original (default), balance |
Refund Reasons (Required):
task_cancelled- Task was cancelled before completionquality_issue- Work quality doesn't meet requirementstask_failed- Worker failed to complete taskduplicate_payment- Accidental duplicate paymentother- Other reason (provide details)
Example Request
curl -X POST "https://www.zhenrent.com/api/v1/payment/refund" \
-H "Authorization: Bearer zr_live_your_api_key" \
-H "X-API-Secret: your_api_secret" \
-H "Content-Type: application/json" \
-d '{
"order_id": "ord_abc123def456",
"reason": "task_cancelled",
"refund_method": "original"
}'
Python Example
def request_refund(order_id, reason):
response = requests.post(
"https://www.zhenrent.com/api/v1/payment/refund",
headers={
"Authorization": f"Bearer {API_KEY}",
"X-API-Secret": API_SECRET,
"Content-Type": "application/json"
},
json={
"order_id": order_id,
"reason": reason,
"refund_method": "original"
}
)
return response.json()
refund = request_refund("ord_abc123def456", "task_cancelled")
print(f"Refund ID: {refund['refund_id']}")
print(f"Expected in: {refund['estimated_arrival']}")
Success Response (200 OK)
{
"refund_id": "ref_xyz789",
"order_id": "ord_abc123def456",
"amount": 5000,
"currency": "CNY",
"status": "processing",
"refund_method": "alipay",
"estimated_arrival": "2026-04-03T11:00:00Z",
"created_at": "2026-04-01T11:30:00Z"
}
Refund Processing Times:
- Alipay: 1-3 business days
- WeChat Pay: 1-3 business days
- Credit/Debit cards: 5-10 business days
- Bank transfer: 3-5 business days
- Account balance: Instant
Error Responses
| Status Code | Error Code | Description |
|---|---|---|
| 400 | refund_not_allowed | Refund not permitted for this order |
| 400 | already_refunded | Order already refunded |
| 404 | order_not_found | Order doesn't exist |
400 Example (Already Refunded)
{
"error": {
"code": "already_refunded",
"message": "Order has already been fully refunded",
"refund_id": "ref_xyz789",
"refunded_at": "2026-04-01T11:30:00Z"
}
}
Check Account Balance
Query your current account balance and transaction summary.
Endpoint
GET /api/v1/payment/balance
Example Request
curl "https://www.zhenrent.com/api/v1/payment/balance" \
-H "Authorization: Bearer zr_live_your_api_key"
Python Example
def get_balance():
response = requests.get(
"https://www.zhenrent.com/api/v1/payment/balance",
headers={"Authorization": f"Bearer {API_KEY}"}
)
return response.json()
balance = get_balance()
print(f"Available balance: ¥{balance['balance_cents']/100:.2f}")
print(f"Pending in escrow: ¥{balance['escrow_cents']/100:.2f}")
Success Response (200 OK)
{
"balance_cents": 50000,
"balance_rmb": 500.00,
"currency": "CNY",
"escrow_cents": 15000,
"escrow_rmb": 150.00,
"lifetime_spent_cents": 200000,
"lifetime_spent_rmb": 2000.00,
"last_updated": "2026-04-01T11:00:00Z"
}
Balance Fields:
balance_cents- Available balance you can spendescrow_cents- Funds held in escrow for active taskslifetime_spent_cents- Total amount spent since account creation
Get Transaction History
Retrieve paginated list of all payment transactions.
Endpoint
GET /api/v1/payment/transactions
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| type | string | all | Filter: payment, refund, deposit, all |
| status | string | all | Filter: completed, pending, failed, all |
| limit | integer | 20 | Results per page (max: 100) |
| starting_after | string | - | Cursor for pagination |
Example Request
curl "https://www.zhenrent.com/api/v1/payment/transactions?type=payment&limit=10" \
-H "Authorization: Bearer zr_live_your_api_key"
Python Example
def get_transactions(transaction_type='all', limit=20):
response = requests.get(
"https://www.zhenrent.com/api/v1/payment/transactions",
headers={"Authorization": f"Bearer {API_KEY}"},
params={"type": transaction_type, "limit": limit}
)
return response.json()
transactions = get_transactions(transaction_type='payment')
for tx in transactions['data']:
print(f"{tx['created_at']}: ¥{tx['amount']/100} - {tx['description']}")
Success Response (200 OK)
{
"data": [
{
"transaction_id": "txn_abc123",
"type": "payment",
"order_id": "ord_abc123def456",
"task_id": "550e8400-e29b-41d4-a716-446655440000",
"amount": 5000,
"currency": "CNY",
"status": "completed",
"description": "Payment for task: Verify restaurant opening hours",
"created_at": "2026-04-01T11:05:23Z"
},
{
"transaction_id": "txn_def456",
"type": "refund",
"order_id": "ord_xyz789",
"amount": 3000,
"currency": "CNY",
"status": "completed",
"description": "Refund for cancelled task",
"created_at": "2026-03-30T14:20:00Z"
}
],
"has_more": true,
"next_cursor": "txn_def456"
}
Payment Webhooks
Configure webhook endpoints to receive real-time payment notifications.
Webhook Events
ZhenRent sends webhook events for the following payment events:
payment.created- Payment order createdpayment.completed- Payment successfulpayment.failed- Payment failedpayment.expired- Payment link expiredrefund.created- Refund initiatedrefund.completed- Refund processedrefund.failed- Refund failed
Webhook Payload
All webhook payloads include these fields:
{
"event": "payment.completed",
"timestamp": "2026-04-01T11:05:23Z",
"data": {
"order_id": "ord_abc123def456",
"task_id": "550e8400-e29b-41d4-a716-446655440000",
"amount": 5000,
"currency": "CNY",
"payment_method": "alipay",
"status": "completed",
"transaction_id": "txn_xyz789"
},
"signature": "sha256=abc123def456..."
}
Verifying Webhook Signatures
Always verify webhook signatures to ensure authenticity.
Python Example
import hmac
import hashlib
def verify_webhook_signature(payload, signature, secret):
"""
Verify ZhenRent webhook signature.
Args:
payload: Raw request body (bytes)
signature: X-Signature header value
secret: Your webhook secret from Platform Console
Returns:
bool: True if signature is valid
"""
expected_signature = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
# Extract signature from header (format: "sha256=...")
provided_signature = signature.split('=')[1] if '=' in signature else signature
return hmac.compare_digest(expected_signature, provided_signature)
# Flask example
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhooks/payment', methods=['POST'])
def payment_webhook():
# Get signature from header
signature = request.headers.get('X-Signature')
if not signature:
return jsonify({'error': 'Missing signature'}), 401
# Verify signature
if not verify_webhook_signature(request.data, signature, WEBHOOK_SECRET):
return jsonify({'error': 'Invalid signature'}), 401
# Process webhook
payload = request.json
event = payload['event']
if event == 'payment.completed':
order_id = payload['data']['order_id']
print(f"Payment completed: {order_id}")
# Update your database, send confirmation email, etc.
elif event == 'payment.failed':
order_id = payload['data']['order_id']
print(f"Payment failed: {order_id}")
# Notify user, retry payment, etc.
return jsonify({'status': 'received'}), 200
Node.js Example
const crypto = require('crypto');
const express = require('express');
const app = express();
// Important: Use raw body for signature verification
app.use('/webhooks/payment', express.raw({ type: 'application/json' }));
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
const providedSignature = signature.split('=')[1] || signature;
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(providedSignature)
);
}
app.post('/webhooks/payment', (req, res) => {
const signature = req.headers['x-signature'];
if (!signature) {
return res.status(401).json({ error: 'Missing signature' });
}
if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const payload = JSON.parse(req.body);
const { event, data } = payload;
switch (event) {
case 'payment.completed':
console.log(`Payment completed: ${data.order_id}`);
// Handle successful payment
break;
case 'payment.failed':
console.log(`Payment failed: ${data.order_id}`);
// Handle failed payment
break;
default:
console.log(`Unhandled event: ${event}`);
}
res.json({ status: 'received' });
});
Webhook Best Practices
- Always verify signatures - Never process unverified webhooks
- Use HTTPS endpoints - Webhook URLs must use HTTPS
- Respond quickly - Return 200 OK within 5 seconds
- Handle idempotency - Same webhook may be sent multiple times
- Store webhook IDs - Use
event_idto prevent duplicate processing
Idempotency Example
# Store processed webhook IDs in database
processed_webhooks = set() # In production, use Redis or database
@app.route('/webhooks/payment', methods=['POST'])
def payment_webhook():
payload = request.json
event_id = payload.get('event_id')
# Check if already processed
if event_id in processed_webhooks:
return jsonify({'status': 'already_processed'}), 200
# Process webhook
process_payment_event(payload)
# Mark as processed
processed_webhooks.add(event_id)
return jsonify({'status': 'received'}), 200
Error Codes Reference
Common payment-related error codes:
| Error Code | Description | Resolution |
|---|---|---|
PAYMENT_FAILED | Payment processing failed | User should retry with different method |
INSUFFICIENT_FUNDS | User account has insufficient balance | User should add funds to account |
PAYMENT_TIMEOUT | Payment expired (15 min default) | Create new payment order |
DUPLICATE_ORDER | Order already exists for task | Query existing order status |
INVALID_SIGNATURE | Webhook signature verification failed | Check webhook secret configuration |
REFUND_NOT_ALLOWED | Refund not permitted | Check refund policy and task status |
AMOUNT_MISMATCH | Payment amount doesn't match task cost | Use correct amount from task details |
PAYMENT_METHOD_UNAVAILABLE | Selected payment method not available | Choose different payment method |
Error Response Format
{
"error": {
"code": "PAYMENT_FAILED",
"message": "Payment processing failed due to insufficient funds",
"details": {
"provider_code": "NSF",
"provider_message": "Insufficient balance"
},
"retry_allowed": true,
"suggested_actions": [
"Add funds to your account",
"Try a different payment method"
]
}
}
Security Best Practices
1. Protect API Credentials
Never expose API keys or secrets in client-side code.
# Bad - Hardcoded credentials
API_KEY = "zr_live_abc123..."
API_SECRET = "secret_xyz..."
# Good - Environment variables
import os
API_KEY = os.getenv("ZHENRENT_API_KEY")
API_SECRET = os.getenv("ZHENRENT_API_SECRET")
2. Use HTTPS Only
All payment endpoints require HTTPS. HTTP requests will be rejected.
# Bad
url = "http://www.zhenrent.com/api/v1/payment/create"
# Good
url = "https://www.zhenrent.com/api/v1/payment/create"
3. Verify All Webhooks
Always verify webhook signatures before processing.
# Bad - Processing unverified webhook
@app.route('/webhooks/payment', methods=['POST'])
def webhook():
payload = request.json
process_payment(payload) # DANGEROUS!
# Good - Verify signature first
@app.route('/webhooks/payment', methods=['POST'])
def webhook():
if not verify_signature(request.data, request.headers['X-Signature']):
return jsonify({'error': 'Invalid signature'}), 401
payload = request.json
process_payment(payload)
4. Implement Rate Limiting
Protect your webhook endpoints from abuse.
from flask_limiter import Limiter
limiter = Limiter(app, key_func=lambda: request.remote_addr)
@app.route('/webhooks/payment', methods=['POST'])
@limiter.limit("100 per minute")
def webhook():
# Process webhook
pass
5. Store Sensitive Data Securely
Never log or store full payment details.
# Bad - Logging sensitive data
logger.info(f"Processing payment: {payment_data}")
# Good - Log only non-sensitive identifiers
logger.info(f"Processing payment order: {order_id}")
6. Handle Errors Gracefully
Don't expose internal error details to users.
try:
response = create_payment(task_id, amount)
except Exception as e:
# Bad - Exposing internal error
return jsonify({'error': str(e)}), 500
# Good - Generic error message
logger.error(f"Payment creation failed: {e}")
return jsonify({
'error': 'Payment processing failed. Please try again.'
}), 500
Testing Payment Integration
Sandbox Environment
Use the sandbox environment for testing without real money.
Sandbox Base URL:
https://sandbox.zhenrent.com/api/v1
Test API Credentials:
- API Key:
zr_test_sandbox_key_123 - API Secret:
test_secret_456
Test Payment Methods:
All test payments automatically succeed after 5 seconds:
# Test with Alipay
payment = create_payment(
task_id="test_task_123",
amount=5000,
payment_method="alipay"
)
# Payment will auto-complete in 5 seconds
Test Card Numbers:
| Card Number | Result |
|---|---|
| 4242 4242 4242 4242 | Success |
| 4000 0000 0000 0002 | Declined |
| 4000 0000 0000 9995 | Insufficient funds |
Test Scenarios:
# Test successful payment
def test_successful_payment():
payment = create_payment(task_id, 5000, "alipay")
assert payment['status'] == 'pending'
# Wait for auto-complete (sandbox only)
time.sleep(6)
status = query_payment(payment['order_id'])
assert status['status'] == 'completed'
# Test failed payment
def test_failed_payment():
payment = create_payment(
task_id,
5000,
"card",
test_card="4000000000000002"
)
# Will fail automatically in sandbox
# Test refund
def test_refund():
payment = create_payment(task_id, 5000, "alipay")
time.sleep(6) # Wait for completion
refund = request_refund(payment['order_id'], "task_cancelled")
assert refund['status'] == 'processing'
Webhook Testing
Use webhook.site or ngrok for local testing:
# Start ngrok tunnel
ngrok http 5000
# Use ngrok URL as callback_url
curl -X POST "https://sandbox.zhenrent.com/api/v1/payment/create" \
-H "Authorization: Bearer zr_test_sandbox_key_123" \
-d '{
"task_id": "test_task_123",
"amount": 5000,
"payment_method": "alipay",
"callback_url": "https://abc123.ngrok.io/webhooks/payment"
}'
Frequently Asked Questions
When is payment charged?
Payment is charged when you dispatch a task via the Task API. The POST /api/v1/tasks endpoint automatically creates a payment order and holds funds in escrow.
What happens to payment if task fails?
Automatic full refund within 1-3 business days. No action required on your part.
Can I get an invoice?
Yes. Invoices are automatically generated for all payments and available through the billing area in Platform Console.
What is the payment timeout?
Payment links expire after 15 minutes by default. You can configure this when creating the payment order (minimum: 5 minutes, maximum: 24 hours).
payment = create_payment(
task_id="...",
amount=5000,
payment_method="alipay",
expires_in_seconds=1800 # 30 minutes
)
How long until refunds are processed?
Processing times vary by payment method:
- Alipay/WeChat: 1-3 business days
- Credit cards: 5-10 business days
- Bank transfer: 3-5 business days
- Account balance: Instant
Can I dispute a payment?
Yes. Contact support@zhenrent.com with your order ID and reason. Response time: under 24 hours.
What currencies are supported?
Currently supported:
- CNY (Chinese Yuan) - Primary
- USD (US Dollar) - Converted to CNY at current exchange rate
Are there transaction limits?
Per Transaction:
- Minimum: 10 RMB (1,000 cents)
- Maximum: 50,000 RMB (5,000,000 cents)
Daily Limits:
- Standard accounts: 100,000 RMB/day
- Verified accounts: 500,000 RMB/day
- Enterprise accounts: No limit
Contact enterprise@zhenrent.com to increase limits.
How do I add funds to my account?
Open billing in Platform Console and add funds or activate the required package. Minimum deposit: 100 RMB.
Integration Checklist
Before going live, ensure you've completed:
- Tested all payment flows in sandbox environment
- Implemented webhook signature verification
- Set up error handling for all payment endpoints
- Configured webhook endpoint with HTTPS
- Tested refund scenarios
- Implemented idempotency for webhooks
- Stored API credentials securely (environment variables)
- Added logging for payment transactions
- Tested payment timeout scenarios
- Reviewed and understood all error codes
- Set up monitoring for failed payments
- Tested with all supported payment methods
Related Documentation
- Task API - Create and manage tasks
- Webhooks - Real-time event notifications
- Error Codes - Complete error reference
- Pricing Guide - Understand platform fees and costs
- Quick Start - Get started in 15 minutes
Support
Payment Issues: support@zhenrent.com
Enterprise Sales: enterprise@zhenrent.com
Response Time: Under 24 hours
Documentation Feedback: Found an issue or have a suggestion? Email docs@zhenrent.com