博客 / 詳情

返回

[親媽版]SpringBoot集成網頁支付寶支付對接流程

場景:
針對PC網站支付場景

一、支付寶開放平台設置

1、登錄支付寶開放平台

2、進入控制枱,在【我的應用】選擇【網頁&移動應用】,然後點擊【創建應用】

image.png

3、填寫好響應的應用信息,點擊【確認創建】

image.png

4、創建好之後,在控制枱進入應用,添加"電腦網站支付能力"

説明:添加這個能力,會需要進行填寫簽約信息,按着步驟走就行,不過填寫的網站必須和企業支付寶的營業執照一致
image.png

5、點擊左側【應用信息】,進行設置開發信息,只設置了接口加簽

image.png

5.1、設置接口加簽方式,有公鑰證書和公鑰兩個選擇,一般選擇公鑰

5.2、然後需要生成密鑰,才能生成支付寶公鑰,生成密鑰也有三種方式:

第一種:下載密鑰生成工具
1、下載密鑰生成工具:https://opendocs.alipay.com/o...
windows版本電腦下載windows版本工具。
mac版本電腦下載mac_osx版本工具。
image.png

2、運行安裝密鑰生成工具
點擊下載的“AlipayDevelopmentAssistant-1.0.1.exe”(即支付寶開放平台開放助手)進行安裝並運行。
説明:本工具只會記錄上傳點擊事件操作行為,不會記錄上傳用户的任何用户信息以及公私鑰等敏感信息。
image.png

3、生成密鑰
選擇密鑰格式和密鑰長度,點擊“生成密鑰”進行密鑰生成 。
(1)密鑰長度
RSA2:密鑰長度為2048位。
注:開放平台從2018年1月5日開始創建的應用都沒有RSA密鑰的設置入口,只能上傳RSA2格式密鑰

RSA:密鑰長度為1024位。
國密:目前暫不支持國密的加簽方式,即使獲取後也無法設置加簽。

(2)密鑰格式
PKCS8:JAVA開發語言適用。
PKCS1:非JAVA開發語言適用。
image.png
4、配置密鑰
開放平台(open.alipay.com)選擇要上傳的應用,點擊應用進入詳細頁,選擇應用信息內的“接口加簽方式”進行設置。

如應用未上線,需要選擇概覽裏面的接口加簽方式進行設置。
(1)應用私鑰:開放平台沒有上傳設置的位置,需要自己進行保存並設置到代碼中,且由於其涉及資金安全不能將其提供給他人,若不小心丟失或泄露,請及時進行更新修改。
(2)應用公鑰:需要將其傳入開放平台應用中(每次更換密鑰時都要將其重新上傳開放平台),如圖:
image.png
5、接口中以及支付寶開放平台配置密鑰
生成密鑰後切記成對妥善保管,避免測試時由於公私鑰不匹配導致簽名驗籤等一系列不必要的錯誤產生。

(1)正式環境中配置密鑰
開發技術人員在接口中配置正式環境配置密鑰,需選擇要上傳的應用,點擊應用進入詳細頁,選擇應用信息內的“接口加簽方式”進行設置。
image.png

(2)沙箱環境中配置密鑰
沙箱環境配置密鑰,需選擇在沙箱應用中,選擇應用信息內的“RSA2(SHA256)密鑰(推薦)”進行設置。

沙箱環境應用配置點擊沙箱應用瞭解。
image.png

第二種:web在線生成密鑰
1、生成密鑰
1.1、打開Web 在線加密(無下載,新上線)->選擇“生成密鑰”->選擇“保存密鑰”。
1.2、選擇密鑰格式和密鑰長度,點擊“生成密鑰”進行密鑰生成 。
(1)密鑰長度
RSA2:密鑰長度為2048位。
注:開放平台從2018年1月5日開始創建的應用都沒有RSA密鑰的設置入口,只能上傳RSA2格式密鑰
RSA:密鑰長度為1024位。
(2)密鑰格式
PKCS8:JAVA開發語言適用。
PKCS1:非JAVA開發語言適用。
2、配置密鑰
配置密鑰流程與“密鑰生成工具生成密鑰”的配置密鑰流程相同。
將應用公鑰上傳到開放平台應用的接口加簽中,應用私鑰請放在接口代碼私鑰位置。
image.png

