packagecom.card.mp.controller;importcom.alibaba.fastjson.JSON;importcom.alibaba.fastjson.JSONObject;importcom.card.dto.PaymentDto;import com.card.framework.utils.*;importorg.dom4j.Document;importorg.dom4j.DocumentException;importorg.dom4j.Element;importorg.dom4j.io.SAXReader;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.ResponseBody;importjavax.servlet.http.HttpServletRequest;importjava.io.ByteArrayInputStream;importjava.io.InputStream;importjava.io.UnsupportedEncodingException;importjava.text.SimpleDateFormat;importjava.util.Date;importjava.util.HashMap;importjava.util.List;importjava.util.Map;
@Controllerpublic class WeiXinPaymentController extendsBaseController {private final String mch_id = "填写商户号";//商户号
private final String spbill_create_ip = "填写终端IP";//终端IP
private final String notify_url = "域名/weixin/paycallback.do";//通知地址
private final String trade_type = "JSAPI";//交易类型
private final String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";//统一下单API接口链接
private final String key = "&key=填写商户支付密钥"; //商户支付密钥
private final String appid = "填写小程序AppId";/***
*@paramopenId
*@paramtotal_fee 订单总金额,单位为分。
*@parambody 商品简单描述,该字段请按照规范传递。 例:腾讯充值中心-心悦会员充值
*@paramattach 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 例:广州分店
*@return*@throwsUnsupportedEncodingException
*@throwsDocumentException*/@RequestMapping("/weixin/payment.do")
@ResponseBodypublic JSONObject payment(@RequestParam(required = true) String openId, @RequestParam(required = true)String total_fee, @RequestParam(required = false) String body, @RequestParam(required = false) String attach) throwsUnsupportedEncodingException, DocumentException {
JSONObject JsonObject= newJSONObject() ;
body= new String(body.getBytes("UTF-8"),"ISO-8859-1");
String nonce_str= UUIDHexGenerator.generate();//随机字符串
String today = new SimpleDateFormat("yyyyMMddHHmmss").format(newDate());
String code= PayUtil.createCode(8);
String out_trade_no= mch_id + today + code;//商户订单号
String openid= openId;//用户标识
PaymentDto paymentPo = newPaymentDto();
paymentPo.setAppid(appid);
paymentPo.setMch_id(mch_id);
paymentPo.setNonce_str(nonce_str);
String newbody= new String(body.getBytes("ISO-8859-1"),"UTF-8");//以utf-8编码放入paymentPo,微信支付要求字符编码统一采用UTF-8字符编码
paymentPo.setBody(newbody);
paymentPo.setOut_trade_no(out_trade_no);
paymentPo.setTotal_fee(total_fee);
paymentPo.setSpbill_create_ip(spbill_create_ip);
paymentPo.setNotify_url(notify_url);
paymentPo.setTrade_type(trade_type);
paymentPo.setOpenid(openid);//把请求参数打包成数组
Map sParaTemp = newHashMap();
sParaTemp.put("appid", paymentPo.getAppid());
sParaTemp.put("mch_id", paymentPo.getMch_id());
sParaTemp.put("nonce_str", paymentPo.getNonce_str());
sParaTemp.put("body", paymentPo.getBody());
sParaTemp.put("out_trade_no", paymentPo.getOut_trade_no());
sParaTemp.put("total_fee",paymentPo.getTotal_fee());
sParaTemp.put("spbill_create_ip", paymentPo.getSpbill_create_ip());
sParaTemp.put("notify_url",paymentPo.getNotify_url());
sParaTemp.put("trade_type", paymentPo.getTrade_type());
sParaTemp.put("openid", paymentPo.getOpenid());//除去数组中的空值和签名参数
Map sPara =PayUtil.paraFilter(sParaTemp);
String prestr= PayUtil.createLinkString(sPara); //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串//MD5运算生成签名
String mysign = PayUtil.sign(prestr, key, "utf-8").toUpperCase();
paymentPo.setSign(mysign);//打包要发送的xml
String respXml =XmlUtil.messageToXML(paymentPo);//打印respXml发现,得到的xml中有“__”不对,应该替换成“_”
respXml = respXml.replace("__", "_");
String param=respXml;//String result = SendRequestForUrl.sendRequest(url, param);//发起请求
String result = PayUtil.httpRequest(url, "POST", param);
System.out.println("请求微信预支付接口,返回 result:"+result);//将解析结果存储在Map中
Map map = newHashMap();
InputStream in=newByteArrayInputStream(result.getBytes());//读取输入流
SAXReader reader = newSAXReader();
Document document=reader.read(in);//得到xml根元素
Element root =document.getRootElement();//得到根元素的所有子节点
List elementList =root.elements();for(Element element : elementList) {
map.put(element.getName(), element.getText());
}//返回信息
String return_code = map.get("return_code").toString();//返回状态码
String return_msg = map.get("return_msg").toString();//返回信息
String result_code = map.get("result_code").toString;//返回状态码
System.out.println("请求微信预支付接口,返回 code:" +return_code);
System.out.println("请求微信预支付接口,返回 msg:" +return_msg);if("SUCCESS".equals(return_code) && "SUCCESS".equals(result_code)){//业务结果
String prepay_id = map.get("prepay_id").toString();//返回的预付单信息
String nonceStr =UUIDHexGenerator.generate();
JsonObject.put("nonceStr", nonceStr);
JsonObject.put("package", "prepay_id=" +prepay_id);
Long timeStamp= System.currentTimeMillis() / 1000;
JsonObject.put("timeStamp", timeStamp + "");
String stringSignTemp= "appId=" + appid + "&nonceStr=" + nonceStr + "&package=prepay_id=" + prepay_id + "&signType=MD5&timeStamp=" +timeStamp;//再次签名
String paySign = PayUtil.sign(stringSignTemp, key, "utf-8").toUpperCase();
JsonObject.put("paySign", paySign);
}returnJsonObject;
}/*** 预支付时填写的 notify_url ,支付成功后的回调接口
*@paramrequest*/@RequestMapping("/weixin/paycallback.do")
@ResponseBodypublic voidpaycallback(HttpServletRequest request) {try{
Map dataMap =XmlUtil.parseXML(request);
System.out.println(JSON.toJSONString(dataMap));//{"transaction_id":"4200000109201805293331420304","nonce_str":"402880e963a9764b0163a979a16e0002","bank_type":"CFT","openid":"oXI6G5Jc4D44y2wixgxE3OPwpDVg","sign":"262978D36A3093ACBE4B55707D6EA7B2","fee_type":"CNY","mch_id":"1491307962","cash_fee":"10","out_trade_no":"14913079622018052909183048768217","appid":"wxa177427bc0e60aab","total_fee":"10","trade_type":"JSAPI","result_code":"SUCCESS","time_end":"20180529091834","is_subscribe":"N","return_code":"SUCCESS"}
} catch(Exception e) {
e.printStackTrace();
}
}
}
后台业务逻辑涉及到的工具类及参数封装类
XmlUtilpackagecom.card.framework.utils;importcom.card.dto.PaymentDto;importcom.thoughtworks.xstream.XStream;importcom.thoughtworks.xstream.core.util.QuickWriter;importcom.thoughtworks.xstream.io.HierarchicalStreamWriter;importcom.thoughtworks.xstream.io.xml.PrettyPrintWriter;importcom.thoughtworks.xstream.io.xml.XppDriver;importorg.dom4j.Document;importorg.dom4j.DocumentException;importorg.dom4j.Element;importorg.dom4j.io.SAXReader;importjavax.servlet.http.HttpServletRequest;importjava.io.IOException;importjava.io.Writer;importjava.util.HashMap;importjava.util.List;importjava.util.Map;public classXmlUtil {public static Map parseXML(HttpServletRequest request) throwsIOException, DocumentException {
Map map=new HashMap();/*通过IO获得Document*/SAXReader reader= newSAXReader();
Document doc=reader.read(request.getInputStream());//得到xml的根节点
Element root=doc.getRootElement();
recursiveParseXML(root,map);returnmap;
}private static void recursiveParseXML(Element root,Mapmap){//得到根节点的子节点列表
List elementList=root.elements();//判断有没有子元素列表
if(elementList.size()==0){
map.put(root.getName(), root.getTextTrim());
}else{//遍历
for(Element e:elementList){
recursiveParseXML(e,map);
}
}
}private static XStream xstream = new XStream(newXppDriver() {publicHierarchicalStreamWriter createWriter(Writer out) {return newPrettyPrintWriter(out) {//对所有xml节点都增加CDATA标记
boolean cdata = true;public voidstartNode(String name, Class clazz) {super.startNode(name, clazz);
}protected voidwriteText(QuickWriter writer, String text) {if(cdata) {
writer.write(text);
}else{
writer.write(text);
}
}
};
}
});public staticString messageToXML(PaymentDto paymentPo){
xstream.alias("xml",PaymentDto.class);returnxstream.toXML(paymentPo);
}
}
PaymentDto//封装支付参数实体
packagecom.card.dto;importjava.io.Serializable;public class PaymentDto implementsSerializable {private String appid;//小程序ID
private String mch_id;//商户号
private String device_info;//设备号
private String nonce_str;//随机字符串
private String sign;//签名
private String body;//商品描述
private String detail;//商品详情
private String attach;//附加数据
private String out_trade_no;//商户订单号
private String fee_type;//货币类型
private String spbill_create_ip;//终端IP
private String time_start;//交易起始时间
private String time_expire;//交易结束时间
private String goods_tag;//商品标记
private String total_fee;//总金额
private String notify_url;//通知地址
private String trade_type;//交易类型
private String limit_pay;//指定支付方式
private String openid;//用户标识
publicString getAppid() {returnappid;
}public voidsetAppid(String appid) {this.appid =appid;
}publicString getMch_id() {returnmch_id;
}public voidsetMch_id(String mch_id) {this.mch_id =mch_id;
}publicString getNonce_str() {returnnonce_str;
}public voidsetNonce_str(String nonce_str) {this.nonce_str =nonce_str;
}publicString getSign() {returnsign;
}public voidsetSign(String sign) {this.sign =sign;
}publicString getBody() {returnbody;
}public voidsetBody(String body) {this.body =body;
}publicString getOut_trade_no() {returnout_trade_no;
}public voidsetOut_trade_no(String out_trade_no) {this.out_trade_no =out_trade_no;
}publicString getTotal_fee() {returntotal_fee;
}public voidsetTotal_fee(String total_fee) {this.total_fee =total_fee;
}publicString getNotify_url() {returnnotify_url;
}public voidsetNotify_url(String notify_url) {this.notify_url =notify_url;
}publicString getTrade_type() {returntrade_type;
}public voidsetTrade_type(String trade_type) {this.trade_type =trade_type;
}publicString getOpenid() {returnopenid;
}public voidsetOpenid(String openid) {this.openid =openid;
}publicString getSpbill_create_ip() {returnspbill_create_ip;
}public voidsetSpbill_create_ip(String spbill_create_ip) {this.spbill_create_ip =spbill_create_ip;
}publicString getDevice_info() {returndevice_info;
}public voidsetDevice_info(String device_info) {this.device_info =device_info;
}publicString getDetail() {returndetail;
}public voidsetDetail(String detail) {this.detail =detail;
}publicString getAttach() {returnattach;
}public voidsetAttach(String attach) {this.attach =attach;
}publicString getFee_type() {returnfee_type;
}public voidsetFee_type(String fee_type) {this.fee_type =fee_type;
}publicString getTime_start() {returntime_start;
}public voidsetTime_start(String time_start) {this.time_start =time_start;
}publicString getTime_expire() {returntime_expire;
}public voidsetTime_expire(String time_expire) {this.time_expire =time_expire;
}publicString getGoods_tag() {returngoods_tag;
}public voidsetGoods_tag(String goods_tag) {this.goods_tag =goods_tag;
}publicString getLimit_pay() {returnlimit_pay;
}public voidsetLimit_pay(String limit_pay) {this.limit_pay =limit_pay;
}
}
PayUtilpackagecom.card.framework.utils;importorg.apache.commons.codec.digest.DigestUtils;import java.io.*;importjava.net.HttpURLConnection;importjava.net.URL;import java.util.*;public classPayUtil {/*** 签名字符串
*@paramtext 需要签名的字符串
*@paramkey 密钥
*@paraminput_charset 编码格式
*@return签名结果*/
public staticString sign(String text, String key, String input_charset) {
text= text +key;returnDigestUtils.md5Hex(getContentBytes(text, input_charset));
}/*** 签名字符串
*@paramtext 需要签名的字符串
*@paramsign 签名结果
*@paramkey 密钥
*@paraminput_charset 编码格式
*@return签名结果*/
public static booleanverify(String text, String sign, String key, String input_charset) {
text= text +key;
String mysign=DigestUtils.md5Hex(getContentBytes(text, input_charset));returnmysign.equals(sign);
}/***@paramcontent
*@paramcharset
*@return*@throwsUnsupportedEncodingException*/
public static byte[] getContentBytes(String content, String charset) {if (charset == null || "".equals(charset)) {returncontent.getBytes();
}try{returncontent.getBytes(charset);
}catch(UnsupportedEncodingException e) {throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" +charset);
}
}/*** 生成6位或10位随机数 param codeLength(多少位)
*@return
*/
public static String createCode(intcodeLength) {
String code= "";for (int i = 0; i < codeLength; i++) {
code+= (int) (Math.random() * 9);
}returncode;
}private static boolean isValidChar(charch) {if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))return true;return (ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f);
}/*** 除去数组中的空值和签名参数
*@paramsArray 签名参数组
*@return去掉空值与签名参数后的新签名参数组*/
public static Map paraFilter(MapsArray) {
Map result= newHashMap();if (sArray == null || sArray.size() <= 0) {returnresult;
}for(String key : sArray.keySet()) {
String value=(String) sArray.get(key);if (value == null || value.equals("") || key.equalsIgnoreCase("sign")|| key.equalsIgnoreCase("sign_type")) {continue;
}
result.put(key, value);
}returnresult;
}/*** 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*@paramparams 需要排序并参与字符拼接的参数组
*@return拼接后字符串*/
public staticString createLinkString(Map params) {
List keys= newArrayList(params.keySet());
Collections.sort(keys);
String prestr= "";for (int i = 0; i < keys.size(); i++) {
String key=(String) keys.get(i);
String value=(String) params.get(key);if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符
prestr = prestr + key + "=" +value;
}else{
prestr= prestr + key + "=" + value + "&";
}
}returnprestr;
}/***
*@paramrequestUrl 请求地址
*@paramrequestMethod 请求方法
*@paramoutputStr 参数*/
public staticString httpRequest(String requestUrl,String requestMethod,String outputStr){//创建SSLContext
StringBuffer buffer=null;try{
URL url= newURL(requestUrl);
HttpURLConnection conn=(HttpURLConnection) url.openConnection();
conn.setRequestMethod(requestMethod);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.connect();//往服务器端写内容
if(null !=outputStr){
OutputStream os=conn.getOutputStream();
os.write(outputStr.getBytes("utf-8"));
os.close();
}//读取服务器端返回的内容
InputStream is =conn.getInputStream();
InputStreamReader isr= new InputStreamReader(is, "utf-8");
BufferedReader br= newBufferedReader(isr);
buffer= newStringBuffer();
String line= null;while ((line = br.readLine()) != null) {
buffer.append(line);
}
}catch(Exception e){
e.printStackTrace();
}returnbuffer.toString();
}public staticString urlEncodeUTF8(String source){
String result=source;try{
result=java.net.URLEncoder.encode(source, "UTF-8");
}catch(UnsupportedEncodingException e) {//TODO Auto-generated catch block
e.printStackTrace();
}returnresult;
}
}
UUIDHexGenerator//生成随机数工具类
packagecom.card.framework.utils;importjava.net.InetAddress;public classUUIDHexGenerator {private static String sep = "";private static final intIP;private static short counter = (short) 0;private static final int JVM = (int) (System.currentTimeMillis() >>> 8);private static UUIDHexGenerator uuidgen = newUUIDHexGenerator();static{intipadd;try{
ipadd=toInt(InetAddress.getLocalHost().getAddress());
}catch(Exception e) {
ipadd= 0;
}
IP=ipadd;
}public staticUUIDHexGenerator getInstance() {returnuuidgen;
}public static int toInt(byte[] bytes) {int result = 0;for (int i = 0; i < 4; i++) {
result= (result << 8) - Byte.MIN_VALUE +bytes[i];//result = (result << - Byte.MIN_VALUE + (int) bytes);
}returnresult;
}protected static String format(intintval) {
String formatted=Integer.toHexString(intval);
StringBuffer buf= new StringBuffer("00000000");
buf.replace(8 - formatted.length(), 8, formatted);returnbuf.toString();
}protected static String format(shortshortval) {
String formatted=Integer.toHexString(shortval);
StringBuffer buf= new StringBuffer("0000");
buf.replace(4 - formatted.length(), 4, formatted);returnbuf.toString();
}protected static intgetJVM() {returnJVM;
}protected synchronized static shortgetCount() {if (counter < 0) {
counter= 0;
}return counter++;
}protected static intgetIP() {returnIP;
}protected static shortgetHiTime() {return (short) (System.currentTimeMillis() >>> 32);
}protected static intgetLoTime() {return (int) System.currentTimeMillis();
}public staticString generate() {return new StringBuffer(36).append(format(getIP())).append(sep).append(format(getJVM())).append(sep)
.append(format(getHiTime())).append(sep).append(format(getLoTime())).append(sep)
.append(format(getCount())).toString();
}/***@paramargs*/
public static voidmain(String[] args) {
String id="";
UUIDHexGenerator uuid=UUIDHexGenerator.getInstance();/*for (int i = 0; i < 100; i++) {
id = uuid.generate();
}*/id=generate();
System.out.println(id);
}
}