How to Implement Paid Community Features with QR Codes and Refunds

To implement paid community features with QR codes and refunds, configure Alipay Web-Pay for ¥9.90 transactions, persist order states in Supabase, store protected QR images in a dedicated table, and process asynchronous payment notifications via webhooks to dynamically toggle access eligibility.

The freestylefly/awesome-gpt-image-2 repository provides a production-ready implementation of a paid community feature that operates independently from existing membership or credit systems. This architecture uses Alipay for payment processing, Supabase for state management, and atomic database operations to ensure QR codes remain secure while supporting full refund lifecycles. Below is a complete technical breakdown of how to implement paid community features with QR codes and refunds, referencing the actual source code and database schema.

Architecture Overview

The system decouples payment processing from community access through a state-machine-driven flow. According to the source code in docs/paid-community.md, the implementation follows this sequence:

  1. Frontend Initiation: Users navigate to /community to view the QR code page or /community/result for payment outcomes.
  2. Alipay Integration: The backend calls alipay.trade.page.pay to create a ¥9.90 (990¢) order.
  3. State Persistence: The community_orders table tracks status transitions from PENDING to PAID, and finally to terminal states like REFUNDED, CLOSED, or REVOKED.
  4. Access Control: The community_qr table stores the protected image as a bytea blob, with access granted only when a user's latest order status equals PAID (or PROCESSING during active refunds).
  5. Asynchronous Updates: Alipay webhooks push final status updates to POST /api/community/alipay/notify.

Database Schema Design

The community_orders Table

Located in supabase/migrations/20260722090000_paid_community.sql, this table links payments to Supabase user IDs:

  • status column manages the lifecycle: PENDING → PAID → REFUNDED/CLOSED/REVOKED
  • Foreign key relationships ensure each order maps to a specific user
  • The table supports idempotent updates to handle duplicate Alipay notifications safely

The community_qr Table

QR images are stored as binary blobs with strict size limits (PNG/JPEG/WebP ≤ 2MB). The schema enforces atomic replacement operations through RPC transactions, ensuring the old image is disabled before the new one becomes live. This prevents race conditions where users might access outdated or revoked community codes.

Implementing the Payment Flow

Checkout Initiation

The frontend initiates payments via POST /api/community/alipay/checkout. In api/_lib/alipay.js, the server generates an Alipay order and returns a redirect URL:

// Start an Alipay checkout (frontend)
async function startCheckout() {
  const res = await fetch('/api/community/alipay/checkout', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({amount: 990})   // fixed amount in cents
  });
  const {checkoutUrl} = await res.json();
  window.location = checkoutUrl;           // redirects to Alipay
}

Webhook Handling

The POST /api/community/alipay/notify endpoint processes Alipay server-to-server notifications. As implemented in the API layer, this webhook updates the community_orders table status and handles idempotent processing:

// Alipay webhook (server side) – records payment status
import {recordAlipayNotification} from '../_lib/alipay.js';
export default async function handler(req, res) {
  if (req.method !== 'POST') return res.status(405).end();
  await recordAlipayNotification(req.body); // updates `community_orders`
  res.status(200).json({ok: true});
}

Status Polling and Eligibility

The GET /api/community/status endpoint aggregates payment state, QR availability, and user eligibility. The frontend uses this to conditionally render the protected QR code:

// Get community status (frontend)
fetch('/api/community/status')
  .then(r => r.json())
  .then(({eligible, qrReady, order}) => {
    if (!eligible) return alert('Purchase required.');
    if (qrReady) showQrImage();               // renders the protected QR
    else console.log('QR not yet uploaded.');
  });

Behind this endpoint, api/_lib/community.js provides helper functions including canAccessCommunityQr, getCurrentCommunityQr, and getLatestCommunityOrder to encapsulate the eligibility logic.

QR Code Access Control

Storage and Retrieval

Protected QR images are never served directly from static files. Instead, the system queries the community_qr table and streams the bytea blob only to eligible users. The src/community.jsx and src/community.css files handle the frontend rendering once access is verified.

