1. 程式人生 > >支付寶APP支付服務端詳解(JAVA)

支付寶APP支付服務端詳解(JAVA)

支付寶APP支付服務端詳解

前面接了微信支付,相比微信支付,支付寶APP支付提供了支付分裝類,下面將實現支付寶APP支付、訂單查詢、支付結果非同步通知、APP支付申請引數說明,以及服務端返回APP端發起支付的簽名、商戶私鑰、支付寶公鑰的配置使用等。

支付注意事項

1、APP支付不能在沙箱測試、只能申請上線測試 
2、需要建立RSA金鑰設定文件,設定後上傳rsa_public_key.pem【開發者公鑰,上傳時需要去掉公鑰的頭和尾】上傳成功後換取支付寶公鑰,為專案的alipay_public_key.pem 
3、rsa_private_key_pkcs8.pem【開發者私鑰】,去掉頭和尾為專案的alipay_private_key_pkcs8.pem 
4、需要匯入所需支付包:alipay-sdk-java.jar 和 commons-logging.jar,具體參考:

服務端SDK

支付流程

支付文件參考:支付文件支付文件2

APP支付:伺服器端按照文件【統一收單交易支付介面】建立支付OrderStr返回APP端——-APP端拿到OrderStr發起支付—–支付寶伺服器端回撥服務端非同步通知介面——-伺服器端按照【App支付結果非同步通知】校驗簽名等做業務邏輯處理

APP支付訂單查詢:伺服器端呼叫【統一收單線下交易查詢】查詢支付訂單

APP支付申請退款:每筆支付可以申請多次退款,但退款總金額不能超過支付金額,呼叫【統一收單交易退款介面】發起退款申請

APP支付退款查詢:服務端呼叫【 統一收單交易退款查詢】查詢退款訂單資訊

支付專案結構

支付專案demo結構如下

支付寶支付demo

支付程式碼實現

支付程式碼

PayController.java :包含支付、支付查詢、非同步通知、退款申請、退款查詢

package org.andy.alipay.controller;

