小编公司的项目是公众号里嵌H5页面调起微信支付,这也属于公众号支付的内容。
业务流程图:
统一下单接口地址:
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1这里写链接内容
小编项目业务流程:
点击支付按钮—-查询收获地址–若无,提示,若有提交订单—提交订单成功—调起支付—支付成功修改订单状态
在提交订单返回success时调起支付:
/*支付*/
$http.get(urlpath + '/weChat/pay.do', {
params: {
username: localStorage.getItem("username"),
totalMoney: productInfo.nhprice * buyCount
}
}).success(function(response) {
if(typeof WeixinJSBridge == "undefined") {
if(document.addEventListener) { document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else if(document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
} else {
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId": response.data.appid, //调后台pay方法返回的
"timeStamp": response.data.timeStamp,
"nonceStr": response.data.nonceStr,
"package": response.data.packageValue,
"signType": "MD5",
"paySign": response.data.paySign
},
function(res) {
if(res.err_msg == "get_brand_wcpay_request:ok") {
/*更新订单状态*/
}
}
})
controller中Pay方法:
@RequestMapping(value="/pay")
public String pay(HttpServletRequest request,HttpServletResponse response){
/*根据username查询用户的openid*/
String username=request.getParameter("username");
User user = userH5Service.getUserByUsername(username);
String openid="";
if(user!=null){
openid=user.getOpenid();
}
String money=request.getParameter("totalMoney");
response.setHeader("Access-Control-Allow-Origin", "*"); // 允许所有域名访问
String GZHID = "";// 微信公众号id,对应自己的账号
String GZHSecret = "";// 微信公众号密钥id,对应自己的账号
String SHHID = "";// 财付通商户号,对应自己的账号
String SHHKEY = "";// 商户号对应的密钥,对应自己的账号
/*------1.获取参数信息-------*/
//商户订单号
String out_trade_no = WXPayUtil.generateUUID();
//金额转化为分为单位
String finalmoney = WeChat.getMoney(money);
/*------2.生成预支付订单需要的的package数据-------*/
//随机数
String nonce_str= WXPayUtil.generateUUID();//MD5Encode.getMessageDigest(String.valueOf(new Random().nextInt(10000)).getBytes()).toUpperCase();
//订单生成的机器 IP
String spbill_create_ip = request.getRemoteAddr();
//交易类型 :jsapi代表微信公众号支付
String trade_type = "JSAPI";
//这里notify_url是 微信处理完支付后的回调的应用系统接口url。
String notify_url =".../weixinNotify.do"; //自己项目回调的地址
SortedMap<String, String> packageParams = new TreeMap<String, String>();
packageParams.put("appid", GZHID);
packageParams.put("mch_id", SHHID);
packageParams.put("nonce_str", nonce_str);
packageParams.put("body", "test");
packageParams.put("out_trade_no", out_trade_no);
packageParams.put("total_fee", finalmoney);
packageParams.put("spbill_create_ip", spbill_create_ip);
packageParams.put("notify_url", notify_url);
packageParams.put("trade_type", trade_type);
packageParams.put("openid", openid);
String createOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String prepay_id="";
try {
String xml1 = WXPayUtil.generateSignedXml(packageParams, SHHKEY);
System.err.println(xml1);
prepay_id = GetWxOrderno.getPayNo(createOrderURL, xml1);
System.out.println("prepay_id===="+prepay_id);
} catch (Exception e1) {
e1.printStackTrace();
}
/* ------3.将预支付订单的id和其他信息生成签名并一起返回 -------*/
//随机数
nonce_str= WXPayUtil.generateUUID();
SortedMap<String, String> finalpackage = new TreeMap<String, String>();
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String packages ="prepay_id="+prepay_id;
finalpackage.put("appId", GZHID);
finalpackage.put("timeStamp", timestamp);
finalpackage.put("nonceStr", nonce_str);
finalpackage.put("package", packages);
finalpackage.put("signType", "MD5");
String finalsign="";
try{
finalsign=WXPayUtil.generateSignature(finalpackage, SHHKEY);
}catch(Exception e1){
e1.printStackTrace();
}
JSONObject jo = new JSONObject();
JSONObject jos = new JSONObject(); // 声明对象
jos.put("appid", GZHID);
jos.put("timeStamp", timestamp);
jos.put("nonceStr", nonce_str);
jos.put("packageValue", packages);
jos.put("paySign", finalsign);
jo.put("code", 200);
jo.put("msg","成功");
jo.put("success",true);
jo.put("data",jos.toString());
String json=jo.toString();
OutJson.outJson(response, json);
return null;
}
回调方法weixinNotify,小编也是写在controller里的:
/** * 提交支付后的微信异步返回接口 */
@RequestMapping(value = "/weixinNotify")
public void weixinNotify(HttpServletRequest request, HttpServletResponse response) {
response.setHeader("Access-Control-Allow-Origin", "*"); // 允许所有域名访问
String out_trade_no = null;
String return_code = null;
try {
InputStream inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
String resultStr = new String(outSteam.toByteArray(), "utf-8");
System.out.println("支付成功的回调:" + resultStr);
// logger.info("支付成功的回调:"+resultStr);
Map<String, Object> resultMap = parseXmlToList(resultStr);
String result_code = (String) resultMap.get("result_code");
String is_subscribe = (String) resultMap.get("is_subscribe");
String transaction_id = (String) resultMap.get("transaction_id");
String sign = (String) resultMap.get("sign");
String time_end = (String) resultMap.get("time_end");
String bank_type = (String) resultMap.get("bank_type");
out_trade_no = (String) resultMap.get("out_trade_no");
return_code = (String) resultMap.get("return_code");
request.setAttribute("out_trade_no", out_trade_no);
// 通知微信.异步确认成功.必写.不然微信会一直通知后台.八次之后就认为交易失败了.
response.getWriter().write(RequestHandler.setXML("SUCCESS", ""));
} catch (Exception e) {
// logger.error("微信回调接口出现错误:",e);
try {
response.getWriter().write(RequestHandler.setXML("FAIL", "error"));
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (return_code.equals("SUCCESS")) {
System.out.println("支付成功+SUCCESS");
// 支付成功的业务逻辑
} else {
System.out.println("支付失败+SUCCESS");
// 支付失败的业务逻辑
}
}
工具类:
WXPayUtil:
public class WXPayUtil {
/** * 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>();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.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 {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
org.w3c.dom.Document document = documentBuilder.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() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
/** * 生成 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();
}
/** * 生成 uuid, 即用来标识一笔单,也用做 nonce_str * @return */
public static String generateUUID() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
}
WeChat:
public class WeChat {
/** * 元转换成分 * @param money * @return */
public static String getMoney(String amount) {
if(amount==null){
return "";
}
// 金额转化为分为单位
String currency = amount.replaceAll("\\$|\\¥|\\,", ""); //处理包含, ¥ 或者$的金额
int index = currency.indexOf(".");
int length = currency.length();
Long amLong = 0l;
if(index == -1){
amLong = Long.valueOf(currency+"00");
}else if(length - index >= 3){
amLong = Long.valueOf((currency.substring(0, index+3)).replace(".", ""));
}else if(length - index == 2){
amLong = Long.valueOf((currency.substring(0, index+2)).replace(".", "")+0);
}else{
amLong = Long.valueOf((currency.substring(0, index+1)).replace(".", "")+"00");
}
return amLong.toString();
}
}
GetWxOrderno:
public class GetWxOrderno {
public static DefaultHttpClient httpclient;
static
{
httpclient = new DefaultHttpClient();
httpclient = (DefaultHttpClient)HttpClientConnectionManager.getSSLInstance(httpclient);
}
/** *description:获取预支付id *@param url *@param xmlParam *@return * @author ex_yangxiaoyi * @see */
public static String getPayNo(String url,String xmlParam){
DefaultHttpClient client = new DefaultHttpClient();
client.getParams().setParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true);
HttpPost httpost= HttpClientConnectionManager.getPostMethod(url);
String prepay_id = "";
try {
httpost.setEntity(new StringEntity(xmlParam, "UTF-8"));
HttpResponse response = httpclient.execute(httpost);
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
if(jsonStr.indexOf("FAIL")!=-1){
return prepay_id;
}
Map map = doXMLParse(jsonStr);
prepay_id = (String) map.get("prepay_id");
} catch (Exception e) {
e.printStackTrace();
}
return prepay_id;
}
/** * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。 * @param strxml * @return * @throws JDOMException * @throws IOException */
public static Map doXMLParse(String strxml) throws Exception {
if(null == strxml || "".equals(strxml)) {
return null;
}
Map m = new HashMap();
InputStream in = String2Inputstream(strxml);
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(in);
Element root = doc.getRootElement();
List list = root.getChildren();
Iterator it = list.iterator();
while(it.hasNext()) {
Element e = (Element) it.next();
String k = e.getName();
String v = "";
List children = e.getChildren();
if(children.isEmpty()) {
v = e.getTextNormalize();
} else {
v = getChildrenText(children);
}
m.put(k, v);
}
//关闭流
in.close();
return m;
}
/** * 获取子结点的xml * @param children * @return String */
public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if(!children.isEmpty()) {
Iterator it = children.iterator();
while(it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<" + name + ">");
if(!list.isEmpty()) {
sb.append(getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
public static InputStream String2Inputstream(String str) {
return new ByteArrayInputStream(str.getBytes());
}
}
解析微信通知xml,也是放在controller里的:
/** * description: 解析微信通知xml * * @param xml * @return * @author ex_yangxiaoyi * @see */
@SuppressWarnings({ "unused", "rawtypes", "unchecked" })
private static Map parseXmlToList(String xml) {
Map retMap = new HashMap();
try {
StringReader read = new StringReader(xml);
// 创建新的输入源SAX 解析器将使用 InputSource 对象来确定如何读取 XML 输入
InputSource source = new InputSource(read);
// 创建一个新的SAXBuilder
SAXBuilder sb = new SAXBuilder();
// 通过输入源构造一个Document
Document doc = (Document) sb.build(source);
Element root = doc.getRootElement();// 指向根节点
List<Element> es = root.getChildren();
if (es != null && es.size() != 0) {
for (Element element : es) {
retMap.put(element.getName(), element.getValue());
}
}
} catch (Exception e) {
e.printStackTrace();
}
return retMap;
}
总结:
小编有写的不明白的地方或者少了那个工具类,欢迎留言!