背景:前端使用HBuilder打包h5,后端使用java.
首先在微信开放平台注册一个移动应用:https://open.weixin.qq.com/cgi-bin/index?t=home/index&lang=zh_CN
对创建的应用进行一些功能上的申请:由于我使用的是公司账号,通过绑定公司的商户号获取微信支付功能.
如图微信支付功能为已获得状态的时候,就可以继续进行下一步操作了.
微信开放平台的包名和签名有获取的规则,不过多进行叙述.
由于使用HBuilder打包h5,我们先看一下微信的统一下单:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
按照开放平台给的文档的规则请求:https://api.mch.weixin.qq.com/pay/unifiedorder
得到返回的订单信息:
注意点:
对统一下单接口进行请求之后返回的sign签名并不是调用支付接口需要的签名.只是生成的签名方式要保持一次.
也就是说,统一下单我们需要生成一次sign,调用支付接口还需要生成一次sign.
由于package是java的关键字,
"package","Sign=WXPay"
只能进行前端拼接,但是后端生成sign的时候还要添加进去.
到这里后端的一些处理已经基本完成.
下面是前端的一些问题:
我们进入HBuilder官网,找到支付插件配置页面:https://ask.dcloud.net.cn/article/71
以下是HBuilder给的前端实例代码:
var channel=null;
// 1. 获取支付通道
function plusReady(){ //uni-app中将此function里的代码放入vue页面的onLoad生命周期中
// 获取支付通道
plus.payment.getChannels(function(channels){
channel=channels[0];
},function(e){
alert("获取支付通道失败:"+e.message);
});
}
document.addEventListener('plusready',plusReady,false);//uni-app不需要此代码
var ALIPAYSERVER='http://demo.dcloud.net.cn/helloh5/payment/alipay.php?total=';
var WXPAYSERVER='http://demo.dcloud.net.cn/helloh5/payment/wxpay.php?total=';
// 2. 发起支付请求
function pay(id){
// 从服务器请求支付订单
var PAYSERVER='';
if(id=='alipay'){
PAYSERVER=ALIPAYSERVER;
}else if(id=='wxpay'){
PAYSERVER=WXPAYSERVER;
}else{
plus.nativeUI.alert("不支持此支付通道!",null,"捐赠");
return;
}
var xhr=new XMLHttpRequest(); //uni-app中请使用uni的request api联网
xhr.onreadystatechange=function(){
switch(xhr.readyState){
case 4:
if(xhr.status==200){
plus.payment.request(channel,xhr.responseText,function(result){
plus.nativeUI.alert("支付成功!",function(){
back();
});
},function(error){
plus.nativeUI.alert("支付失败:" + error.code);
});
}else{
alert("获取订单信息失败!");
}
break;
default:
break;
}
}
xhr.open('GET',PAYSERVER);
xhr.send();
}
然后按下面的配置操作一番,进行打包Android版本,进行真机测试.
这时候我们发现,可以进行支付宝测试,但是无法唤起微信支付.
下面是对HBuilder前端进行的一些修改:
<html>
<head>
<meta charset="utf-8">
<title>Hello MUI</title>
<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!--标准mui.css-->
<link rel="stylesheet" href="css/mui.min.css">
<link rel="stylesheet" href="css/mui.css" />
<!--App自定义的css-->
</head>
<body>
<div class="mui-content">
<div class="top" id="testLogin">
<input type="button" value="微信支付" class="weixin" id="weixin" />
<input type="button" value="支付宝支付" class="zhifubao" id="zhifubao" />
</div>
</div>
</body>
<script src="js/mui.min.js"></script>
<script>
var wxChannel = null; // 微信支付
var aliChannel = null; //支付宝支付
// 支付宝支付
//启用右滑关闭功能
var channel = null;
mui.init({
swipeBack: true
});
mui.plusReady(function() {
// 获取支付通道
plus.payment.getChannels(function(channels) {
for(var i in channels) {
if(channels[i].id == "wxpay") {
wxChannel = channels[i];
}
if(channels[i].id == "alipay") {
aliChannel = channels[i];
}
}
},
function(e) {
alert("获取支付通道失败:" + e.message);
});
})
document.getElementById('weixin').addEventListener('tap',
function() {
console.log("微信");
pay('wxpay');
})
document.getElementById('zhifubao').addEventListener('tap',
function() {
console.log("zhifubao");
pay('alipay');
})
var ALIPAYSERVER = 'http://192.168.3.3:9999/users/ALIPay?totalAmount=0.01&subject=兔子';
var WXPAYSERVER = 'http://192.168.3.3:9999/users/WXPay?body=兔子&fee=1';
// 2. 发起支付请求
function pay(id) {
//支付宝支付
if(id == "alipay") {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
switch(xhr.readyState) {
case 4:
if(xhr.status == 200) {
alert(xhr.responseText + "这是支付宝需要的参数类型");
plus.payment.request(aliChannel, xhr.responseText, function(result) {
plus.nativeUI.alert("支付成功!", function() {
back();
});
}, function(error) {
plus.nativeUI.alert("支付失败:" + error.code);
});
} else {
alert("获取订单信息失败!");
}
break;
default:
break;
}
}
xhr.open('GET', ALIPAYSERVER);
xhr.send();
} else {
//微信支付
// 从服务器请求支付订单
var PAYSERVER = '';
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
switch(xhr.readyState) {
case 4:
if(xhr.status == 200) {
var jsonStr = JSON.parse(xhr.responseText);
var myData = {
retcode: 0, //5+必备参数
retmsg: "ok", //5+必备参数
appid: jsonStr.data.appid,
noncestr: jsonStr.data.noncestr,
package: "Sign=WXPay",
partnerid: jsonStr.data.partnerid,
prepayid: jsonStr.data.prepayid,
timestamp: jsonStr.data.timestamp,
sign: jsonStr.data.sign
}
plus.payment.request(wxChannel, myData, function(result) {
plus.nativeUI.alert("支付成功!", function() {
back();
});
}, function(error) {
plus.nativeUI.alert("支付失败:" + error.code);
});
} else {
alert("获取订单信息失败!");
}
break;
default:
break;
}
}
xhr.open('GET', WXPAYSERVER);
xhr.send();
}
}
</script>
</html>
这样就能唤醒微信支付,同时完成付款了.
HBuilder打包的时候使用自有证书,同时保证签名与微信开放平台一致.
此处Appid与微信开放平台生成的一致:
HBuilder打包h5微信支付,最主要的问题就是HBuilder官网给的前端数据是有问题的.
下面是一些后端使用到的工具类;
生成sign签名:
package com.jixiu.user.utils;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.util.Lists;
import java.security.MessageDigest;
import java.util.*;
public class EncodeSign {
/**
* sign 签名 (参数名按ASCII码从小到大排序(字典序)+key+MD5+转大写签名)
* @param map
* @return
*/
public static String encodeSign(SortedMap<String,String> map, String key){
if(StringUtils.isEmpty(key)){
throw new RuntimeException("签名key不能为空");
}
Set<Map.Entry<String, String>> entries = map.entrySet();
Iterator<Map.Entry<String, String>> iterator = entries.iterator();
List<String> values = Lists.newArrayList();
while(iterator.hasNext()){
Map.Entry entry = (Map.Entry) iterator.next();
String k = String.valueOf(entry.getKey());
String v = String.valueOf(entry.getValue());
if (StringUtils.isNotEmpty(v) && entry.getValue() !=null && !"sign".equals(k) && !"key".equals(k)) {
values.add(k + "=" + v);
}
}
values.add("key="+ key);
String sign = StringUtils.join(values, "&");
System.out.println(sign+"计算之前的值");
return MD5.MD5Encode(sign,"utf8").toUpperCase();
}
}
调起支付接口需要的参数封装的实体类:
package com.jixiu.user.pojo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
@Data
@Component
@ConfigurationProperties(prefix = "weixin")
@XmlAccessorType(XmlAccessType.FIELD)
//xml文件根标识
@XmlRootElement(name = "xml")
//控制JAXB绑定类中属性和字段的排序
@XmlType(propOrder = {
"appid",
"mch_id",
"nonce_str",
"body",
"out_trade_no",
"total_fee",
"spbill_create_ip",
"notify_url",
"trade_type",
"sign",
"sign_type"
})
public class PlaceAnOrder {
//应用id
private String appid;
//商户号
private String mch_id;
//随机字符串
private String nonce_str;
//商品描述
private String body;
//商户订单号
private String out_trade_no;
//总金额
private String total_fee;
//终端ip
private String spbill_create_ip;
//通知地址
private String notify_url;
//交易类型
private String trade_type;
//签名
private String sign;
//签名类型
private String sign_type;
}
对象转XML字符串:
package com.jixiu.user.utils;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
/**
* 封装了XML转换成object,object转换成XML的代码
*
* @author Steven
*
*/
public class XMLUtil {
/**
* 将对象直接转换成String类型的 XML输出
*
* @param obj
* @return
*/
public static String convertToXml(Object obj) {
// 创建输出流
StringWriter sw = new StringWriter();
try {
// 利用jdk中自带的转换类实现
JAXBContext context = JAXBContext.newInstance(obj.getClass());
Marshaller marshaller = context.createMarshaller();
// 格式化xml输出的格式
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
Boolean.TRUE);
// 将对象转换成输出流形式的xml
marshaller.marshal(obj, sw);
} catch (JAXBException e) {
e.printStackTrace();
}
return sw.toString();
}
/**
* 将对象根据路径转换成xml文件
*
* @param obj
* @param path
* @return
*/
public static void convertToXml(Object obj, String path) {
try {
// 利用jdk中自带的转换类实现
JAXBContext context = JAXBContext.newInstance(obj.getClass());
Marshaller marshaller = context.createMarshaller();
// 格式化xml输出的格式
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
Boolean.TRUE);
// 将对象转换成输出流形式的xml
// 创建输出流
FileWriter fw = null;
try {
fw = new FileWriter(path);
} catch (IOException e) {
e.printStackTrace();
}
marshaller.marshal(obj, fw);
} catch (JAXBException e) {
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
/**
* 将String类型的xml转换成对象
*/
public static Object convertXmlStrToObject(Class clazz, String xmlStr) {
Object xmlObject = null;
try {
JAXBContext context = JAXBContext.newInstance(clazz);
// 进行将Xml转成对象的核心接口
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader sr = new StringReader(xmlStr);
xmlObject = unmarshaller.unmarshal(sr);
} catch (JAXBException e) {
e.printStackTrace();
}
return xmlObject;
}
@SuppressWarnings("unchecked")
/**
* 将file类型的xml转换成对象
*/
public static Object convertXmlFileToObject(Class clazz, String xmlPath) {
Object xmlObject = null;
try {
JAXBContext context = JAXBContext.newInstance(clazz);
Unmarshaller unmarshaller = context.createUnmarshaller();
FileReader fr = null;
try {
fr = new FileReader(xmlPath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
xmlObject = unmarshaller.unmarshal(fr);
} catch (JAXBException e) {
e.printStackTrace();
}
return xmlObject;
}
}
java发送post请求:
public static String sendPost(String url, Map<String,Object> param) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "application/json");
//设置请求类型,注意第三方文档接口的要求,一定要保持一致
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
JSONObject jsonObject = new JSONObject(param);
//配置
jsonObject.put("grant_type", "client_credentials");
jsonObject.put("client_id", "YXA6_9BOlrnhSWSb-FUk0fvzqw");
jsonObject.put("client_secret", "YXA6FIe8F_euvzf-NosJ7nXCx-MMosk");
out.print(jsonObject);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
结束.