import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.andy.alipay
.model.JsonResult; import org.andy.alipay.model.ResponseData; import org.andy.alipay.util.AlipayUtil; import org.andy.alipay.util.DatetimeUtil; import org.andy.alipay.util.PayUtil; import org.andy.alipay.util.SerializerFeatureUtil; import org.andy.alipay.util.StringUtil; import org.andy.alipay.util.WebUtil; import org.apache.log4j.Logger; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import com.alibaba.fastjson.JSON; import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayConstants; import com.alipay.api.internal.util.AlipaySignature; import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest; import com.alipay.api.request.AlipayTradeQueryRequest; import com.alipay.api.request.AlipayTradeRefundRequest; import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse; import com.alipay.api.response.AlipayTradeQueryResponse; import com.alipay.api.response.AlipayTradeRefundResponse; /** * 建立時間:2016年11月2日 下午4:16:32 * * @author andy * @version 2.2 */ @Controller @RequestMapping("/order") public class PayController { private static final Logger LOG = Logger.getLogger(PayController.class); /** * 支付下訂單 * * @param request * @param response * @param cashnum * 支付金額 * @param mercid * 商品id * @param callback */ @RequestMapping(value = "/pay", method = RequestMethod.POST) public void orderPay(HttpServletRequest request, HttpServletResponse response, @RequestParam(required = false, defaultValue = "0") Double cashnum, String mercid, String callback) { LOG.info("[/order/pay]"); if (!"001".equals(mercid)) { WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(-1, "商品不存在", new ResponseData()), SerializerFeatureUtil.FEATURES))); } Map<String, String> param = new HashMap<>(); // 公共請求引數 param.put("app_id", AlipayUtil.ALIPAY_APPID);// 商戶訂單號 param.put("method", "alipay.trade.app.pay");// 交易金額 param.put("format", AlipayConstants.FORMAT_JSON); param.put("charset", AlipayConstants.CHARSET_UTF8); param.put("timestamp", DatetimeUtil.formatDateTime(new Date())); param.put("version", "1.0"); param.put("notify_url", "https://www.andy.org/alipay/order/pay/notify.shtml"); // 支付寶伺服器主動通知商戶服務 param.put("sign_type", AlipayConstants.SIGN_TYPE_RSA); Map<String, Object> pcont = new HashMap<>(); // 支付業務請求引數 pcont.put("out_trade_no", PayUtil.getTradeNo()); // 商戶訂單號 pcont.put("total_amount", String.valueOf(cashnum));// 交易金額 pcont.put("subject", "測試支付"); // 訂單標題 pcont.put("body", "Andy");// 對交易或商品的描述 pcont.put("product_code", "QUICK_MSECURITY_PAY");// 銷售產品碼 param.put("biz_content", JSON.toJSONString(pcont)); // 業務請求引數 不需要對json字串轉義 Map<String, String> payMap = new HashMap<>(); try { param.put("sign", PayUtil.getSign(param, AlipayUtil.APP_PRIVATE_KEY)); // 業務請求引數 payMap.put("orderStr", PayUtil.getSignEncodeUrl(param, true)); } catch (Exception e) { e.printStackTrace(); } WebUtil.response(response, WebUtil.packJsonp(callback, JSON.toJSONString( new JsonResult(1, "訂單獲取成功", new ResponseData(null, payMap)), SerializerFeatureUtil.FEATURES))); } /** * * @param request * @param response * @param tradeno * 支付寶訂單交易編號 * @param orderno * 商家交易編號 * @param callback */ @RequestMapping(value = "/pay/query", method = RequestMethod.POST) public void orderPayQuery(HttpServletRequest request, HttpServletResponse response, String tradeno, String orderno, String callback) { LOG.info("[/order/pay/query]"); if (StringUtil.isEmpty(tradeno) && StringUtil.isEmpty(orderno)) { WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(-1, "訂單號不能為空", new ResponseData()), SerializerFeatureUtil.FEATURES))); } AlipayTradeQueryRequest alipayRequest = new AlipayTradeQueryRequest(); // 統一收單線下交易查詢 // 只需要傳入業務引數 Map<String, Object> param = new HashMap<>(); param.put("out_trade_no", orderno); // 商戶訂單號 param.put("trade_no", tradeno);// 交易金額 alipayRequest.setBizContent(JSON.toJSONString(param)); // 不需要對json字串轉義 Map<String, String> restmap = new HashMap<String, String>();// 返回提交支付寶訂單交易查詢資訊 boolean flag = false; // 查詢狀態 try { AlipayTradeQueryResponse alipayResponse = AlipayUtil.getAlipayClient().execute(alipayRequest); if (alipayResponse.isSuccess()) { // 呼叫成功,則處理業務邏輯 if ("10000".equals(alipayResponse.getCode())) { // 訂單建立成功 flag = true; restmap.put("order_no", alipayResponse.getOutTradeNo()); restmap.put("trade_no", alipayResponse.getTradeNo()); restmap.put("buyer_logon_id", alipayResponse.getBuyerLogonId()); restmap.put("trade_status", alipayResponse.getTradeStatus()); LOG.info("訂單查詢結果:" + alipayResponse.getTradeStatus()); } else { LOG.info("訂單查詢失敗:" + alipayResponse.getMsg() + ":" + alipayResponse.getSubMsg()); } } } catch (AlipayApiException e) { e.printStackTrace(); } if (flag) { // 訂單查詢成功 WebUtil.response(response, WebUtil.packJsonp(callback, JSON.toJSONString(new JsonResult(1, "訂單查詢成功", new ResponseData(null, restmap)), SerializerFeatureUtil.FEATURES))); } else { // 訂單查詢失敗 WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(-1, "訂單查詢失敗", new ResponseData()), SerializerFeatureUtil.FEATURES))); } } /** * 訂單支付微信伺服器非同步通知 * * @param request * @param response */ @RequestMapping(value = "/pay/notify", method = RequestMethod.POST) public void orderPayNotify(HttpServletRequest request, HttpServletResponse response) { LOG.info("[/order/pay/notify]"); // 獲取到返回的所有引數 先判斷是否交易成功trade_status 再做簽名校驗 // 1、商戶需要驗證該通知資料中的out_trade_no是否為商戶系統中建立的訂單號, // 2、判斷total_amount是否確實為該訂單的實際金額(即商戶訂單建立時的金額), // 3、校驗通知中的seller_id(或者seller_email) 是否為out_trade_no這筆單據的對應的操作方(有的時候,一個商戶可能有多個seller_id/seller_email), // 4、驗證app_id是否為該商戶本身。上述1234有任何一個驗證不通過,則表明本次通知是異常通知,務必忽略。在上述驗證通過後商戶必須根據支付寶不同型別的業務通知,正確的進行不同的業務處理,並且過濾重複的通知結果資料。在支付寶的業務通知中,只有交易通知狀態為TRADE_SUCCESS或TRADE_FINISHED時,支付寶才會認定為買家付款成功。 if ("TRADE_SUCCESS".equals(request.getParameter("trade_status"))) { Enumeration<?> pNames = request.getParameterNames(); Map<String, String> param = new HashMap<String, String>(); try { while (pNames.hasMoreElements()) { String pName = (String) pNames.nextElement(); param.put(pName, request.getParameter(pName)); } boolean signVerified = AlipaySignature.rsaCheckV1(param, AlipayUtil.ALIPAY_PUBLIC_KEY, AlipayConstants.CHARSET_UTF8); // 校驗簽名是否正確 if (signVerified) { // TODO 驗籤成功後 // 按照支付結果非同步通知中的描述,對支付結果中的業務內容進行1\2\3\4二次校驗,校驗成功後在response中返回success,校驗失敗返回failure LOG.info("訂單支付成功:" + JSON.toJSONString(param)); } else { // TODO 驗籤失敗則記錄異常日誌,並在response中返回failure. } } catch (Exception e) { e.printStackTrace(); } } } /** * 訂單退款 * * @param request * @param response * @param tradeno * 支付寶交易訂單號 * @param orderno * 商家交易訂單號 * @param callback */ @RequestMapping(value = "/pay/refund", method = RequestMethod.POST) public void orderPayRefund(HttpServletRequest request, HttpServletResponse response, String tradeno, String orderno, String callback) { LOG.info("[/pay/refund]"); if (StringUtil.isEmpty(tradeno) && StringUtil.isEmpty(orderno)) { WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(-1, "訂單號不能為空", new ResponseData()), SerializerFeatureUtil.FEATURES))); } AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest(); // 統一收單交易退款介面 // 只需要傳入業務引數 Map<String, Object> param = new HashMap<>(); param.put("out_trade_no", orderno); // 商戶訂單號 param.put("trade_no", tradeno);// 交易金額 param.put("refund_amount", 0.01);// 退款金額 param.put("refund_reason", "測試支付退款");// 退款金額 param.put("out_request_no", PayUtil.getRefundNo()); //退款單號 alipayRequest.setBizContent(JSON.toJSONString(param)); // 不需要對json字串轉義 Map<String, Object> restmap = new HashMap<>();// 返回支付寶退款資訊 boolean flag = false; // 查詢狀態 try { AlipayTradeRefundResponse alipayResponse = AlipayUtil.getAlipayClient().execute(alipayRequest); if (alipayResponse.isSuccess()) { // 呼叫成功,則處理業務邏輯 if ("10000".equals(alipayResponse.getCode())) { // 訂單建立成功 flag = true; restmap.put("out_trade_no", alipayResponse.getOutTradeNo()); restmap.put("trade_no", alipayResponse.getTradeNo()); restmap.put("buyer_logon_id", alipayResponse.getBuyerLogonId());// 使用者的登入id restmap.put("gmt_refund_pay", alipayResponse.getGmtRefundPay()); // 退看支付時間 restmap.put("buyer_user_id", alipayResponse.getBuyerUserId());// 買家在支付寶的使用者id LOG.info("訂單退款結果:退款成功"); } else { LOG.info("訂單查詢失敗:" + alipayResponse.getMsg() + ":" + alipayResponse.getSubMsg()); } } } catch (AlipayApiException e) { e.printStackTrace(); } if (flag) { // 訂單查詢成功 WebUtil.response(response, WebUtil.packJsonp(callback, JSON.toJSONString(new JsonResult(1, "訂單退款成功", new ResponseData(null, restmap)), SerializerFeatureUtil.FEATURES))); } else { // 訂單查詢失敗 WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(-1, "訂單退款失敗", new ResponseData()), SerializerFeatureUtil.FEATURES))); } } /** * * @param request * @param response * @param orderno * 商家訂單號 * @param tradeno * 支付寶訂單號 * @param callback */ @RequestMapping(value = "/pay/refund/query", method = RequestMethod.POST) public void orderPayRefundQuery(HttpServletRequest request, HttpServletResponse response, String orderno, String tradeno, String callback) { LOG.info("[/pay/refund/query]"); if (StringUtil.isEmpty(orderno) && StringUtil.isEmpty(tradeno)) { WebUtil.response(response, WebUtil.packJsonp(callback, JSON.toJSONString(new JsonResult(-1, "商家訂單號或支付寶訂單號不能為空", new ResponseData()), SerializerFeatureUtil.FEATURES))); } AlipayTradeFastpayRefundQueryRequest alipayRequest = new AlipayTradeFastpayRefundQueryRequest(); // 統一收單交易退款查詢 // 只需要傳入業務引數 Map<String, Object> param = new HashMap<>(); param.put("out_trade_no", orderno); // 商戶訂單號 param.put("trade_no", tradeno);// 交易金額 param.put("out_request_no", orderno);// 請求退款介面時,傳入的退款請求號,如果在退款請求時未傳入,則該值為建立交易時的外部交易號 alipayRequest.setBizContent(JSON.toJSONString(param)); // 不需要對json字串轉義 Map<String, Object> restmap = new HashMap<>();// 返回支付寶退款資訊 boolean flag = false; // 查詢狀態 try { AlipayTradeFastpayRefundQueryResponse alipayResponse = AlipayUtil.getAlipayClient().execute(alipayRequest); if (alipayResponse.isSuccess()) { // 呼叫成功,則處理業務邏輯 if ("10000".equals(alipayResponse.getCode())) { // 訂單建立成功 flag = true; restmap.put("out_trade_no", alipayResponse.getOutTradeNo()); restmap.put("trade_no", alipayResponse.getTradeNo()); restmap.put("out_request_no", alipayResponse.getOutRequestNo());// 退款訂單號 restmap.put("refund_reason", alipayResponse.getRefundReason()); // 退款原因 restmap.put("total_amount", alipayResponse.getTotalAmount());// 訂單交易金額 restmap.put("refund_amount", alipayResponse.getTotalAmount());// 訂單退款金額 LOG.info("訂單退款結果:退款成功"); } else { LOG.info("訂單失敗:" + alipayResponse.getMsg() + ":" + alipayResponse.getSubMsg()); } } } catch (AlipayApiException e) { e.printStackTrace(); } if (flag) { // 訂單查詢成功 WebUtil.response(response, WebUtil.packJsonp(callback, JSON.toJSONString(new JsonResult(1, "訂單退款成功", new ResponseData(null, restmap)), SerializerFeatureUtil.FEATURES))); } else { // 訂單查詢失敗 WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(-1, "訂單退款失敗", new ResponseData()), SerializerFeatureUtil.FEATURES))); } } }

