Aggregated Payment Platform Architecture: Integrating Alipay and WeChat Pay

System Overview

An aggregated payment platform consolidates multiple payment channels (Alipay, WeChat Pay, etc.) into a unified payment gateway that merchants can integrate with. The core functionality includes:

  • Merchant registration and authentication
  • Application management for each merchant
  • Payment channel configuration and binding
  • Unified QR code generation for in-store payments
  • Payment order management and status tracking

Core Architecture

Client Request → API Gateway → Application Layer → Microservices → Database
                                       ↓
                                  Message Queue

The system uses a microservices architecture where each microservice handles specific business domains. The payment channel agent service isolates third-party payment provider integration logic from the core transaction service, reducing coupling.

Database Design

Merchant Service Database (shanjupay_merchant_service)

Core tables: merchant, app, staff, store, store_staff

Transaction Database (shanjupay_transaction)

Core tables for payment processing and order management.

Key Design Pattern: Dual ID Strategy

Each entity uses two identifiers:

  • Database auto-increment ID: Used for internal table operations, avoiding random I/O overhead
  • Business ID (UUID/Snowflake): Used for external references and cross-table relationships

This separation improves query performance while maintaining unique business references across distributed systems.

Payment Channel Parameter Caching

Payment operations are high-frequency events. To improve performance, payment channel parameters are cached in Redis:

public class PaymentChannelCache {
    private RedisTemplate<String, Object> cache;
    
    public void updateChannelCache(String merchantId, String channelCode) {
        String cacheKey = buildCacheKey(merchantId, channelCode);
        
        // Invalidate existing cache entry
        if (Boolean.TRUE.equals(cache.hasKey(cacheKey))) {
            cache.delete(cacheKey);
        }
        
        // Fetch from database and re-cache
        List<ChannelParameter> params = fetchParametersFromDB(merchantId, channelCode);
        if (params != null && !params.isEmpty()) {
            cache.opsForValue().set(cacheKey, JsonUtil.toJson(params));
        }
    }
}

Cache invalidation occurs when parameters are updated, ensuring consistency between database and cache layers.

C2B Payment Flow (Consumer Scans Business QR Code)

Process Overview

Merchant Side:
  Store Management → Select Application → Generate QR Code

Consumer Side:
  Scan QR Code → Enter Amount → Confirm Payment → Enter Password → Complete

Platform Workflow

  1. Generate unified payment QR code containing merchant and store identifiers
  2. Platform communicates with Alipay/WeChat APIs as an intermediary
  3. Payment execution (bank transaction) occurs through the third-party provider

Alipay Integration

Gateway Configuration

public class AlipayConfig {
    private String gatewayUrl = "https://openapi.alipay.com/gateway.do";
    private String appId;           // Assigned by Alipay
    private String privateKey;     // RSA private key
    private String publicKey;      // Alipay public key
    private String format = "JSON";
    private String charset = "UTF-8";
    private String signType = "RSA2";
}

Order Creation Request

public class AlipayOrderRequest {
    private String outTradeNo;      // Merchant's unique order number
    private String totalAmount;     // Order total (decimal, e.g., "88.88")
    private String subject;         // Product title
    private String body;            // Product description
    private String productCode = "QUICK_WAP_PAY";
    private String timeoutExpress = "30m";
}

Implementation

public PaymentResponse createAlipayOrder(AlipayConfig config, AlipayOrderRequest request) {
    AlipayClient client = new DefaultAlipayClient(
        config.getGatewayUrl(),
        config.getAppId(),
        config.getPrivateKey(),
        config.getFormat(),
        config.getCharset(),
        config.getPublicKey(),
        config.getSignType()
    );
    
    AlipayTradeWapPayRequest payRequest = new AlipayTradeWapPayRequest();
    AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
    model.setOutTradeNo(request.getOutTradeNo());
    model.setTotalAmount(request.getTotalAmount());
    model.setSubject(request.getSubject());
    model.setProductCode(request.getProductCode());
    payRequest.setBizModel(model);
    payRequest.setReturnUrl(config.getReturnUrl());
    payRequest.setNotifyUrl(config.getNotifyUrl());
    
    AlipayTradeWapPayResponse response = client.pageExecute(payRequest);
    return new PaymentResponse(response.getBody());
}

WeChat Pay Integration

JSAPI Payment Flow

WeChat JSAPI requires OAuth 2.0 authentication before下单:

  1. Client requests authorization code
  2. Platform redirects to WeChat authorization endpoint
  3. User grants permission
  4. Platform receives callback with authorization code
  5. Platform exchanges code for openid
  6. Platform creates order with openid

Authorization Flow

