一、支付流程
PS:做这个之前 ,先去下载官方的SDK吧 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1
1.首先要拿到appid,key,AppSecret, mch_id (商户号) 。 这几个参数由你或者是你公司去申请
2.前端需要给后端的(统一下单接口)传递code(登录凭证),标题body(自定义,比如游戏充值)和支付金额 。
3.后端需要有统一下单的接口和一个支付成功后的回调接口
4.统一下单接口在收到这三个参数后,先根据code去获取openid(JSAPI必传openid),
官网放出的获取openid的请求地址 需要appid ,secret, code, grant_type的值写成固定的->authorization_code
GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
5.然后把各种参数封装为一个map 再根据secret和map进行MD5加密返回一个签名 ,再把这个签名封装到map中
最后 发送请求并返回结果
6.获取到第一次签名请求的结果后,再对返回的数据进行第二次签名,最后返回给前端一系列的参数
7.前端在收到返回的参数后,如果为success ,就调起微信支付页面,付款成功后微信会自动调用我们定义的回调函数,在回调函数中需要响应结果给微信系统,表示我们已经支付完成 ,不然微信会调用我们的回调函数8次。
8.回调函数会返回结果,如果为success就表示此次支付成功完成
官网放出的获取openid的请求地址 需要appid , secret,code,grant_type写成固定的->authorization_code
具体代码:
统一下单,回调函数,查询订单--具体代码:
注意: 这里面的地址我都不是用的localhost ,需要弄成外网的样子 所以这里我做了一个内网穿透 不然回调函数无法执行
内网穿透我用的是ngrok,官网下载后启动执行ngrok http 端口号 。然后就会生成一行地址,你把你接口的地址加在生成的地址后面就可以访问了
1.
package com.github.wxpay.sdk.wxpaydemo.controller;
import com.github.wxpay.sdk.wxpaydemo.config.WXPay;
import com.github.wxpay.sdk.wxpaydemo.config.WXPayConstants;
import com.github.wxpay.sdk.wxpaydemo.config.WXPayUtil;
import com.github.wxpay.sdk.wxpaydemo.domain.OrderQueryReturnDomain;
import com.github.wxpay.sdk.wxpaydemo.domain.OrderReturnDomain;
import com.github.wxpay.sdk.wxpaydemo.domain.PayDomain;
import com.github.wxpay.sdk.wxpaydemo.util.PayUtil;
import com.github.wxpay.sdk.wxpaydemo.util.RandomStringGenerator;
import com.github.wxpay.sdk.wxpaydemo.util.WxPayGobal;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.XStream;
import org.springframework.web.bind.annotation.*;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
/**
* 微信小程序支付接口
*/
@RestController
@RequestMapping("/pay")
public class wxpay {
//JSAPI和APP 统一下单请求地址 (固定的)
private static final String JSAPI_URL="https://api.mch.weixin.qq.com/pay/unifiedorder";
//支付回调地址(你自己的请求地址,可以自己随意配置啦,写在这方便你理解)
private static final String NOTIFY_URL = "http://04a3af668030.ngrok.io/pay/back/wxNotify";
//支付订单查询地址 (固定的)
private static final String ORDER_URL="https://api.mch.weixin.qq.com/pay/orderquery";
//交易类型(这里是公众号支付)
private static final String TRADE_TYPE = "JSAPI";
/**
* JSAPI 统一下单接口
*
* @param code 登录凭证
* @param body 标题
* @param money 支付金额
* @return 返回交易类型和预支付会话标识
* @throws Exception
*/
@PostMapping("/wxJSApiPay")
public Map<String, Object> wxJSApiPay(String code, String body, String money) throws Exception {
String orderNumber = WxPayGobal.orderNumber();
Map<String, String> data = new HashMap<String, String>();
Map<String, Object> map = new HashMap<String, Object>();
data.put("appid", PayDomain.getAppid()); //appid
data.put("mch_id", PayDomain.getMch_id()); //商户id
data.put("body", body); //标题
data.put("out_trade_no", orderNumber);//订单号
data.put("total_fee", money); //金额 单位为分
data.put("spbill_create_ip", InetAddress.getLocalHost().getHostAddress());//终端IP
data.put("notify_url", NOTIFY_URL); //回调地址
data.put("trade_type", TRADE_TYPE); // 交易类型
data.put("openid", WxPayGobal.getOpenId(code)); //用户标识
data.put("nonce_str", RandomStringGenerator.getRandomStringByLength(32)); //随机字符串
System.out.println(data);
String result = WxPayGobal.wxPay(data,JSAPI_URL); //传递参数发送请求并返回结果
XStream xStream = new XStream();
//出于安全考虑,这里必须限制类型,不然会报错
xStream.allowTypes(new Class[]{OrderReturnDomain.class});
xStream.alias("xml", OrderReturnDomain.class);
OrderReturnDomain s = (OrderReturnDomain) xStream.fromXML(result); //返回结果转换为实体
//当请求返回结果为success时 进行二次签名
if ("SUCCESS".equals(s.getReturn_code()) && s.getReturn_code().equals(s.getResult_code())) {
//生成签名(官方给出来的签名方法)
Map<String, String> map2 = new HashMap<String, String>();
long time = System.currentTimeMillis() / 1000;
map2.put("appId", PayDomain.getAppid());
map2.put("timeStamp", String.valueOf(time));
//这边的随机字符串必须是第一次生成sign时,微信返回的随机字符串,不然小程序支付时会报签名错误
map2.put("nonceStr", s.getNonce_str());
map2.put("package", "prepay_id=" + s.getPrepay_id());
map2.put("signType", "MD5");
String sign2 = WXPayUtil.generateSignature(map2, PayDomain.getKey(), WXPayConstants.SignType.MD5);
System.out.println("二次签名的sign2----->" + sign2);
Map<String, Object> payInfo = new HashMap<String, Object>();
payInfo.put("timeStamp", String.valueOf(time));
payInfo.put("nonceStr", s.getNonce_str());
payInfo.put("package", "prepay_id="+s.getPrepay_id());
payInfo.put("signType", "MD5");
payInfo.put("paySign", sign2);
payInfo.put("orderNumber",orderNumber);
map.put("status", 200);
map.put("msg", "success");
map.put("data", payInfo);
//此处写逻辑代码实现
/**
* --
* --
*/
} else {
return null;
}
return map;
}
/**
*
* @param request
* @param response
* @param orderNumber 下单成功后的订单号
* @param sign 下单成功后的签名
* @return
* @throws Exception
*/
@PostMapping(value = "/orderQuery")
public static OrderQueryReturnDomain orderQuery(HttpServletRequest request, HttpServletResponse response,String orderNumber,String sign) throws Exception {
Map<String, String> data = new HashMap<String, String>();
data.put("appid", PayDomain.getAppid());
data.put("mch_id", PayDomain.getMch_id());
data.put("out_trade_no", orderNumber);//订单号
data.put("nonce_str", RandomStringGenerator.getRandomStringByLength(32)); //随机字符串
data.put("sign",sign); //签名
System.out.println(data);
String s = WxPayGobal.wxPay(data,ORDER_URL); //传递参数发送请求并返回结果
XStream xStream = new XStream();
OrderQueryReturnDomain result = (OrderQueryReturnDomain) xStream.fromXML(s);
return result;
}
/**
* 微信小程序支付成功回调函数
*
* @param request
* @param response
* @throws Exception
*/
@RequestMapping(value = "/callback/wxNotify")
public static void wxNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream()));
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
//sb为微信返回的xml
String notityXml = sb.toString();
String resXml = "";
System.out.println("接收到的报文:" + notityXml);
@SuppressWarnings("unchecked")
Map<String, String> map = PayUtil.doXMLParse(notityXml);
String returnCode = (String) map.get("return_code");
if ("SUCCESS".equals(returnCode)) {
//验证签名是否正确
Map<String, String> validParams = PayUtil.paraFilter(map); //回调验签时需要去除sign和空值参数
String validStr = PayUtil.createLinkString(validParams);//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
String sign = PayUtil.sign(validStr, PayDomain.getKey(), "utf-8").toUpperCase();//拼装生成服务器端验证的签名
// 因为微信回调会有八次之多,所以当第一次回调成功了,那么我们就不再执行逻辑了
//根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等
if (sign.equals(map.get("sign"))) {
/**此处添加自己的业务逻辑代码start**/
// bla bla bla....
/**此处添加自己的业务逻辑代码end**/
//通知微信服务器已经支付成功
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
} else {
System.out.println("微信支付回调失败!签名不一致");
}
} else {
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
System.out.println(resXml);
System.out.println("微信支付回调数据结束");
BufferedOutputStream out = new BufferedOutputStream(
response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
}
}
2.工具类 用于发送请求 获取openid 生成订单号(具体的订单号生成规则需要修改,我这里只是演示,不能用于上线)
package com.github.wxpay.sdk.wxpaydemo.util;
import com.github.wxpay.sdk.wxpaydemo.config.WXPayUtil;
import com.github.wxpay.sdk.wxpaydemo.domain.PayDomain;
import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
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.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Map;
import com.alibaba.fastjson.JSONObject;
/**
* 小程序支付工具类
*/
@Component
public class WxPayGobal {
/**
* 请求地址 (用于获取openid)
*/
private final static String url = "https://api.weixin.qq.com/sns/jscode2session";
private static Logger logger = LoggerFactory.getLogger(WxPayGobal.class);
public static String wxPay(Map<String, String> data,String url) {
//生成签名
String sign = null;
try {
sign = WXPayUtil.generateSignature(data, PayDomain.getKey());
} catch (Exception e) {
e.printStackTrace();
}
data.put("sign", sign);
String s = null;
try {
s = WXPayUtil.mapToXml(data);
} catch (Exception e) {
e.printStackTrace();
}
//发生请求并返回请求结果
String s1 = doPostXml(url, s);
System.out.println(s1);
return s1;
}
/**
* 发送请求
*
* @param url 请求地址
* @param requestDataXml 请求的xml类型的字符串
* @return
*/
public static String doPostXml(String url, String requestDataXml) {
CloseableHttpClient httpClient = null;
CloseableHttpResponse httpResponse = null;
httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(15000)
.setConnectionRequestTimeout(60000)
.setSocketTimeout(60000)
.build();
httpPost.setConfig(requestConfig);
httpPost.setEntity(new StringEntity(requestDataXml, "UTF-8"));
httpPost.addHeader("Content-Type", "text/xml");
String result = "";
try {
httpResponse = httpClient.execute(httpPost);
HttpEntity entity = httpResponse.getEntity();
result = EntityUtils.toString(entity, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 获取openId
* @param code 登录凭证
* @return
*/
public static String getOpenId(String code) {
//请求参数
String params = url + "?appid=" + PayDomain.getAppid() + "&secret=" + PayDomain.getSecret() + "&js_code=" + code + "&grant_type=" + "authorization_code";
logger.info("请求地址:"+params);
//发送请求
HttpGet httpGet = new HttpGet(params);
CloseableHttpClient httpClients = HttpClients.createDefault();
String result = null;
try {
CloseableHttpResponse execute = httpClients.execute(httpGet);
HttpEntity entity = execute.getEntity();
result = EntityUtils.toString(entity);
System.out.println(result+"...");
} catch (IOException e) {
e.printStackTrace();
}
JSONObject jsonObject = JSONObject.parseObject(result);
logger.info("获取openid" + jsonObject.getString("openid"));
if (null!=jsonObject.getString("openid")) {
String openid = jsonObject.getString("openid");
return openid;
}
return null;
}
/**
* 订单号生成 当前日期+随机数字+商户ID
*/
public static String orderNumber(){
LocalDateTime now = LocalDateTime.now();
String str=now.getYear()+""+now.getMonthValue()+now.getDayOfMonth()+"";//当前年月日
String randomNumber=""; //随机数字
for (int i = 0; i < 6; i++) {
int max=9,min=0;
int ran2 = (int) (Math.random()*(max-min)+min);
randomNumber=ran2+randomNumber;
}
System.out.println(str+randomNumber);
return str+randomNumber;
}
}
实体类:
package com.github.wxpay.sdk.wxpaydemo.domain;
import lombok.Data;
/**
* 接收统一下单调用微信接口返回的参数
* @author Administrator
* */
@Data
public class OrderReturnDomain {
/**
* 返回状态码 (SUCCESS或 fail)
*/
private String return_code;
/**
* 返回信息
*/
private String return_msg;
/**
* 返回的结果状态码
*/
private String result_code;
/**
* 返回的appid
*/
private String appid;
/**
* 返回的商户号
*/
private String mch_id;
/**
* 返回的随机字符串
*/
private String nonce_str;
/**
* 返回的签名
*/
private String sign;
/**
*微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时
*/
private String prepay_id;
/**
* 返回的交易类型 (JSAPI,APP,NATIVE)
*/
private String trade_type;
/**
* 返回的错误码
*/
private String err_code;
/**
* 返回的错误信息描述
*/
private String err_code_des;
}
package com.github.wxpay.sdk.wxpaydemo.domain;
import lombok.Data;
/**
* 封装订单支付成功后的返回结果
*/
@Data
public class OrderQueryReturnDomain {
private String return_code; //返回返回状态码
private String return_msg; //返回信息
private String appid; //公众账号ID
private String mch_id; //商户号
private String nonce_str; //随机字符串
private String sign; //签名
private String result_code; //业务结果
private String openid; //用户标识
private String is_subscribe; //是否关注公众账号
private String trade_type; //交易类型
private String bank_type; //付款银行
private String total_fee; //标价金额
private String fee_type; //标价币种
private String transaction_id; //微信支付订单号
private String out_trade_no; //商户订单号
private String time_end; //支付完成时间
private String trade_state; //交易状态
private String cash_fee; //现金支付金额
private String trade_state_desc;//交易状态描述
private String cash_fee_type; //现金支付币种
}
package com.github.wxpay.sdk.wxpaydemo.domain;
import com.github.wxpay.sdk.wxpaydemo.config.WXPayConfig;
import lombok.Data;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
public class PayDomain {
/**
* 密钥key
*/
private static String key;
/**
* 应用ID
*/
private static String appid;
/**
* 商户号
*/
private static String mch_id;
/**
* 密钥
* @param key
*/
private static String secret;
@Value("${com.wx.wxpay.key}")
public void setKey(String key) {
this.key = key;
}
@Value("${com.wx.wxpay.appid}")
public void setAppid(String appid) {
this.appid = appid;
}
@Value("${com.wx.wxpay.mch_id}")
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
@Value("${com.wx.wxpay.secret}")
public void setSecret(String secret) {
PayDomain.secret = secret;
}
public static String getSecret() {
return secret;
}
public static String getKey() {
return key;
}
public static String getAppid() {
return appid;
}
public static String getMch_id() {
return mch_id;
}
}
pom:
<dependencies>
<!-- map和xml之间的转换器-->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.9</version>
</dependency>
<!-- 操作xml标签-->
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
记得一定要下载官方SDK !!! 然后把我这些代码加上去就OK 。
由于需要得朋友太多了,我就直接上传文件吧。
完整demo文件下载地址:不需要积分
https://download.csdn.net/download/aSmart_Q/36142998
觉得好用,给我点个赞吧!!感谢