Skip to main content

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

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


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

ParameterTypeRequiredDescription
task_idstringYesUUID of the task to pay for
amountintegerYesAmount in cents (must match task cost)
currencystringYesCurrency code: CNY or USD
payment_methodstringYesMethod: alipay, wechat, card, bank_transfer
return_urlstringYesURL to redirect user after payment
callback_urlstringNoWebhook URL for payment notifications
metadataobjectNoCustom 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 payment
  • qr_code_url - QR code image for mobile payments (Alipay/WeChat)
  • expires_at - Payment link expiration (default: 15 minutes)

Error Responses

Status CodeError CodeDescription
400validation_errorInvalid request parameters
400amount_mismatchAmount doesn't match task cost
401invalid_credentialsInvalid API key or secret
404task_not_foundTask doesn't exist
409duplicate_orderPayment order already exists for task
429rate_limit_exceededToo 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

ParameterTypeRequiredDescription
order_idstringYesPayment 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 action
  • processing - Payment submitted, processing
  • completed - Payment successful, funds in escrow
  • failed - Payment failed
  • expired - Payment link expired (15 min timeout)
  • refunded - Payment refunded to user

Error Responses

Status CodeError CodeDescription
401invalid_credentialsInvalid authentication
404order_not_foundOrder 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

ParameterTypeRequiredDescription
order_idstringYesPayment order ID to refund
amountintegerNoPartial refund amount in cents (default: full)
reasonstringYesRefund reason (max 500 chars)
refund_methodstringNoMethod: original (default), balance

Refund Reasons (Required):

  • task_cancelled - Task was cancelled before completion
  • quality_issue - Work quality doesn't meet requirements
  • task_failed - Worker failed to complete task
  • duplicate_payment - Accidental duplicate payment
  • other - 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 CodeError CodeDescription
400refund_not_allowedRefund not permitted for this order
400already_refundedOrder already refunded
404order_not_foundOrder 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 spend
  • escrow_cents - Funds held in escrow for active tasks
  • lifetime_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

ParameterTypeDefaultDescription
typestringallFilter: payment, refund, deposit, all
statusstringallFilter: completed, pending, failed, all
limitinteger20Results per page (max: 100)
starting_afterstring-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 created
  • payment.completed - Payment successful
  • payment.failed - Payment failed
  • payment.expired - Payment link expired
  • refund.created - Refund initiated
  • refund.completed - Refund processed
  • refund.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 dashboard

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

  1. Always verify signatures - Never process unverified webhooks
  2. Use HTTPS endpoints - Webhook URLs must use HTTPS
  3. Respond quickly - Return 200 OK within 5 seconds
  4. Handle idempotency - Same webhook may be sent multiple times
  5. Store webhook IDs - Use event_id to 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 CodeDescriptionResolution
PAYMENT_FAILEDPayment processing failedUser should retry with different method
INSUFFICIENT_FUNDSUser account has insufficient balanceUser should add funds to account
PAYMENT_TIMEOUTPayment expired (15 min default)Create new payment order
DUPLICATE_ORDEROrder already exists for taskQuery existing order status
INVALID_SIGNATUREWebhook signature verification failedCheck webhook secret configuration
REFUND_NOT_ALLOWEDRefund not permittedCheck refund policy and task status
AMOUNT_MISMATCHPayment amount doesn't match task costUse correct amount from task details
PAYMENT_METHOD_UNAVAILABLESelected payment method not availableChoose 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 NumberResult
4242 4242 4242 4242Success
4000 0000 0000 0002Declined
4000 0000 0000 9995Insufficient 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 in your dashboard at https://www.zhenrent.com/dev/dashboard/invoices.

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?

Visit your dashboard at https://www.zhenrent.com/dev/dashboard/wallet and click "Recharge". 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


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