支付寶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,具體參考:
支付流程
APP支付:伺服器端按照文件【統一收單交易支付介面】建立支付OrderStr返回APP端——-APP端拿到OrderStr發起支付—–支付寶伺服器端回撥服務端非同步通知介面——-伺服器端按照【App支付結果非同步通知】校驗簽名等做業務邏輯處理
APP支付訂單查詢:伺服器端呼叫【統一收單線下交易查詢】查詢支付訂單
APP支付申請退款:每筆支付可以申請多次退款,但退款總金額不能超過支付金額,呼叫【統一收單交易退款介面】發起退款申請
APP支付退款查詢:服務端呼叫【 統一收單交易退款查詢】查詢退款訂單資訊
支付專案結構
支付專案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是否為該商戶本身。上述1、2、3、4有任何一個驗證不通過,則表明本次通知是異常通知,務必忽略。在上述驗證通過後商戶必須根據支付寶不同型別的業務通知,正確的進行不同的業務處理,並且過濾重複的通知結果資料。在支付寶的業務通知中,只有交易通知狀態為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支付測試完成