首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

易寶支付接口對接全流程:從 Demo 到生產環境最佳實踐

     編輯:Java 相關知識

支付流程時序圖


用戶          商戶系統           易寶網關            銀行
  |              |                  |                |
  |---提交訂單-->|                  |                |
  |              |---構建請求/簽名-->|                |
  |              |<--返回表單/跳轉---|                |
  |<--支付頁面---|                  |                |
  |---輸入卡號/密碼---------------->|                |
  |              |                  |---扣款請求---->|
  |              |                  |<--扣款結果-----|
  |              |<--異步通知/同步跳轉---------------|
  |              |---驗簽/更新訂單---|                |
  |<--支付結果---|                  |                |

核心參數說明 (易寶文檔標準)

參數含義示例
p0_Cmd業務類型Buy
p1_MerId商戶號10001123456
p2_Order訂單號(唯一)202401010001
p3_Amt金額(元,兩位小數)100.00
p4_Cur幣種CNY
p8_Url同步跳轉頁面https://shop.com/pay/return
p9_SAF傳輸類型0 (POST)
pd_FrpId銀行編碼ICBC-NET, CCB-NET
hmacMD5/HMAC 簽名a1b2c3d4...

完整 Java 客戶端實現

public class YeePayClient {

    private final String merchantId;
    private final String secretKey; // 易寶分配的密鑰
    private final String gatewayUrl = "https://www.yeepay.com/app-merchant-proxy/node";
    private final String notifyUrl;  // 異步通知地址
    private final String returnUrl;  // 同步跳轉地址

    public YeePayClient(String merchantId, String secretKey, String notifyUrl, String returnUrl) {
        this.merchantId = merchantId;
        this.secretKey = secretKey;
        this.notifyUrl = notifyUrl;
        this.returnUrl = returnUrl;
    }

