微信支付和支付寶支付開發案列
以下基礎方法可以參考支付寶支付開放平台(https://doc.open.alipay.com/docs/doc.htm?treeId=204&articleId=105051&docType=1)
和微信支付平台相關文檔(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3)
//===========================【weixinPay】==========2017.01.09=======by.Tommy==================
String timeMillis = String.valueOf(System.currentTimeMillis() / 1000);
String randomString = PayCommonUtil.getRandomString(32);
public static String wxnotify = "/commodityOrderbase/weixinpayNotify.do";
public static String alipayNotifyUrl = "/commodityOrderbase/alipayNotify.do";
/**
* @throws UnsupportedEncodingException
*
* @Title: weixinPrePay
* @Description: 調用微信支付系統 ---統一下單API()
* API位址:https://pay.weixin.QQ.com/wiki/doc/api/jsapi.php?chapter=9_1
* 看文檔,主要流程就是把20個左右的參數封裝為xml格式發送到微信給的接口位址,然後就可以擷取到傳回的内容了,如果成功裡面就有支付所需要的預支付ID
* @param @param trade_no
* @param @param totalAmount
* @param @param description
* @param @param openid
* @param @param sym
* @param @param request
* @param @return 參數
* @return Map<String,String> 傳回類型
* @throws
*/
@SuppressWarnings("unchecked")
public Map<String, String> weixinPrePay(String trade_no,String totalAmount,
String description, String openid,String sym, HttpServletRequest request) throws UnsupportedEncodingException {
SortedMap<String, Object> parameterMap = new TreeMap<String, Object>();
parameterMap.put("appid", PayCommonUtil.APPID); //應用ID
parameterMap.put("mch_id", PayCommonUtil.MCH_ID); //商戶号
parameterMap.put("nonce_str", randomString);
/**
* 商品描述 商品描述交易字段格式根據不同的應用場景按照以下格式: APP——需傳入應用市場上的APP名字-實際商品名稱,天天愛消除-遊戲充值。
*/
parameterMap.put("body", description);
//商戶系統内部的訂單号,32個字元内、可包含字母, 其他說明見商戶訂單号
parameterMap.put("out_trade_no", trade_no);
//貨币類型
parameterMap.put("fee_type", "CNY");
//訂單總金額 支付金額機關為【分】,參數值不能帶小數。
parameterMap.put("total_fee", totalAmount);
//使用者端實際ip
parameterMap.put("spbill_create_ip", request.getRemoteAddr());
/**
* 通知位址 接收微信支付異步通知回調位址,通知url必須為直接可通路的url,不能攜帶參數。
*/
//parameterMap.put("notify_url", sym + wxnotify);
String basePath = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()+ request.getContextPath();
parameterMap.put("notify_url", basePath + wxnotify);
//交易類型 支付類型
parameterMap.put("trade_type", "APP");
//trade_type為JSAPI是 openid為必填項
//parameterMap.put("openid", openid);
//生成簽名
String sign = PayCommonUtil.createSign("UTF-8", parameterMap);
parameterMap.put("sign", sign);
String requestXML = PayCommonUtil.getRequestXml(parameterMap);
//發起HTTP POST 請求 并獲得微信支付系統傳回結果
String result = PayCommonUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST",requestXML);
Map<String, String> map = null;
try {
map = PayCommonUtil.doXMLParse(result);
log.info("【微信APP支付】請求微信統一下單接口結果:"+result);
} catch (JDOMException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
/**
* @throws Exception
*
* @Title: weixinRefund
* @Description: 微信退款
* @param @param trade_no 商戶側傳給微信的訂單号
* @param @param out_refund_no 商戶系統内部的退款單号,商戶系統内部唯一,同一退款單号多次請求隻退一筆
* @param @param totalAmount
* @param @param openid
* @param @param sym
* @param @param request
* @param @return
* @param @throws UnsupportedEncodingException 參數
* @return Map<String,String> 傳回類型
* @throws
*/
@SuppressWarnings("unchecked")
public Map<String, String> weixinRefund(String trade_no,String out_refund_no,String totalAmount
,HttpServletRequest request) throws Exception {
SortedMap<String, Object> parameterMap = new TreeMap<String, Object>();
parameterMap.put("appid", PayCommonUtil.APPID); //應用ID
parameterMap.put("mch_id", PayCommonUtil.MCH_ID); //商戶号
parameterMap.put("nonce_str", randomString);
parameterMap.put("out_trade_no", trade_no);
parameterMap.put("out_refund_no", out_refund_no);
//訂單總金額 支付金額機關為【分】,參數值不能帶小數。
parameterMap.put("total_fee", totalAmount);
//訂單退款總金額 支付金額機關為【分】,參數值不能帶小數。
parameterMap.put("refund_fee", totalAmount);
//操作員帳号, 預設為商戶号
parameterMap.put("op_user_id", PayCommonUtil.MCH_ID);
//生成簽名
String sign = PayCommonUtil.createSign("UTF-8", parameterMap);
parameterMap.put("sign", sign);
String requestXML = PayCommonUtil.getRequestXml(parameterMap);
//發起HTTP POST 請求 并獲得微信支付系統傳回結果
String result = PayCommonUtil.httpsRequest2("https://api.mch.weixin.qq.com/secapi/pay/refund", "POST",requestXML);
Map<String, String> map = null;
try {
map = PayCommonUtil.doXMLParse(result);
log.info("【微信APP支付】送出微信退款業務結果:"+result);
} catch (JDOMException e) {
log.info("【微信APP支付】送出微信退款業務結果:"+result);
} catch (IOException e) {
log.info("【微信APP支付】送出微信退款業務結果:"+result);
}
return map;
}
/**
*
* @Title: weixinpayNotify
* @Description: 接收微信支付異步通知回調位址,通知url必須為直接可通路的url,不能攜帶參數。
* @param @param request
* @param @param response
* @param @return
* @param @throws UserExecuteFailedException
* @param @throws IOException
* @param @throws JDOMException 參數
* @return String 傳回類型
* @throws
*/
@RequestMapping(value = "weixinpayNotify.do")
public String weixinpayNotify(HttpServletRequest request, HttpServletResponse response)
throws UserExecuteFailedException, IOException, JDOMException {
// /
log.info("=【微信APP支付】異步通知=");
InputStream inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
String resultxml = new String(outSteam.toByteArray(), "UTF-8");
Map<String, String> params = PayCommonUtil.doXMLParse(resultxml);
outSteam.close();
inStream.close();
if (!PayCommonUtil.isTenpaySign(params)) {
log.info("【微信APP支付】異步通知結果:fail");
return "fail";
}else{
// ------------------------------
// 處理業務開始
// ------------------------------
// 此處處理訂單狀态,結合自己的訂單資料完成訂單狀态的更新
// ------------------------------
String out_trade_no = params.get("out_trade_no"); //商戶系統的訂單号,與請求一緻。
String transaction_id = params.get("transaction_id"); //商戶系統的訂單号,與請求一緻。
CommodityOrderBase cob = orderBaseService.queryByOrderNum(out_trade_no);
if(cob != null){
//避免付款後立馬取消了訂單,異步通知還未處理,仍将取消狀态改為待發貨的bug
if(cob.getOrderStatus() == 0 ){
cob.setOrderStatus(1); //訂單狀态:0 待付款 1待發貨 2已發貨 3已完成 4 已取消
cob.setPayTime(DateUtils.formatNowDate()); //支付時間
cob.setTransactionId(transaction_id); //微信支付訂單号
cob.setCheckCancel(1); //是否可以退款:1可以2訂單進行中
orderBaseService.updateCommodityOrderBase(cob);
}
}
// 處理業務完畢
// ------------------------------
log.info("【微信APP支付】異步通知結果:"+"訂單号為:"+out_trade_no);
return "success";
}
// /
}
//======================【alipay】=======================================================
/**
* @throws AlipayApiException
*
* @Title: aliPay
* @Description: 【支付寶APP支付】下訂單
* @param body 對一筆交易的具體描述資訊。如果是多種商品,請将商品描述字元串累加傳給body。 示例:Iphone6 16G
* @param subject 商品的标題/交易标題/訂單标題/訂單關鍵字等。 示例:大樂透
* @param out_trade_no 商戶網站唯一訂單号
* @param total_amount 商戶網站唯一訂單号訂單總金額,機關為元,精确到小數點後兩位,取值範圍[0.01,100000000] 示例:9.00
* @param request
* @param @return
* @param @throws UnsupportedEncodingException 參數
* @return Map<String,String> 傳回類型
* @throws
*/
@SuppressWarnings("unchecked")
public String aliPay(String body, String subject, String out_trade_no, String total_amount
, HttpServletRequest request) throws UnsupportedEncodingException, AlipayApiException {
log.info("【支付寶APP支付】下訂單開始==========-》");
//1.公共參數=========->
Map<String, String> param = new HashMap<String, String>();
param.put("app_id", AliPayUtil.APPID);
param.put("method", "alipay.trade.app.pay");
param.put("format", AliPayUtil.FORMAT);
param.put("charset",AliPayUtil.CHARSET);
param.put("sign_type", AliPayUtil.SIGN_TYPE);
//發送請求的時間,格式"yyyy-MM-dd HH:mm:ss"
param.put("timestamp", DateUtils.formatNowDate());
param.put("version", "1.0");
String notify_url = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()+ request.getContextPath()
+alipayNotifyUrl;
param.put("notify_url", notify_url);
//2.業務參數=========->
Map<String, String> pcont = new HashMap<String, String>();
pcont.put("body", body);
pcont.put("subject", subject);
pcont.put("out_trade_no", out_trade_no);
/**
* 該筆訂單允許的最晚付款時間,逾期将關閉交易。
* 取值範圍:1m~15d。m-分鐘,h-小時,d-天,1c-當天(1c-當天的情況下,無論交易何時建立,都在0點關閉)。
* 該參數數值不接受小數點, 如 1.5h,可轉換為 90m。
*/
pcont.put("timeout_express", "90m");
pcont.put("total_amount", total_amount);
pcont.put("seller_id", AliPayUtil.PARTNER_ID);
//銷售産品碼,商家和支付寶簽約的産品碼,為固定值QUICK_MSECURITY_PAY
pcont.put("product_code", "QUICK_MSECURITY_PAY");
//請求參數按照key=value&key=value方式拼接的未簽名原始字元串:
JSONObject bizcontentJson= JSONObject.fromObject(pcont);
//業務請求參數的集合,最大長度不限,除公共參數外所有請求參數都必須放在這個參數中傳遞,具體參照各産品快速接入文檔
param.put("biz_content", bizcontentJson.toString()); // 業務請求參數 不需要對json字元串轉義
/**
* 方式一==============================================================================
*/
param.put("sign", AliPayUtil.getSign(param, AliPayUtil.APP_PRIVATE_KEY)); // 業務請求參數
String orderStr = AliPayUtil.getSignEncodeUrl(param, true);
log.info("【支付寶APP支付】請求參數為:"+orderStr);
return orderStr;
/**
* 方式二==============================================================================
*/
/* //3.對未簽名原始字元串進行簽名=========->
String rsaSign = AlipaySignature.rsaSign(param, AliPayUtil.APP_PRIVATE_KEY, "UTF-8");
Map<String, String> map4 = new HashMap<String, String>();
map4.put("app_id", AliPayUtil.APPID);
map4.put("method", "alipay.trade.app.pay");
map4.put("format", AliPayUtil.FORMAT);
map4.put("charset", AliPayUtil.CHARSET);
map4.put("sign_type", AliPayUtil.SIGN_TYPE);
map4.put("timestamp", URLEncoder.encode(DateUtils.formatNowDate(),"UTF-8"));
map4.put("version", "1.0");
map4.put("notify_url", URLEncoder.encode(notify_url,"UTF-8"));
//4.最後對請求字元串的所有一級value(biz_content作為一個value)進行encode,編碼格式按請求串中的charset為準,沒傳charset按UTF-8處理,獲得最終的請求字元串:
map4.put("biz_content", URLEncoder.encode(bizcontentJson.toString(), "UTF-8"));
Map par = AlipayCore.paraFilter(map4); //除去數組中的空值和簽名參數
String json4 = AlipayCore.createLinkString(map4); //拼接後的字元串
json4 = json4 + "&sign=" + URLEncoder.encode(rsaSign, "UTF-8");
log.info("【支付寶APP支付】請求參數為:"+json4.toString());
return json4.toString(); */
}
/**
*
* @Title: alipayNotify
* @Description: TODO 支付寶APP支付異步通知
* 對于APP支付産生的交易,支付寶會根據原始支付API中傳入的異步通知位址notify_url,通過POST請求的形式将支付結果作為參數通知到商戶系統。
* 參考連結:
* http://blog.csdn.net/fengshizty/article/details/53215196
* https://doc.open.alipay.com/docs/api.htm?spm=a219a.7395905.0.0.sS4WFQ&docType=4&apiId=759
* https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.y9tReT&treeId=193&articleId=105303&docType=1
* @param @param request
* @param @throws Exception 參數
* @return void 傳回類型
* @throws
*/
@ResponseBody
@RequestMapping(value = "/alipayNotify.do",method={RequestMethod.POST})
public String alipayNotify( HttpServletRequest request ){
/**
* 擷取到傳回的所有參數 先判斷是否交易成功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時,支付寶才會認定為買家付款成功。
*
* 交易狀态: WAIT_BUYER_PAY 交易建立,等待買家付款 TRADE_CLOSED 未付款交易逾時關閉,或支付完成後全額退款
* TRADE_SUCCESS 交易支付成功 TRADE_FINISHED 交易結束,不可退款
*/
String trade_status = request.getParameter("trade_status");
String out_trade_no = request.getParameter("out_trade_no");
String trade_no = request.getParameter("trade_no"); //支付寶交易憑證号
log.info("【支付寶APP支付】支付異步通知結果:"+trade_status+" ====-》訂單号為:"+out_trade_no);
if ("TRADE_SUCCESS".equals(trade_status)) {
Enumeration<?> pNames = request.getParameterNames();
Map<String, String> param = new HashMap<String, String>();
try {
//将異步通知中收到的待驗證所有參數都存放到map中
while (pNames.hasMoreElements()) {
String pName = (String) pNames.nextElement();
param.put(pName, request.getParameter(pName));
}
//log.info("【支付寶APP支付】異步通知傳回參數--param:"+param);
boolean signVerified = AlipaySignature.rsaCheckV1(param, AliPayUtil.ALIPAY_PUBLIC_KEY,
AliPayUtil.CHARSET);
log.info("【支付寶APP支付】校驗簽名是否正确--signVerified:"+signVerified);
//調用SDK驗證簽名
if (signVerified) {
// TODO 驗簽成功後
// 按照支付結果異步通知中的描述,對支付結果中的業務内容進行1\2\3\4二次校驗,校驗成功後在response中傳回success,校驗失敗傳回failure
CommodityOrderBase cob = orderBaseService.queryByOrderNum(out_trade_no);
if(cob != null){
//避免付款後立馬取消了訂單,異步通知還未處理,仍将取消狀态改為待發貨的bug
if(cob.getOrderStatus() == 0){
cob.setOrderStatus(1); //訂單狀态:0 待付款 1待發貨 2已發貨 3已完成 4 已取消
cob.setPayTime(DateUtils.formatNowDate()); //支付時間
cob.setAlipayTradeNo(trade_no); //支付寶交易憑證号
cob.setCheckCancel(1); //是否可以退款:1可以2訂單進行中
orderBaseService.updateCommodityOrderBase(cob);
}
}
log.info("【支付寶APP支付】訂單支付成功:" + JSON.toJSONString(param));
return "success";
} else {
// TODO 驗簽失敗則記錄異常日志,并在response中傳回failure.
log.info("【支付寶APP支付】訂單支付失敗:" + JSON.toJSONString(param));
return "failure";
}
} catch (Exception e) {
log.info("【支付寶APP支付】訂單支付抛出異常:" + e.getMessage());
return "failure";
}
}else{
return "failure";
}
}
/**
*
* @Title: alipayRefund
* @Description: TODO【阿裡APP支付】申請退款
* @param trade_no 支付寶交易号,和商戶訂單号不能同時為空
* @param out_trade_no 訂單支付時傳入的商戶訂單号,不能和 trade_no同時為空。
* @param refund_amount 需要退款的金額,該金額不能大于訂單金額,機關為元,支援兩位小數
* @param request
* @param @return
* @param @throws Exception 參數
* @return Map<String,String> 傳回類型
* @throws
*/
public Map<String, String> alipayRefund(String alipay_trade_no,String out_trade_no,String refund_amount
,HttpServletRequest request) throws Exception {
// 統一收單交易退款接口
AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest();
//隻需要傳入業務參數
Map<String, Object> param = new HashMap<>();
param.put("trade_no", alipay_trade_no);
param.put("out_trade_no", out_trade_no);
param.put("refund_amount", refund_amount);
param.put("refund_reason", "正常退款");//退款的原因說明
alipayRequest.setBizContent(JSON.toJSONString(param)); // 請求參數的集合 ,不需要對json字元串轉義
//傳回支付寶退款資訊
Map<String, String> restmap = new HashMap<>();
try {
AlipayTradeRefundResponse alipayResponse = AlipayAPIClientFactory.getAlipayClient().execute(alipayRequest);
if (alipayResponse.isSuccess()) {
/**
* 調用成功,則處理業務邏輯
* code:
* 10000 接口調用成功
* 20000 服務不可用
* 檢視位址:https://doc.open.alipay.com/docs/doc.htm?treeId=291&articleId=105806&docType=1
*/
if ("10000".equals(alipayResponse.getCode())) {
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", DateUtils.formatWithDefaultPattern(alipayResponse.getGmtRefundPay())); //退款支付時間
restmap.put("buyer_user_id", alipayResponse.getBuyerUserId());// 買家在支付寶的使用者id
restmap.put("return_code", "SUCCESS");
restmap.put("return_msg", "SUCCESS");
log.info("【支付寶APP支付】訂單退款結果:退款成功");
} else {
log.info("【支付寶APP支付】訂單退款失敗:" + alipayResponse.getMsg() + ":" + alipayResponse.getSubMsg());
restmap.put("return_code", "FAIL");
restmap.put("return_msg", "支付寶申請退款失敗原因:"+alipayResponse.getMsg()+" 錯誤碼檢視位址:https://doc.open.alipay.com/docs/doc.htm?treeId=291&articleId=105806&docType=1");
}
}
} catch (AlipayApiException e) {
restmap.put("return_code", "FAIL");
log.info("【支付寶APP支付】訂單退款失敗:"+e.getErrMsg());
}
return restmap;
}
送出訂單部分邏輯代碼
//1 微信支付 2支付寶支付
if(payway.equals(1)){
log.info("=【微信APP支付】進入微信支付統一下單接口=");
//微信支付 總金額機關為 分
String weixinTotalAmount = ""+ (int)(StringUtil.getDouble(totalAmount)*100);
Map<String, String> map = weixinPrePay(ob.getOrderNum(),weixinTotalAmount, description,"", "", request);
String return_code = map.get("return_code");
String result_code = map.get("result_code"); //業務結果 參考連結:https://pay.weixin.qq.com/wiki/doc/api/wap.php?chapter=9_1
String return_msg = map.get("return_msg");
if(return_code.equalsIgnoreCase("SUCCESS") && result_code.equalsIgnoreCase("SUCCESS")){
//傳回給APP端相關參數,用于調用微信支付
SortedMap<String, Object> finalpackage = new TreeMap<String, Object>();
finalpackage.put("appid", PayCommonUtil.APPID);
finalpackage.put("partnerid", PayCommonUtil.MCH_ID);
finalpackage.put("prepayid", map.get("prepay_id"));
finalpackage.put("package", "Sign=WXPay");
finalpackage.put("noncestr", PayCommonUtil.getRandomString(32));
finalpackage.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
String sign = PayCommonUtil.createSign("UTF-8", finalpackage);
finalpackage.put("sign", sign);
finalpackage.put("packages", "Sign=WXPay"); //用于Android避免關鍵字 ,意義與上package同等
maps.putAll(finalpackage);
//修改訂單狀态
ob.setOrderStatus(0); //訂單狀态:0 待支付 1待發貨 2已發貨 3已完成 4 已取消
ob.setPayway(1);
ob.setCheckCancel(2); //是否可以退款:1可以2訂單進行中
orderBaseService.updateCommodityOrderBase(ob);
}else{
errorCode = 222; //下單失敗
log.info("【微信APP支付】請求統一下單接口失敗,訂單号為:"+ob.getOrderNum()+" 原因為:"+return_msg);
}
}else if(payway.equals(2)){
//支付寶 總金額機關為 元 保留小數點後2位
String alipaySign = aliPay(description, "快遞費", ob.getOrderNum(), totalAmount, request);
maps.put("alipaySign", alipaySign);
//修改訂單支付類型
ob.setPayway(2);
ob.setCheckCancel(2); //是否可以退款:1可以2訂單進行中
orderBaseService.updateCommodityOrderBase(ob);
}
取消訂單部分邏輯代碼
int payway = cob.getPayway(); //支付方式:1 微信 2 支付寶
if(payway == 1){
log.info("【微信APP支付】開始調用微信申請退款接口,訂單号為:"+cob.getOrderNum());
Double totalfee = cob.getTotalFee();
String totalAmount = ""+(int)(totalfee*100);
Map<String, String> map = weixinRefund(orderNum, outRefundNo, totalAmount, request);
String return_code = map.get("return_code"); //SUCCESS/FAIL
String return_msg = map.get("return_msg"); //傳回資訊,如非空,為錯誤原因 簽名失敗 參數格式校驗錯誤
String result_code = map.get("result_code"); //SUCCESS/FAIL SUCCESS退款申請接收成功,結果通過退款查詢接口查詢 FAIL 送出業務失敗
String err_code = map.get("err_code");
if(!StringUtil.nullOrBlank(return_code) && return_code.equalsIgnoreCase("SUCCESS") && result_code.equalsIgnoreCase("SUCCESS")){
log.info("【微信APP支付】原路退款至微信成功,商戶訂單号為:"+cob.getOrderNum());
//更改訂單為【已取消】
orderBaseService.delOrderBase(orderNum, userId,diamond,outRefundNo);
cob.setOrderStatus(4);
cob.setCancelTime(DateUtils.formatNowDate());
cob.setOutRefundNo(outRefundNo);
orderBaseService.updateCommodityOrderBase(cob);
}else{
errorCode = 223; //微信申請退款失敗
log.info("【微信APP支付】原路退款至微信失敗,商戶訂單号為:"+cob.getOrderNum()+"原因為:"+return_msg+" 錯誤碼:"+err_code);
log.info("錯誤碼參見:https://pay.weixin.qq.com/wiki/doc/api/wap.php?chapter=9_4");
}
}else if(payway == 2){
log.info("【支付寶APP支付】原路退款至支付寶,商戶訂單号為:"+cob.getOrderNum());
Map<String, String> map = alipayRefund(cob.getAlipayTradeNo(), cob.getOrderNum(), ""+cob.getTotalFee(), request);
String return_code = map.get("return_code");
String return_msg = map.get("return_msg");
String gmt_refund_pay = map.get("gmt_refund_pay");
if(!StringUtil.nullOrBlank(return_code) && return_code.equals("SUCCESS")){
log.info("【支付寶APP支付】申請原路退款至支付寶成功,商戶訂單号為:"+cob.getOrderNum()+" 退款支付時間為:"+gmt_refund_pay);
//更改訂單為【已取消】
orderBaseService.delOrderBase(orderNum, userId,diamond,outRefundNo);
cob.setOrderStatus(4);
cob.setCancelTime(DateUtils.formatNowDate());
cob.setOutRefundNo(outRefundNo);
orderBaseService.updateCommodityOrderBase(cob);
}else{
errorCode = 224; //支付寶申請退款失敗
log.info("【支付寶APP支付】申請原路退款至支付寶失敗,商戶訂單号為:"+cob.getOrderNum()+"原因為:"+return_msg);
}
}else{
//更改訂單為【已取消】
orderBaseService.delOrderBase(orderNum, userId,diamond,outRefundNo);
}
PayCommonUtil.java
package com.skin.pay.weixin;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.net.ssl.SSLContext;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import com.skin.util.MD5Util;
/**
*
* @ClassName: PayCommonUtil
* @Description: 微信支付幫助類
* 參考連結:http://www.cnblogs.com/007sx/p/5811137.html https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
* @author by.Tommy
* @date 2017年1月9日
*
*/
public class PayCommonUtil {
//微信支付參數配置
public static String API_KEY="*****";
public static String APPID="****";
public static String MCH_ID="****";
protected static Logger log = Logger.getLogger(PayCommonUtil.class);
/**
*
* @Title: getRandomString
* @Description: 随機字元串生成
* @param @param length 生成字元串的長度
* @param @return 參數
* @return String 傳回類型
* @throws
*/
public static String getRandomString(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
/**
*
* @Title: getRequestXml
* @Description: 請求xml組裝
* @param @param parameters
* @param @return 參數
* @return String 傳回類型
* @throws
*/
public static String getRequestXml(SortedMap<String,Object> parameters){
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String key = (String)entry.getKey();
String value = (String)entry.getValue();
if ("attach".equalsIgnoreCase(key)||"body".equalsIgnoreCase(key)||"sign".equalsIgnoreCase(key)) {
//sb.append("<"+key+">"+"<![CDATA["+value+"]]></"+key+">");
sb.append("<"+key+">"+value+"</"+key+">");
}else {
sb.append("<"+key+">"+value+"</"+key+">");
}
}
sb.append("</xml>");
return sb.toString();
}
/**
*
* @Title: createSign
* @Description: 生成簽名
* @param characterEncoding
* @param parameters
* @param @return 參數
* @return String 傳回類型
* @throws
*/
public static String createSign(String characterEncoding,SortedMap<String,Object> parameters){
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
if(null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
/**
* 驗證回調簽名
* @param packageParams
* @param key
* @param charset
* @return
*/
public static boolean isTenpaySign(Map<String, String> map) {
String charset = "UTF-8";
String signFromAPIResponse = map.get("sign");
if (signFromAPIResponse == null || signFromAPIResponse.equals("")) {
log.info("==API傳回的資料簽名資料不存在,有可能被第三方篡改!!!==");
return false;
}
//System.out.println("伺服器回包裡面的簽名是:" + signFromAPIResponse);
//過濾空 設定 TreeMap
SortedMap<String,String> packageParams = new TreeMap<>();
for (String parameter : map.keySet()) {
String parameterValue = map.get(parameter);
String v = "";
if (null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if(!"sign".equals(k) && null != v && !"".equals(v)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
//将API傳回的資料根據用簽名算法進行計算新的簽名,用來跟API傳回的簽名進行比較
//算出簽名
String resultSign = "";
String tobesign = sb.toString();
if (null == charset || "".equals(charset)) {
//resultSign = MD5Util.MD5Encode(tobesign,characterEncoding).toUpperCase();
resultSign = MD5Util.MD5Encode(tobesign,"").toUpperCase();
} else {
try {
//resultSign = MD5Util.MD5Encode(tobesign,characterEncoding).toUpperCase();
resultSign = MD5Util.MD5Encode(tobesign,"").toUpperCase();
} catch (Exception e) {
//resultSign = MD5Util.MD5Encode(tobesign,characterEncoding).toUpperCase();
resultSign = MD5Util.MD5Encode(tobesign,"").toUpperCase();
}
}
String tenpaySign = ((String)packageParams.get("sign")).toUpperCase();
return tenpaySign.equals(resultSign);
}
/**
* 發起HTTP請求方法
*/
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
try {
URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 設定請求方式(GET/POST)
conn.setRequestMethod(requestMethod);
conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
// 當outputStr不為null時向輸出流寫資料
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意編碼格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 從輸入流讀取傳回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 釋放資源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
conn.disconnect();
return buffer.toString();
} catch (ConnectException ce) {
log.info("https連接配接逾時:{}"+ ce);
} catch (Exception e) {
log.info("https請求異常:{}"+ e);
}
return null;
}
/**
* 退款的請求方法
*
* 當交易發生之後一段時間内,由于買家或者賣家的原因需要退款時,賣家可以通過退款接口将支付款退還給買家,微信支付将在收到退款請求并且驗證成功之後,按照退款規則将支付款按原路退到買家帳号上。
* 注意:
* 1、交易時間超過一年的訂單無法送出退款;
* 2、微信支付退款支援單筆交易分多次退款,多次退款需要送出原支付訂單的商戶訂單号和設定不同的退款單号。一筆退款失敗後重新送出,要采用原來的退款單号。總退款金額不能超過使用者實際支付金額。
* 3、退款有一定延時,用零錢支付的退款20分鐘内到賬,銀行卡支付的退款3個工作日後重新查詢退款狀态。
* 接口連結:https://api.mch.weixin.qq.com/secapi/pay/refund
*/
public static String httpsRequest2(String requestUrl, String requestMethod, String outputXml) throws Exception {
/*----1.讀驗證書檔案,這一段是直接從微信支付平台提供的demo中copy的,是以一般不需要修改---- */
KeyStore keyStore = KeyStore.getInstance("PKCS12");
StringBuilder res = new StringBuilder("");
FileInputStream instream = new FileInputStream(new File("/home/weixin/apiclient_cert.p12"));
try {
keyStore.load(instream, MCH_ID.toCharArray());
} finally {
instream.close();
}
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, MCH_ID.toCharArray())
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1" },
null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
try {
/*----2.發送資料到微信的退款接口---- */
HttpPost httpost = new HttpPost(requestUrl);
httpost.addHeader("Connection", "keep-alive");
httpost.addHeader("Accept", "*/*");
httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
httpost.addHeader("Host", "api.mch.weixin.qq.com");
httpost.addHeader("X-Requested-With", "XMLHttpRequest");
httpost.addHeader("Cache-Control", "max-age=0");
httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
StringEntity stringEntity = new StringEntity(outputXml ,Consts.UTF_8);
httpost.setEntity(stringEntity);
CloseableHttpResponse response = httpclient.execute(httpost);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent()));
String text = "";
res.append(text);
while ((text = bufferedReader.readLine()) != null) {
res.append(text);
//System.out.println(text);
}
}
EntityUtils.consume(entity);
} finally {
response.close();
}
} finally {
httpclient.close();
}
return res.toString();
}
/**
*
* @Title: doXMLParse
* @Description: xml解析
* @param strxml
* @param @return
* @param @throws JDOMException
* @param @throws IOException 參數
* @return Map 傳回類型
* @throws
*/
public static Map doXMLParse(String strxml) throws JDOMException, IOException {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if(null == strxml || "".equals(strxml)) {
return null;
}
Map m = new HashMap();
InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(in);
Element root = doc.getRootElement();
List list = root.getChildren();
Iterator it = list.iterator();
while(it.hasNext()) {
Element e = (Element) it.next();
String k = e.getName();
String v = "";
List children = e.getChildren();
if(children.isEmpty()) {
v = e.getTextNormalize();
} else {
v = getChildrenText(children);
}
m.put(k, v);
}
//關閉流
in.close();
return m;
}
public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if(!children.isEmpty()) {
Iterator it = children.iterator();
while(it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<" + name + ">");
if(!list.isEmpty()) {
sb.append(getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
}
AliPayUtil.java
package com.skin.pay.alipay;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
/**
* 參考文檔: https://doc.open.alipay.com/docs/api.htm?spm=a219a.7395905.0.0.Ewddf6&docType=4&apiId=757
* @ClassName: AliPayUtil
* @Description: 阿裡支付寶支付幫助類
* @author by.Tommy
* @date 2017年1月11日
*
*/
public class AliPayUtil {
protected static Logger log = Logger.getLogger(AliPayUtil.class);
//=============阿裡支付寶支付參數配置 ===================================
//支付寶網關(固定)
public static String ALIPAY_GATEWAY="https://openapi.alipay.com/gateway.do";
//APPID即建立應用後生成
public static String APPID="*****";
//開發者應用私鑰,由開發者自己生成
public static String APP_PRIVATE_KEY="******";
//參數傳回格式,隻支援json
public static String FORMAT="json";
//請求和簽名使用的字元編碼格式,支援GBK和UTF-8
public static String CHARSET="utf-8";
//應用公鑰, 由支付寶生成
//public static String ALIPAY_PUBLIC_KEY="*******";
//支付寶公鑰,由支付寶生成
public static String ALIPAY_PUBLIC_KEY="*********";
//商戶生成簽名字元串所使用的簽名算法類型,目前支援RSA2和RSA,推薦使用RSA2
public static String SIGN_TYPE="RSA";
//合作夥伴身份(PID) 檢視位址:https://openhome.alipay.com/platform/keyManage.htm?keyType=partner
public static String PARTNER_ID="*******";
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;
}
/**
* 對支付參數資訊進行簽名
* @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 sign(authInfo.toString(), rsaKey);
}
/**
* 傳回簽名編碼拼接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), CHARSET));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
authInfo.append(map.get(key));
}
}
try {
encodedSign = authInfo.toString() + "&sign=" + URLEncoder.encode(sign, CHARSET);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
return encodedSign.replaceAll("\\+", "%20");
}
}
CollectionUtil.java
package com.skin.pay.alipay;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class CollectionUtil {
private CollectionUtil() {
super();
}
// 判斷一個集合是否為空
public static <T> boolean isEmpty(Collection<T> col) {
if (col == null || col.isEmpty()) {
return true;
}
return false;
}
// 判斷一個集合是否不為空
public static <T> boolean isNotEmpty(Collection<T> col) {
return !isEmpty(col);
}
// 判斷Map是否為空
public static <K, V> boolean isEmpty(Map<K, V> map) {
if (map == null || map.isEmpty()) {
return true;
}
return false;
}
// 判斷Map是否不為空為空
public static <K, V> boolean isNotEmpty(Map<K, V> map) {
return !isEmpty(map);
}
// 去除list中的重複資料
public static <T> List<T> removeRepeat(List<T> list) {
if (isEmpty(list)) {
return list;
}
List<T> result = new ArrayList<T>();
for (T e : list) {
if (!result.contains(e)) {
result.add(e);
}
}
return result;
}
// 将集合轉換為String數組
public static <T> String[] toArray(List<T> list) {
if (isEmpty(list)) {
return null;
}
String[] result = new String[list.size()];
for (int i = 0; i < list.size(); i++) {
result[i] = String.valueOf(list.get(i));
}
return result;
}
}