01授权登录
说到微信小程序,不可避免会触及到微信账号的授权登录
首先需要自己的一个小程序,可以到微信公众平台注册一个自己的小程序:
https://mp.weixin.qq.com/wxopen/waregister?action=step1
注册后会获取到一个APPID和SECRET两个参数,这两个要好好保存。
微信官方文档:
https://developers.weixin.qq.com/miniprogram/dev/api/
说明:首先需要小程序端触发 wx.login方法拿到code,将code传到后台服务器,后台服务器结合小程序的appid和appsecret去请求微信接口服务器,微信接口服务器返回openid和session_key给后台服务器,然后进行自己的业务办理。
/**
* 微信授权时录入用户数据
*/
@RequestMapping(value = "wxLogin")
@ResponseBody
@Transactional
public Map<String,Object> insert(@RequestParam("code")String code, HttpSession session) throws ClientProtocolException, IOException {
//返回map
System.err.println("微信授权登录");
System.err.println("code值: "+code);
Map<String,Object> resultMap = new HashMap<>();
String appid = WXPayConstants.APP_ID; //自己的APPID
String secret = WXPayConstants.SECRET; //自己小程序的SECRET
String loginUrl="https://api.weixin.qq.com/sns/jscode2session?appid="+appid+"&secret="+secret+"&js_code="+code+"&grant_type=authorization_code";
try {
CloseableHttpClient client = null;
CloseableHttpResponse response = null;
try {
// 创建http GET请求
HttpGet httpGet = new HttpGet(loginUrl);
client = HttpClients.createDefault();
// 执行请求
response = client.execute(httpGet);
HttpEntity entity = response.getEntity();//得到返回数据
String result = EntityUtils.toString(entity);
System.err.println("微信返回的结果"+result);
resultMap.put("data", result);//进行封装
System.err.println(resultMap);
JSONObject json_test = JSONObject.parseObject(result);
String wxOpenid = json_test.getString("openid");
String sessionKey = json_test.getString("session_key");
System.err.println("openid值: "+wxOpenid);//得到微信openID
System.err.println("sessionKey值: "+sessionKey);
//根据id数据库数据查询
WxUser user = wxUserService.selectByid(wxOpenid);
System.err.println("用户信息: "+user);
if (user == null){//如果user等于null说明该用户第一次登录,数据库没有该用户信息。
resultMap.put("state", 2000);
resultMap.put("data", wxOpenid);
resultMap.put("sessionKey", sessionKey);
resultMap.put("message", "未查询到用户信息");
}else {
resultMap.put("state", 2000);
resultMap.put("data", wxOpenid);//查找的用户信息进行封装返回
resultMap.put("sessionKey", sessionKey);
resultMap.put("user", user);//查找的用户信息进行封装返回
resultMap.put("message", "该用户已存在");
return resultMap;
}
if(StringUtils.isEmpty(wxOpenid)){
resultMap.put("state", 2000);
resultMap.put("message", "未获取到openid");
return resultMap;
}
} finally {
if (response != null) {
response.close();
}
if (client != null) {
client.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return resultMap;
}
说明:如果数据库没有查到该用户的数据就让前端去调取获取微信用户信息接口,然后把拿到的信息调用下边的接口往数据库保存微信用户的信息。
/*保存微信用户信息*/
@RequestMapping("/insertWxUser")
@ResponseBody
public JsonResult<WxUser> insertUser(WxUser user){
JsonResult result = new JsonResult();
System.out.println("微信用户信息保存");
String username = user.getUsername();
String avatar = user.getAvatar();
String wxOpenid = user.getOpenid();
System.out.println("用户名: "+username);
System.out.println("头像: "+avatar);
System.out.println("openid: "+wxOpenid);
try {
WxUser user1 = wxUserService.selectByid(wxOpenid);//根据id数据库数据查询
if (user1==null) {
wxUserService.insertWxUser(username, avatar, wxOpenid);
result.setState(SUCCESS);
}
else {
wxUserService.updateWxUser(username, avatar, wxOpenid);
result.setState(SUCCESS);
}
}catch (Exception e){
result.setMessage("保存用户信息失败,请重新尝试");
}
return result;
}
说明:获取微信用户绑定的手机号信息,往数据库保存时需要对电话号解析后才能获取电话。
@PostMapping("/getphone")
@ResponseBody
public String mini_getPhone(HttpServletRequest request,@Param("encryptedData")String encryptedData,@Param("iv")String iv,@Param("sessionKey")String sessionKey)
{
//
System.out.println("encryptedData= "+encryptedData);
System.out.println("iv= "+iv);
System.out.println("sessionKey= "+sessionKey);
JSONObject obj=getPhoneNumber(sessionKey,encryptedData,iv);//解密电话号码
//System.out.println(obj);
String sphone=obj.get("phoneNumber").toString();
System.err.println("sphone= "+sphone);
return sphone;
}
//解析电话号码
public JSONObject getPhoneNumber(String session_key, String encryptedData, String iv) {
byte[] dataByte = Base64.decode(encryptedData);
byte[] keyByte = Base64.decode(session_key);
byte[] ivByte = Base64.decode(iv);
try {
int base = 16;
if (keyByte.length % base != 0) {
int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
byte[] temp = new byte[groups * base];
Arrays.fill(temp, (byte) 0);
System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
keyByte = temp;
}
// 初始化
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
parameters.init(new IvParameterSpec(ivByte));
cipher.init(Cipher.DECRYPT_MODE, spec, parameters);
byte[] resultByte = cipher.doFinal(dataByte);
if (null != resultByte && resultByte.length > 0) {
String result = new String(resultByte, "UTF-8");
return JSONObject.parseObject(result);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
02微信支付
当然在开发之前,我们需要有下面这些东西:
- appId
- 密钥(小程序配置界面)
- 商户号
- api密钥(商家后台自己设置)
商户号和密钥需要开通微信小程序支付功能才有,需要商家才能开通微信支付,并且还需要300元费用的一个认证通过后才可以。
微信支付开发文档:
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1
下单时需要传的参数都说的很清楚。
代码上的注释也写的很清楚
/*调用支付接口*/
@RequestMapping("prePay")
@ResponseBody
public Map<String, Object> prePay(Integer id, HttpServletRequest request){
// 返回参数
Map<String, Object> resMap = new HashMap<>();
//获取请求ip地址
String ip = request.getHeader("x-forwarded-for");
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getHeader("Proxy-Client-IP");
}
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getHeader("WL-Proxy-Client-IP");
}
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getRemoteAddr();
}
if(ip.indexOf(",")!=-1){
String[] ips = ip.split(",");
ip = ips[0].trim();
}
try {
System.out.println("id= "+id);
// 拼接统一下单地址参数
Map<String, Object> paraMap = new HashMap<>();
Order order = orderMapper.selectByPrimaryKey(id);
String body = order.getHeader();//商品名称
String orderNum = order.getOrdernum();//订单号
String openId = order.getOpenid();
String money = order.getMoney();
Integer price = Integer.valueOf(money);
// Integer price = 1;//支付金额,单位:分,这边需要转成字符串类型,否则后面的签名会失败
System.out.println("body= "+body);
// body = new String(body.getBytes("ISO-8859-1"),"UTF-8").toString();
// System.out.println("body= "+body);
// 封装11个必需的参数
paraMap.put("appid", WXPayConstants.APP_ID);
paraMap.put("mch_id", WXPayConstants.MCH_ID);//商家ID
paraMap.put("nonce_str", WXPayUtil.generateNonceStr());//获取随机字符串 Nonce Str
paraMap.put("body", body); //商品名称
paraMap.put("out_trade_no", orderNum);//订单号
paraMap.put("total_fee",price); //测试改为固定金额
paraMap.put("spbill_create_ip", ip);
paraMap.put("notify_url",WXPayConstants.CALLBACK_URL);// 此路径是微信服务器调用支付结果通知路径
paraMap.put("trade_type", "JSAPI");
paraMap.put("openid", openId);
String sign = WXPayUtil.generateSignature(paraMap, WXPayConstants.PATERNER_KEY);//商户密码
//生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
paraMap.put("sign", sign);
//将所有参数(map)转xml格式
String xml = WXPayUtil.mapToXml(paraMap);
// xml = new String(xml.getBytes("ISO-8859-1"), "UTF-8");
// String xml = new String(WXPayUtil.mapToXml(paraMap).getBytes(), "utf-8");
System.err.println("xml=: "+xml);
// 统一下单 https://api.mch.weixin.qq.com/pay/unifiedorder
String unifiedorder_url = WXPayConstants.UNIFIEDORDER_URL;//统一下单接口
System.out.println("统一下单接口unifiedorder_url:"+unifiedorder_url);
//发送post请求"统一下单接口"返回预支付id:prepay_id
String xmlStr = HttpClientUtil.doPostXml(unifiedorder_url, xml);
System.out.println("xmlStr:"+xmlStr);
//以下内容是返回前端页面的json数据
//预支付id
String prepay_id = "";
if (xmlStr.indexOf("SUCCESS") != -1) {
Map<String, Object> map = WXPayUtil.xmlToMap(xmlStr);//XML格式字符串转换为Map
prepay_id = map.get("prepay_id").toString();
System.err.println("prepay_id_1= "+prepay_id);
}
System.err.println("prepay_id_2= "+prepay_id);
Map<String, Object> payMap = new HashMap<String, Object>();
// 封装所需6个参数调支付页面
payMap.put("appId", WXPayConstants.APP_ID);
payMap.put("timeStamp", WXPayUtil.getCurrentTimestamp()+"");//获取当前时间戳,单位秒
payMap.put("nonceStr", WXPayUtil.generateNonceStr());//获取随机字符串 Nonce Str
payMap.put("signType", "MD5");
payMap.put("package", "prepay_id="+prepay_id);
//生成带有 sign 的 XML 格式字符串
String paySign = WXPayUtil.generateSignature(payMap, WXPayConstants.PATERNER_KEY);
payMap.put("paySign", paySign);
// 封装正常情况返回数据
resMap.put("success",true);
resMap.put("payMap",payMap);
} catch (Exception e) {
// 封装异常情况返回数据
resMap.put("success",false);
resMap.put("message","调用统一订单接口错误");
e.printStackTrace();
}
return resMap;
}
/*支付成功回调*/
@RequestMapping("callBack")
public JsonResult callBack(HttpServletRequest request, HttpServletResponse response){
JsonResult result = new JsonResult();
System.err.println("微信支付成功,微信发送的callback信息,请注意修改订单信息");
InputStream is = null;
String resXml = "";
try {
is = request.getInputStream();//获取请求的流信息(这里是微信发的xml格式所有只能使用流来读)
String xml = WXPayUtil.inputStream2String(is);
Map<String, Object> notifyMap = WXPayUtil.xmlToMap(xml);//将微信发的xml转map
if(notifyMap.get("return_code").equals("SUCCESS")){
String ordersNum = notifyMap.get("out_trade_no").toString();//商户订单号
//处理订单状态
// String openid = notifyMap.get("openid");
Date zhifutime = new Date();
Integer ordertype = 1;//1支付完成
String complete = "待服务";
try {
orderMapper.updateByOrdersNum(ordersNum,ordertype,zhifutime,complete);
result.setData(SUCCESS);
result.setData("支付回调成功,修改订单状态为支付成功");
//告诉微信服务器收到信息了,不要在调用回调action了========这里很重要回复微信服务器信息用流发送一个xml即可
// response.getWriter().write(XMLUtil.setXML("SUCCESS", "OK")); // 告诉微信服务器,我收到信息了,不要在调用回调action了
// StringBuffer s = new StringBuffer();
// s.append("<xml>");
// s.append("<return_code><![CDATA[SUCCESS]]></return_code>");
// s.append("<return_msg><![CDATA[OK]]></return_msg>");
// s.append("</xml>");
// response.getWriter().write(String.valueOf(s));
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
BufferedOutputStream out = new BufferedOutputStream(
response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
System.err.println("返回给微信的值:"+resXml.getBytes());
is.close();
}catch (Exception e){
result.setMessage("订单状态修改失败");
}
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
03退款
退款和支付其实相同,唯一不同的地方就是退款需要配置一个退款证书,这个可以再微信商户平台下载证书进行配置。
/*调用退款接口,取消订单*/
@RequestMapping("refund")
@ResponseBody
public Map<String, Object> refund(Integer id,HttpServletResponse response){
// 返回参数
Map<String, Object> resMap = new HashMap<>();
Date newtime = new Date();
String resXml = "";
try {
// 拼接统一下单地址参数
Map<String, Object> paraMap = new HashMap<>();
Order order = orderMapper.selectByPrimaryKey(id);
String orderNum = order.getOrdernum();//订单号
String money = order.getMoney();//金额
Integer price = Integer.valueOf(money);
// Integer price = 1;//支付金额,单位:分,这边需要转成字符串类型,否则后面的签名会失败
System.out.println("订单号= "+orderNum);
// 封装必需的参数
paraMap.put("appid", WXPayConstants.APP_ID);
paraMap.put("mch_id", WXPayConstants.MCH_ID);//商家ID
paraMap.put("nonce_str", WXPayUtil.generateNonceStr());//获取随机字符串 Nonce Str
paraMap.put("out_trade_no", orderNum);//订单号
paraMap.put("out_refund_no", orderNum);//商户退款单号
paraMap.put("total_fee",price); //测试改为固定金额 订单金额
paraMap.put("refund_fee",price); //退款金额
// paraMap.put("notify_url", WXPayConstants.notify_url); //退款路径
String sign = WXPayUtil.generateSignature(paraMap, WXPayConstants.PATERNER_KEY);//商户密码
//生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
paraMap.put("sign", sign);
//将所有参数(map)转xml格式
String xml = WXPayUtil.mapToXml(paraMap);
System.out.println("xml:"+xml);
// 退款 https://api.mch.weixin.qq.com/secapi/pay/refund
String refund_url = WXPayConstants.REFUND_URL;//申请退款路径接口
System.out.println("refund_url:"+refund_url);
//发送post请求"申请退款"
String xmlStr = HttpClientUtil.doRefund(refund_url, xml);
System.out.println("退款xmlStr:"+xmlStr);
/*退款成功回调修改订单状态*/
if (xmlStr.indexOf("SUCCESS") != -1) {
Map<String, Object> map = WXPayUtil.xmlToMap(xmlStr);//XML格式字符串转换为Map
if(map.get("return_code").equals("SUCCESS")){
resMap.put("success",true);//此步说明退款成功
resMap.put("data","退款成功");
System.out.println("退款成功");
Date refundtime = new Date();
Integer ordertype = -1;//-1取消订单
String complete = "已取消";
try {
orderMapper.updateRefundByOrdersNum(orderNum,ordertype,refundtime,complete);
//告诉微信服务器收到信息了,不要在调用回调action了========这里很重要回复微信服务器信息用流发送一个xml即可
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
BufferedOutputStream out = new BufferedOutputStream(
response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
System.err.println("返回给微信的值:"+resXml.getBytes());
}catch (Exception e){
resMap.put("fail","订单状态修改失败");
}
}
}else {
resMap.put("success","fail");//此步说明退款成功
resMap.put("data","退款失败");
}
} catch (Exception e) {
e.printStackTrace();
}
return resMap;
}
记录一下