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

Struts2 整合支付寶即時到賬接口完整實戰(含配置、簽名、異步通知)

     編輯:Java 程式設計入門

業務流程圖解

  1. 用戶下單 → 後台生成訂單號、金額
  2. 調用支付寶 create_direct_pay_by_user 接口,構建表單/URL
  3. 用戶跳轉支付寶收銀台完成支付
  4. 支付寶同步跳轉 return_url(頁面提示)
  5. 支付寶異步通知 notify_url(核心業務:驗簽、更新訂單狀態、發貨)

核心依賴

<!-- 支付寶官方 SDK(含簽名、HTTP 工具) -->
<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>20230615</version>
</dependency>
<!-- JSON 處理 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>

1. 配置類 AlipayConfig

package com.hzdracom.config;

public class AlipayConfig {
    // ===== 必須在商家後台獲取 =====
    public static final String PARTNER      = "2088001466494907"; // PID
    public static final String SELLER_EMAIL = "jjj@hzdracom.com";   // 賣家賬號
    public static final String PRIVATE_KEY  = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD..."; // RSA2 私鑰
    public static final String ALIPAY_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."; // 支付寶公鑰

    // ===== 固定參數 =====
    public static final String SIGN_TYPE    = "RSA2";
    public static final String CHARSET      = "UTF-8";
    public static final String GATEWAY_URL  = "https://openapi.alipay.com/gateway.do";
    public static final String FORMAT       = "JSON";

    // ===== 業務回調地址(需外網可訪問,建議配置域名)=====
    public static final String NOTIFY_URL   = "https://pay.example.com/alipay/notify";
    public static final String RETURN_URL   = "https://pay.example.com/alipay/return";

    // ===== 可選防釣魚 =====
    public static final String ANTI_PHISHING_KEY = "";
    public static final String EXTER_INVOKE_IP   = "";
}

2. 統一下單 Action AliPayAction

package com.hzdracom.alipay.action;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.hzdracom.config.AlipayConfig;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.ServletActionContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class AliPayAction extends ActionSupport {
    private static final Logger log = LogManager.getLogger(AliPayAction.class);

    private String totalFee;   // 前端傳入金額(元)
    private String subject;    // 訂單標題
    private String body;       // 訂單描述

    public String pay() {
        try {
            // 1. 生成唯一訂單號(時間戳+隨機數)
            String outTradeNo = System.currentTimeMillis() + "" + (int)(Math.random()*10000);

            // 2. 初始化客戶端
            AlipayClient client = new DefaultAlipayClient(
                AlipayConfig.GATEWAY_URL,
                AlipayConfig.PARTNER,
                AlipayConfig.PRIVATE_KEY,
                AlipayConfig.FORMAT,
                AlipayConfig.CHARSET,
                AlipayConfig.ALIPAY_PUBLIC_KEY,
                AlipayConfig.SIGN_TYPE
            );

            // 3. 構建請求參數(JSON 格式)
            AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
            request.setReturnUrl(AlipayConfig.RETURN_URL);
            request.setNotifyUrl(AlipayConfig.NOTIFY_URL);
            request.setBizContent(String.format(
                "{\"out_trade_no\":\"%s\",\"product_code\":\"FAST_INSTANT_TRADE_PAY\",\"total_amount\":\"%s\",\"subject\":\"%s\",\"body\":\"%s\"}",
                outTradeNo, totalFee, subject, body
            ));

            // 4. 調用 SDK 獲取表單 HTML(自動簽名)
            String formHtml = client.pageExecute(request).getBody();

            // 5. 直接輸出到瀏覽器,用戶自動跳轉支付寶
            ServletActionContext.getResponse().setContentType("text/html;charset=UTF-8");
            ServletActionContext.getResponse().getWriter().write(formHtml);
            return NONE; // 已手動輸出,不需 Struts 結果視圖
        } catch (AlipayApiException | IOException e) {
            log.error("支付寶下單失敗", e);
            addActionError("下單失敗:" + e.getMessage());
            return ERROR;
        }
    }

    // Getter/Setter 省略
}

3. 異步通知處理 NotifyAction(核心業務邏輯)

package com.hzdracom.alipay.action;

import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.hzdracom.config.AlipayConfig;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.ServletActionContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

public class NotifyAction extends ActionSupport {
    private static final Logger log = LogManager.getLogger(NotifyAction.class);