簽名程式碼

SignUtils.java:支付寶支付簽名實現

package org.andy.alipay.util;

import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;

public class SignUtils {

    private static final String ALGORITHM = "RSA";

    private static final String SIGN_ALGORITHMS = "SHA1WithRSA";

    private static final String DEFAULT_CHARSET = "UTF-8";

    public static String sign(String content, String privateKey) {
        try {
            PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(
                    Base64.decode(privateKey));
            KeyFactory keyf = KeyFactory.getInstance(ALGORITHM);
            PrivateKey priKey = keyf.generatePrivate(priPKCS8);

            java.security.Signature signature = java.security.Signature
                    .getInstance(SIGN_ALGORITHMS);

            signature.initSign(priKey);
            signature.update(content.getBytes(DEFAULT_CHARSET));

            byte[] signed = signature.sign();

            return new String(Base64.encode(signed));
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

}

Ali支付工具類

AlipayUtil.java:初始化AlipayClient、開發者私有、支付寶公鑰等

package org.andy.alipay.util;

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import com.alipay.api.AlipayClient;
import com.alipay.api.AlipayConstants;
import com.alipay.api.DefaultAlipayClient;

/**
 * 建立時間:2016年11月10日 下午7:09:08
 * 
 * alipay支付
 * 
 * @author andy
 * @version 2.2
 */

public class AlipayUtil {

    public static final String ALIPAY_APPID = ConfigUtil.getProperty("alipay.appid"); // appid

    public static String APP_PRIVATE_KEY = null; // app支付私鑰

    public static String ALIPAY_PUBLIC_KEY = null; // 支付寶公鑰

    static {
        try {
            Resource resource = new ClassPathResource("alipay_private_key_pkcs8.pem");
            APP_PRIVATE_KEY = FileUtil.readInputStream2String(resource.getInputStream());
            resource = new ClassPathResource("alipay_public_key.pem");
            ALIPAY_PUBLIC_KEY = FileUtil.readInputStream2String(resource.getInputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 統一收單交易建立介面
    private static AlipayClient alipayClient = null;

    public static AlipayClient getAlipayClient() {
        if (alipayClient == null) {
            synchronized (AlipayUtil.class) {
                if (null == alipayClient) {
                    alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", ALIPAY_APPID,
                            APP_PRIVATE_KEY, AlipayConstants.FORMAT_JSON, AlipayConstants.CHARSET_UTF8,
                            ALIPAY_PUBLIC_KEY);
                }
            }
        }
        return alipayClient;
    }
}

支付工具類

PayUtil.java:生成簽名、訂單生成等

package org.andy.alipay.util;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import com.alipay.api.AlipayConstants;

/**
 * 建立時間:2016年11月2日 下午7:12:44
 * 
 * @author andy
 * @version 2.2
 */

public class PayUtil {

    /**
     * 生成訂單號
     * 
     * @return
     */
    public static String getTradeNo() {
        // 自增8位數 00000001
        return "TNO" + DatetimeUtil.formatDate(new Date(), DatetimeUtil.TIME_STAMP_PATTERN) + "00000001";
    }

    /**
     * 退款單號
     * 
     * @return
     */
    public static String getRefundNo() {
        // 自增8位數 00000001
        return "RNO" + DatetimeUtil.formatDate(new Date(), DatetimeUtil.TIME_STAMP_PATTERN) + "00000001";
    }

    /**
     * 退款單號
     * 
     * @return
     */
    public static String getTransferNo() {
        // 自增8位數 00000001
        return "TNO" + DatetimeUtil.formatDate(new Date(), DatetimeUtil.TIME_STAMP_PATTERN) + "00000001";
    }

    /**
     * 返回客戶端ip
     * 
     * @param request
     * @return
     */
    public static String getRemoteAddrIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtil.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            // 多次反向代理後會有多個ip值,第一個ip才是真實ip
            int index = ip.indexOf(",");
            if (index != -1) {
                return ip.substring(0, index);
            } else {
                return ip;
            }
        }
        ip = request.getHeader("X-Real-IP");
        if (StringUtil.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            return ip;
        }
        return request.getRemoteAddr();
    }

    /**
     * 獲取伺服器的ip地址
     * 
     * @param request
     * @return
     */
    public static String getLocalIp(HttpServletRequest request) {
        return request.getLocalAddr();
    }

    /**
     * 建立支付隨機字串
     * 
     * @return
     */
    public static String getNonceStr() {
        return RandomUtil.randomString(RandomUtil.LETTER_NUMBER_CHAR, 32);
    }

    /**
     * 支付時間戳
     * 
     * @return
     */
    public static String payTimestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }

    /**
     * 返回簽名編碼拼接url
     * 
     * @param params
     * @param isEncode
     * @return
     */
    public static String getSignEncodeUrl(Map<String, String> map, boolean isEncode) {
        String sign = map.get("sign");
        String encodedSign = "";
        if (CollectionUtil.isNotEmpty(map)) {
            map.remove("sign");
            List<String> keys = new ArrayList<String>(map.keySet());
            // key排序
            Collections.sort(keys);

            StringBuilder authInfo = new StringBuilder();

            boolean first = true;// 是否第一個
            for (String key: keys) {
                if (first) {
                    first = false;
                } else {
                    authInfo.append("&");
                }
                authInfo.append(key).append("=");
                if (isEncode) {
                    try {
                        authInfo.append(URLEncoder.encode(map.get(key), AlipayConstants.CHARSET_UTF8));
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                } else {
                    authInfo.append(map.get(key));
                }
            }

            try {
                encodedSign = authInfo.toString() + "&sign=" + URLEncoder.encode(sign, AlipayConstants.CHARSET_UTF8);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }

        return encodedSign.replaceAll("\\+", "%20");
    }

    /**
     * 對支付引數資訊進行簽名
     * 
     * @param map
     *            待簽名授權資訊
     * 
     * @return
     */
    public static String getSign(Map<String, String> map, String rsaKey) {
        List<String> keys = new ArrayList<String>(map.keySet());
        // key排序
        Collections.sort(keys);

        StringBuilder authInfo = new StringBuilder();
        boolean first = true;
        for (String key : keys) {
            if (first) {
                first = false;
            } else {
                authInfo.append("&");
            }
            authInfo.append(key).append("=").append(map.get(key)); 
        }

        return SignUtils.sign(authInfo.toString(), rsaKey);
    }

}

支付結果

以下為支付寶支付和支付寶退款 
APP支付 
APP支付退款

支付寶App支付測試完成