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 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

  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 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


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