参考链接
准备事项
-
小程序appid
进入
-
小程序AppSecret
-
商户id
-
商户平台api密钥
-
后台服务器(听说可以使用云开发就不用准备服务器,但是我这边使用的不是云开发)
开发工具
eclipse+tomcat+微信开发者工具
使用框架
spring,spring-mvc
小程序代码
//获取用户openid
getWXOpenId: function () {
var that = this;
wx.login({
//获取code
success: function (res) {
var code = res.code; //返回code
var appId = '填入appid';
var secret = '填入sercret';
wx.request({
url: 'https://api.weixin.qq.com/sns/jscode2session?appid=' + appId + '&secret=' + secret + '&js_code=' + code + '&grant_type=authorization_code',
data: {},
header: {
'content-type': 'json'
},
success: function (res) {
var openid = res.data.openid //返回openid
that.setData({
openId: openid
});
that.buyGoods(openid);
}
})
}
});
},
//发送信息到后台
buyGoods: function (openId) {
var that = this;
var courseName = 0;
//后台服务地址
var address = "后台接口路径";
wx.request({
url: address,
data: {
openId: openId,
courseName: courseName
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
method: "POST",
success: function (res) {
console.log(res);
that.doWxPay(res.data);
},
fail: function (err) {
wx.showToast({
icon: "none",
title: '服务器异常,清稍候再试'
})
},
});
},
doWxPay(param) {
console.log(param);
//小程序发起微信支付
wx.requestPayment({
timeStamp: param.data.timeStamp,//记住,这边的timeStamp一定要是字符串类型的,不然会报错
nonceStr: param.data.nonceStr,
package: param.data.package,
signType: 'MD5',
paySign: param.data.paySign,
success: function (event) {
// success
console.log(event);
wx.showToast({
title: '支付成功',
icon: 'success',
duration: 2000
});
},
fail: function (error) {
// fail
console.log("支付失败")
console.log(error)
},
complete: function () {
// complete
console.log("pay complete")
}
});
},
pom.xml
有很多是用不到的,可以自己去掉
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jt</groupId>
<artifactId>pay</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.29</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-httpclient/commons-httpclient -->
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
</dependencies>
</project>
后台java代码
package com.jt.sys.controller;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.apache.commons.httpclient.HttpException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.DigestUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
import com.jt.common.vo.HttpRequest;
import com.jt.common.vo.HttpUtil;
import com.jt.common.vo.JsonResult;
@RequestMapping("/")
@Controller
public class PageController {
@RequestMapping("test")
public String test(){
return "test";
}
@RequestMapping("buyStepOne")
@ResponseBody
public JsonResult buyStepOne(String openId,String courseName) throws UnsupportedEncodingException {
String appId = "appid";
String mch_id = "商户id";
String notify_url = "支付成功后台回调地址";
String trade_type = "JSAPI";
//签名类型
String SIGNTYPE = "MD5";
//第一次发起支付请求的接口地址
String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//key为商户平台设置的密钥key
String WXKeHukey = "商户平台密钥";
//生成的随机字符串
String nonce_str = getRandomString(32);
//商品名称
String body = new String(courseName.getBytes("ISO-8859-1"),"UTF-8");
//本机的ip地址
String spbill_create_ip = "ip地址";
//商户订单号(先用时间代替)
Date date = new Date();
String orderNo = date.getTime()+"";
String money = "1";//支付金额,单位:分,这边需要转成字符串类型,否则后面的签名会失败
Map<String, String> packageParams = new HashMap<String, String>();
//小程序ID,微信分配的小程序ID
packageParams.put("appid", appId);
//商户号,微信支付分配的商户号
packageParams.put("mch_id", mch_id);
//随机字符串,长度要求在32位以内。
packageParams.put("nonce_str", nonce_str);
//商品简单描述
packageParams.put("body", body);
//商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一
packageParams.put("out_trade_no", orderNo);//商户订单号
//订单总金额,单位为分
packageParams.put("total_fee", money);
//终端IP,支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP
packageParams.put("spbill_create_ip", spbill_create_ip);
//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
packageParams.put("notify_url", notify_url);
//交易类型(JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付,不同trade_type决定了调起支付的方式,请根据支付产品正确上传)
packageParams.put("trade_type", trade_type);
//用户标识,trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。
packageParams.put("openid", openId);
// 除去数组中的空值和签名参数
packageParams = paraFilter(packageParams);
String prestr = createLinkString(packageParams); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
//MD5运算生成签名,这里是第一次签名,用于调用统一下单接口,key为商户平台设置的密钥key
String mysign = sign(prestr, WXKeHukey, "utf-8").toUpperCase();
System.out.println("=======================第一次签名:" + mysign + "=====================");
//拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去
String xml = "<xml version='1.0' encoding='gbk'>" + "<appid>" + appId + "</appid>"
+ "<body><![CDATA[" + body + "]]></body>"
+ "<mch_id>" + mch_id + "</mch_id>"
+ "<nonce_str>" + nonce_str + "</nonce_str>"
+ "<notify_url>" + notify_url + "</notify_url>"
+ "<openid>" + openId + "</openid>"
+ "<out_trade_no>" + orderNo + "</out_trade_no>"
+ "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>"
+ "<total_fee>" + money + "</total_fee>"
+ "<trade_type>" + trade_type + "</trade_type>"
+ "<sign>" + mysign + "</sign>"
+ "</xml>";
System.out.println("调试模式_统一下单接口 请求XML数据:" + xml);
//调用统一下单接口,并接受返回的结果
String result = httpRequest(pay_url, "POST", xml);
System.out.println("调试模式_统一下单接口 返回XML数据:" + result);
// 将解析结果存储在HashMap中
// Map map = doXMLParse(result);
// String return_code = (String) map.get("return_code");//返回状态码
//解析结果,将结果存在map中
Map map = doXMLToMap(result);
String return_code = (String) map.get("return_code");
//返回给移动端需要的参数
Map<String, Object> response = new HashMap<String, Object>();
if(return_code == "SUCCESS" || return_code.equals(return_code)){
// 业务结果
String prepay_id = (String) map.get("prepay_id");//返回的预付单信息
response.put("nonceStr", nonce_str);
response.put("package", "prepay_id=" + prepay_id);
Long timeStamp = System.currentTimeMillis() / 1000;
response.put("timeStamp", timeStamp + "");//这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误
String stringSignTemp = "appId=" + appId + "&nonceStr=" + nonce_str + "&package=prepay_id=" + prepay_id+ "&signType=" + SIGNTYPE + "&timeStamp=" + timeStamp;
//再次签名,这个签名用于小程序端调用wx.requesetPayment方法
String paySign = sign(stringSignTemp, WXKeHukey, "utf-8").toUpperCase();
System.out.println("=======================第二次签名:" + paySign + "=====================");
response.put("paySign", paySign);
response.put("appid", appId);
}
return new JsonResult(response);
}
/**
* 解析xml,取出其中的result_code,和prepay_id
*/
public HashMap<String ,String> doXMLToMap(String results) {
HashMap<String ,String> result = new HashMap<String ,String>();
String return_code = results.substring(results.indexOf("<return_code><![CDATA[")+22, results.indexOf("]]></return_code>"));
result.put("return_code", return_code);
if(return_code.equals("SUCCESS")) {
String prepay_id = results.substring(results.indexOf("<prepay_id><![CDATA[")+20, results.indexOf("]]></prepay_id>"));
result.put("prepay_id", prepay_id);
}
return result;
}
/**
*
* @param requestUrl请求地址
* @param requestMethod请求方法
* @param outputStr参数
*/
public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
// 创建SSLContext
StringBuffer buffer = null;
try {
URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(requestMethod);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.connect();
// 往服务器端写内容
if (null != outputStr) {
OutputStream os = conn.getOutputStream();
os.write(outputStr.getBytes("utf-8"));
os.close();
}
// 读取服务器端返回的内容
InputStream is = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
buffer = new StringBuffer();
String line = null;
while ((line = br.readLine()) != null) {
buffer.append(line);
}
br.close();
} catch (Exception e) {
e.printStackTrace();
}
return buffer.toString();
}
/**
* 除去数组中的空值和签名参数
*
* @param sArray 签名参数组
* @return 去掉空值与签名参数后的新签名参数组
*/
public static Map<String, String> paraFilter(Map<String, String> sArray) {
Map<String, String> result = new HashMap<String, String>();
if (sArray == null || sArray.size() <= 0) {
return result;
}
for (String key : sArray.keySet()) {
String value = sArray.get(key);
if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
|| key.equalsIgnoreCase("sign_type")) {
continue;
}
result.put(key, value);
}
return result;
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*
* @param params 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
*/
public static String createLinkString(Map<String, String> params) {
List<String> keys = new ArrayList<String>(params.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符
prestr = prestr + key + "=" + value;
} else {
prestr = prestr + key + "=" + value + "&";
}
}
return prestr;
}
/**
* 签名字符串
* @param text需要签名的字符串
* @param key 密钥
* @param input_charset编码格式
* @return 签名结果
*/
public static String sign(String text, String key, String input_charset) {
text = text + "&key=" + key;
return DigestUtils.md5DigestAsHex(getContentBytes(text, input_charset));
}
/**
* @param content
* @param charset
* @return
* @throws SignatureException
* @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);
}
}
/**
* 支付成功后回调方法
* @return
*/
@RequestMapping("buySteptwo")
@ResponseBody
public JsonResult buySteptwo() {
Map<String, Object> params = getParamsMap();
try {
//读取参数
InputStream inputStream ;
StringBuffer sb = new StringBuffer();
inputStream = getRequest().getInputStream();
String s ;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null){
sb.append(s);
}
in.close();
inputStream.close();
//解析xml成map
Map<String, String> m = new HashMap<String, String>();
m = XMLUtil.doXMLParse(sb.toString());
//过滤空 设置 TreeMap
SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();
Iterator<String> it = m.keySet().iterator();
while (it.hasNext()) {
String parameter = it.next();
String parameterValue = m.get(parameter);
String v = "";
if(null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
// 微信支付的API密钥
String key = WXKeHukey; // key
//判断签名是否正确
if(PayForUtil.isTenpaySign("UTF-8", packageParams,key)) {
//------------------------------
//处理业务开始
//------------------------------
String resXml = "";
if("SUCCESS".equals((String)packageParams.get("result_code"))){
System.out.println("+++++++++++++++++++回调参数+++++++++++++++++++");
System.out.println(packageParams);
// 这里是支付成功
//执行自己的业务逻辑开始
String app_id = (String)packageParams.get("appid");
String mch_id = (String)packageParams.get("mch_id");
String openid = (String)packageParams.get("openid");
String is_subscribe = (String)packageParams.get("is_subscribe");//是否关注公众号
//公用回传参数
String attach = (String)packageParams.get("attach");
//商户订单号
String out_trade_no = ((String)packageParams.get("out_trade_no"));
out_trade_no = out_trade_no.replace(" ", "");
//付款金额【以分为单位】
String total_fee = (String)packageParams.get("total_fee");
//微信生成的交易订单号
String transaction_id = (String)packageParams.get("transaction_id");//微信支付订单号
//支付完成时间
String time_end=(String)packageParams.get("time_end");
// //回调一次后进行判断,查询是否以及添加记录到数据库
// Record huidiao = Db.findById("c_order" , "order_number", transaction_id);
// if(huidiao != null) {
// redirect("/");
// return;
// }
//通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
/**
* 支付成功后的处理
*/
Record order =
Db.findFirst("select * from c_order where order_number = '"+out_trade_no+"'");
System.out.println("+++++++++++++++++++++++++sql+++++++++++++++++++++++++");
System.out.println("select * from c_order where order_number = '"+out_trade_no+"'");
System.out.println(order);
//Record order = Db.findById("c_order" , "order_number", out_trade_no);
//付款金额
order.set("order_money" ,total_fee);
order.set("order_flag", 1 );
order.set("order_time", new Date());
Db.update("c_order", "order_id",order);
//TODO 支付成功后去哪个页面 我这里先写首页
rendSuccessJson();
return;
} else {
lg.info("支付失败,错误信息:" + packageParams.get("err_code"));
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
//TODO 支付失败要做什么 我这里先不写
//------------------------------
//处理业务完毕
//------------------------------
BufferedOutputStream out = new BufferedOutputStream(
getResponse().getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
}
} else{
lg.info("通知签名验证失败");
}
}catch(IOException e) {
e.printStackTrace();
rendFailedJson("IO处理失败,请联系管理员!");
} catch (JDOMException e) {
e.printStackTrace();
rendFailedJson("JDOM处理失败,请联系管理员!");
}
}
//length用户要求产生字符串的长度
public static String getRandomString(int length){
String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random=new Random();
StringBuffer sb=new StringBuffer();
for(int i=0;i<length;i++){
int number=random.nextInt(62);
sb.append(str.charAt(number));
}
return sb.toString();
}
}
用到的外部jar包和工具类
https://download.csdn.net/download/qq_40488121/11704169
补充说明
上线阶段出现了问题,因为微信不希望开发者将appid和密钥存储在前台,所以直接调用微信的接口获取appid是不行的,只能通过后台来获取,这样的话应该是前台一步到位发一次请求就好了,但是我比较懒,就直接把请求微信的接口直接改到后台,直接后台获取openid后放回,其他都不用变
public void getOpenId() {
Map<String, Object> params = getParamsMap();
String code = params.get("code").toString();
String secret = "ede5a99e35a0c99a1ea61df19a1aeb72";
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + secret + "&js_code=" + code + "&grant_type=authorization_code";
String openid = HttpUtil.doGet(url);
renderText(openid);
}