对接微信扫码支付
刚刚做完微信支付,发现网上好多教程坑多的一逼,为了广大java小白程序员少走一点弯路特地整理了一下笔记供大家参考,由于本人只做后端,所以只有后端的代码;
首先准备工作
1.第一步注册公众号(类型必须为服务号)
只能选择 企业/公司 政府 媒体三种,其他的不可以
2.认证公众号(公众号认证后才可以对接微信支付,认证费用300)
3.提交资料申请微信支付
登录公众号平台,点击左侧菜单(微信支付),填写资料等待审 核,审核 时间为1-5个工作日;
4.开户成功,登录商户平台进行验证
资料审核通过后,登录联系人邮箱接收商户号和密码,,并登录商 户平台填写财付通备付金打的小笔资金数额,完成账户验证;
5,在线签署协议
签署完协议方可进行交易及资金结算,签署完立即生效;
准备工作做完了就可以进行开发了
1,先查看官方文档,微信官方文档地址:
https://pay.weixin.qq.com/wiki/doc/api/index.html
2,进行官方文档选择 Native支付(扫码支付)
3,微信扫码支付有两种模式,第一种模式微信给生成二维码,但是依赖设置的回调支付URL;第二种模式流程更为简单,我们利用微信系统返回的url自己生成二维码图片;
我选择的是第二种;
4,找到左侧的API列表,主要用到里面的两个接口,统一下单接口和查询订单接口
5,先看统一下单API接口,请求参数,也就是微信需要我们给他传的参数,请求参数里面的必传的,也就是必须带着的,我数了下一共10个参数;
1,公众账号ID appid(不知道怎么查看自己百度)
2,商户号 mch_id(同上)
3,随机字符串 nonce_str(下载微信SDK,里面有两个工具类能用到WXPayUtil和WXPayXmlUtil)
4,签名 sign(留着最后处理)
5,商品描述 body(随便填写)
6,商户订单号 out_trade_no(测试用先随便写一个)
7,标价金额 total_fee(单位为分,填个1测试用)
8,终端IP spbill_create_ip(发送请求的ip地址,测试可以用 “127.0.0.1”)
9,通知地址 notify_url(就是回调地址,测试用可以随便写一个,注意必须是外网可以访问的网址)
10,交易类型 trade_type( “NATIVE” )
就是凑齐10个参数就可以了,下面来说下第4个参数怎么获取
上代码
//通过封装的其他9个参数的map加上密钥partnerkey,通过 generateSignedXml方法获取到带签名的字符串
String xmlParam = WXPayUtil.generateSignedXml(param, partnerkey);
这个方法就是刚才下载的工具类WXPayUtil中的一个方法;
凑齐上面10个参数就可以发送请求了;发送请求后微信会返回给你一个url,根据这个url就可以生成二维码了,但是支付完成后我们的后台并不会知道支付是否完成了,所以就有了下面;
请求发送完后,再调用查询订单API接口
需要携带的参数上面都有了自己看文档,注意微信给你返回的信息里面有个字段
交易状态 trade_state ,看这个字段返回的信息如果是SUCCESS就成功了;
完整代码如下
public class PayController {
@Autowired
private WeixinPayService weixinPayService;
@Autowired
private OrderService orderService;
@RequestMapping("/createNative")
public Map createNative(){
// //1.获取当前登录用户
// String username = SecurityContextHolder.getContext().getAuthentication().getName();
// //2.提取支付日志(从缓存 )
// TbPayLog payLog = orderService.searchPayLogFromRedis(username);//这里自己定义一个查询方法(通过用户ID来查询)
// //3.调用微信支付接口
// if(payLog!=null){
// return weixinPayService.createNative(payLog.getOutTradeNo(), payLog.getTotalFee()+"");
// }else{
// return new HashMap<>();
// }
return weixinPayService.createNative("123", "1");
}
@RequestMapping("/queryPayStatus")//需要订单ID
public Map<String, Object> queryPayStatus(Integer orderId){
int x=0;
//首先通过订单id查询出订单号码
Orders orders =orderService.findOrderById(orderId);
String orderNum = orders.getOrder_number();
while(true){
Map<String,String> map = weixinPayService.queryPayStatus(orderNum);//调用查询
if(map==null){
return ResultVueUtil.resultVueError("false","支付发生错误");
}
if(map.get("trade_state").equals("SUCCESS")){//支付成功
//orderService.updateOrderStatus(orderNumber, map.get("transaction_id"));
//String transaction_id = map.get("transaction_id");获取到返回来的交易流水号
//订单支付成功,改变订单状态,使用更新
orderService.updateOrderStatus(orderNum);
return ResultVueUtil.resultVueSuccess("true","支付成功");
}
try {
//休眠3秒钟再循环调用
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
x++;
if(x>=100){
//超过100次 5分钟二维码超时
return ResultVueUtil.resultVueError("false","二维码超时");
}
}
}
}
@Service
public class WeixinPayService {
/**
* 生成微信支付二维码
* @param orderNumber
* @param total_fee
* @return
*/
public Map createNative(String orderNumber, String total_fee) {
/**
* 公众账号ID appid
* 商户号 mch_id
* 随机字符串 nonce_str
* 签名 sign
* 商品描述 body
* 商户订单号 out_trade_no
* 标价金额 total_fee
* 终端IP spbill_create_ip
* 通知地址(回调地址) notify_url
* 交易类型(支付类型) trade_type
*/
//1.参数封装
Map param=new HashMap();
param.put("appid", appid);//公众账号ID
param.put("mch_id", mch_id);//商户号
param.put("nonce_str", WXPayUtil.generateNonceStr());//随机字符串
param.put("body", "郝同K");
param.put("out_trade_no", orderNumber);//(商户订单号)交易订单号
param.put("total_fee", total_fee);//金额(分)
param.put("spbill_create_ip", "127.0.0.1");//终端IP(发送请求的ip地址)
param.put("notify_url", "https://www.baidu.com");//通知地址,不重要只要符合规则能打开就行
param.put("trade_type", "NATIVE");//交易类型
System.out.println("param = " + param);
System.out.println(mch_id);
try {
//通过封装的其他9个参数的map加上密钥partnerkey,通过generateSignedXml方法获取到带签名的字符串
String xmlParam = WXPayUtil.generateSignedXml(param, partnerkey);
System.out.println("请求的参数:"+xmlParam);
//2.发送请求
HttpClient httpClient=new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
httpClient.setHttps(true);
httpClient.setXmlParam(xmlParam);
httpClient.post();
//3.获取结果
String xmlResult = httpClient.getContent();
//把获取到的结果转换成map集合
Map<String, String> mapResult = WXPayUtil.xmlToMap(xmlResult);
System.out.println("微信返回结果"+mapResult);
Map map=new HashMap<>();
map.put("code_url", mapResult.get("code_url"));//生成支付二维码的链接
map.put("out_trade_no", orderNumber);//订单号
map.put("total_fee", total_fee);//金额
return map;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return new HashMap();
}
}
/**
* 查询支付订单状态
* (判断订单是否已经支付,是否进行支付页面跳转)
* @param orderId
* @return
*/
public Map queryPayStatus(String orderNum) {
//1.封装参数
Map param=new HashMap();
param.put("appid", appid);//公众号id
param.put("mch_id", mch_id);//商户号id
param.put("out_trade_no", orderNum);//商户订单号
param.put("nonce_str", WXPayUtil.generateNonceStr());//随机字符串
try {
String xmlParam = WXPayUtil.generateSignedXml(param, partnerkey);//返回的是带签名的字符串
//2.发送请求
HttpClient httpClient=new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
httpClient.setHttps(true);
httpClient.setXmlParam(xmlParam);
httpClient.post();
//3.获取结果
String xmlResult = httpClient.getContent();
Map<String, String> map = WXPayUtil.xmlToMap(xmlResult);
System.out.println("调动查询API返回结果:"+xmlResult);
return map;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}
下面是用到的三个工具类
/**
* http请求客户端
*/
public class HttpClient {
private String url;
private Map<String, String> param;
private int statusCode;
private String content;
private String xmlParam;
private boolean isHttps;
public boolean isHttps() {
return isHttps;
}
public void setHttps(boolean isHttps) {
this.isHttps = isHttps;
}
public String getXmlParam() {
return xmlParam;
}
public void setXmlParam(String xmlParam) {
this.xmlParam = xmlParam;
}
public HttpClient(String url, Map<String, String> param) {
this.url = url;
this.param = param;
}
public HttpClient(String url) {
this.url = url;
}
public void setParameter(Map<String, String> map) {
param = map;
}
public void addParameter(String key, String value) {
if (param == null)
param = new HashMap<String, String>();
param.put(key, value);
}
public void post() throws ClientProtocolException, IOException {
HttpPost http = new HttpPost(url);
setEntity(http);
execute(http);
}
public void put() throws ClientProtocolException, IOException {
HttpPut http = new HttpPut(url);
setEntity(http);
execute(http);
}
public void get() throws ClientProtocolException, IOException {
if (param != null) {
StringBuilder url = new StringBuilder(this.url);
boolean isFirst = true;
for (String key : param.keySet()) {
if (isFirst)
url.append("?");
else
url.append("&");
url.append(key).append("=").append(param.get(key));
}
this.url = url.toString();
}
HttpGet http = new HttpGet(url);
execute(http);
}
/**
* set http post,put param
*/
private void setEntity(HttpEntityEnclosingRequestBase http) {
if (param != null) {
List<NameValuePair> nvps = new LinkedList<NameValuePair>();
for (String key : param.keySet())
nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数
http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数
}
if (xmlParam != null) {
http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
}
}
private void execute(HttpUriRequest http) throws ClientProtocolException,
IOException {
CloseableHttpClient httpClient = null;
try {
if (isHttps) {
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(null, new TrustStrategy() {
// 信任所有
public boolean isTrusted(X509Certificate[] chain,
String authType)
throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext);
httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
.build();
} else {
httpClient = HttpClients.createDefault();
}
CloseableHttpResponse response = httpClient.execute(http);
try {
if (response != null) {
if (response.getStatusLine() != null)
statusCode = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
// 响应内容
content = EntityUtils.toString(entity, Consts.UTF_8);
}
} finally {
response.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
httpClient.close();
}
}
public int getStatusCode() {
return statusCode;
}
public String getContent() throws ParseException, IOException {
return content;
}
}
```java
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key: data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
try {
writer.close();
}
catch (Exception ex) {
}
return output;
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
return generateSignedXml(data, key, SignType.MD5);
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名类型
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key, SignType signType) throws Exception {
String sign = generateSignature(data, key, signType);
data.put(WXPayConstants.FIELD_SIGN, sign);
return mapToXml(data);
}
/**
* 判断签名是否正确
*
* @param xmlStr XML格式数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
Map<String, String> data = xmlToMap(xmlStr);
if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key).equals(sign);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
*
* @param data Map类型数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
return isSignatureValid(data, key, SignType.MD5);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名方式
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key, signType).equals(sign);
}
/**
* 生成签名
*
* @param data 待签名数据
* @param key API密钥
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key) throws Exception {
return generateSignature(data, key, SignType.MD5);
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
Set<String> keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals(WXPayConstants.FIELD_SIGN)) {
continue;
}
if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append("key=").append(key);
if (SignType.MD5.equals(signType)) {
return MD5(sb.toString()).toUpperCase();
}
else if (SignType.HMACSHA256.equals(signType)) {
return HMACSHA256(sb.toString(), key);
}
else {
throw new Exception(String.format("Invalid sign_type: %s", signType));
}
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
/**
* 生成 MD5
*
* @param data 待处理数据
* @return MD5结果
*/
public static String MD5(String data) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 生成 HMACSHA256
* @param data 待处理数据
* @param key 密钥
* @return 加密结果
* @throws Exception
*/
public static String HMACSHA256(String data, String key) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 日志
* @return
*/
public static Logger getLogger() {
Logger logger = LoggerFactory.getLogger("wxpay java sdk");
return logger;
}
/**
* 获取当前时间戳,单位秒
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis()/1000;
}
/**
* 获取当前时间戳,单位毫秒
* @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
}
package com.mddb.util.weChatPay.sdk;
import org.w3c.dom.Document;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/**
* 2018/7/3
*/
public final class WXPayXmlUtil {
public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
return documentBuilderFactory.newDocumentBuilder();
}
public static Document newDocument() throws ParserConfigurationException {
return newDocumentBuilder().newDocument();
}
}
jar包坐标
<!--微信支付所用到的jar坐标-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
上面的代码因为加入了公司项目订单的一些订单业务,和调用的返回的公共类所以拷贝过去代码会有错误,不过我的注释都很详细和完整,后面3个工具类没必要理解,前面的两个类的东西必须自己理解了才能更好的结合自己项目的需求对接,不要嫌麻烦;好了晚上3点了该睡觉了,喜欢的点个赞,谢谢!