    /** 生成支付表單 HTML (前端直接提交) */
    public String buildPayForm(PayRequest req) {
        Map params = new LinkedHashMap<>(); // 保持順序
        params.put("p0_Cmd", "Buy");
        params.put("p1_MerId", merchantId);
        params.put("p2_Order", req.getOrderNo());
        params.put("p3_Amt", String.format("%.2f", req.getAmount()));
        params.put("p4_Cur", "CNY");
        params.put("p5_Pid", req.getProductName());
        params.put("p6_Pcat", req.getProductCategory());
        params.put("p7_Pdesc", req.getProductDesc());
        params.put("p8_Url", returnUrl);
        params.put("p9_SAF", "0");
        params.put("pa_MP", req.getAttach()); // 商戶擴展信息
        params.put("pd_FrpId", req.getBankCode());
        params.put("pr_NeedResponse", "1");
        
        // 計算簽名
        String hmac = sign(params);
        params.put("hmac", hmac);
        
        // 生成自動提交表單
        StringBuilder html = new StringBuilder();
        html.append("
"); params.forEach((k, v) -> html.append("")); html.append(""); html.append("
"); return html.toString(); } /** 驗證異步通知/同步返回 */ public boolean verifyCallback(Map params) { String receivedHmac = params.remove("hmac"); // 移除後驗證 String calculatedHmac = sign(params); return calculatedHmac.equals(receivedHmac); } /** 簽名算法:按 key 排序拼接 + 密鑰 -> MD5 */ private String sign(Map params) { StringBuilder sb = new StringBuilder(); params.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .forEach(e -> sb.append(e.getValue())); sb.append(secretKey); return DigestUtils.md5Hex(sb.toString()).toUpperCase(); } private String escapeHtml(String s) { return s.replace("&", "&").replace("<", "<").replace(">", ">") .replace("\"", """).replace("'", "'"); } // ==================== 內部類 ==================== @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class PayRequest { private String orderNo; private BigDecimal amount; private String productName; private String productCategory; private String productDesc; private String bankCode; // 為空則易寶選銀行頁 private String attach; // 回調時原樣返回 } }

Controller 層調用示例

@RestController
@RequestMapping("/pay/yeepay")
public class YeePayController {

    @Autowired YeePayClient yeePayClient;
    @Autowired OrderService orderService;

    @PostMapping("/create")
    public Result createPayForm(@RequestBody PayCreateDTO dto) {
        Order order = orderService.createOrder(dto); // 生成訂單,狀態 UNPAID
        YeePayClient.PayRequest req = YeePayClient.PayRequest.builder()
            .orderNo(order.getOrderNo())
            .amount(order.getAmount())
            .productName(order.getProductName())
            .bankCode(dto.getBankCode())
            .attach(order.getOrderNo()) // 回調攜帶訂單號
            .build();
        String formHtml = yeePayClient.buildPayForm(req);
        return Result.success(formHtml); // 前端 document.write(formHtml) 或 iframe 載入
    }

    @PostMapping("/notify") // 異步通知
    public String notify(HttpServletRequest request) {
        Map params = new HashMap<>();
        request.getParameterMap().forEach((k, v) -> params.put(k, v[0]));
        
        if (!yeePayClient.verifyCallback(params)) {
            log.warn("易寶通知驗簽失敗: {}", params);
            return "FAIL";
        }
        
        String orderNo = params.get("r6_Order");
        String amount = params.get("r3_Amt");
        String resultCode = params.get("r1_Code"); // "1" 成功
        
        if ("1".equals(resultCode)) {
            // 幂等處理:只更新 UNPAID -> PAID
            boolean updated = orderService.updateStatusIfUnpaid(orderNo, OrderStatus.PAID, 
                new PaymentRecord(orderNo, amount, "YEEPAY", params.get("r2_TrxId")));
            if (updated) {
                notificationService.sendOrderConfirmation(orderNo); // 異步發郵件/短信
            }
        }
        return "SUCCESS"; // 必須返回 SUCCESS,否則易寶會重試
    }

    @GetMapping("/return") // 同步跳轉
    public String returnPage(HttpServletRequest request, Model model) {
        Map params = new HashMap<>();
        request.getParameterMap().forEach((k, v) -> params.put(k, v[0]));
        boolean verified = yeePayClient.verifyCallback(params);
        model.addAttribute("verified", verified);
        model.addAttribute("orderNo", params.get("r6_Order"));
        return "pay/result"; // 支付結果頁
    }
}

生產環境關鍵加固

  1. 密鑰管理:密鑰存放於配置中心/密保箱,絕不硬編碼、提交代碼倉庫
  2. 幂等性:異步通知可能重複,基於 訂單號 + 狀態機 保證冪等
  3. 金額校驗:回調時核對 r3_Amt 與訂單金額一致,防範竄改
  4. 異步通知可靠性:處理邏輯拋異常不返回 SUCCESS,易寶按策略重試(間隔遞增)
  5. 日誌審計:完整記錄請求/回調報文(脫敏),便於對賬排查
  6. 測試環境隔離:使用易寶測試商戶號,真實銀行卡小額驗證

常見問題排查表

現象可能原因解決
驗簽失敗參數順序錯、編碼不一致、密鑰錯嚴格按文檔排序、UTF-8、核對密鑰
異步通知收不到notifyUrl 不可達、防火牆、返回非 SUCCESS公網可訪問、HTTPS、邏輯不拋異常
金額不符分/元單位混淆、格式化精度丟失統一用 BigDecimal、格式化 %.2f
重複扣款用戶重複提交、無防重機制前端按鈕防抖、後端訂單號唯一索引

總結

易寶支付對接核心在於:請求參數嚴格按文檔排序簽名、異步通知驗簽+幂等處理、金額精度用 BigDecimal、密鑰安全管理。封裝通用 YeePayClient,Controller 薄調用,業務邏輯集中在 Service,是穩健接入第三方支付的標準架構。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved