微信小程序支付
微信小程序中调用支付的方法
-
在微信小程序中,我们只需要简单的调用下面的这个方法
wx.requestPayment({ timeStamp: '', nonceStr: '', package: '', signType: 'MD5', paySign: '', success:function(res){ console.log(res); }, fail:function(res){ console.log(res); } });
参数说明:
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
timeStamp | String | 是 | 时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间 |
nonceStr | String | 是 | 随机字符串,长度为32个字符以下。 |
package | String | 是 | 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*** |
signType | String | 是 | 签名算法,暂支持 MD5 |
paySign | String | 是 | 签名,具体签名方案参见小程序支付接口文档; |
success | Function | 否 | 接口调用成功的回调函数 |
fail | Function | 否 | 接口调用失败的回调函数 |
complete | Function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
开发者服务器
-
在微信小程序中,需要注意有几个参数是需要我们在开发者的服务器提供的。
timeStamp、nonceStr、package、paySign
要想得到这个几个参数,我们需要费点脑子和心思,毕竟微信的开发文档远远不如阿里云的文档好用。
开发准备
-
打开pom.xml添加以下的dependency
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.3</version> </dependency>
- 编写一个HttpApiService.java 用于在服务器上发送http请求
package com.mmyhs.wechat.service; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.sun.org.apache.regexp.internal.RE; import org.springframework.stereotype.Service; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; @Service public class HttpAPIService { public static final String HTTP_METHOD_POST="POST"; public static final String HTTP_METHOD_GET="GET"; public String get(String url,String output){ return this.request(url,HTTP_METHOD_GET,output,5000,5000); } public String get(String url, String output, int connTimeout, int readTimeout){ return this.request(url,HTTP_METHOD_GET,output,connTimeout,readTimeout); } public String post(String url,String output){ return this.request(url,HTTP_METHOD_POST,output,5000,5000); } public String post(String url, String output, int connTimeout, int readTimeout){ return this.request(url,HTTP_METHOD_POST,output,connTimeout,readTimeout); } private String request(String url, String method, String output, int connTimeout, int readTimeout) { StringBuilder stringBuilder=null; OutputStream outputStream=null; InputStream inputStream=null; BufferedReader br=null; try{ URL url1=new URL(url); HttpURLConnection conn= (HttpURLConnection) url1.openConnection(); conn.setRequestMethod(method); conn.setConnectTimeout(connTimeout); conn.setReadTimeout(readTimeout); conn.setDoInput(true); conn.setDoOutput(true); conn.connect(); outputStream = conn.getOutputStream(); if(null!=output && !output.trim().equals("")){ outputStream.write(output.getBytes("utf-8")); } inputStream = conn.getInputStream(); br = new BufferedReader(new InputStreamReader(inputStream)); String temp=null; stringBuilder = new StringBuilder(); while((temp=br.readLine())!=null){ stringBuilder.append(temp); } }catch (Exception e){ e.printStackTrace(); }finally { try { if(null!=br){ br.close(); } if(null!=inputStream){ inputStream.close(); } if(null!=outputStream){ outputStream.close(); } }catch (Exception e){ e.printStackTrace(); } return stringBuilder==null ? null:stringBuilder.toString(); } } }
- 新建一个微信配置文件,包含一下的内容。
package com.mmyhs.wechat.config; public class WeChatConfig { // 小程序唯一标识 public static String APPID=""; // 小程序的 app secret public static String SECRET=""; // 商户号 public static String MCHID=""; // 商户密钥 public static final String MCH_KEY = ""; // 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 public static final String NOTIFY_URL=""; // 交易类型 public static final String TRADE_TYPE = "JSAPI"; // 支付密钥 public static final String PAY_KEY=""; }
- 通过wx.login()得到临时code,通过临时code得到session_key、openid
@RequestMapping(value = "/login") public Map<String, Object> login(String code, HttpSession session) throws Exception { Map<String, Object> map = new HashMap<>(); String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + WechatConfig.APPID + "&secret=" + WechatConfig.SECRET + "&js_code=" + code + "&grant_type=authorization_code"; String result = httpAPIService.doGet(url); JSONObject json = JSONObject.fromObject(result); Object sessionKey = json.get("session_key"); Object openid = json.get("openid"); Object errcode = json.get("errcode"); Object errmsg = json.get("errmsg"); if(null!=errcode){ map.put("code",errcode); map.put("errmsg",errmsg); }else{ session.setAttribute("sessionKey",sessionKey); map.put("code",200); map.put("openId",openid); map.put("sessionId",session.getId()); } System.out.println(result); return map; }
在上面的代码需要注意的地方
-
登录凭证校验
-
https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
-
请求参数
参数 必填 说明 appid 是 小程序唯一标识 secret 是 小程序的 app secret js_code 是 登录时获取的 code grant_type 是 填写为 authorization_code -
在不满足UnionID下发条件的情况下,返回参数
-
参数 说明 openid 用户唯一标识 session_key 会话密钥 -
在满足UnionID下发条件的情况下,返回参数、
-
参数 说明 openid 用户唯一标识 session_key 会话密钥 unionid 用户在开放平台的唯一标识符
-
统一下订单
文档地址:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1
统一下单实体类
public class UnifiedOrder {
private String appid; // 小程序ID
private String mchId; // 商户号
private String deviceInfo; // 设备号
private String nonceStr; // 随机字符串
private String sign; // 签名
private String signType; // 签名类型
private String body; // 商品描述
private String detaill; // 商品详情
private String attach; // 附加数据
private String outTradeNo; // 商户订单号
private String feeType; // 标价币种
private String totalFee; // 标价金额
private String spbillCreateIp; // 终端IP
private String timeStart; // 交易起始时间
private String timeExpire; // 交易结束时间
private String goodsTag; // 订单优惠标记
private String notifyUrl; // 通知地址
private String tradeType; // 交易类型
private String productId; // 商品ID
private String limitpay; // 指定支付方式
private String openid; // 用户标识
// 省略了get/set方法
}
在这里我们还需要一个微信支付一个帮助类-- WechatPayUtil.java
public class WechatPayUtil {
private static final String NONCE_STR="1234567890asdfghjklzxcvbnmqwertyuiopASDFGHJKLZXCVBNMQWERTYUIOP";
private static final char[] NONCE_STRS=NONCE_STR.toCharArray();
private static final SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyyMMddHHmmss");
public static StringBuffer mapToXmlStr(Map<String,Object> map){
StringBuffer xml = new StringBuffer();
xml.append("<xml>");
for(String key:map.keySet()){
Object value=map.get(key);
if("total_fee".equals(key)){
xml.append("<"+key+">"+value+"</"+key+">");
}else{
xml.append("<"+key+"><![CDATA["+value+"]]></"+key+">");
}
}
xml.append("</xml>");
return xml;
}
public static Map<String,Object> xmlToMap(String xmlStr){
Map<String,Object> map=null;
InputStream is=null;
try {
is= new ByteArrayInputStream(xmlStr.getBytes("utf-8"));
map=xmlToMap(is);
} catch (JDOMException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null!=is){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return map;
}
}
public static Map<String,Object> xmlToMap(InputStream isXml) throws JDOMException, IOException {
Map<String,Object> map=new HashMap<>();
SAXBuilder builder=new SAXBuilder();
Document document=builder.build(isXml);
Element element=document.getRootElement();
List<Element> elements=element.getContent();
for(Element e:elements){
String name=e.getName();
String value=e.getValue();
map.put(name,value);
}
return map;
}
public static String getOuTradeNo(){
String result=getNonceStr(6);
result+=simpleDateFormat.format(new Date());
return result;
}
public static String getNonceStr(){
return getNonceStr(32);
}
public static String getNonceStr(Integer length){
StringBuilder sb=new StringBuilder();
for(int i=0;i<length;i++){
int index=CommonUtil.randNumer(0,NONCE_STR.length()-1);
sb.append(NONCE_STRS[index]);
}
return sb.toString();
}
/**
* 签名字符串
* @param text 需要签名的字符串
* @param key 密钥
* @param input_charset 编码格式
* @return 签名结果
*/
public static String sign(String text, String key, String input_charset) {
text = text + "&key=" + key;
System.out.println(text);
return DigestUtils.md5Hex(getContentBytes(text, input_charset));
}
/**
* @param content
* @param charset
* @return
* @throws UnsupportedEncodingException
*/
public static byte[] getContentBytes(String content, String charset) {
if (charset == null || "".equals(charset)) {
return content.getBytes();
}
try {
return content.getBytes(charset);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
}
}
public static String createLinkString(Map<String,String> params){
List<String> keys=new ArrayList<String>(params.keySet());
Collections.sort(keys);
StringBuilder sb=new StringBuilder();
for(int i=0;i<keys.size();i++){
String key=keys.get(i);
String value=params.get(key);
if(keys.size()-1==i){
sb.append(key+"="+value);
}else{
sb.append(key+"="+value+"&");
}
}
return sb.toString();
}
/**
* 除去数组中的空值和签名参数
* @param map 签名参数组
* @return 去掉空值与签名参数后的新签名参数组
*/
public static Map<String,String> paraFilter(Map<String,String> map){
Map<String,String> tempMap=new HashMap<>();
if(null==map || map.size()<=0){
return map;
}
for(String key:map.keySet()){
String value=map.get(key).trim();
if(null==value || "".equals(value) || "sign".equals(value) || "sign_type".equals(value)){
continue;
}
tempMap.put(key,value);
}
return tempMap;
}
}
开发者服务器上的统一下订单接口
@RequestMapping("/unifiedOrder")
public HttpResult unifiedOrder(UnifiedOrder unifiedOrder, HttpServletRequest request) throws Exception {
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String nonceStr=WechatPayUtil.getNonceStr();
String outTradeNo=WechatPayUtil.getOuTradeNo();
String spbillCreateIp= IpUtil.getIpAddr(request);
Map<String, String> map = new HashMap<>();
map.put("appid",WechatConfig.APPID);
map.put("mch_id",WechatConfig.MCHID);
map.put("device_info","WEB");
map.put("nonce_str",nonceStr);
map.put("body",unifiedOrder.getBody());
map.put("out_trade_no",outTradeNo);
map.put("total_fee",unifiedOrder.getTotalFee());
map.put("spbill_create_ip",spbillCreateIp);
map.put("notify_url",WechatConfig.NOTIFY_URL);
map.put("trade_type",WechatConfig.TRADE_TYPE);
map.put("openid",unifiedOrder.getOpenid());
map=WechatPayUtil.paraFilter(map);
Map<String,Object> map2=new HashMap<>();
map2.putAll(map);
String prestr=WechatPayUtil.createLinkString(map);
String mysign=WechatPayUtil.sign(prestr,WechatConfig.MCH_KEY,"UTF-8").toUpperCase();
map2.put("sign",mysign);
WechatPayUtil.mapToXmlStr(map2);
System.out.println("======================第一次签名======================");
StringBuffer xml=WechatPayUtil.mapToXmlStr(map2);
System.out.println(xml.toString());
String result= httpAPIService.doPost(url,xml.toString());
Map<String,Object> resultMap=WechatPayUtil.xmlToMap(result);
Map<String,Object> returnMap=new HashMap<>();
if("SUCCESS".equals(resultMap.get("return_code")) && "SUCCESS".equals(resultMap.get("result_code"))){
Long timeStamp=System.currentTimeMillis()/1000;
returnMap.put("msg","success");
returnMap.put("timeStamp",timeStamp.toString());
returnMap.put("nonceStr",nonceStr);
returnMap.put("package","prepay_id="+resultMap.get("prepay_id"));
returnMap.put("signType","MD5");
System.out.println("======================第二次签名======================");
String stringSignTemp="appId="+WechatConfig.APPID+"&nonceStr="+nonceStr+"&package=prepay_id="+resultMap.get("prepay_id")+"&signType=MD5&timeStamp="+timeStamp.toString();
String paySign=WechatPayUtil.sign(stringSignTemp,WechatConfig.MCH_KEY,"UTF-8").toUpperCase();
returnMap.put("paySign",paySign);
}else{
returnMap.put("msg","error");
returnMap.put("return_msg",returnMap.get("return_msg"));
}
return new HttpResult(200,returnMap);
}
在开发者服务器上的 /unifiedOrder 接口中,需要注意的是签名的次数,一定是需要签名两次。
第一次的签名是为了将数据发送到微信服务器上进行数据的校验。
第二次的签名是为了提供给微信小程序进行微信支付的参数。