    public String notify() {
        HttpServletRequest request = ServletActionContext.getRequest();
        try {
            // 1. 獲取所有參數
            Map params = new HashMap<>();
            request.getParameterMap().forEach((k, v) -> params.put(k, v[0]));

            // 2. 驗證簽名(防偽造通知)
            boolean verified = AlipaySignature.rsaCheckV1(
                params, AlipayConfig.ALIPAY_PUBLIC_KEY, AlipayConfig.CHARSET, AlipayConfig.SIGN_TYPE
            );
            if (!verified) {
                log.warn("簽名驗證失敗,疑似偽造通知:{}", params);
                return "fail"; // 支付寶收到非 success 會重試
            }

            // 3. 核心業務字段
            String outTradeNo = params.get("out_trade_no");   // 商戶訂單號
            String tradeNo    = params.get("trade_no");       // 支付寶交易號
            String tradeStatus = params.get("trade_status");  // TRADE_SUCCESS / TRADE_FINISHED
            String buyerId    = params.get("buyer_id");       // 買家用戶號

            log.info("收到支付寶異步通知:outTradeNo={}, tradeNo={}, status={}", outTradeNo, tradeNo, tradeStatus);

            // 4. 幂等性處理:防止重複通知重複發貨
            if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
                boolean processed = OrderService.markPaid(outTradeNo, tradeNo, buyerId);
                if (!processed) {
                    log.warn("訂單已處理,忽略重複通知:{}", outTradeNo);
                }
            }

            // 5. 必須返回純文本 "success",否則支付寶會按失敗重試(最長 24h)
            ServletActionContext.getResponse().getWriter().write("success");
            return NONE;
        } catch (AlipayApiException | IOException e) {
            log.error("異步通知處理異常", e);
            return "fail";
        }
    }
}

4. 同步跳轉頁面 ReturnAction

package com.hzdracom.alipay.action;

import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.ServletActionContext;

public class ReturnAction extends ActionSupport {
    public String returnView() {
        // 僅做頁面提示,不可作為訂單狀態最終依據(用戶可能關閉頁面)
        ServletActionContext.getRequest().setAttribute("msg", "支付完成,正在為您跳轉...");
        return SUCCESS; // 對應 result 頁面 return.jsp
    }
}

5. Struts 配置 struts.xml

<package name="alipay" namespace="/alipay" extends="struts-default">
    <action name="pay" class="com.hzdracom.alipay.action.AliPayAction" method="pay">
        <result name="error">/error.jsp</result>
    </action>

    <action name="notify" class="com.hzdracom.alipay.action.NotifyAction" method="notify">
        <result name="success" type="stream">
            <param name="contentType">text/plain</param>
            <param name="inputName">inputStream</param>
        </result>
        <result name="fail" type="stream">
            <param name="contentType">text/plain</param>
            <param name="inputName">inputStream</param>
        </result>
    </action>

    <action name="return" class="com.hzdracom.alipay.action.ReturnAction" method="returnView">
        <result name="success">/alipay/return.jsp</result>
    </action>
</package>

6. 關鍵注意事項(避坑指南)

項目說明
回調地址notify_url/return_url 必須是 外網可訪問的完整 HTTPS URL,不能用 localhost、內網 IP、帶參數 URL
金額格式total_amount 單位為元,保留兩位小數,如 "0.01"
字符集全鏈路統一 UTF-8,JSP 頁面、Tomcat URIEncoding、MySQL 連接串均需一致
幂等性異步通知可能重複,必須用 out_trade_no + 狀態機判斷是否已處理
簽名算法強制使用 RSA2(SHA256withRSA),RSA(SHA1withRSA)已停止支持
SDK 版本定期更新 alipay-sdk-java,支付寶會下線舊版接口
沙箱測試開發階段請使用 支付寶開放平台沙箱,配置沙箱 PID/密鑰/網關

7. 測試檢查清單

  1. 配置文件中 PID、私鑰、公鑰、賣家賬號是否正確?
  2. notify_url/return_url 是否已在支付寶後台「應用配置」綁定?
  3. 服務器防火牆/安全組是否放行 443 端口入站?
  4. 日誌中是否出現「簽名驗證失敗」或「參數解析異常」?
  5. 沙箱環境完整跑通:下單 → 支付 → 異步通知入庫 → 同步頁面提示
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved