package com.ruoyi.app.pay; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.ruoyi.system.domain.PaymentGatewayLog; import com.ruoyi.system.service.IPaymentGatewayLogService; import com.ruoyi.app.utils.linepay.LinePayResponse; import org.springframework.stereotype.Service; import java.util.UUID; /** Appends LINE gateway audit rows without making audit availability part of money facts. */ @Service public class LinePayGatewayAuditService { private final IPaymentGatewayLogService gatewayLogService; public LinePayGatewayAuditService(IPaymentGatewayLogService gatewayLogService) { this.gatewayLogService = gatewayLogService; } public T execute(String action, String source, Long paymentId, Long refundId, Long credentialId, Long storeId, String ddId, String gatewayOrderId, String transactionId, GatewayCall call) throws Exception { long started = System.currentTimeMillis(); String correlationId = UUID.randomUUID().toString(); append(correlationId, action, source, paymentId, refundId, credentialId, storeId, ddId, gatewayOrderId, transactionId, 0, 0L, null, "REQUEST"); try { T result = call.call(); append(correlationId, action, source, paymentId, refundId, credentialId, storeId, ddId, gatewayOrderId, transactionId, 1, System.currentTimeMillis() - started, result instanceof LinePayResponse ? (LinePayResponse) result : null, "RESPONSE"); return result; } catch (Exception exception) { append(correlationId, action, source, paymentId, refundId, credentialId, storeId, ddId, gatewayOrderId, transactionId, 0, System.currentTimeMillis() - started, null, "RESPONSE"); throw exception; } } public void event(String action, String source, Long paymentId, Long credentialId, Long storeId, String ddId, String gatewayOrderId, String transactionId) { append(UUID.randomUUID().toString(), action, source, paymentId, null, credentialId, storeId, ddId, gatewayOrderId, transactionId, 1, 0L, null, "EVENT"); } private void append(String correlationId, String action, String source, Long paymentId, Long refundId, Long credentialId, Long storeId, String ddId, String gatewayOrderId, String transactionId, int success, long durationMs, LinePayResponse response, String direction) { PaymentGatewayLog log = new PaymentGatewayLog(); log.setCorrelationId(correlationId); log.setAction(action); log.setDirection(direction); log.setSource(source); log.setPaymentId(paymentId); log.setRefundId(refundId); log.setCredentialId(credentialId); log.setStoreId(storeId); log.setDdId(ddId); log.setGatewayOrderId(gatewayOrderId); log.setTransactionId(response != null && response.transactionId() != null ? response.transactionId() : transactionId); log.setHttpStatus(response == null ? null : response.httpStatus()); log.setReturnCode(response == null ? null : response.returnCode()); log.setReturnMessage(response == null ? null : response.returnMessage()); log.setPayload(response == null ? null : safePayload(response.rawBody())); log.setSuccess(response == null ? success : response.isSuccess() ? 1 : 0); log.setDurationMs(durationMs); try { gatewayLogService.append(log); } catch (Exception ignored) { // Audit failure must not alter an already known gateway or payment outcome. } } private static String safePayload(String rawBody) { if (rawBody == null || rawBody.isBlank()) { return rawBody; } try { JSONObject root = JSON.parseObject(rawBody); JSONObject info = root.getJSONObject("info"); if (info != null) { info.remove("paymentAccessToken"); JSONObject paymentUrl = info.getJSONObject("paymentUrl"); if (paymentUrl != null) { paymentUrl.remove("app"); paymentUrl.remove("web"); } } return root.toJSONString(); } catch (RuntimeException ignored) { return null; } } @FunctionalInterface public interface GatewayCall { T call() throws Exception; } }