第三種:使用OpenSSL生成
除了使用支付寶提供的一鍵生成工具外,也可以使用OpenSSL工具命令生成密鑰。

使用OpenSSL工具命令生成,命令語句如下:
1、生成RSA2私鑰(2048位)命令: genrsa -out app_private_key.pem 2048 ;

2、把私鑰轉換成PKCS8格式並輸出新文件命令: pkcs8 -topk8 -inform PEM -in app_private_key.pem -outform PEM -nocrypt -out app_private_key_pkcs8.pem;

3、生成公鑰命令: rsa -in app_private_key.pem -pubout -out app_public_key.pem;
之後把生成的公鑰上傳給支付寶,並獲取支付寶公鑰(ALIPAY_PUBLIC_KEY)。

詳細見使用OpenSSL工具生成密鑰在線文檔。

注:生成密鑰後切記成對妥善保管,若不小心丟失或泄露,請及時進行生成生成並重新上傳到支付寶開放平台,避免出現損失。

6、配置好開發信息,然後再編輯應用信息,發佈上線,即可開始開發正式環境

image.png

二、Springboot代碼編寫

1、首先添加maven依賴

 <!--支付寶支付依賴-->
        <!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java -->
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
            <version>4.10.209.ALL</version>
        </dependency>

2、創建支付寶支付工具類

/**
 * 支付寶支付工具類
 */
public class ALiPayUtils {
    /*======沙箱環境=======*/
    //沙箱網關
//    private static final String GATEWAY_URL = "https://openapi.alipaydev.com/gateway.do";
    //沙箱APPID
//    private static final String APP_ID = "換成自己沙箱APPID";
    //自己應用私鑰
//    private static final String PRIVATE_KEY = "生成的應用私鑰";
    //支付寶公鑰(非應用公鑰)
//    private static final String PUBLIC_KEY = "生成的支付寶公鑰";
    //沙箱支付完成跳轉頁面
    private static final String RETURN_URL = PropertiesValues.getPropertiesValue("pay.service.return_url", "application.properties");
    //沙箱支付完成回調接口
    private static final String NOTIFY_URL = PropertiesValues.getPropertiesValue("pay.service.notify_url", "application.properties");

    /*========基礎數據===========*/
    //網關
    private static final String GATEWAY_URL = "https://openapi.alipay.com/gateway.do";
    //APPID
    public static final String APP_ID = "換成自己正式APPID";
    //自己應用私鑰
    private static final String PRIVATE_KEY = "生成的應用私鑰";
    //支付寶公鑰(非應用公鑰)
    private static final String PUBLIC_KEY = "生成的支付寶公鑰";
    //返回數據格式
    private static final String FORMAT = "json";
    //編碼類型
    private static final String CHART_TYPE = "utf-8";
    //簽名類型
    private static final String SIGN_TYPE = "RSA2";

    /*支付銷售產品碼,目前支付寶只支持FAST_INSTANT_TRADE_PAY*/
    public static final String PRODUCT_CODE = "FAST_INSTANT_TRADE_PAY";

    private static AlipayClient alipayClient = null;

    /*=====商户對應數據=====*/
    //商户PID
    public static final String PID = "換成自己商户PID";

    /*========相應接口=========*/
    //支付完成跳轉頁面
//    private static final String RETURN_URL = "";
    //支付完成回調接口
//    private static final String NOTIFY_URL = "";

