JAVA微信扫码支付功能实现(模式二)
“微信扫码支付模式二”官方文档地址在这 先认真看一下。
由于我是第一次给公司做微信支付功能,我就选择了微信支付里的扫码支付中的模式二,原因很简单模式二更简单,废话毕。
微信扫码支付模式二流程。
相信写微信支付的大部分已经写过登录了如果没有可以参考这里,其中需要的各种参数已经准备好,这里需要注意一下的是支付的AppId和登录的并不是一个。如图所示
下面是设计的一些工具类可以直接用就行,上代码
这是配置文件读取的工具类并不是只有微信支付才能用,当然也可以不用(如果没有配置文件)
package xkhd.weixinpay.utility;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* 配置文件读取
*
* @author Administrator
*
*/
public class ConfUtil {
public static void main(String[] args) {
ConfUtil cf=new ConfUtil();
System.out.println(cf.getConfig("weixinpayconfig.properties", "APP_ID"));
}
public String getConfig(String ConfigName,String str) {
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(ConfigName);
Properties p = new Properties();
try {
p.load(inputStream);
} catch (IOException e1) {
e1.printStackTrace();
}
//System.out.println("ip:"+p.getProperty("ip")+",port:"+p.getProperty("port"));
return p.getProperty(str);
}
}
负责发起post请求并获取的返回的工具类(需要注意的是微信支付里向微信发送的所有请求都是post请求)
package xkhd.weixinpay.utility;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
/**
* http工具类,负责发起post请求并获取的返回
*/
public class HttpUtil {
private final static int CONNECT_TIMEOUT = 5000; // in milliseconds
private final static String DEFAULT_ENCODING = "UTF-8";
public static String postData(String urlStr, String data){
System.out.println("http1");
return postData(urlStr, data, null);
}
public static String postData(String urlStr, String data, String contentType){
System.out.println("http2");
BufferedReader reader = null;
try {
URL url = new URL(urlStr);
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(CONNECT_TIMEOUT);
if(contentType != null)
conn.setRequestProperty("content-type", contentType);
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), DEFAULT_ENCODING);
if(data == null)
data = "";
writer.write(data);
writer.flush();
writer.close();
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), DEFAULT_ENCODING));
System.out.println("reader="+reader);
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println("line="+line);
sb.append(line);
sb.append("\r\n");
}
return sb.toString();
} catch (IOException e) {
//logger.error("Error connecting to " + urlStr + ": " + e.getMessage());
} finally {
try {
if (reader != null)
reader.close();
} catch (IOException e) {
}
}
return null;
}
}
MD5算法工具类
package xkhd.weixinpay.utility;
import java.security.MessageDigest;
public class MD5Util {
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString
.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString
.getBytes(charsetname)));
} catch (Exception exception) {
}
return resultString;
}
private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
}
签名工具类
package xkhd.weixinpay.utility;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
public class PayToolUtil {
/**
* 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名
* @return boolean
*/
public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if(!"sign".equals(k) && null != v && !"".equals(v)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
//算出摘要
String mysign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase();
String tenpaySign = ((String)packageParams.get("sign")).toLowerCase();
return tenpaySign.equals(mysign);
}
/**
* @author
* @Description:sign签名
* @param characterEncoding
* 编码格式
* @param parameters
* 请求参数
* @return
*/
public static String createSign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
/**
* @author
* @Description:将请求参数转换为xml格式的string
* @param parameters
* 请求参数
* @return
*/
public static String getRequestXml(SortedMap<Object, Object> parameters) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
} else {
sb.append("<" + k + ">" + v + "</" + k + ">");
}
}
sb.append("</xml>");
return sb.toString();
}
/**
*取出一个指定长度大小的随机正整数.
*
* @param length
* int 设定所取出随机数的长度。length小于11
* @return int 返回生成的随机数
*/
public static int buildRandom(int length) {
int num = 1;
double random = Math.random();
if (random < 0.1) {
random = random + 0.1;
}
for (int i = 0; i < length; i++) {
num = num * 10;
}
return (int) ((random * num));
}
/**
* 获取当前时间 yyyyMMddHHmmss
*
* @return String
*/
public static String getCurrTime() {
Date now = new Date();
SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String s = outFormat.format(now);
return s;
}
}
二维码生产工具类
package xkhd.weixinpay.utility;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import javax.imageio.ImageIO;
import com.google.zxing.common.BitMatrix;
/**
* 二维码生产工具类
*/
public class QRUtil {
private static final int BLACK = 0xFF000000;
private static final int WHITE = 0xFFFFFFFF;
private QRUtil() {}
public static BufferedImage toBufferedImage(BitMatrix matrix) {
int width = matrix.getWidth();
int height = matrix.getHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
}
}
return image;
}
public static void writeToFile(BitMatrix matrix, String format, File file)
throws IOException {
BufferedImage image = toBufferedImage(matrix);
if (!ImageIO.write(image, format, file)) {
throw new IOException("Could not write an image of format " + format + " to " + file);
}
}
public static void writeToStream(BitMatrix matrix, String format, OutputStream stream)
throws IOException {
System.out.println("%%%%%");
BufferedImage image = toBufferedImage(matrix);
System.out.println("image="+image);
if (!ImageIO.write(image, format, stream)) {
throw new IOException("Could not write an image of format " + format);
}
}
// public static void main(String[] args) throws Exception {
// String text = "http://www.baidu.com";
// int width = 300;
// int height = 300;
// //二维码的图片格式
// String format = "gif";
// Hashtable hints = new Hashtable();
// //内容所使用编码
// hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
// BitMatrix bitMatrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints);
// //生成二维码
// File outputFile = new File("d:"+File.separator+"new.gif");
// QRUtil.writeToFile(bitMatrix, format, outputFile);
// }
}
XML文件封及解析类
package xkhd.weixinpay.utility;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
public class XMLUtil4jdom {
/**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public static Map doXMLParse(String strxml) throws JDOMException, IOException {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if(null == strxml || "".equals(strxml)) {
return null;
}
Map<String, String> m = new HashMap<String, String>();
InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
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 = XMLUtil4jdom.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(XMLUtil4jdom.getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
}
所用到的工具类就这些
这段代码是本人实现微信支付逻辑业务层。
主要用来生成订单然后按要求向微信服务器发送请求获得微信返回的预支付交易单,并返回交易会话的二维码链接code_url。后台系统根据返回的code_url生成二维码。其中有的代码不能直接复用但可以借鉴。
主要接口有三个
(1)扫码支付
(2)查询支付状态
(3)微信通知接口
package xkhd.weixinpay.service.impl;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import xkhd.weixinpay.dao.WeiXinPayDao;
import xkhd.weixinpay.entity.Pay_order;
import xkhd.weixinpay.entity.Pay_record;
import xkhd.weixinpay.service.WeiXinPayService;
import xkhd.weixinpay.utility.ConfUtil;
import xkhd.weixinpay.utility.HttpUtil;
import xkhd.weixinpay.utility.PayToolUtil;
import xkhd.weixinpay.utility.XMLUtil4jdom;
/**
* 微信支付业务实现层
*
* @author Administrator
*
*/
@Service
public class WeiXinPayServiceImpl implements WeiXinPayService {
@Autowired
private WeiXinPayDao weiXinPayDao;
/**
* 扫码支付
*/
public Map<String, Object> Pay(String player_id, String machine_sn, String product_id) {
Map<String, Object> resultMap = new HashMap<String, Object>();
int pd = Integer.parseInt(player_id);
int ptd = Integer.parseInt(product_id);
long l1 = System.currentTimeMillis();
Timestamp date = new Timestamp(l1-(60*60*1000));
Pay_order pay_order = weiXinPayDao.findByPldMsPrdPay_order(pd, machine_sn, ptd,date);
if (pay_order == null) {
// 获取订单价格
int price = weiXinPayDao.findByPdPay_product(ptd);
// 商品描述
String body = weiXinPayDao.findByPdPay_productBody(ptd);
// 商品订单号
String out_trade_no = product_id + System.currentTimeMillis();
ConfUtil cf = new ConfUtil();
// 账号信息
String appid = cf.getConfig("weixinpayconfig.properties", "APP_ID");
// 商业号
String mch_id = cf.getConfig("weixinpayconfig.properties", "MCH_ID");
// key
String key = cf.getConfig("weixinpayconfig.properties", "API_KEY");
String currTime = PayToolUtil.getCurrTime();
String strTime = currTime.substring(8, currTime.length());
String strRandom = PayToolUtil.buildRandom(4) + "";
String nonce_str = strTime + strRandom;
// 获取发起电脑 ip
String spbill_create_ip = cf.getConfig("weixinpayconfig.properties", "CREATE_IP");
// 回调接口
String notify_url = cf.getConfig("weixinpayconfig.properties", "NOTIFY_URL");
// 扫码支付
String trade_type = "NATIVE";
SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
// 公众账号ID
packageParams.put("appid", appid);
// 商户号
packageParams.put("mch_id", mch_id);
// 随机字符串
packageParams.put("nonce_str", nonce_str);
// 商品描述
packageParams.put("body", body);
// 商户订单号
packageParams.put("out_trade_no", out_trade_no);
// 标价金额
packageParams.put("total_fee", Integer.toString(price));
// 终端IP
packageParams.put("spbill_create_ip", spbill_create_ip);
// 通知地址
packageParams.put("notify_url", notify_url);
// 交易类型
packageParams.put("trade_type", trade_type);
// 签名
String sign = PayToolUtil.createSign("UTF-8", packageParams, key);
packageParams.put("sign", sign);
String requestXML = PayToolUtil.getRequestXml(packageParams);
String resXml = HttpUtil.postData(cf.getConfig("weixinpayconfig.properties", "UFDODER_URL"), requestXML);
Map<?, ?> map = null;
try {
map = XMLUtil4jdom.doXMLParse(resXml);
//System.out.println("map=" + map);
} catch (Exception e) {
resultMap.put("api", "user/pay");
resultMap.put("code", 210);
}
String urlCode = (String) map.get("code_url");
//System.out.println("code_url=" + urlCode);
// 把数据存入pay_order表中
Pay_order pay_order1 = new Pay_order();
pay_order1.setPlayer_id(pd);
pay_order1.setMachine_sn(machine_sn);
pay_order1.setOut_trade_no(Long.parseLong(out_trade_no));
pay_order1.setProduct_id(ptd);
pay_order1.setBody(body);
pay_order1.setPrice(price);
pay_order1.setPrepay_id(map.get("prepay_id").toString());
pay_order1.setCode_url("http://api.xiaokanghudong.com/"+cf.getConfig("weixinpayconfig.properties", "VERSION")+"/user/obtain?code_url=" + urlCode);
pay_order1.setSign(sign);
pay_order1.setNonce_str(nonce_str);
long l = System.currentTimeMillis();
Timestamp create_time = new Timestamp(l);
pay_order1.setCreate_time(create_time);
//System.out.println("pay_order1=" + pay_order1);
weiXinPayDao.insertPay_order(pay_order1);
resultMap.put("api", "user/pay");
resultMap.put("code", 200);
resultMap.put("out_trade_no", out_trade_no);
resultMap.put("product_id", product_id);
resultMap.put("body", body);
resultMap.put("price", price);
resultMap.put("code_url", "http://api.xiaokanghudong.com/"+cf.getConfig("weixinpayconfig.properties", "VERSION")+"/user/obtain?code_url=" + urlCode);
} else {
resultMap.put("api", "user/pay");
resultMap.put("code", 200);
resultMap.put("out_trade_no", pay_order.getOut_trade_no());
resultMap.put("product_id", pay_order.getProduct_id());
resultMap.put("body", pay_order.getBody());
resultMap.put("price", pay_order.getPrice());
resultMap.put("code_url", pay_order.getCode_url());
}
return resultMap;
}
/**
* 查询支付状态
*/
public Map<String, Object> Pay_status(String out_trade_no) {
long otn = Long.parseLong(out_trade_no);
Pay_record pay_record = weiXinPayDao.findByOtnPay_record(otn);
Map<String, Object> resultMap = new HashMap<String, Object>();
if (pay_record == null) {
resultMap.put("api", "user/pay_status");
resultMap.put("code", 210);
} else {
if (pay_record.getResult_code() == "SUCCESS") {
resultMap.put("api", "user/pay_status");
resultMap.put("code", 200);
} else {
resultMap.put("api", "user/pay_status");
resultMap.put("code", 220);
}
}
return resultMap;
}
/**
* 微信通知接口
*/
public void wxpay_notify(String out_trade_no, String nonce_str, String result_code, String err_code,
String err_code_des) {
//long l1 = System.currentTimeMillis();
//Timestamp date = new Timestamp(l1);
// 根据out_trade_no查找到pay_order中的数据
Pay_order pay_order = weiXinPayDao.findByOtnPay_order(out_trade_no);
// weiXinPayDao.test(2, date);
if (pay_order != null) {
// weiXinPayDao.test(3, date);
// 再根据微信返回的结果,在pay_record中创建1条数据
Pay_record pay_record = new Pay_record();
pay_record.setPlayer_id(pay_order.getPlayer_id());
pay_record.setMachine_sn(pay_order.getMachine_sn());
pay_record.setOut_trade_no(pay_order.getOut_trade_no());
pay_record.setProduct_id(pay_order.getProduct_id());
pay_record.setBody(pay_order.getBody());
pay_record.setPrice(pay_order.getPrice());
pay_record.setSign(pay_order.getSign());
pay_record.setNonce_str(nonce_str);
pay_record.setResult_code(result_code);
pay_record.setErr_code(err_code);
pay_record.setErr_code_des(err_code_des);
pay_record.setOpenid(null);
pay_record.setBank_type(null);
pay_record.setTotal_fee(0);
pay_record.setTransaction_id(null);
pay_record.setTime_end(null);
pay_record.setTrade_state(null);
pay_record.setTrade_state_desc(null);
long l = System.currentTimeMillis();
Timestamp create_time = new Timestamp(l);
pay_record.setCreate_time(create_time);
//System.out.println("pay_record=" + pay_record);
// weiXinPayDao.test(4, date);
weiXinPayDao.insertPay_record(pay_record);
// weiXinPayDao.test(5, date);
// 根据out_trade_no和player_id将pay_order中相应的数据清掉
weiXinPayDao.deleteByPdOtdPay_order(pay_order.getPlayer_id(), pay_order.getOut_trade_no());
if ("SUCCESS".equals(result_code)) {
// weiXinPayDao.test(6, date);
// 在user_game_info中将player_id(该订单中的player_id)的payment_flag值置为0
weiXinPayDao.updatePdUser_game_infoPg(0, pay_order.getPlayer_id());
}
}
}
}
当然如果还需要其他功能只需用按官方文档发送相应的请求。
这里的逻辑和涉及到的功能可能比较简单,如果有哪里说得不完善的地方欢迎各方大神留言告知。