com.github.wxpay
wxpay-sdk
0.0.3
dom4j
dom4j
1.6.1
import com.github.wxpay.sdk.WXPayConfig;
import com.juehua.single.constant.PayConstant;
import com.juehua.single.model.qingshiyuanpay.ExceptionUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 微信 配置实现类
* Created by Jason on 2018/11/20.
*/
public class WXPayConfigImpl implements WXPayConfig{
private byte[] certData;
private static WXPayConfigImpl INSTANCE;
private static String cert_path;
private static String notify_url;
private WXPayConfigImpl(String certPath,String notifyUrl) throws Exception{
ExceptionUtil.checkEmpty(certPath,"Sorry,Don't found your cert!"); // 校验证书路径
ExceptionUtil.checkEmpty(notifyUrl,"Notify url can not null!"); // 校验回路径
// 读取API证书
File file = new File(certPath);
InputStream certStream = new FileInputStream(file);
this.certData = new byte[(int) file.length()];
certStream.read(this.certData);
certStream.close();
}
/**
* 获取实现类对象
* @param certPath
* @param notifyUrl
* @return
* @throws Exception
*/
public static WXPayConfigImpl getInstance(String certPath,String notifyUrl) throws Exception{
notify_url = notifyUrl;
cert_path = certPath;
if (INSTANCE == null) {
synchronized (WXPayConfigImpl.class) {
if (INSTANCE == null) {
INSTANCE = new WXPayConfigImpl(cert_path,notify_url);
}
}
}
return INSTANCE;
}
/**
* 获取证书字节流
* @return
*/
public InputStream getCertStream() {
ByteArrayInputStream certBis;
certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
public String getAppID() {
return PayConstant.APPID;
}
public String getMchID() {
return PayConstant.MCHID;
}
public String getKey() {
return PayConstant.APIKEY;
}
public String getNotifyUrl() {
return notify_url;
}
public String getTradeType() {
return PayConstant.TRADE_TYPE;
}
// 请求连接超时时长
public int getHttpConnectTimeoutMs() {
return PayConstant.HTTP_CONNECT_TIMEOUT;
}
// 请求读取超时时长
public int getHttpReadTimeoutMs() {
return PayConstant.HTTP_READ_TIMEOUT;
}
public String getPrimaryDomain() {
return PayConstant.PRIMARY_DEMAIN;
}
public String getAlternateDomain() {
return PayConstant.ALTERNATE_DEMAIN;
}
public int getReportWorkerNum() {
return PayConstant.REPORT_WORKER_NUM;
}
public int getReportBatchSize() {
return PayConstant.REPORT_BATCH_SIZE;
}
public String getSpbillCreateIp() {
return PayConstant.BILL_CREATE_IP;
}
}
/**
* 支付接口相关常量
* Created by Jason on 2018/11/15.
*/
public final class PayConstant {
public static String ORDER_NAME = "XXX技术服务费";
public static String BODY = "备注";
/**
* 以下常量是 微信 支付相关
*/
// 公众号
public static String APPID ="你的公众号appid";
// 商户号
public static String MCHID ="你的商户号";
// API 安全密钥
public static String APIKEY ="你配置的API安全密钥";
// 支付类型 NATIVE
public static String TRADE_TYPE="NATIVE";
// 链接超时时长
public static Integer HTTP_CONNECT_TIMEOUT=2000;
// 请求读取超时时长
public static Integer HTTP_READ_TIMEOUT = 10000;
public static String PRIMARY_DEMAIN = "api.mch.weixin.qq.com";
public static String ALTERNATE_DEMAIN = "api2.mch.weixin.qq.com";
public static Integer REPORT_WORKER_NUM = 1;
public static Integer REPORT_BATCH_SIZE= 2;
public static String BILL_CREATE_IP="192.168.1.1";
}
import com.github.wxpay.sdk.WXPay;
import com.juehua.single.model.qingshiyuanpay.MD5Util;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.Map.Entry;
/**
* 微信支付业务
* Created by Jason on 2018/11/20.
*/
public class WXPayService {
private static Logger logger = LoggerFactory.getLogger(WXPayService.class);
private WXPay wxpay;
private WXPayConfigImpl config;
private static WXPayService INSTANCE;
private WXPayService(String certPath, String notifyUrl) throws Exception {
config = WXPayConfigImpl.getInstance(certPath,notifyUrl);
wxpay = new WXPay(config);
}
public static WXPayService getInstance(String certPath,String notifyUrl) throws Exception {
if (INSTANCE == null) {
synchronized (WXPayConfigImpl.class) {
if (INSTANCE == null) {
INSTANCE = new WXPayService(certPath,notifyUrl);
}
}
}
return INSTANCE;
}
/**
* 微信下单业务
*
* @param out_trade_no 订单号
* @param body 商家商品名
* @param money 总金额
* @param applyNo 商品编号
* @return
*/
public String doUnifiedOrder(String out_trade_no, String body, Double money, String applyNo) {
String amt = String.valueOf(money * 100);
HashMap<String, String> data = new HashMap<String, String>();
data.put("body", body);
data.put("out_trade_no", out_trade_no);
data.put("device_info", "web");
data.put("fee_type", "CNY");
data.put("total_fee", amt.substring(0, amt.lastIndexOf(".")));
data.put("spbill_create_ip", config.getSpbillCreateIp());
data.put("notify_url", config.getNotifyUrl());
data.put("trade_type", config.getTradeType());
data.put("product_id", applyNo);
System.out.println(String.valueOf(money * 100));
// data.put("time_expire", "20170112104120");
try {
Map<String, String> r = wxpay.unifiedOrder(data);
logger.info("返回的参数是" + r);
return r.get("code_url");
} catch (Exception e) {
e.printStackTrace();
logger.info(e.getMessage());
return null;
}
}
/**
* 退款业务
*/
public void doRefund(String out_trade_no, String total_fee) {
logger.info("退款时的订单号为:" + out_trade_no + "退款时的金额为:" + total_fee);
String amt = String.valueOf(Double.parseDouble(total_fee) * 100);
HashMap<String, String> data = new HashMap<String, String>();
data.put("out_trade_no", out_trade_no);
data.put("out_refund_no", out_trade_no);
data.put("total_fee", amt.substring(0, amt.lastIndexOf(".")));
data.put("refund_fee", amt.substring(0, amt.lastIndexOf(".")));
data.put("refund_fee_type", "CNY");
data.put("op_user_id", config.getMchID());
try {
Map<String, String> r = wxpay.refund(data);
logger.info("退款操作返回的参数为" + r);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 微信验证签名
*
* @return
* @throws DocumentException
*/
public boolean checkSign(String strXML) throws DocumentException {
SortedMap<String, String> smap = new TreeMap<String, String>();
Document doc = DocumentHelper.parseText(strXML);
Element root = doc.getRootElement();
for (Iterator iterator = root.elementIterator(); iterator.hasNext();) {
Element e = (Element) iterator.next();
smap.put(e.getName(), e.getText());
}
return isWechatSign(smap,config.getKey());
}
private boolean isWechatSign(SortedMap<String, String> smap,String apiKey) {
StringBuffer sb = new StringBuffer();
Set<Entry<String, String>> es = smap.entrySet();
Iterator<Entry<String, String>> it = es.iterator();
while (it.hasNext()) {
Entry<String, String> entry = it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if (!"sign".equals(k) && null != v && !"".equals(v) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + apiKey);
/** 验证的签名 */
String sign = MD5Util.MD5Encode(sb.toString(), "utf-8").toUpperCase();
/** 微信端返回的合法签名 */
String validSign = ((String) smap.get("sign")).toUpperCase();
return validSign.equals(sign);
}
}
import com.alibaba.druid.support.json.JSONUtils;
import com.alibaba.druid.util.StringUtils;
import com.github.wxpay.sdk.WXPayUtil;
import com.google.zxing.WriterException;
import com.juehua.single.constant.DicConstant;
import com.juehua.single.constant.PayConstant;
import com.juehua.single.controller.BaseController;
import com.juehua.single.model.qingshiyuanpay.ExceptionUtil;
import com.juehua.single.model.qingshiyuanpay.UnifiedOrderRespose;
import com.juehua.single.po.biz.BizOrder;
import com.juehua.single.po.sys.SysInfo;
import com.juehua.single.qrcode.DateCompareUtil;
import com.juehua.single.qrcode.QRCodeUtil;
import com.juehua.single.service.biz.BizOrderService;
import com.juehua.single.service.biz.BizServiceService;
import com.juehua.single.service.bse.BseDicService;
import com.juehua.single.service.impl.alipay.AlipayViewServiceImpl;
import com.juehua.single.service.impl.wxpay.WXPayService;
import com.juehua.single.service.sys.SysInfoService;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 微信支付接口开发控制器
* Created by Jason on 2018/11/20.
*/
@Controller
@Slf4j
@RequestMapping("/portal")
public final class WXPayController extends BaseController{
// ----------------这些是个人业务 因人而异 --------------
@Autowired
private AlipayViewServiceImpl alipayViewServiceImpl;
@Autowired
private BizOrderService bizOrderService;
@Autowired
private BseDicService bseDicService;
@Autowired
private BizServiceService bizServiceService;
@Autowired
private SysInfoService sysInfoService;
// -------------- 这些是在配置文件中配置的 使用@Value注解注入 ----------------
@Value("${juehua.request.address}")
private String reqPath; //资源请求路径
@Value("${weixin.qrcode.path}")
private String qrcodePath; //生成的二维码地址
@Value("${weixin.cert.path}")
private String certPath; //注入证书路径
@Value("${weixin.notify.url}")
private String notifyUrl; //回调地址(异步)
@Value("${img.server.port}")
private String imgPort; //图片服务器端口
/**
* 映射微信支付
*
* @param orderId 商户单号
* @param amount 购买商品总数量
* @param map freemaker 返回渲染数据
* @return
*/
@RequestMapping("/wxpay")
public String wxPay(String orderId, Integer amount, String orgId, Map<String, Object> map) throws Exception {
// 计算总费用 这里调用你的计算你的
Double total_fee = alipayViewServiceImpl.wxCalculatePrice(amount);
if (total_fee > 0) {
WXPayService wxPayService = WXPayService.getInstance(certPath, notifyUrl);
String codeUrl = wxPayService.doUnifiedOrder(orderId, "清视元", total_fee, "产品id");
// 订单保存到数据库 这是我的,具体需要看你们自己的业务
BizOrder bizOrder = new BizOrder();
bizOrder.setAmount(amount);
bizOrder.setAppid(PayConstant.APPID);
bizOrder.setMacId(orderId);
bizOrder.setTotalFee(String.valueOf(total_fee));
bizOrder.setOrgId(Long.parseLong(orgId));
BizOrder bo = bizOrderService.insertOrder(bizOrder);
if (bo == null)
throw new NullPointerException("Don't save order!");
// ---------------------这是我的方式,有点无奈,你们可以用第二种写法 方便
// 写法1 生成二维码图片保存到服务器,然后渲染页面
// 后台生成二维码图片(本地与服务器系统不同)
String imgName = orderId+".jpg";
log.info("图片名称为:"+imgName);
// 物理路径
String imgPath = qrcodePath + File.separator + imgName;
log.info("图片存放路径为:"+imgPath);
// 相对路径(通过请求获得)
String reqImgPath = reqPath + ":" + imgPort + File.separator + "qrcode" + File.separator + imgName;
log.info("图片请求路径为:"+reqImgPath);
QRCodeUtil.createQrCode(imgPath, codeUrl, 900, "JPEG");
boolean isLocal = isLocalPath(reqPath);
map.put("reqPath", reqPath); // 请求路径
map.put("orderId", orderId); // 订单号
map.put("goodsType", "技术服务"); // 商品类型
map.put("amount", amount); // 商品数量
map.put("total_fee", total_fee); // 金额总数
if (isLocal) { // 本地路径
map.put("codeUrl", imgPath);// 返回二维码图片路径 路径1
}else
map.put("reqImgPath",reqImgPath);// 路径2
delOldQrCode(qrcodePath); // 删除旧二维码
}
return "wxpay";
}
/**
* 是否是本地路径
* @param reqPath 请求路径
* @return
*/
private boolean isLocalPath(String reqPath) {
log.info("检测到的请求路径为:"+reqPath);
if (reqPath != null) {
return reqPath.contains("localhost");
}
throw new NullPointerException("request is null!");
}
/**
* 二维码生成写法2
* 渲染二维码
* @param content 二维码内容 返回的二维码链接 页面img src =“/qrcode”
*
* @param response 响应页面
*/
@RequestMapping("/qrcode")
public void showQrCode(String content,HttpServletResponse response) {
try {
QRCodeUtil.createQrCodeStream(content,900,"JPEG",response);
} catch (IOException e) {
e.printStackTrace();
} catch (WriterException e) {
e.printStackTrace();
}
}
/**
* 微信支付成功回调接口
*
* @param map 返回给页面
* @param request 接收请求
* @param response 响应
* @return
* @throws Exception
*/
@RequestMapping("/callback")
public void callback(Map<String, Object> map, HttpServletRequest request,
HttpServletResponse response) throws Exception {
map.put("reqPath", reqPath); // 返回给freemaker 渲染页面
log.info("用户支付成功,回调!");
String inputLine;
String notityXml = "";
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
response.setHeader("Access-Control-Allow-Origin", "*");
// 微信给返回的东西
try {
while ((inputLine = request.getReader().readLine()) != null) {
notityXml += inputLine;
}
request.getReader().close();
} catch (Exception e) {
e.printStackTrace();
log.info("xml获取失败");
response.getWriter().write(setXml("FAIL", "xml获取失败"));
}
if (StringUtils.isEmpty(notityXml)) {
log.info("xml为空");
response.getWriter().write(setXml("FAIL", "xml为空"));
}
WXPayService wxService = WXPayService.getInstance(certPath, notifyUrl);
if (!wxService.checkSign(notityXml)) {
response.getWriter().write(setXml("FAIL", "验签失败"));
}
log.info("xml的值为:" + notityXml);
Map<String, String> xmlMap = WXPayUtil.xmlToMap(notityXml); // 解析成map
String json = JSONUtils.toJSONString(xmlMap); // map 转成json
// JSON json = xmlSerializer.read(notityXml);
log.info(json);
JSONObject jsonObject = JSONObject.fromObject(json);
UnifiedOrderRespose returnPay = (UnifiedOrderRespose) JSONObject.toBean(jsonObject, UnifiedOrderRespose.class);
log.info(("转换后对象为:" + returnPay.toString()));
log.info(("订单号:" + returnPay.getOut_trade_no() + "总金额:" + returnPay.getTotal_fee()));
if (returnPay.getReturn_code().equals("SUCCESS") && returnPay.getOut_trade_no() != null
&& !returnPay.getOut_trade_no().isEmpty()) {
double fee = Double.parseDouble(returnPay.getTotal_fee());
returnPay.setTotal_fee(String.valueOf(fee / 100));
log.info("微信的支付状态为SUCCESS");
// 支付成功业务
synchronized (WXPayController.class) { // 加入同步锁
Thread.sleep(1000); //睡一秒,防止并发倒致数据不一致
addServiceCount(returnPay,response);// 这里写你自己的业务
}
// 返回false的原因有可能是:订单已完成支付,或者订单已退款
}else
response.getWriter().write(setXml("FAIL","ORDER IS FINISH"));
}
/**
* 充值服务次数
* 加同步锁 处理并发
* @param returnPay
* @return
* @throws Exception
*/
private synchronized void addServiceCount(UnifiedOrderRespose returnPay,HttpServletResponse response) throws Exception {
return false;
}
/**
* 计算商品数量
* 商品数量只能为整数,采取四舍五入制
* @param total_fee 总金额
* @param price 商品单价
* @return
*/
private int getAmount(String total_fee,String price) {
ExceptionUtil.checkEmpty(total_fee,"总金额不能为空");
ExceptionUtil.checkEmpty(price,"单价不能为空");
double v1 = Double.parseDouble(total_fee);
double v = Double.parseDouble(price);
double amount = v1/v;
log.info("商品数量为:"+amount);
long num = Math.round(amount);
if (num < 1)
throw new IllegalArgumentException("服务充值基数不能小于0");
else
return (int)num;
}
/**
* 支付成功,回调成功,返回xml类型的数据
* @param returnCode
* @param msg
* @return
* @throws Exception
*/
private String setXml(String returnCode, String msg) throws Exception {
Map<String,String> xmlMap = new HashMap<>();
xmlMap.put("return_code",returnCode);
xmlMap.put("return_msg",msg);
String data = WXPayUtil.mapToXml(xmlMap);
return data;
}
/**
* 删除旧二维码
*/
private void delOldQrCode(String qrcodePath) {
ExceptionUtil.checkEmpty(qrcodePath,"二维码存放路径不能为空!");
String compareRule = "yyyyMMdd"; // 日期比较格式
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(compareRule);
String nowDate = simpleDateFormat.format(compareRule);
try {
File qrcodeDir = new File(qrcodePath);
if (qrcodeDir.isDirectory()) { // 如果目录存在
File[] files = qrcodeDir.listFiles();
for (File file:files) {
if (file.exists()&&file.isFile()) {
String date = file.getName().substring(0, 8);
boolean bool = DateCompareUtil.compareDate(date, nowDate, compareRule);
if (!bool) {
file.delete(); // 删除二维码
}
}
}
}
} catch (Exception e) {
log.error("获取二维码图片路径为空");
}
}
}
二维码生成工具类
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Hashtable;
/**
* 二维码生成工具类
* Created by jason on 2018/11/28 0028.
*/
public final class QRCodeUtil {
/**
* 生成包含字符串信息的二维码图片
* @param filePath 文件输出流路径
* @param content 二维码携带信息
* @param qrCodeSize 二维码图片大小
* @param imageFormat 二维码的格式
* @throws WriterException
* @throws IOException
*/
public static boolean createQrCode(String filePath, String content, int qrCodeSize, String imageFormat) throws WriterException, IOException {
//设置二维码纠错级别MAP
Hashtable<EncodeHintType, ErrorCorrectionLevel> hintMap = new Hashtable<EncodeHintType, ErrorCorrectionLevel>();
hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); // 矫错级别
QRCodeWriter qrCodeWriter = new QRCodeWriter();
//创建比特矩阵(位矩阵)的QR码编码的字符串
BitMatrix byteMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, qrCodeSize, qrCodeSize, hintMap);
// 使BufferedImage勾画QRCode (matrixWidth 是行二维码像素点)
int matrixWidth = byteMatrix.getWidth();
BufferedImage image = new BufferedImage(matrixWidth-200, matrixWidth-200, BufferedImage.TYPE_INT_RGB);
image.createGraphics();
Graphics2D graphics = (Graphics2D) image.getGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, matrixWidth, matrixWidth);
// 使用比特矩阵画并保存图像
graphics.setColor(Color.BLACK);
for (int i = 0; i < matrixWidth; i++){
for (int j = 0; j < matrixWidth; j++){
if (byteMatrix.get(i, j)){
graphics.fillRect(i-100, j-100, 1, 1);
}
}
}
OutputStream outputStream=new FileOutputStream(new File(filePath));
return ImageIO.write(image, imageFormat, outputStream);
}
/**
* 创建二维码输入流 可以直接将二维码以流的形式返回给页面
* @param content 二维码内容
* @param qrCodeSize 二维码尺寸
* @param imageFormat 图片格式
* @param response 响应给页面
*/
public static void createQrCodeStream(String content, int qrCodeSize, String imageFormat, HttpServletResponse response) throws IOException, WriterException {
//设置二维码纠错级别MAP
Hashtable<EncodeHintType, ErrorCorrectionLevel> hintMap = new Hashtable<EncodeHintType, ErrorCorrectionLevel>();
hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); // 矫错级别
QRCodeWriter qrCodeWriter = new QRCodeWriter();
//创建比特矩阵(位矩阵)的QR码编码的字符串
BitMatrix byteMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, qrCodeSize, qrCodeSize, hintMap);
// 使BufferedImage勾画QRCode (matrixWidth 是行二维码像素点)
int matrixWidth = byteMatrix.getWidth();
BufferedImage image = new BufferedImage(matrixWidth-200, matrixWidth-200, BufferedImage.TYPE_INT_RGB);
image.createGraphics();
Graphics2D graphics = (Graphics2D) image.getGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, matrixWidth, matrixWidth);
// 使用比特矩阵画并保存图像
graphics.setColor(Color.BLACK);
for (int i = 0; i < matrixWidth; i++){
for (int j = 0; j < matrixWidth; j++){
if (byteMatrix.get(i, j)){
graphics.fillRect(i-100, j-100, 1, 1);
}
}
}
// 返回给页面
OutputStream outputStream= response.getOutputStream();
// 关流
try {
ImageIO.write(image, imageFormat, outputStream);
} finally {
outputStream.close();
}
}
/**
* 读二维码并输出携带的信息
*/
public static void readQrCode(InputStream inputStream) throws IOException{
//从输入流中获取字符串信息
BufferedImage image = ImageIO.read(inputStream);
//将图像转换为二进制位图源
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
QRCodeReader reader = new QRCodeReader();
Result result = null ;
try {
result = reader.decode(bitmap);
} catch (ReaderException e) {
e.printStackTrace();
}
System.out.println(result.getText());
}
/**
* 测试代码
* @throws WriterException
*/
public static void main(String[] args) throws IOException, WriterException {
// createQrCode("E:\\qrcode.jpg","china is good",900,"JPEG");
// readQrCode(new FileInputStream(new File("E:\\qrcode.jpg")));
}
}
核心代码就告一段落了,摘取你要的,用不到的方法以及代码可以删去。后续会把原代码发上来。
注意: 生成二维码成功后,扫码支付成功后腾讯会回调你的接口callback。
还有一个坑,腾讯如论回调成功还是失败 都会重复回调,所以我加了同步锁,订单状态改变后,就不再执行任何操作,去除重复回调!
后续会更新对接支付宝支付
累死了,码了一晚上,希望大家多多支持。不懂得下方评论,大神可直接忽略。