Atomic Replacement

Administrators rotate QR codes through admin-only endpoints (/api/admin/...). The replaceCommunityQr function executes an RPC transaction to swap images atomically:

// Admin: Rotate QR code (server side)
import {replaceCommunityQr} from '../_lib/community.js';
export default async function handler(req, res) {
  const {qrFile} = req.body;               // binary image uploaded by admin
  await replaceCommunityQr(qrFile);        // RPC transaction, atomic swap
  res.json({ok: true});
}

This ensures zero-downtime rotation and immediate invalidation of previous codes.

Handling Refunds and Revocation

Refund processing follows Alipay's refund API workflow. When an administrator issues a refund through the admin panel, the system calls the createRefund function from api/_lib/alipay.js and immediately updates the corresponding order status to REFUNDED or PROCESSING:

// Admin: Issue a refund (server side)
import {createRefund} from '../_lib/alipay.js';
export default async function handler(req, res) {
  const {orderId} = req.body;
  const result = await createRefund(orderId); // Alipay refund API call
  res.status(200).json(result);
}

Once the status transitions to REFUNDED, the canAccessCommunityQr helper returns false, instantly revoking QR code access without requiring manual intervention.

Environment Configuration and Safety Flags

The implementation uses feature flags to prevent premature activation. The COMMUNITY_PAYMENT_ENABLED environment variable (default false) guards all payment endpoints until production acceptance testing completes. Additionally, COMMUNITY_ALIPAY_NOTIFY_URL defines the webhook endpoint for Alipay asynchronous notifications.

Production Deployment Checklist

According to the paid-community runbook in docs/paid-community.md, deploy the feature using this verification sequence:

  1. Deploy code with COMMUNITY_PAYMENT_ENABLED=false
  2. Upload an initial QR image via admin endpoints
  3. Enable the flag temporarily: COMMUNITY_PAYMENT_ENABLED=true
  4. Execute a real ¥9.90 payment flow
  5. Process a full-cycle refund and verify:
    • Alipay async notification and query APIs are idempotent
    • QR code becomes unavailable after successful refund
    • Order status correctly transitions through all states
  6. If any step fails, immediately revert COMMUNITY_PAYMENT_ENABLED to false

Summary

  • Alipay Web-Pay handles ¥9.90 transactions through alipay.trade.page.pay, redirecting users to complete payment externally.
  • Supabase stores order states in community_orders and protected images in community_qr, linking access to PAID status only.
  • API Layer provides public endpoints for status checks and checkout initiation, with admin endpoints for QR rotation and refunds.
  • Idempotent Webhooks ensure POST /api/community/alipay/notify safely processes duplicate notifications without corrupting order state.
  • Atomic Operations in replaceCommunityQr prevent race conditions during QR code rotation.
  • Environment Flags like COMMUNITY_PAYMENT_ENABLED provide circuit-breaker protection during deployment.

Frequently Asked Questions

How does the system prevent users from accessing QR codes after a refund?

When a refund is processed via createRefund in api/_lib/alipay.js, the order status updates to REFUNDED or PROCESSING. The canAccessCommunityQr helper function in api/_lib/community.js checks this status and returns false for any non-PAID state, immediately blocking QR image retrieval.

What happens if Alipay sends duplicate webhook notifications?

The recordAlipayNotification function implements idempotent updates. It checks the existing order state in the community_orders table before writing, ensuring duplicate notifications from Alipay do not create duplicate transactions or corrupt the payment status.

Can administrators change the QR code without downtime?

Yes. The replaceCommunityQr function executes an RPC transaction that atomically disables the old image before enabling the new one. This approach, defined in api/_lib/community.js, ensures users never see a broken or outdated QR code during rotation.

How is the payment amount fixed and protected?

The backend enforces the ¥9.90 (990 cents) amount in the POST /api/community/alipay/checkout handler. While the frontend may display the amount, the server validates or hardcodes the parameter when calling the Alipay SDK, preventing price tampering through client-side requests.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →