最近的項目中要用到移動支付。在此總結下
1、微信支付 :因為需求是掃碼支付即時到賬,用的是native方式。按照官方文檔的說法,調用“https://api.mch.weixin.qq.com/pay/unifiedorder” 這個接口生成一個預支付的訂單,微信會傳回給你一個訂單的二維碼的位址,然後把二維碼解析展示出來使用者掃碼就可以支付了。
//微信支付
String random = RandomStringGenerator.getRandomStringByLength(32);
Map<String,Object> params = new HashMap<String,Object>();
params.put("appid", TenConfig.getAppid());
params.put("mch_id", TenConfig.getMchid());
params.put("nonce_str", random);
params.put("body", body.toString().trim());
params.put("out_trade_no", order.getOrderNumber());
params.put("total_fee", sump);
params.put("spbill_create_ip", TenConfig.SPBILL_CREATE_IP);
params.put("notify_url", TenConfig.getNotify_url());
params.put("trade_type", "NATIVE");
params.put("product_id", productId);
String sign = TenUtils.getSign(params);
//拼接xml
StringBuilder xml = new StringBuilder("<xml>");
xml.append("<appid>").append("<![CDATA["+TenConfig.getAppid()+"]]>").append("</appid>");
xml.append("<mch_id>").append("<![CDATA["+TenConfig.getMchid()+"]]>").append("</mch_id>");
xml.append("<nonce_str>").append("<![CDATA["+random+"]]>").append("</nonce_str>");
xml.append("<body>").append("<![CDATA["+body.toString().trim()+"]]>").append("</body>");
xml.append("<out_trade_no>").append("<![CDATA["+order.getOrderNumber()+"]]>").append("</out_trade_no>");
xml.append("<total_fee>").append("<![CDATA["+sump+"]]>").append("</total_fee>");
xml.append("<spbill_create_ip>").append("<![CDATA["+TenConfig.SPBILL_CREATE_IP+"]]>").append("</spbill_create_ip>");
xml.append("<notify_url>").append("<![CDATA["+TenConfig.getNotify_url()+"]]>").append("</notify_url>");
xml.append("<trade_type>").append("<![CDATA[NATIVE]]>").append("</trade_type>");
xml.append("<product_id>").append("<![CDATA["+productId+"]]>").append("</product_id>");
xml.append("<sign>").append("<![CDATA["+sign+"]]>").append("</sign>");
xml.append("</xml>");<pre name="code" class="java"> logger.info("XML:\n"+xml.toString());
String resStr = TenUtils.postData(TenConfig.UNIFIED_ORDER, xml.toString(), "text/html;charset=utf-8");
這一步主要是拼接預下單接口的參數,要注意的是請求的要用utf-8編碼下,不然可能會出現簽名錯誤之類的傳回。
把MD5簽名算法也貼上 在這裡被編碼坑了。
ArrayList<String> list = new ArrayList<String>();
for(Map.Entry<String,Object> entry:map.entrySet()){
if(entry.getValue()!=""){
list.add(entry.getKey() + "=" + entry.getValue() + "&");
}
}
int size = list.size();
String [] arrayToSort = list.toArray(new String[size]);
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
StringBuilder sb = new StringBuilder();
for(int i = 0; i < size; i ++) {
sb.append(arrayToSort[i]);
}
String result = sb.toString();
result += "key=" + TenConfig.getKey();
result = MD5Encrypt.MD5Encode(result, "utf-8").toUpperCase();
return result;
MD5簽名的時候切記要用“utf-8”編碼,不然會出現簽名錯誤,這是個深坑,接口文檔是沒有說明的,
貼上MD5算法
public static String md5(String s)
{
byte[] input = s.getBytes();
String output = null;
char[] hexChar = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f' };
try
{
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(input);
byte[] tmp = md.digest();
char[] str = new char[32];
byte b = 0;
for (int i = 0; i < 16; i++)
{
b = tmp[i];
str[(2 * i)] = hexChar[(b >>> 4 & 0xF)];
str[(2 * i + 1)] = hexChar[(b & 0xF)];
}
output = new String(str);
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
return output;
}
private static String byteArrayToHexString(byte[] b)
{
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++) {
resultSb.append(byteToHexString(b[i]));
}
return resultSb.toString();
}
private static String byteToHexString(byte b)
{
int n = b;
if (n < 0) {
n += 256;
}
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
public static String MD5Encode(String origin, String charsetname)
{
String resultString = null;
try
{
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if ((charsetname == null) || ("".equals(charsetname))) {
resultString = byteArrayToHexString(md.digest(resultString
.getBytes()));
} else {
resultString = byteArrayToHexString(md.digest(resultString
.getBytes(charsetname)));
}
}
catch (Exception localException) {}
return resultString;
}
private static final String[] hexDigits = { "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
然後建立連接配接去請求微信接口
BufferedReader reader = null;
try {
URL url = new URL(urlStr);
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(CONNECT_TIMEOUT);
if(contentType!=null && !"".equals(contentType))
conn.setRequestProperty("content-type", contentType);
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), DEFAULT_ENCODING);
if(data == null)
data = "";
writer.write(data);
writer.flush();
writer.close();
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), DEFAULT_ENCODING));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append("\r\n");
}
return sb.toString();
} catch (IOException e) {
logger.error("Error connecting to " + urlStr + ": " + e.getMessage());
} finally {
try {
if (reader != null)
reader.close();
} catch (IOException e) {
}
}
return null;
調用之後會拿到一個xml 裡面是傳回值。解析xml
Map resMap = XMLParser.getMapFromXML(resStr);
if(resMap == null){
logger.info("統一下單接口調用失敗");
}else{
String returnCode = (String) resMap.get("return_code");
if(returnCode.equals("SUCCESS")){
logger.info("統一下單接口調用成功");
String codeUrl = (String) resMap.get("code_url");
//把二維碼連接配接傳回給頁面
result.put("msg", codeUrl);
//更新訂單的支付方式,金額等
order.setPayAmount(Float.parseFloat(sumprice));
order.setPayType(payType);
order.setWxPayNonceStr(random);
order.setBody(body.toString());
if(orderService.updateWhenPay(order) > 0){
result.put("code", "ok");
}else{
result.put("code", "err");
}
}
解析xml把生成的二維碼給頁面 通過jQuery的qrcode.js把位址生成二維碼。
<span style="white-space:pre"> </span>$.post("/xxx/xxxxx.jspx",{"orderId":orderId
,"payType":payType,"sumPrice":price},
function(data){
if(data&&data.code=="ok"){
if(payType==1){
//微信支付
$("#model_content").css("display","block");
jQuery('#qrcodeCanvas').qrcode({
render : "canvas",//設定渲染方式
width : 210, //設定寬度
height : 210, //設定高度
typeNumber : -1, //計算模式
correctLevel : QRErrorCorrectLevel.H,//糾錯等級
background : "#ffffff",//背景顔色
foreground : "#000000", //前景顔色
text : data.msg
});
intel =setInterval(function(){
//請求查詢訂單狀态
queryOrderStatus(orderId,intel);
},2000);
}else if(payType==2){
//支付寶
$("#ali_pay").html(data.msg);
}
}else{
window.wxc.xcConfirm("支付失敗!", window.wxc.xcConfirm.typeEnum.error);
}
});
用qrcode.js的生成二維碼的時候 render 建議用canvas方式 雖然它還提供了table方式,但是我用的時候table方式無法生成二維碼的但是canvas是可以的 ,沒弄懂為什麼。如果順利的話 二維碼就生成了,掃碼就可以支付了。解釋下這段js代碼,請求成功後用一個遮罩層,把二維碼放遮罩層上,然後用一個函數去輪詢這個訂單的狀态,因為調用時異步的嗎,如果查詢到訂單已經支付了的話,清除掉輪詢并就跳轉到支付完成頁面。
下面貼微信回調的代碼
try {
//擷取微信回調的流
BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream)request.getInputStream()));
<span style="white-space:pre"> </span> String line = null;
<span style="white-space:pre"> </span>StringBuilder sb = new StringBuilder();
<span style="white-space:pre"> </span> while((line = br.readLine())!=null){
sb.append(line);
}
//把微信傳回的xml轉換成map
Map result = XMLParser.getMapFromXML(sb.toString());
//傳回給微信的xml
StringBuilder reXML = new StringBuilder("<xml>");
//return_code 字段是通信辨別,非交易辨別,交易是否成功需要檢視result_code來判斷
if(result.get("return_code").toString().equals("SUCCESS")){
//回調成功
logger.info("微信支付成功,傳回的XML:\n"+sb.toString());
//擷取微信回調的參數
String orderNum = (String) result.get("out_trade_no");
//業務結果
String resultCode = (String) result.get("result_code");
String transactionId = result.get("transaction_id").toString();
String sign = result.get("sign").toString();
if(result.get("sign")!=null){
//去除Sign
result.remove("sign");
}
if(sign.equals(TenUtils.getSign(result))){
//簽名通過,判斷交易狀态
if(resultCode.equals("SUCCESS")){
if(orderNum !=null && !"".equals(orderNum)){
Order order = orderService.findByOrderNum(orderNum);
if(order != null){
//支付成功
if(order.getStatus() == 0){
//訂單如果是待支付狀态,更改訂單狀态為确認中狀态(1),更新支付完成時間
orderService.updateStatus(order.getId(), 1);
orderService.updatePayCompleteTime(order.getId());
reXML.append("<return_code><![CDATA[SUCCESS]]></return_code>");
reXML.append("<return_msg><![CDATA[OK]]></return_msg>");
//插入支付日志
Map<String,String> map = new HashMap<String,String>();
map.put("pro_type", "銷售");
map.put("pay_type", "微信");
map.put("pro_info", order.getBody());
map.put("user_name", order.getBuyMember().getUsername());
map.put("trade_no", orderNum);
map.put("pay_amount", order.getPayAmount()+"");
map.put("status", "支付完成");
map.put("buyer_email", transactionId);
orderService.insertPayLog(map);
}else{
reXML.append("<return_code><![CDATA[FAIL]]></return_code>");
reXML.append("<return_msg><![CDATA[訂單狀态異常]]></return_msg>");
}
}else{
reXML.append("<return_code><![CDATA[FAIL]]></return_code>");
reXML.append("<return_msg><![CDATA[參數[out_trade_no]錯誤]]></return_msg>");
}
}else{
reXML.append("<return_code><![CDATA[FAIL]]></return_code>");
reXML.append("<return_msg><![CDATA[參數[out_trade_no]為空]]></return_msg>");
}
}else{
<span style="white-space:pre"> </span>logger.info("交易狀态異常:"+resultCode);
}
}else{
reXML.append("<return_code><![CDATA[FAIL]]></return_code>");
reXML.append("<return_msg><![CDATA[簽名失敗]]></return_msg>");
}
}else{
logger.info("微信支付回調失敗,失敗原因:"+result.get("return_msg").toString());
}
<span style="white-space:pre"> </span>reXML.append("</xml>");
<span style="white-space:pre"> </span>logger.info("傳回給微信的XML:\n"+reXML);
<span style="white-space:pre"> </span>response.getWriter().write(new String(reXML.toString().getBytes("UTF-8"),"ISO-8859-1"));
} catch (Exception e) {
<span style="white-space:pre"> </span>logger.info(e.getMessage(), e);
}
支付成功後微信會調用這個方法,這個方法主要用于修改訂單狀态。當訂單狀态改變了,js的queryOrderStatus方法檢測到訂單支付完成了,就會跳轉到支付完成頁面。就完成了。把js輪訓方法也貼上
//查詢訂單狀态
function queryOrderStatus(orderId,intel){
$.post("/xxx/xxxx.jspx",{"orderId":orderId},
function(data){
if(data && data.orderId == orderId){
//查詢成功
if(data.code=="ok"){
//訂單查詢成功
if(data.status==1){
//支付成功,清除輪訓并重新整理頁面
clearInterval(intel);
window.location.href = "/xxxx/xxxx.jspx?orderId="+orderId+"&guid="+guid();
}
}
}
});
}
醬紫微信支付就完成了。
下面說說支付寶支付,因為支付官方提供的有demo 拿過來改改就能直接用了,這個比微信友善多了。直接貼代碼
//支付寶支付
//把請求參數打包成數組
Map<String, String> params = new HashMap<String, String>();
params.put("service", AlipayConfig.service);
params.put("partner", AlipayConfig.partner);
params.put("seller_id", AlipayConfig.seller_id);
params.put("_input_charset", AlipayConfig.input_charset);
//params.put("qr_pay_mode", AlipayConfig.qr_pay_mode);
//異步通知支付結果連接配接
//params.put("notify_url", AlipayConfig.notify_url);
//支付寶處理完請求後,自動跳轉到指定頁面
params.put("return_url", AlipayConfig.return_url);
//傻逼支付寶“anti_phishing_key”同步回調不傳回這個參數,請求的時候不傳遞
//params.put("anti_phishing_key", AlipaySubmit.query_timestamp());
//支付使用者的ip
params.put("exter_invoke_ip", AlipayConfig.exter_invoke_ip);
params.put("out_trade_no", order.getOrderNumber());
params.put("subject", body.toString());
params.put("payment_type", AlipayConfig.payment_type);
params.put("it_b_pay", AlipayConfig.it_b_pay);
params.put("total_fee", sumprice);
params.put("body", body.toString());
//請求支付接口
html = AlipaySubmit.buildRequest(params, "POST","确認");
logger.info("支付寶請求HTML:\n"+html);
//更新訂單的支付方式,金額等
order.setPayAmount(Float.parseFloat(sumprice));
order.setPayType(payType);
if(orderService.updatePay(order) > 0){
result.put("code", "ok");
}else{
result.put("code", "err");
}
result.put("msg", html);
我這裡用的是同步回調方式,當然也可以用異步和微信是一樣的,支付寶是拼接一個自動送出的form表單,然後把這歌表單寫到頁面就能跳轉到支付寶收銀台,代碼在上面微信生成二維碼那裡,拿下來
//支付寶
$("#ali_pay").html(data.msg);
這樣子表單會自動送出,頁面會跳轉到支付寶收銀台,完成支付後會從支付寶那裡跳轉 需要一個處理的方法,貼代碼
try {
//擷取支付寶POST過來回報資訊
Map<String,String> params = new HashMap<String,String>();
Map requestParams = request.getParameterMap();
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] + ",";
}
//亂碼解決,這段代碼在出現亂碼時使用。如果mysign和sign不相等也可以使用這段代碼轉化
//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "UTF-8");
params.put(name, valueStr);
}
//商戶訂單号
String out_trade_no = request.getParameter("out_trade_no");
//支付寶交易号
String trade_no = request.getParameter("trade_no");
//交易狀态
String trade_status = request.getParameter("trade_status");
//支付價格
String total_fee = request.getParameter("total_fee");
String seller_id = request.getParameter("seller_id");
//買家支付寶賬号
String buyer_email = request.getParameter("buyer_email");
//産品描述
String body = request.getParameter("body");
Float total_pay = Float.parseFloat(total_fee);
boolean verify_result = AlipayNotify.verify(params);
if(verify_result){//驗證成功
Order o = orderService.findByOrderNum(out_trade_no);
if(o != null){
//請務必判斷請求時的total_fee、seller_id與通知時擷取的total_fee、seller_id為一緻的
if(o.getPayAmount() == total_pay && AlipayConfig.seller_id.equals(seller_id)){
//請求參數和回調參數一緻
if(trade_status.equals("TRADE_FINISHED") || trade_status.equals("TRADE_SUCCESS")){
//注意:付款完成後,支付寶系統發送該交易狀态通知
//判斷該筆訂單是否是待付款狀态
if(o.getStatus() == 0){
//如果沒有做過處理,更改訂單狀态為确認中狀态(1),更新支付完成時間
orderService.updateStatus(o.getId(), 1);
orderService.updatePayCompleteTime(o.getId());
//插入支付日志
Map<String,String> map = new HashMap<String,String>();
map.put("pro_type", "銷售");
map.put("pay_type", "支付寶");
map.put("pro_info", body);
map.put("user_name", o.getBuyMember().getUsername());
map.put("trade_no", trade_no);
map.put("pay_amount", o.getPayAmount()+"");
map.put("status", "支付完成");
map.put("buyer_email", buyer_email);
orderService.insertPayLog(map);
//跳轉到支付成功頁面
return "redirect:/xxx/xxxx.jspx?orderId="+o.getId();
}
}
}
}
}else{//驗證失敗
response.setContentType("text/html;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.getWriter().write("Sign Failure , 驗簽失敗!");
}
} catch (Exception e) {
logger.info(e.getMessage(), e);
}
這個地方要注意 支付寶給的demo 是這樣寫的
<span style="white-space:pre"> </span>亂碼解決,這段代碼在出現亂碼時使用。如果mysign和sign不相等也可以使用這段代碼轉化
<span style="white-space:pre"> </span>valueStr = new String(valueStr.getBytes("ISO-8859-1"), "UTF-8");
這裡千萬不要一上來就用“UTF-8” 不然會亂碼 然後就會出現簽名錯誤的問題。至此微信和支付寶支付都做成功了。