@GetMapping("/wx-oauth-redirect")
public String handleWeChatOAuth(@RequestParam String code, @RequestParam String state) {
    // Exchange authorization code for openid
    String openid = fetchWeChatOpenId(code);
    
    // Redirect to payment confirmation page
    return "redirect:/payment-confirm?openid=" + openid + "&state=" + state;
}

private String fetchWeChatOpenId(String code) {
    String tokenUrl = String.format(
        "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
        appId, appSecret, code
    );
    
    ResponseEntity<String> response = restTemplate.exchange(tokenUrl, HttpMethod.GET, null, String.class);
    JSONObject body = JSON.parseObject(response.getBody());
    return body.getString("openid");
}

Unified Order Creation

public Map<String, String> createWeChatOrder(WXConfig config, UnifiedOrderRequest request) {
    WXPay wxPay = new WXPay(new WXPayConfigImpl(config));
    
    Map<String, String> params = new HashMap<>();
    params.put("out_trade_no", request.getTradeNo());
    params.put("body", request.getDescription());
    params.put("total_fee", String.valueOf(request.getAmountInFen())); // WeChat uses fen
    params.put("spbill_create_ip", request.getClientIp());
    params.put("trade_type", "JSAPI");
    params.put("openid", request.getOpenid());
    
    Map<String, String> response = wxPay.unifiedOrder(params);
    
    // Generate JSAPI parameters for H5 page
    Map<String, String> jsapiParams = new HashMap<>();
    jsapiParams.put("appId", config.getAppId());
    jsapiParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
    jsapiParams.put("nonceStr", UUID.randomUUID().toString());
    jsapiParams.put("package", "prepay_id=" + response.get("prepay_id"));
    jsapiParams.put("signType", "HMAC-SHA256");
    jsapiParams.put("paySign", WXPayUtil.generateSignature(jsapiParams, config.getMchKey(), WXPayConstants.SignType.HMACSHA256));
    
    return jsapiParams;
}

Payment Result Handling

Challenge

Third-party payment providers use two mechanisms:

  1. Async notification: Provider calls merchant's callback URL
  2. Active polling: Merchant queries provider for status

Neither mechanism is fully reliable alone, so both must be implemented.

Delayed Query Strategy

After initiating payment, delay before querying status:

// In payment channel agent service
public void sendDelayedQueryMessage(PaymentContext context) {
    PaymentResponseDTO notice = new PaymentResponseDTO();
    notice.setOutTradeNo(context.getOrderId());
    notice.setContent(context.getChannelConfig());
    notice.setMsg(context.getChannelType()); // e.g., "ALIPAY_WAP"
    
    // Send delayed message (level 3 = 10 seconds)
    rocketMQTemplate.syncSend("TP_PAYMENT_QUERY", 
        MessageBuilder.withPayload(notice).build(), 
        1000, 3);
}

Message Consumer

@Component
@RocketMQMessageListener(topic = "TP_PAYMENT_QUERY", consumerGroup = "CG_PAYMENT_AGENT")
public class PaymentQueryConsumer implements RocketMQListener<MessageExt> {
    
    @Override
    public void onMessage(MessageExt message) {
        PaymentResponseDTO queryRequest = JSON.parseObject(
            new String(message.getBody()), PaymentResponseDTO.class);
        
        PaymentResponseDTO result = paymentAgentService.queryOrderStatus(
            queryRequest.getContent(), 
            queryRequest.getOutTradeNo());
        
        if (result.getTradeState() == TradeStatus.SUCCESS) {
            // Notify transaction service
            paymentResultProducer.sendSuccessNotification(result);
        } else if (result.getTradeState() == TradeStatus.UNKNOWN) {
            throw new RuntimeException("Payment status unclear, will retry");
        }
    }
}

Status Mapping

Alipay Status Platform Status
WAIT_BUYER_PAY PENDING
TRADE_SUCCESS SUCCESS
TRADE_CLOSED CANCELLED
TRADE_FINISHED COMPLETED

Order Number Generation

The platform generates unique order numbers using the Snowflake algorithm:

public class OrderNumberGenerator {
    private final long workerId;
    private final long dataCenterId;
    private long sequence = 0L;
    
    public String generateOrderNumber() {
        long timestamp = System.currentTimeMillis() - EPOCH;
        long sequenceId = nextSequence();
        
        return String.format("%d%02d%012d%010d",
            timestamp,
            workerId,
            dataCenterId,
            sequenceId
        );
    }
}

The Snowflake algorithm ensures uniqueness across distributed systems without coordination.

Unified Payment Entry

The payment entry point handles all payment requests uniformly:

