Production-Grade Payment System Guide: Java Implementation for Alipay & WeChat All-Channel Integration
This article explains why a payment system is more than a simple SDK integration, presents a four‑layer architecture (access, state, event, governance), details state machines, idempotent callbacks, outbox pattern, reconciliation, high‑concurrency handling, observability, security, and provides Java code examples for integrating Alipay and WeChat across all channels.
Production‑Grade Payment System Overview
Payment is often mistaken for a simple SDK call. In production it is a strongly stateful, strongly consistent, and strongly auditable money‑collaboration system.
Why Payment Is Not an Ordinary Integration
Which source (order DB, Redis, front‑end page, or third‑party channel) determines the final payment status?
How to handle duplicate or out‑of‑order callbacks?
How to recover when only part of the post‑payment actions (delivery, points, membership, notifications) succeed?
How to support multiple channels (Alipay, WeChat, App, Mini‑Program, PC) with a unified entry?
How to guarantee fast channel confirmation during traffic spikes?
How to close the loop when end‑of‑day reconciliation finds discrepancies?
Goals
Explain the state and trust model behind Alipay and WeChat integration.
Provide a production‑grade, unified payment middle‑platform architecture.
Cover the full chain: order creation, callbacks, queries, refunds, reconciliation, compensation, and governance.
Offer Java/Spring Boot skeleton code.
Discuss high concurrency, observability, and security.
Four‑Layer Architecture
Access Layer : unified entry, authentication, request‑level idempotency, parameter standardization, channel routing.
State Layer : payment and refund state machines, state transition rules, optimistic locking.
Event Layer : reliable event chain for callbacks, delivery, points, coupons, notifications.
Governance Layer : metrics, tracing, audit, reconciliation, compensation, risk control, rate‑limiting, gray‑release, rollback.
Access Layer Details
Authentication & merchant identification.
Request‑level idempotency (e.g., pay:create:{merchantId}:{bizOrderId}).
Parameter standardization.
Channel & product routing.
State Layer Details
Payment order state machine:
INIT → PAYING → SUCCESS / FAILED / CLOSED → REFUNDING → REFUNDED.
Refund order state machine: INIT, ACCEPTED, PROCESSING, SUCCESS, FAILED, CLOSED.
Key principle: once SUCCESS is reached it cannot be overwritten by ordinary failures; duplicate events must not cause duplicate business actions.
# State transition example (simplified)
# CurrentState Event NextState Remark
INIT CreateChannelOrderSuccess PAYING Sync order creation
INIT CreateChannelOrderFail FAILED Parameter or channel error
PAYING ChannelAsyncSuccess SUCCESS Main success path
PAYING ChannelQuerySuccess SUCCESS Compensation path
PAYING ChannelCloseOrTimeout CLOSED Timeout or merchant close
SUCCESS RefundRequest REFUNDING Start refund flow
REFUNDING RefundSuccess REFUNDED Refund completedEvent Layer Details
Callbacks are first turned into internal trusted events.
Event ingestion is idempotent (key = {channel}:{eventId}).
After a successful state transition an outbox record is created for asynchronous downstream processing.
Outbox table stores
aggregate_type, aggregate_id, event_type, payload_json, status, retry_count, next_retry_time.
Governance Layer Details
Compensation jobs query long‑running PAYING orders and reconcile with the channel.
End‑of‑day reconciliation compares channel statements, local payment orders, and downstream consumption results.
Core metrics: order success rate, channel call success, callback verification failures, PAYING backlog, reconciliation diff rate, outbox backlog, MQ latency.
Unified logging fields:
traceId, merchantId, bizOrderId, paymentId, channel, eventType, statusBefore, statusAfter.
Audit logs record who, when, which order, what action, before/after status, reason, and remarks.
Security: certificate management, callback signature verification, nonce & timestamp checks, WAF protection, secret management via KMS/Vault.
Key Code Samples
Create Payment Command (Java record)
public record CreatePaymentCommand(
Long merchantId,
String bizOrderId,
PayChannel channel,
PayProduct product,
long amountCent,
String subject,
String description,
String clientIp,
String openId,
String appId,
Instant expireTime,
String requestId,
Map<String, String> metadata
) {}Payment Channel Result (Java record)
public record PaymentChannelResult(
String paymentId,
String channelTradeNo,
String payUrl,
String codeUrl,
String prepayId,
Instant expireTime
) {}PaymentChannelGateway Interface
public interface PaymentChannelGateway {
PayChannel channel();
PaymentChannelResult createOrder(ChannelCreateOrderRequest request);
ChannelQueryResult queryOrder(ChannelQueryRequest request);
ChannelRefundResult createRefund(ChannelRefundRequest request);
ChannelRefundQueryResult queryRefund(ChannelRefundQueryRequest request);
ChannelCloseResult closeOrder(ChannelCloseRequest request);
}PaymentApplicationService (core orchestration)
@Service
public class PaymentApplicationService {
private final PaymentOrderRepository paymentOrderRepository;
private final PaymentChannelRegistry paymentChannelRegistry;
private final IdempotencyService idempotencyService;
@Transactional
public CreatePaymentResponse createPayment(CreatePaymentCommand command) {
idempotencyService.checkAndLock(
"pay:create:" + command.merchantId() + ":" + command.bizOrderId(),
Duration.ofSeconds(10)
);
PaymentOrder existed = paymentOrderRepository.findByMerchantAndBizOrder(
command.merchantId(), command.bizOrderId());
if (existed != null) {
return CreatePaymentResponse.fromExisting(existed);
}
PaymentOrder paymentOrder = PaymentOrder.create(command);
paymentOrderRepository.save(paymentOrder);
PaymentChannelGateway gateway = paymentChannelRegistry.mustGet(command.channel());
PaymentChannelResult result = gateway.createOrder(ChannelCreateOrderRequest.from(paymentOrder, command));
paymentOrder.markPaying(result.channelTradeNo());
paymentOrderRepository.updateAfterChannelAccept(paymentOrder);
return CreatePaymentResponse.from(paymentOrder, result);
}
}Alipay Adapter Skeleton
@Component
public class AlipayChannelGateway implements PaymentChannelGateway {
private final AlipayClient alipayClient;
private final AlipayProperties properties;
@Override
public PayChannel channel() { return PayChannel.ALIPAY; }
@Override
public PaymentChannelResult createOrder(ChannelCreateOrderRequest request) {
try {
AlipayTradePrecreateRequest apiRequest = new AlipayTradePrecreateRequest();
apiRequest.setNotifyUrl(properties.getNotifyUrl());
apiRequest.setBizContent("""
{"out_trade_no":"%s","total_amount":"%s","subject":"%s","timeout_express":"30m"}
""".formatted(
request.paymentId(),
FenAmount.of(request.amountCent()).toYuanString(),
request.subject()
));
AlipayTradePrecreateResponse response = alipayClient.certificateExecute(apiRequest);
if (!response.isSuccess()) {
throw new ChannelBizException("ALIPAY_PRECREATE_FAIL", response.getSubMsg());
}
return new PaymentChannelResult(
request.paymentId(),
response.getTradeNo(),
null,
response.getQrCode(),
null,
request.expireTime()
);
} catch (AlipayApiException ex) {
throw new ChannelInvokeException("ALIPAY_API_ERROR", ex.getErrMsg(), ex);
}
}
// other methods omitted for brevity
}WeChat Adapter Skeleton
@Component
public class WechatPayChannelGateway implements PaymentChannelGateway {
private final NativePayService nativePayService;
private final JsapiServiceExtension jsapiServiceExtension;
@Override
public PayChannel channel() { return PayChannel.WECHAT; }
@Override
public PaymentChannelResult createOrder(ChannelCreateOrderRequest request) {
if (request.product() == PayProduct.NATIVE) {
PrepayRequest prepayRequest = new PrepayRequest();
prepayRequest.setOutTradeNo(request.paymentId());
prepayRequest.setDescription(request.subject());
prepayRequest.setNotifyUrl(request.notifyUrl());
Amount amount = new Amount();
amount.setTotal((int) request.amountCent());
prepayRequest.setAmount(amount);
NativePrepayResponse response = nativePayService.prepay(prepayRequest);
return new PaymentChannelResult(
request.paymentId(),
null,
null,
response.getCodeUrl(),
null,
request.expireTime()
);
}
if (request.product() == PayProduct.JSAPI) {
PrepayRequest requestBody = new PrepayRequest();
requestBody.setOutTradeNo(request.paymentId());
requestBody.setDescription(request.subject());
requestBody.setNotifyUrl(request.notifyUrl());
Amount amount = new Amount();
amount.setTotal((int) request.amountCent());
requestBody.setAmount(amount);
Payer payer = new Payer();
payer.setOpenid(request.openId());
requestBody.setPayer(payer);
PrepayWithRequestPaymentResponse response = jsapiServiceExtension.prepayWithRequestPayment(requestBody);
return new PaymentChannelResult(
request.paymentId(),
null,
null,
null,
response.getPrepayId(),
request.expireTime()
);
}
throw new IllegalArgumentException("Unsupported pay product: " + request.product());
}
// other methods omitted for brevity
}Trusted Callback Controllers
WeChat Callback
@RestController
@RequestMapping("/callbacks/wechat")
public class WechatCallbackController {
private final WechatCallbackVerifier verifier;
private final PaymentEventIngressService paymentEventIngressService;
@PostMapping("/pay")
public ResponseEntity<Map<String, String>> callback(
@RequestHeader("Wechatpay-Timestamp") String timestamp,
@RequestHeader("Wechatpay-Nonce") String nonce,
@RequestHeader("Wechatpay-Signature") String signature,
@RequestHeader("Wechatpay-Serial") String serial,
@RequestBody String body) {
VerifiedWechatCallback callback = verifier.verifyAndDecrypt(timestamp, nonce, signature, serial, body);
paymentEventIngressService.ingressChannelEvent(ChannelCallbackCommand.wechat(callback));
return ResponseEntity.ok(Map.of("code", "SUCCESS", "message", "成功"));
}
}Alipay Callback
@RestController
@RequestMapping("/callbacks/alipay")
public class AlipayCallbackController {
private final AlipayNotifyVerifier notifyVerifier;
private final PaymentEventIngressService paymentEventIngressService;
@PostMapping("/pay")
public String callback(HttpServletRequest request) {
Map<String, String> params = RequestParamUtils.toFlatMap(request);
notifyVerifier.verify(params);
paymentEventIngressService.ingressChannelEvent(ChannelCallbackCommand.alipay(params));
return "success";
}
}Event Ingress Service (Idempotent Ingestion)
@Service
public class PaymentEventIngressService {
private final PaymentEventRepository paymentEventRepository;
private final PaymentOrderRepository paymentOrderRepository;
private final OutboxRepository outboxRepository;
@Transactional
public void ingressChannelEvent(ChannelCallbackCommand command) {
String idempotencyKey = command.channel() + ":" + command.eventId();
if (paymentEventRepository.existsByIdempotencyKey(idempotencyKey)) {
return;
}
PaymentEvent event = PaymentEvent.fromCallback(command, idempotencyKey);
paymentEventRepository.save(event);
PaymentOrder paymentOrder = paymentOrderRepository.mustFindByPaymentId(command.paymentId());
boolean changed = paymentOrder.applyChannelEvent(event);
if (changed) {
paymentOrderRepository.update(paymentOrder);
outboxRepository.save(OutboxMessage.paymentSucceeded(paymentOrder));
}
}
}Outbox Publisher Job
@Service
public class OutboxPublisherJob {
private final OutboxRepository outboxRepository;
private final MessagePublisher messagePublisher;
@Transactional
public int publishBatch() {
List<OutboxMessage> messages = outboxRepository.lockBatchForPublish(100);
int count = 0;
for (OutboxMessage message : messages) {
try {
messagePublisher.publish(message.topic(), message.businessKey(), message.payload());
outboxRepository.markSent(message.id());
count++;
} catch (Exception ex) {
outboxRepository.markRetry(message.id(), Backoff.nextTime(message.retryCount()));
}
}
return count;
}
}Compensation Job for PAYING Orders
@Service
public class PaymentReconcileJob {
private final PaymentOrderRepository paymentOrderRepository;
private final PaymentChannelRegistry paymentChannelRegistry;
private final PaymentEventIngressService paymentEventIngressService;
public void reconcilePayingOrders() {
List<PaymentOrder> payingOrders = paymentOrderRepository.findNeedQueryOrders(
PaymentStatus.PAYING, Instant.now().minusSeconds(120), 200);
for (PaymentOrder order : payingOrders) {
try {
PaymentChannelGateway gateway = paymentChannelRegistry.mustGet(order.getPayChannel());
ChannelQueryResult result = gateway.queryOrder(ChannelQueryRequest.of(order));
if (result.isSuccess()) {
paymentEventIngressService.ingressChannelEvent(ChannelCallbackCommand.fromQueryResult(result));
} else if (result.isClosed()) {
paymentEventIngressService.ingressChannelEvent(ChannelCallbackCommand.fromClosedResult(result));
}
} catch (Exception ex) {
// log compensation failure, do not break the batch
}
}
}
}High‑Concurrency Governance
Identify bottlenecks such as DB connection pool saturation, Redis hot keys, synchronous channel calls, and MQ consumption delays. Isolate resources with dedicated thread pools and HTTP connection pools per channel. Apply layered rate‑limiting: order‑level, merchant‑level, channel‑level, and activity‑level limits.
Observability & Audit
Core metrics: order success rate, channel call success, callback verification failures, PAYING backlog, compensation success rate, reconciliation diff rate, outbox backlog, MQ latency.
Unified log fields:
traceId, merchantId, bizOrderId, paymentId, channel, eventType, statusBefore, statusAfter.
Audit logs capture who, when, which order, what action, before/after status, reason, and remarks.
Security Governance
Certificates and private keys are stored in KMS/Secrets Manager; rotation is performed via gray‑release.
Callback signature verification, timestamp tolerance, nonce checks, and WAF protection are mandatory.
Sensitive personal data (real name, full card number, phone, ID) are masked in logs.
Production Checklist (selected items)
Merchant authentication completed.
Request‑level idempotency for order creation.
Channel routing rules support gray release.
State machines for payment and refund are immutable.
Callback entry verifies signature before persisting events.
Outbox and state update share the same transaction.
Compensation jobs isolated from online traffic.
Certificate expiration alerts configured.
Core metrics have alert thresholds.
Audit logs are traceable.
Conclusion
A production‑grade payment system must be treated as a money‑state engine rather than a simple SDK wrapper. By separating concerns into Access, State, Event, and Governance layers, handling idempotent callbacks, using an outbox pattern for reliable downstream processing, and implementing compensation and reconciliation, the system becomes robust, observable, and secure. The provided Java/Spring Boot skeletons for Alipay and WeChat illustrate how to materialize these principles in code.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Cloud Architecture
Focuses on cloud‑native and distributed architecture engineering, sharing practical solutions and lessons learned. Covers microservice governance, Kubernetes, observability, and stability engineering to help your systems run stable, fast, and cost‑effectively.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
