支付寶與微信二碼合一,掃碼支付(學習筆記)
1:頁面生成的二維碼:
(頁面生成的二維碼(掃描二維碼會向服務端的判斷掃碼類型的接口發送帶參數請求!)
( 帶上訂單號和商品圖片參數 例如: 生成二維碼參數地址 localhost:8080/common/qrcode?order=XX&imgurl=xx)
2: 後端接收參數判斷是什麼發起的支付方式:
- 判斷是什麼發起請求的工具類(AgentUtils.java):
<!-- User Agent依賴 -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.20</version>
</dependency>
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.DeviceType;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
/**
* 客戶端識別工具
*/
public class AgentUtils {
/**
* 獲取客戶端爲 微信/支付寶
*/
public static String getUserAgentWap(HttpServletRequest request) {
String agent = ((HttpServletRequest) request).getHeader("user-agent");
if (StringUtils.isNotBlank(agent)) {
agent = agent.toLowerCase();
if (agent.indexOf("micromessenger") >= 0) {
return "weixin";
} else if (agent.indexOf("alipayclient") >= 0) {
return "alipay";
} else {
return "other";
}
}
return "unknown";
}
}
- 控制層:
/**
* 掃碼支付URL
*/
@Controller
@RequestMapping("/common")
public class CommonsController extends BaseController{
@RequestMapping(value="/qrcode",method=RequestMethod.GET)
public String scan(HttpServletRequest request,RedirectAttributes attr) {
String order = request.getParameter("order");
String imgurl = request.getParameter("imgurl");
String agent = AgentUtils.getUserAgentWap(request);
if ("weixin".equals(agent)) {
//重定向發送微信服務端請求
return "redirect:/weixin/getcode?imgurl=" + imgurl + "&order=" + order + "";
} else if ("alipay".equals(agent)) {
//重定向發起服務端支付寶(確定下單的接口/pay/confirmpay)
return "redirect:/pay/confirmpay?imgurl=" + imgurl + "&order=" + order + "";
}
return null;
}
支付寶方式
: (建議先看一次官網文檔)
要先去進行配置應用獲取應用App的ID(APP_ID),應用公鑰(ALI_PUBLIC_KEY),私鑰(APP_PRIVATE_KEY)和支付寶公鑰4個參數
控制層Controller
@Controller
@RequestMapping("/pay")
public class PayController {
// 服務器異步通知頁面路徑 需http://或者https://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問
public static String notify_url = "http://商戶網關地址/alipay.trade.wap.pay-JAVA-UTF-8/notify_url";
// 跳轉頁面,買家支付成功後跳轉的頁面,僅當買家支付成功後跳轉一次 需http://或者https://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問 商戶可以自定義同步跳轉地址
public static String return_url = "http://商戶網關地址/alipay.trade.wap.pay-JAVA-UTF-8/return_url";
/**
* (重定向目標接口, 二選一,可以選擇直接進行請求支付接口/orderpay進行支付)
* 先進到商品詳情頁面,讓用戶知道買的是什麼,由用戶點確定發起調用支付寶支付接口
* 確認支付界面 (使用的是更好的用戶體驗)
*/
@RequestMapping(value = "/confirmpay", method = RequestMethod.GET)
public String ConfirmPay(HttpServletRequest req, HttpServletResponse res,Model model) {
String imgurl = req.getParameter("imgurl");
String order = req.getParameter("order");
model.addAttribute("imgurl", imgurl);
model.addAttribute("order", order);
//返回confirmspays.jsp商品頁面
return "confirmspays";
}
下單支付前的商品詳情頁面.jsp
:
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ include file="/WEB-INF/views/include/taglib.jsp"%>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<%--jQuery--%>
<script src="${ctxStatic}/common/js/jquery-2.1.3.min.js"></script>
<title>支付確認</title>
<style type="text/css">
body, html {
height: 100%;
}
body{
padding: 0;
margin: 0;
font-size: 1.2rem;
}
.btn_style{
width: 90%;
margin-top: 50px;
margin-left: 5%;
height: 50px;
background-color: #805f45;
color: white;
font-size: 16px;
}
</style>
<div id="tz"></div>
<script type="text/javascript">
function submitData() {
$.ajax({
url : "${ctx}/pay/orderpay",
data : {
"urlSmallPicture": $("#imgurl").val(),
"billNumber": $("#order").val()
},
type : 'post',
dataType : 'json',
success : function(data) {
if(data.return_code == 'success'){
//把響應的確定支付表單顯示到div中
$("#tz").html(data.return_data);
}
},
error : function() {
console.log('request error!');
}
});
}
</script>
</head>
<body>
<input type="hidden" name="imgurl" id="imgurl" value="${imgurl}">
<input type="hidden" name="order" id="order" value="${order}">
<div style="width: 100%;margin-top: 100px;text-align: center;">
<img src="${imgurl}" style="width: 200px;">
</div>
<div>
<button class="btn btn_style" id="btnSubmit" οnclick="submitData();">確 認</button>
</div>
</body>
</html>
支付寶下單支付接口:
/**
* 訂單統一下單支付
* @param req
* @param resp
* @return
*/
@RequestMapping(value="/orderpay", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> orderpay(HttpServletRequest req, HttpServletResponse resp) throws IOException, ParseException, AlipayApiException{
Map<String, Object> map = new HashMap<String, Object>();
String order = req.getParameter("order");
//判斷商戶是否存在該訂單和獲取訂單價格什麼的(自行操作)
IdmOrder idmOrder = (IdmOrder)緩存Redis.getIdmOrderByBillNo(order);
......
//創建阿里客戶端對象
AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", "App的ID", "應用私鑰","json", "字符集(UTF-8)", "應用公鑰", "(簽名類型例如:RSA2)");
//創建交易信息模型對象
AlipayTradeWapPayModel precreateModel = new AlipayTradeWapPayModel();
//商戶訂單號,需要保證不重複
precreateModel.setOutTradeNo(order);
//訂單金額
precreateModel.setTotalAmount(money);
//交易主題
precreateModel.setSubject("Ada");
//商品名稱
precreateModel.setBody("訂單對象.get()");
//對一筆交易的具體描述信息。如果是多種商品,請將商品描述字符串累加傳給body
precreateModel.setSellerId("出售商品");
//銷售產品碼,商家和支付寶簽約的產品碼,該產品請填寫固定值:QUICK_WAP_WAY
precreateModel.setProductCode("QUICK_WAP_WAY");
//設置支付寶交易超時 取值範圍:1m~15d。m-分鐘,h-小時,d-天,1c-當天(1c-當天的情況下,無論交易何時創建,都在0點關閉)
precreateModel.setTimeoutExpress("5m");
//創建阿里請求對象
AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
//業務請求參數的集合
alipayRequest.setBizModel(precreateModel);
//設置後臺異步通知的地址,在手機端支付後支付寶會通知後臺(成功或者失敗),手機端的真實支付結果依賴於此地址
alipayRequest.setNotifyUrl(notify_url);
//支付成功後的跳轉頁面,由於前臺回跳的不可靠性,前臺回跳只能作爲商戶支付結果頁的入口,最終支付結果必須以異步通知或查詢接口返回爲準,不能依賴前臺回跳
alipayRequest.setReturnUrl(return_url);
//返回響應的輸入密碼完成支付的表單給商品詳情頁面
map.put("return_code", "success");
map.put("return_data", alipayClient.pageExecute(request).getBody());
return map;
//下面是直接拉起支付頁面 沒有到商品詳情頁面
// String form="";
// try {
// //進行執行
// form = alipayClient.pageExecute(alipayRequest).getBody(); //調用SDK生成表單
// } catch (AlipayApiException e) {
// e.printStackTrace();
// }
// httpResponse.setContentType("text/html;charset=UTF-8");
// httpResponse.getWriter().write(form);//直接將完整的表單html輸出到頁面
// httpResponse.getWriter().flush();
// httpResponse.getWriter().close();
}
支付寶NotifyUrl
支付異步通知和ReturnUrl
支付成功回跳:
(只能回調公司綁定好的公網能訪問的域名 例如: https:www.baidu.com/pay/alipaynotify
)
/**
* 支付寶支付狀態結果異步通知()
* @param req
* @param resp
* @return (並返回成功或者失敗給支付寶通知接口,不然支付寶會一直異步通知)
*/
@RequestMapping(value = "/alipaynotify", method = RequestMethod.POST)
@ResponseBody
public String AlipayNotify(HttpServletRequest req, HttpServletResponse resp) throws IOException, AlipayApiException {
//將異步通知中所有參數都經過工具類處理後存放到map中
Map<String, String> params = PayUtil.parseParams(req.getParameterMap());
// 驗證通知合法性
boolean verify_result = AlipaySignature.rsaCheckV1(paramsMap, "應用公匙", "字符集(UTF-8)", "(簽名類型例如:RSA2)");
// 驗證簽名
if(! verify_result){
logger.warn("---支付寶異步通知->簽名驗證失敗");
return PayUtil.generatePayErrorReplyParams();
}
//查看狀態是否是交易成功狀態
if ("TRADE_SUCCESS".equals(params.get("trade_status").toString())) {
//查詢該訂單是否存在.....
IdmOrder idmOrder = (IdmOrder)緩存Redis.getIdmOrderByBillNo(order);
if (order != null) {
//1、商戶需要驗證該通知數據中的out_trade_no是否爲商戶系統中創建的訂單號,
if ( ! "支付接口返回訂單號".equals(數據庫訂單號)) {
logger.info("支付寶異步通知->訂單號與商戶號訂單不一致!" + out_trade_no);
return PayUtil.generatePayErrorReplyParams();
}
//2、校驗返回的訂單金額是否與商戶側的訂單金額一致(查詢商戶後臺的訂單金額進行比較)
if (order.getOrderAmount() != Double.parseDouble(total_amount)) {
logger.info("支付寶異步通知->訂單中的交易金額和返回的金額不一致!" + order.getOrderAmount());
return PayUtil.generatePayErrorReplyParams();
}
//3、校驗通知中的seller_id(或者seller_email) 是否爲out_trade_no這筆單據的對應的操作方
if (!xxxx.seller_id.equals(seller_id)) {
logger.info("支付寶異步通知->收款支付寶賬戶號不一致!" + seller_id);
return PayUtil.generatePayErrorReplyParams();
}
// 4、驗證app_id是否爲該商戶本身
if (!app_id.equals(XXXX.ALI_APP_ID)) {
logger.info("---支付寶異步通知->商戶號不一致!" + app_id);
return PayUtil.generatePayErrorReplyParams();
}
//更改商品訂單狀態....
//把商品訂單存儲進數據庫 (然後刪除緩存的數據)
//查商品庫存.........
//減商品庫存........
//極光通知App客戶端(支付結果通知)
JpushService.pullMessages(別名,"PAY_RESULT","success","支付結果通知");
//返回成功狀態碼給支付寶接口
return PayUtil.generatePaySuccessReplyParams();
}
logger.info("支付寶異步通知->支付結果返回的不是成功的狀態碼,請詳查!");
return PayUtil.generatePayErrorReplyParams();
}
/**
* 支付寶成功支付後頁面跳轉 ReturnUrl
*/
@RequestMapping(value = "/AlipaySuccess", method = RequestMethod.GET)
public String AlipaySuccess(HttpServletRequest req, HttpServletResponse res) {
//獲取支付寶GET過來反饋信息
Map<String, String> params = PayUtil.parseParams(req.getParameterMap());
if (PayUtil.checkedParams(params)) {
//驗證成功返回成功頁面,或者成功狀態給前端來處理
return "paySuccess";
}
return "";
}
//--------------------------交易查詢接口如果需要在官網自行查詢-----------------------------------
微信方式
: (建議先看一次官網文檔) -JSAPI(公衆號)方式 博客教材
要先去進行配置應用獲取應用App的ID(appid), 應用祕鑰(appsecret) , 商戶ID(mch_id) 和API密鑰(Key) 4個參數
控制層用戶授權獲取code
/**
* 微信支付
*/
@Controller
@RequestMapping("/weixin")
public class WeiXinsPayController {
//公網能訪問的公司域名加上項目支付接口地址
private static String WX_REDIRECT_URL ="https://******.com/weixin"
/**
*微信網頁授權-用戶授權獲取code
*openid是微信用戶在公衆號appid下的唯一用戶標識(appid不同,則獲取到的openid就不同),可用於永久標記一個用戶,同時也是微信JSAPI支付的必傳參數
*/
@RequestMapping(value="/getcode")
public String getcode(Model model,HttpServletRequest req){
//購買商品生成的訂單號方便後面調用順便傳遞
String order = req.getParameter("order");
//拼接獲取code的鏈接 ---redirect_uri參數:授權後重定向的回調鏈接地址 (state是重定向會帶上的參數) 最好使用restful風格傳遞參數 (參數是網站鏈接要使用state傳遞)
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+AppID()+
"&redirect_uri="+WX_REDIRECT_URL+"/getopenid/" + order +"&response_type=code&scope=snsapi_base&state="+urlSmallPicture+"#wechat_redirect";
model.addAttribute("authorize", url);
//返回網站鏈接數據使用跳板頁,進行訪問
return "authorize";
}
}
跳板頁.jsp
跳轉url
:
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html
<html lang="zh-CN">
<head>
<title></title>
<script type="text/javascript">
window.location.href = "${authorize}";
</script>
</head>
<body>
</body>
</html>
控制層獲取openid
:
/**
* 獲取openid就夠了
* @param req
* @param model
* @param order 重定向鏈接帶的參數使用restful風格接收
* @param code 響應參數
* @param state 響應參數
* @return
*/
@RequestMapping("/getopenid/{order}")
public String wx(HttpServletRequest req, Model model, @PathVariable("order") String order, @RequestParam("state") String state, @RequestParam("code")String code ){
Map<String, Object> params = new HashMap<String, Object>();
params.put("appid", AppID);
params.put("secret","應用密匙");
//填寫第一步獲取的code參數
params.put("code", code);
//固定寫法
params.put("grant_type", "authorization_code");
//使用發送http請求的工具類發送http. get() 方式的請求獲取響應參數
String openidJson = HttpUtil.get("https://api.weixin.qq.com/sns/oauth2/access_token", params);
//轉換成map類型 K-V
Map<String, Object> parseObject = JSON.parseObject(openidJson);
//獲得openid:
String openId = parseObject.get("openid").toString();
model.addAttribute("openid", openId);
//商品訂單
model.addAttribute("order", order);
//訂單號圖片地址
model.addAttribute("imgurl", state);
//封裝數據返回頁面顯示
return "confirmspay";
}
商品詳情頁面和調起微信前端腳本發起下單支付 .jsp頁面
:
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ include file="/WEB-INF/views/include/taglib.jsp" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<%--jQuery--%>
<script src="${ctxStatic}/common/js/jquery-2.1.3.min.js"></script>
<%--微信支付腳本--%>
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js" type="text/javascript"></script>
<title>支付確認</title>
<style type="text/css">
body, html {
height: 100%;
}
body {
padding: 0;
margin: 0;
font-size: 1.2rem;
}
.btn_style {
width: 90%;
margin-top: 50px;
margin-left: 5%;
height: 50px;
background-color: #805f45;
color: white;
font-size: 16px;
}
</style>
<script type="text/javascript">
function submitData() {
//確定支付調用統一下單接口
$.ajax({
url: "/weixin/paying",
data: {
"openid": $("#openid").val(),
"billNumber": $("#order").val()
},
type: 'post',
dataType: 'json',
success: function (data) {
if (data.return_code == 'success') {
var appId = data.appId;
var timeStamp = data.timeStamp;
var nonceStr = data.nonceStr;
var package = data.package;
var signType = data.signType;
var paySign = data.paySign;
WeixinJSBridge
.invoke(
'getBrandWCPayRequest',
{
"appId": appId, //公衆號名稱,由商戶傳入 obj.appid
"timeStamp": timeStamp, //時間戳,自1970年以來的秒數
"nonceStr": nonceStr, //隨機串 obj.nonce_str
"package": package, //訂單詳情擴展字符串
"signType": signType, //微信簽名方式:
"paySign": paySign //微信簽名
},
function (res) {
if (res.err_msg == 'get_brand_wcpay_request:ok') {
//支付成功,可以做跳轉到支付成功的提示頁面(或者請求後端)
window.location.href = "${pageContext.request.contextPath}/weixin/payingSuccess";
// alert(JSON.stringify(res));
} else if (res.err_msg == 'get_brand_wcpay_request:cancel') {
//支付取消
alert("支付取消");
} else if (res.err_msg == 'get_brand_wcpay_request:fail') {
//支付失敗
alert("支付失敗");
// alert(JSON.stringify(res));
}
});
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}
} else if (data.return_code == 'fail') {
//提示訂單已經支付
alert(data.return_data);
}
},
error: function () {
console.log('request error!');
}
});
}
</script>
</head>
<body>
<%--隱藏域提交數據用--%>
<input type="hidden" name="order" id="order" value="${order}">
<input type="hidden" name="openid" id="openid" value="${openid}">
<div style="width: 100%; margin-top: 100px; text-align: center;">
<img src="${imgurl}" style="width: 200px;">
</div>
<div>
<button class="btn btn_style" id="btnSubmit" οnclick="submitData();">確
認
</button>
</div>
</body>
</html>
控制層:微信統一下單:
/**
* 統一下單
* @param req
* @return
* @throws Exception
*/
@RequestMapping(value="/paying",method=RequestMethod.POST)
@ResponseBody
public Map<String, Object> paying(HttpServletRequest req) throws Exception{
//獲取訂單號和openid參數
String order = req.getParameter("order");
String openid = req.getParameter("openid");
//創建線程安全的Map
Map<String, Object> maps = new ConcurrentHashMap<>();
//根據訂單判斷商戶是否存在該訂單
IdmOrder idmOrder = (IdmOrder)緩存Redis.getIdmOrderByBillNo(order);
if (idmOrder == null) {
logger.info("商戶端->該訂單不存在!");
maps.put("result_Msg", 錯誤信息);
return maps;
}......
Map<String, String> reqData = new HashMap<>();
//隨機字符串 必填-每次請求都要是新的隨機字符串
reqData.put("nonce_str", WXPayUtil.generateNonceStr());//WXPayUtil.generateNonceStr()
//商品描述 必填
reqData.put("body", ##);//"Ada"
//商戶號 必填
reqData.put("mch_id", 商戶ID(mch_id));
//商品訂單號 必填
reqData.put("out_trade_no", order);
//支付金額(將元轉換爲分) 必填
reqData.put("total_fee", PayUtil.changeY2F(###));
//終端IP 不知道怎麼寫可以寫本地127.0.1 必填
reqData.put("spbill_create_ip", "127.0.1");//客戶端主機
//異步回調通知地址通知url必須爲外網可訪問的url,不能攜帶參數 必填
reqData.put("notify_url", ###); //"https://*****.com/wxpay/notifying"
//交易類型JSAPI 必填
reqData.put("trade_type", "JSAPI");
//之前獲取的openid 必填
reqData.put("openid", openid);
//商品id
reqData.put("product_id", 按需);
//創建微信下單對象 (注入微信下單配置類)
WXPay wxpay = new WXPay(new MyWXPayConfig());
//執行下單 統一下單接口unifiedOrder
Map<String, String> responseparams = wxpay.unifiedOrder(reqData);
//預支付id
String prepay_id = "prepay_id=;
if ("SUCCESS".equals(responseparams.return_code)) {
String packages= prepay_id + (String) responseparams.get("prepay_id");
//App的ID
maps.put("appId", ##);
//時間戳
maps.put("timeStamp", timeStamp);
//隨機字符串
maps.put("nonceStr", #);
//訂單詳情擴展字符串package是關鍵字加個S處理
maps.put("package", packages);
//簽名方式 默認爲MD5,支持HMAC-SHA256和MD5。注意此處需與統一下單的簽名類型一致
maps.put("signType", #);
//注:這裏採用 HMACSHA256進行簽名,因爲預下單支付那裏默認採用HMACSHA256,要保持一致
String paySign = WXPayUtil.generateSignature(maps,"API密鑰(Key)",SignType.HMACSHA256);
maps.put("return_code", "success");
maps.put("paySign", paySign);
}
}else {
maps.put("return_code", "error");
maps.put("return_data", "null");
}
return maps;
}
控制層:微信異步通知回調notify_url
:
/**
* 異步回調通知接口 (並返回成功或者失敗給微信通知接口,不然微信會一直異步通知)
* @param request
*/
@ResponseBody
@RequestMapping("/notifying")
public String notifying(HttpServletRequest request, HttpServletResponse response) throws Exception{
// 獲取請求數據,將請求轉成xml
String xmlData = null;
try {
xmlData = PayUtil.copyToString(request.getInputStream(), Charset.forName("utf-8"));
} catch (IOException e) {
logger.info("微信公衆號支付異步通知>>數據無法轉成xml異常!");
//返回支付失敗狀態給微信
return PayUtil.generatePayErrorReplyXML();
}
// 將得到xml轉爲map,驗證簽名是否成功
Map<String, String> xmlToMapData = WXPayUtil.xmlToMap(xmlData);
logger.info("得到的xmlToMapData:"+xmlToMapData);
//獲取簽名sign
String sign = xmlToMapData.get("sign");
//重新生成新sign
String newsign = WXPayUtil.generateSignature(xmlToMapData, API密鑰(Key),SignType.HMACSHA256);
// 驗證簽名驗證是否成功
if(! sign.equals(newsign)) {
logger.info("微信公衆號支付異步通知>>驗證簽名失敗!");
return PayUtil.generatePayErrorReplyXML();
}
//查詢return_code是否是支付成功了success
if (xmlToMapData.get("return_code").equals("SUCCESS")) {
//支付成功之後的邏輯
// 商戶訂單號
String out_trade_no = xmlToMapData.get("out_trade_no").toString();
// 微信支付訂單號
String thrid_trade_no = xmlToMapData.get("transaction_id").toString();
// 訂單金額
String total_fee = xmlToMapData.get("total_fee").toString();
//查詢該訂單是否存在
IdmOrder order = (IdmOrder)緩存Redis.getIdmOrderByBillNo(out_trade_no);
if (order != null) {
// 1、商戶需要驗證該通知數據中的out_trade_no是否爲商戶系統中創建的訂單號,
if (!out_trade_no.equals(order.getBillNo())) {
logger.info("微信公衆號支付異步通知->訂單號與商戶號訂單不一致!" + out_trade_no);
return PayUtil.generatePayErrorReplyXML();
}
//2、校驗返回的訂單金額是否與商戶側的訂單金額一致(查詢商戶後臺的訂單金額進行比較)
if (Integer.parseInt(PayUtil.changeY2F(order.getOrderAmount().toString())) != Integer.parseInt(total_fee)) {
logger.info("微信公衆號支付異步通知->訂單中的交易金額和返回的金額不一致!" + order.getOrderAmount());
return PayUtil.generatePayErrorReplyXML();
}
//更改商品訂單狀態...自行操作
//把商品訂單存儲進數據庫 (然後刪除緩存的數據)
//查商品庫存...自行操作
//減商品庫存...自行操作
//通知客戶端APP(支付結果通知)
JpushService.pullMessages(idmItem.getSn(),"PAY_RESULT","success","支付結果通知"); //別名
}
//返回成功狀態碼給微信端
return PayUtil.generatePaySuccessReplyXML();
}
logger.info("微信公衆號支付異步通知->支付結果返回的不是成功的狀態碼,請詳查!");
return PayUtil.generatePayErrorReplyXML();
}
微信下單配置類:
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/**
* 掃碼支付參數配置,可用於支付和退款的參數
*/
public class MyWXPayConfig extends WXPayConfig {
private byte[] certData;
@Override
public String getAppID() {
return "應用App的ID";
}
public String getAPIKey(){
return "API密鑰Key";
}
@Override
public String getMchID() {
return "商戶ID";
}
@Override
public String getKey() {
return "應用祕鑰(appsecret)";
}
@Override
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
@Override
public int getHttpConnectTimeoutMs() {
// TODO Auto-generated method stub
return 10*1000;
}
@Override
public int getHttpReadTimeoutMs() {
// TODO Auto-generated method stub
return 20*1000;
}
@Override
public IWXPayDomain getWXPayDomain() {
return new MyWXPayDomain();
}
}
支付寶和微信封裝的響應狀態和解析支付接口返回的響應參數工具類:
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class PayUtil {
protected static Logger logger = LoggerFactory.getLogger(PayUtil.class);
public static final int BUFFER_SIZE = 4096;
/**
* 將元爲單位的轉換爲分 替換小數點,支持以逗號區分的金額
* @param amount
* @return
*/
public static String changeY2F(String amount) {
String currency = amount.replaceAll("\\$|\\¥|\\,", ""); // 處理包含, ¥
// 或者$的金額
int index = currency.indexOf(".");
int length = currency.length();
Long amLong = 0l;
if (index == -1) {
amLong = Long.valueOf(currency + "00");
} else if (length - index >= 3) {
amLong = Long.valueOf((currency.substring(0, index + 3)).replace(".", ""));
} else if (length - index == 2) {
amLong = Long.valueOf((currency.substring(0, index + 2)).replace(".", "") + 0);
} else {
amLong = Long.valueOf((currency.substring(0, index + 1)).replace(".", "") + "00");
}
return amLong.toString();
}
/**
* 支付結果成功時,發給微信支付的參數
* @return
*/
public static String generatePaySuccessReplyXML() {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("<xml>").append("<return_code><![CDATA[SUCCESS]]></return_code>")
.append("<return_msg><![CDATA[OK]]></return_msg>").append("</xml>");
return stringBuffer.toString();
}
/**
* 支付結果失敗時,發給微信支付的參數
* @return
*/
public static String generatePayErrorReplyXML() {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("<xml>").append("<return_code><![CDATA[FAIL]]></return_code>")
.append("<return_msg><![CDATA[ERROR]]></return_msg>").append("</xml>");
return stringBuffer.toString();
}
/**
* 支付寶結果成功時,發給支付寶的參數
* @return
*/
public static String generatePaySuccessReplyParams() {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("success");
return stringBuffer.toString();
}
/**
* 支付寶結果失敗時,發給支付寶的參數
* @return
*/
public static String generatePayErrorReplyParams() {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("failure");
return stringBuffer.toString();
}
/**
* 解析alipay異步通知的參數
* @param requestParams 樣例:
* {
* "gmt_create": ["2017-07-14 14:38:54"],
* "charset": ["utf-8"]
* ...
* }
*/
public static Map<String, String> parseParams(Map<?, ?> requestParams){
Map<String,String> params = new HashMap<String,String>();
for (Iterator<?> 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] + ",";
}
params.put(name, valueStr);
}
return params;
}
/**
* 驗證支付寶參數合法性
* 使用支付寶2.0SDK自帶的驗證方法進行驗證 AlipaySignature.rsaCheckV1
* @param params
* @return
*/
public static boolean checkedParams(Map<String, String> params){
try {
//切記alipaypublickey是支付寶的公鑰,請去open.alipay.com對應應用下查看。
return AlipaySignature.rsaCheckV1(params, AliPayConfig.ALI_PUBLIC_KEY, AliPayConfig.ALI_CHARSET, AliPayConfig.ALI_SIGN_TYPE);
} catch (AlipayApiException e) {
// TODO Auto-generated catch block
logger.error("---支付寶API異常>>");
e.printStackTrace();
}
return false;
}
/**
* 根據支付寶對應費率,計算用戶提現費用
* 支付寶費率0.15%,費用區間:2-25,即不低於2元,上到25封頂
* @param amount
* @return
*/
public static double cunWithdrawCost(double amount){
double coust = BigDecimalUtil.mul(amount, 0.0015);
if(coust <= 2 ){
return 2;
}
if(coust >= 25){
return 25;
}
return coust;
}
public static String getAtt(HttpServletRequest request, String key){
return (String)request.getSession().getAttribute(key);
}
/**
* 將元爲單位的轉換爲分 (乘100)
*
* @param amount
* @return
*/
public static String changeY2F(Long amount) {
return BigDecimal.valueOf(amount).multiply(new BigDecimal(100)).toString();
}
/**
* 將流轉換爲xml
* @param in
* @param charset
* @throws IOException
*/
public static String copyToString(InputStream in, Charset charset) throws IOException {
StringBuilder out = new StringBuilder();
InputStreamReader reader = new InputStreamReader(in, charset);
char[] buffer = new char[BUFFER_SIZE];
int bytesRead = -1;
while ((bytesRead = reader.read(buffer)) != -1) {
out.append(buffer, 0, bytesRead);
}
return out.toString();
}
}
支付(支付寶或者微信)依賴:
<!-- 支付寶3.4.49.ALL -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>3.7.4.ALL</version>
</dependency>
<!-- 微信ALL -->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>3.0.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
<!-- 日記 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
擴展: 微信下單調用http
工具類發送下單接口的方式
/**
* 微信下單
* @param req
* @return
* @throws Exception
*/
@RequestMapping(value="/paying",method=RequestMethod.POST)
@ResponseBody
public Map<String, String> paying(HttpServletRequest req) throws Exception{
String billNumber = req.getParameter("billNumber");
String openid = req.getParameter("openid");
//判斷商戶是否存在該訂單..............
Map<String, String> reqData = new HashMap<>();
//商戶APPid
reqData.put("appid", "商戶Appid");
//隨機字符串 必填-每次請求都要是新的隨機字符串
reqData.put("nonce_str", WXPayUtil.generateNonceStr());//WXPayUtil.generateNonceStr()
//商品描述 必填
reqData.put("body", ##);//"咖啡"
//商戶號 必填
reqData.put("mch_id", 商戶ID(mch_id));
//商品訂單號 必填
reqData.put("out_trade_no", order);
//支付金額(將元轉換爲分) 必填
reqData.put("total_fee", PayUtil.changeY2F(###));
//終端IP 不知道怎麼寫可以寫本地127.0.1 必填
reqData.put("spbill_create_ip", "127.0.0.1");//客戶端主機
//異步回調通知地址通知url必須爲外網可訪問的url,不能攜帶參數 必填
reqData.put("notify_url", ###); //"https://www.baidu.com/wxpay/notifying"
//交易類型JSAPI 必填
reqData.put("trade_type", "JSAPI");
//之前獲取的openid 必填
reqData.put("openid", openid);
//商品id
reqData.put("product_id", 按需);
//簽名加密方式,如果不是使用默認MD5就需要指定
reqData.put("sign_type", "HMAC-SHA256");
//生成HMAC-SHA256加密方式簽名的Xml,通過httpClient發送請求得到數據
String xmlParam = WXPayUtil.generateSignedXml(reqData, "API密鑰(paternerKey)",SignType.HMACSHA256);
//使用http工具類發送post請求,工具類自行找!
HttpClient httpClient=new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
httpClient.setHttps(true);
httpClient.setXmlParam(xmlParam);
httpClient.post();
//獲取內容
String content = httpClient.getContent();
//轉換內容爲map
Map<String, String> refund = WXPayUtil.xmlToMap(content);
Map<String,String> maps=new HashMap<>();
//成功下單的話 包裝前端支付需要的參數
if ("SUCCESS".equals(refund.get("return_code").toString())
) {
String timeStamp=String.valueOf(WXPayUtil.getCurrentTimestamp());
String appIds=myWXPayConfig.getAppID();
String nonceStrs=UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
String prepay_id=refund.get("prepay_id").toString();
String packages="prepay_id="+prepay_id;
String signType=SignType.MD5.toString();
//App的ID
maps.put("appId", ##);
//時間戳
maps.put("timeStamp", timeStamp);
//隨機字符串
maps.put("nonceStr", #);
//訂單詳情擴展字符串package是關鍵字加個S處理
maps.put("package", packages);
//簽名方式 默認爲MD5,支持HMAC-SHA256和MD5。注意此處需與統一下單的簽名類型一致
maps.put("signType", #);
/**
* 注:這裏採用 HMACSHA256進行簽名,
* 因爲預下單支付那裏默認採用HMACSHA256,要保持一致
*/
String paySign = WXPayUtil.generateSignature(maps,"API密鑰(Key)",SignType.HMACSHA256);
logger.info("paySign:"+paySign);
maps.put("return_code", "success");
maps.put("paySign", paySign);
return maps;