    public ALiPayUtils(){
        if(alipayClient == null){
            alipayClient = new DefaultAlipayClient(
                    GATEWAY_URL,
                    APP_ID,
                    PRIVATE_KEY,
                    FORMAT,
                    CHART_TYPE,
                    PUBLIC_KEY,
                    SIGN_TYPE);
        }
    };

    /*================PC網頁支付====================*/
    /**
     * 統一下單並調用支付頁面接口
     * @param outTradeNo
     * @param totalAmount
     * @param subject
     * @return
     */
    public Map<String, Object> placeOrderAndPayForPCWeb(String outTradeNo, float totalAmount, String subject){
        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
        request.setNotifyUrl(RETURN_URL);
        request.setReturnUrl(NOTIFY_URL);
        JSONObject bizContent = new JSONObject();
        bizContent.put("out_trade_no", outTradeNo);
        bizContent.put("total_amount", totalAmount);
        bizContent.put("subject", subject);
        bizContent.put("product_code", PRODUCT_CODE);
        //bizContent.put("time_expire", "2022-08-01 22:00:00");

        //// 商品明細信息,按需傳入
        //JSONArray goodsDetail = new JSONArray();
        //JSONObject goods1 = new JSONObject();
        //goods1.put("goods_id", "goodsNo1");
        //goods1.put("goods_name", "子商品1");
        //goods1.put("quantity", 1);
        //goods1.put("price", 0.01);
        //goodsDetail.add(goods1);
        //bizContent.put("goods_detail", goodsDetail);

        //// 擴展信息,按需傳入
        //JSONObject extendParams = new JSONObject();
        //extendParams.put("sys_service_provider_id", "2088511833207846");
        //bizContent.put("extend_params", extendParams);

        request.setBizContent(bizContent.toString());
        AlipayTradePagePayResponse response = null;
        try {
            response = alipayClient.pageExecute(request);
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("isSuccess", response.isSuccess());
        if(response.isSuccess()){
            System.out.println("調用成功");
            System.out.println(JSON.toJSONString(response));
            resultMap.put("body", response.getBody());
        } else {
            System.out.println("調用失敗");
            System.out.println(response.getSubMsg());
            resultMap.put("subMsg", response.getSubMsg());
        }
        return resultMap;
    }

    /**
     * 交易訂單查詢
     * @param out_trade_no
     * @return
     */
    public Map<String, Object> tradeQueryForPCWeb(String out_trade_no){
        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
        JSONObject bizContent = new JSONObject();
//        bizContent.put("out_trade_no", out_trade_no);
        bizContent.put("trade_no", out_trade_no);
        request.setBizContent(bizContent.toString());
        AlipayTradeQueryResponse response = null;
        try {
            response = alipayClient.execute(request);
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("isSuccess", response.isSuccess());
        if(response.isSuccess()){
            System.out.println("調用成功");
            System.out.println(JSON.toJSONString(response));
            resultMap.put("status", response.getTradeStatus());
        } else {
            System.out.println("調用失敗");
            System.out.println(response.getSubMsg());
            resultMap.put("subMsg", response.getSubMsg());
        }
        return resultMap;
    }

    /**
     * 驗證簽名是否正確
     * @param sign
     * @param content
     * @return
     */
    public static boolean CheckSignIn(String sign, String content){
        try {
            return AlipaySignature.rsaCheck(content, sign, PUBLIC_KEY, CHART_TYPE, SIGN_TYPE);
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 將異步通知的參數轉化為Map
     * @return
     */
    public static Map<String, String> paramstoMap(HttpServletRequest request) throws UnsupportedEncodingException {
        Map<String, String> params = new HashMap<String, String>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
            String name = (String) iter.next();
            String[] values = (String[]) requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
            }
            // 亂碼解決,這段代碼在出現亂碼時使用。
//            valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
            params.put(name, valueStr);
        }
        return params;
    }
}

3、編寫下單接口以及回調處理接口

@RestController
@CrossOrigin
@RequestMapping("lcAliPay")
public class LcALiPayController {

    @Autowired
    LcAliPayService lcAliPayService;

    @Autowired
    LcGoodsOrderService lcGoodsOrderService;

    /*==========PC網頁支付============*/
    /*===支付寶===*/

    /**
     * 統一下單並調用支付頁面,PC
     * @param lcGoodsOrder
     * @return
     */
    @PostMapping("/tradeOrderForPCWeb")
    @NoVerify
    public ResultMap tradeOrderForPCWeb(@RequestBody LcGoodsOrder lcGoodsOrder){
        return lcAliPayService.tradeOrderForPCWeb(lcGoodsOrder);
    }

    /**
     * 支付完成回調接口,PC
     * @return
     */
    @RequestMapping("/notifyForPCWeb")
    @NoVerify
    public String notifyForPCWeb(HttpServletRequest request){
        try {
            Map<String, String> map = ALiPayUtils.paramstoMap(request);
            String tradeNo = map.get("trade_no");
//            Map<String, Object> payData = aLiPayUtils.tradeQueryForPCWeb(tradeNo);
            String sign = map.get("sign");
            String content = AlipaySignature.getSignCheckContentV1(map);
            boolean signVerified = ALiPayUtils.CheckSignIn(sign, content);
            //驗證業務數據是否一致
            if(!lcAliPayService.checkData(map)){
                System.out.println("返回業務數據驗證失敗,訂單:" + tradeNo );
                return "fail";
            }
            //簽名驗證成功
            if(signVerified){
                System.out.println("支付寶簽名驗證成功,訂單:" + tradeNo);
                //驗證支付狀態
                String tradeStatus = request.getParameter("trade_status");
                if(tradeStatus.equals("TRADE_SUCCESS")){
                    System.out.println("支付成功,訂單:"+tradeNo);
                    LcGoodsOrder lcGoodsOrder = new LcGoodsOrder();
                    lcGoodsOrder.setTradeNo(map.get("trade_no"));
                    lcGoodsOrder.setSellerId(map.get("seller_id"));
                    lcGoodsOrder.setOrderStatus("2");//支付成功
                    lcGoodsOrderService.updateById(lcGoodsOrder);
                    return "success";
                }else{
                    System.out.println("支付失敗,訂單:" + tradeNo );
                    return "fail";
                }
            }else{
                System.out.println("簽名驗證失敗,訂單:" + tradeNo );
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "fail";
    }
}

4、service層代碼

@Service
public class LcAliPayServiceImpl implements LcAliPayService {

    @Resource
    LcGoodsOrderMapper lcGoodsOrderMapper;
    //日誌對象
    private static Logger logger = LoggerFactory.getLogger(LcAliPayServiceImpl.class);

    @Override
    public ResultMap tradeOrderForPCWeb(LcGoodsOrder lcGoodsOrder) {
        logger.info("【請求開始-在線購買-交易創建】*********統一下單開始*********");
        ALiPayUtils aLiPayUtils = new ALiPayUtils();
        String tradeNo = ALiPayUtils.generateOrderNum();
        Map<String, Object> map = aLiPayUtils.placeOrderAndPayForPCWeb(tradeNo, Float.parseFloat(lcGoodsOrder.getTotalAmount()), lcGoodsOrder.getSubject());
        ResultMap resultMap = new ResultMap();
        if(Boolean.parseBoolean(String.valueOf(map.get("isSuccess")))){
            logger.info("【請求開始-在線購買-交易創建】統一下單成功,開始保存訂單數據");
            resultMap.setCode("200");
            resultMap.setData(map.get("body"));
            String lcGoodsOrderId = UUID.getRandom();
            lcGoodsOrder.setOrderId(lcGoodsOrderId);
            lcGoodsOrder.setOrderCreateTime(DateTime.getNowDateStamp());
            lcGoodsOrder.setOutTradeNo(tradeNo);
            lcGoodsOrder.setOrderStatus("1");//訂單狀態
            lcGoodsOrder.setProductCode(ALiPayUtils.PRODUCT_CODE);
            //保存訂單信息
            int count = lcGoodsOrderMapper.insert(lcGoodsOrder);
            if(count > 0){
                logger.info("【成功-在線購買-交易創建】保存訂單數據成功,添加條數:{}", count);
                resultMap.setCode(ResponseCodeUtis.SERVER_TRUE_200);
                resultMap.setMsg(ResponseCodeUtis.MSG_TRUE_InsertById);
                resultMap.setData(map.get("body"));
            }else{
                logger.error("【失敗-在線購買-交易創建】保存訂單數據失敗");
                resultMap.setCode(ResponseCodeUtis.SERVER_ERROR_500);
                resultMap.setMsg("添加數據失敗");
            }
            logger.info("【請求成功-在線購買-交易創建】*********統一下單結束*********");
            return resultMap;
        }else{
            resultMap.setCode("501");
            resultMap.setMsg(String.valueOf(map.get("subMsg")));
            logger.info("【失敗:請求失敗-在線購買-交易創建】*********統一下單結束*********");
        }
        return resultMap;
    }

    @Override
    public boolean checkData(Map<String, String> map) {
        logger.info("【請求開始-交易回調-訂單確認】*********校驗訂單確認開始*********");
        LcGoodsOrder lcGoodsOrder2 = lcGoodsOrderMapper.selectByOutTradeNo(map.get("out_trade_no"));
        //驗證訂單號是否準確,並且訂單狀態為待支付
        if(lcGoodsOrder2 != null && lcGoodsOrder2.getOrderStatus().equals("1")){
            float amount1 = Float.parseFloat(map.get("total_amount"));
            float amount2 = Float.parseFloat(lcGoodsOrder2.getTotalAmount());
            //判斷金額是否相等
            if(amount1 == amount2){
                //驗證收款商户id是否一致
                if(map.get("seller_id").equals(ALiPayUtils.PID)){
                    //判斷appid是否一致
                    if(map.get("app_id").equals(ALiPayUtils.APP_ID)){
                        logger.info("【成功:請求開始-交易回調-訂單確認】*********校驗訂單確認成功*********");
                        return true;
                    }
                }
            }
        }
        logger.info("【失敗:請求開始-交易回調-訂單確認】*********校驗訂單確認失敗*********");
        return false;
    }
}

5、網站頁面支付下單請求代碼

/**
 * 調用支付寶支付接口
 * @param {Object} data
 */
function aliPay(data){
    var url = "服務器接口";
    $.ajax({
        url: url,
        type: 'post',
        data: JSON.stringify({
            payUserName: data.payUserName,
            payUserPhone: data.payUserPhone,
            payMessage: data.payMessage,
            goodsId: data.goodsId,
            totalAmount: data.totalAmount,
            subject: data.subject,
        }),
        contentType: "application/json",
        dataType: 'json',
        success: function(res){
            if(res.code == "200"){
                
                const div=document.createElement('divform');
                div.innerHTML=res.data;
                document.body.appendChild(div);
                document.forms[0].setAttribute('target', '_blank') 
                document.forms[0].submit();
            }else{
                alert("網絡異常");
                alert(res.msg);
            }
        },
        fail: function(res){
            console.log("失敗")
            console.log(res);
        }
      })
}

6、下單接口回調,處理返回表單處理問題

if(res.code == "200"){
    const div=document.createElement('divform');
    div.innerHTML=res.data;
    document.body.appendChild(div);
    document.forms[0].setAttribute('target', '_blank') 
    document.forms[0].submit();
}else{
    alert("網絡異常");
    alert(res.msg);
}

7、請求成功圖

image.png

8、支付完成回調

這裏跳轉回去的頁面就是之前AlipayUtils類中的return_url 在這之前需要進入notify_url 接口進行驗籤.
image.png

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.