事前准备
1.准备必要的参数
- appid:不多说,懂的自然动
- session_key:会话密钥
- 商户id:直接找公司的要
- 商户API密钥:直接要(需要安装证书,和财付通插件),登录商户平台一目了然,不清楚下边有流程
商户Key获取方法
6.
2.必要的配置
- 回调地址必须外网可访问。可以使用内网穿透解决,(下同)
- 授权地址即你支付接口的域名。在商户平台支付块设置一下。
3.开发过程
- 工具类
package com.loran.lobster.api.comment.wxpay;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* 字符流转字符串
* @param in
* @return
*/
public static String InputStream2String(InputStream in,String charsetName) {
InputStreamReader reader = null;
try {
reader = new InputStreamReader(in,charsetName);
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
BufferedReader br = new BufferedReader(reader);
StringBuilder sb = new StringBuilder();
String line = "";
try {
while ((line = br.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map xmlToMap(String strXML) throws Exception {
try {
Map data = new HashMap();
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 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 data, String key) throws Exception {
return generateSignedXml(data, key, "MD5");
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名类型
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map data, String key, String signType) throws Exception {
String sign = generateSignature(data, key, signType);
data.put("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 data = xmlToMap(xmlStr);
if (!data.containsKey("sign") ) {
return false;
}
String sign = data.get("sign");
return generateSignature(data, key).equals(sign);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
*
* @param data Map类型数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map data, String key) throws Exception {
return isSignatureValid(data, key, "MD5");
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名方式
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map data, String key, String signType) throws Exception {
if (!data.containsKey("sign") ) {
return false;
}
String sign = data.get("sign");
return generateSignature(data, key, signType).equals(sign);
}
/**
* 生成签名
*
* @param data 待签名数据
* @param key API密钥
* @return 签名
*/
public static String generateSignature(final Map data, String key) throws Exception {
return generateSignature(data, key, "MD5");
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map data, String key, String signType) throws Exception {
Set 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("sign")) {
continue;
}
if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append("key=").append(key);
return MD5(sb.toString()).toUpperCase();
}
/**
* 获取随机字符串 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 {
java.security.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.loran.lobster.api.comment.wxpay;
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();
}
}
- controller
package com.loran.lobster.api.controller;
import com.loran.lobster.api.comment.HttpRequest;
import com.loran.lobster.api.comment.UniqueOrderGenerate;
import com.loran.lobster.api.comment.wxpay.WXPayUtil;
import com.loran.lobster.api.entity.Address;
import com.loran.lobster.api.service.AddressService;
import com.loran.lobster.api.service.OrderGoodsService;
import com.loran.lobster.api.service.OrderService;
import com.loran.lobster.api.service.UserinfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("pay")
public class WXPayController {
@Autowired
private AddressService addressService;
@Autowired
private OrderGoodsService orderGoodsService;
@Autowired
private OrderService orderService;
@Autowired
private UserinfoService userinfoService;
@Value("${vendor.wx.pay.mch_id}")
private String mch_id;
@Value("${vendor.wx.pay.key}")
private String MerchantKey;
@Value("${vendor.wx.config.app_id}")
private String app_id;
/**
* @Description 微信浏览器内微信支付/公众号支付(JSAPI)
* @param request
* @return Map
*/
@GetMapping(value="/ordersPay")
public Map orders(HttpServletRequest request, String openid,Integer addressid) {
Map result = new HashMap<>();
try {
//拼接统一下单地址参数
Map paraMap = 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();
}
Address address = addressService.findAddress(addressid).get(0);
String ShippingAddress = address.getAddressReceiving()+address.getAddressHouseNumber();
paraMap.put("appid", app_id);
paraMap.put("body", "飞象鲜生-订单结算");
paraMap.put("attach",ShippingAddress );
paraMap.put("mch_id", "1532416151");
paraMap.put("nonce_str", WXPayUtil.generateNonceStr());
paraMap.put("openid", openid);
paraMap.put("out_trade_no", UniqueOrderGenerate.getOrderIdByTime());//订单号
paraMap.put("spbill_create_ip", ip);
paraMap.put("total_fee","1");
paraMap.put("trade_type", "JSAPI");
paraMap.put("notify_url","http://codingqicf.natapp1.cc/pay/payCallback");// 此路径是微信服务器调用支付结果通知路径随意写
String sign = WXPayUtil.generateSignature(paraMap, MerchantKey);
paraMap.put("sign", sign);
String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
// 统一下单 https://api.mch.weixin.qq.com/pay/unifiedorder
String unifiedorder_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String xmlStr = HttpRequest.sendPost(unifiedorder_url, xml);//发送post请求"统一下单接口"返回预支付id:prepay_id
// ******************************************
//
// 前端调起微信支付必要参数
//
// ******************************************
String prepay_id = "";//预支付id
if (xmlStr.indexOf("SUCCESS") != -1) {
Map map = WXPayUtil.xmlToMap(xmlStr);
prepay_id = (String) map.get("prepay_id");
}
String packages = "prepay_id=" + prepay_id;
Map wxPayMap = new HashMap();
wxPayMap.put("appId", app_id);
wxPayMap.put("timeStamp", String.valueOf(WXPayUtil.getCurrentTimestamp()));
wxPayMap.put("nonceStr", WXPayUtil.generateNonceStr());
wxPayMap.put("package", packages);
wxPayMap.put("signType", "MD5");
String paySign = WXPayUtil.generateSignature(wxPayMap,MerchantKey);
// ******************************************
//
// 返回给前端调起微信支付的必要参数
//
// ******************************************
result.put("prepay_id", prepay_id);
result.put("sign", paySign);
result.putAll(wxPayMap);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 回调方法,必须是无参数方法
* @param request 接收支付参数
* @param response 告诉微信结果
* @return
*/
@PostMapping(value = "/payCallback")
public String callBack(HttpServletRequest request, HttpServletResponse response){
//System.out.println("微信支付成功,微信发送的callback信息,请注意修改订单信息");
InputStream is = null;
String xmlBack="";
try {
is = request.getInputStream();//获取请求的流信息(这里是微信发的xml格式所有只能使用流来读)
String xml = WXPayUtil.InputStream2String(is, "UTF-8");
Map notifyMap = WXPayUtil.xmlToMap(xml);//将微信发的xml转map
System.out.println(notifyMap);
if(notifyMap.get("return_code").equals("SUCCESS")){
if(notifyMap.get("result_code").equals("SUCCESS")){
String out_trade_no = notifyMap.get("out_trade_no");//订单号
if (out_trade_no == null) {
System.err.println("微信支付回调失败订单号: {}"+notifyMap);
xmlBack = "" + "" + "" + " ";
return xmlBack;
}
/*以下是自己的业务处理------仅做参考
* 添加 商品订单表数据。清空购物车。 添加订单信息
*/
xmlBack = "" + "" + "" + " ";
return xmlBack;
}else{
xmlBack = "" + "" + "" + " ";
return xmlBack;
}
}
//告诉微信服务器收到信息了,不要在调用回调action了========这里很重要回复微信服务器信息用流发送一个xml即可
response.getWriter().write("");
is.close();
} catch (Exception e) {
e.printStackTrace();
System.err.println("微信支付回调通知失败");
String result = "" + "" + "" + " ";
return result;
}
return null;
}
}
- 小程序调用。
/**发起支付 */
navigateToOrder: function() {
var that = this;
var userinfo = wx.getStorageSync("userinfo");
/**
* 调用预支付接口
*/
wx.request({
url: app.globalData.server_root + "pay/ordersPay",
data: {
openid: userinfo.userOpenid,
addressid: that.data.selectId
},
success: function(res) {
console.log(res.data)
//调起支付窗口,参数为返回回来的签名
wx.requestPayment({
timeStamp: res.data.timeStamp,
nonceStr: res.data.nonceStr,
package: res.data.package,
paySign: res.data.sign,
signType: res.data.signType,
success(res) {
console.log(res.data)
}
})
}
})
}
- 支付成功调用回调地址设置的接口方法进行业务处理。至此结束