@RequestMapping("/pay/{ticket}")
public String paymentEntry(@PathVariable String ticket, HttpServletRequest request) {
    // Decode base64 ticket containing order parameters
    String jsonParams = Base64Util.decode(ticket);
    PayOrderDTO orderParams = JSON.parseObject(jsonParams, PayOrderDTO.class);
    
    // Determine payment channel from user agent
    BrowserType clientType = BrowserTypeDetector.detect(request.getHeader("user-agent"));
    
    switch (clientType) {
        case ALIPAY:
            return "forward:/confirm-payment-alipay" + buildQueryString(orderParams);
        case WECHAT:
            return "forward:/confirm-payment-wechat" + buildQueryString(orderParams);
        default:
            return "forward:/payment-error";
    }
}

The platform identifies the payment method by examining the user agent string, as Alipay and WeChat clients send different headers when opening URLs.

Store QR Code Generation

Store QR codes encode paymant entry parameters:

public String generateStoreQRCode(StoreQRRequest request) {
    // Verify store belongs to merchant
    verifyStoreOwnership(request.getMerchantId(), request.getStoreId());
    
    // Build payment entry parameters
    PaymentEntryDTO entry = new PaymentEntryDTO();
    entry.setMerchantId(request.getMerchantId());
    entry.setStoreId(request.getStoreId());
    entry.setAppId(request.getAppId());
    entry.setServiceType("shanju_c2b");
    entry.setSubject(request.getSubject());
    
    // Encode parameters as base64 ticket
    String ticket = Base64Util.encode(JSON.toJsonString(entry));
    
    // Generate QR code URL
    return PAYMENT_ENTRY_URL + ticket;
}

The QR code essentially contains a URL with encoded merchant and store identifiers, allowing any client to initiate payment.

Payment Channel Binding

Service Type Binding

Each merchant application can bind multiple service types:

  • shanju_b2c: Business scans consumer QR code
  • shanju_c2b: Consumer scans business QR code

Channel Configuration

public class PayChannelParam {
    private Long merchantId;
    private String appId;
    private String platformChannel;    // shanju_c2b, shanju_b2c
    private String payChannel;        // ALIPAY_WAP, WX_JSAPI
    private String channelName;
    private String configParams;      // JSON: appId, privateKey, publicKey, etc.
}

The relationship between platform service types and actual payment channels:

Platform Service Payment Channel Description
shanju_b2c WX_MICROPAY WeChat payment code
shanju_b2c ALIPAY_BAR_CODE Alipay barcode
shanju_c2b WX_JSAPI WeChat JSAPI
shanju_c2b ALIPAY_WAP Alipay mobile web

Data Transfer Objects

The system uses distinct object types for different layers:

Object Type Layer Purpose
VO View UI display data
DTO Service Inter-layer transfer
DO Domain Business entities
Entity Persistence Database mapping

This separation allows each layer to maintain its own data structure, improving maintainability and reducing coupling.

Key Spring Annotations

Controller Layer

@RestController
@Api(tags = "Payment Controller")
public class PaymentController {
    
    @GetMapping("/orders/{id}")
    @ApiOperation("Query order by ID")
    public OrderDTO getOrder(@PathVariable Long id) {
        return paymentService.findById(id);
    }
    
    @PostMapping("/orders")
    @ApiOperation("Create new order")
    public OrderDTO createOrder(@RequestBody OrderCreateDTO dto) {
        return paymentService.create(dto);
    }
}

Service Layer

@Service
@Slf4j
@Transactional
public class PaymentServiceImpl implements PaymentService {
    
    @Autowired
    private PaymentChannelMapper channelMapper;
    
    @Value("${payment.timeout:30m}")
    private String defaultTimeout;
}

Exception Handling

Java exceptions are categorized as:

  • Unchecked (Runtime): NullPointerException, IndexOutOfBoundsException - not required to be caught
  • Checked: IOException, SQLException - must be handled or declared
public class PaymentException extends RuntimeException {
    private final String errorCode;
    
    public PaymentException(String code, String message) {
        super(message);
        this.errorCode = code;
    }
}

SaaS Integration

The platform integrates with a multi-tenant SaaS system for unified account management:

  1. Create tenant when merchant registers
  2. Create user linked to tenant
  3. Assign default permissions
  4. Set admin role for initial user

This separation allows the payment platform to focus on business logic while delegating authentication and authorization to the SaaS layer.

Payment Result Status

Status Code Description
0 Order created
1 Payment in progress
2 Payment successful
3 Payment completed
4 Order closed
5 Payment failed

Status transitions are managed through message queue consumers that listen for payment results from channel agents.

Tags: payment-platform alipay wechat-pay microservices distributed-systems

Posted on Thu, 03 Sep 2026 16:35:20 +0000 by drayarms