一、先去微信申请相应的appid等,然后在yml文件增加相应配置
pay:
wxpay:
appID: ******
mchID: *****
key: *****
notifyUrl: *****
appSecret: *****
建立配置类:
@ConfigurationProperties(prefix = "pay.wxpay")
public class MyWXPayConfig implements WXPayConfig {
private String appSecret;
/** 公众账号ID */
private String appID;
/** 商户号 */
private String mchID;
/** API 密钥 */
private String key;
/** API 沙箱环境密钥 */
private String sandboxKey;
/** API证书绝对路径 */
private String certPath;
/** 退款异步通知地址 */
private String notifyUrl;
private Boolean useSandbox;
/** HTTP(S) 连接超时时间,单位毫秒 */
private int httpConnectTimeoutMs = 80000;
/** HTTP(S) 读数据超时时间,单位毫秒 */
private int httpReadTimeoutMs = 100000;
/**
* 获取商户证书内容
*
* @return 商户证书内容
*/
@Override
public InputStream getCertStream() {
File certFile = new File(certPath);
InputStream inputStream = null;
try {
inputStream = new FileInputStream(certFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return inputStream;
}
@Override
public String getKey() {
if (useSandbox) {
return sandboxKey;
}
return key;
}
public String getAppSecret() {
return appSecret;
}
public void setAppSecret(String appSecret) {
this.appSecret = appSecret;
}
public String getAppID() {
return appID;
}
public void setAppID(String appID) {
this.appID = appID;
}
public String getMchID() {
return mchID;
}
public void setMchID(String mchID) {
this.mchID = mchID;
}
public String getSandboxKey() {
return sandboxKey;
}
public void setSandboxKey(String sandboxKey) {
this.sandboxKey = sandboxKey;
}
public String getCertPath() {
return certPath;
}
public void setCertPath(String certPath) {
this.certPath = certPath;
}
public String getNotifyUrl() {
return notifyUrl;
}
public void setNotifyUrl(String notifyUrl) {
this.notifyUrl = notifyUrl;
}
public Boolean getUseSandbox() {
return useSandbox;
}
public void setUseSandbox(Boolean useSandbox) {
this.useSandbox = useSandbox;
}
public int getHttpConnectTimeoutMs() {
return httpConnectTimeoutMs;
}
public void setHttpConnectTimeoutMs(int httpConnectTimeoutMs) {
this.httpConnectTimeoutMs = httpConnectTimeoutMs;
}
public int getHttpReadTimeoutMs() {
return httpReadTimeoutMs;
}
public void setHttpReadTimeoutMs(int httpReadTimeoutMs) {
this.httpReadTimeoutMs = httpReadTimeoutMs;
}
public void setKey(String key) {
this.key = key;
}
}
二、建立生成支付二维码的类:
public class PayUtil {
/**
* 根据url生成二位图片对象
*
* @param codeUrl
* @return
* @throws WriterException
*/
public static BufferedImage getQRCodeImge(String codeUrl) throws WriterException {
Map hints = new Hashtable();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
hints.put(EncodeHintType.CHARACTER_SET, "UTF8");
int width = 256;
BitMatrix bitMatrix = (new MultiFormatWriter()).encode(codeUrl, BarcodeFormat.QR_CODE, width, width, hints);
BufferedImage image = new BufferedImage(width, width, 1);
for(int x = 0; x < width; ++x) {
for(int y = 0; y < width; ++y) {
image.setRGB(x, y, bitMatrix.get(x, y) ? -16777216 : -1);
}
}
return image;
}
}
三、建立服务类
/**
* 对WXPay的简单封装,处理支付密切相关的逻辑.
* @author wangCJ
*
*/
public class WXPayClient extends WXPay {
/** 密钥算法 */
private static final String ALGORITHM = "AES";
/** 加解密算法/工作模式/填充方式 */
private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding";
/** 用户支付中,需要输入密码 */
private static final String ERR_CODE_USERPAYING = "USERPAYING";
private static final String ERR_CODE_AUTHCODEEXPIRE = "AUTHCODEEXPIRE";
/** 交易状态: 未支付 */
private static final String TRADE_STATE_NOTPAY = "NOTPAY";
/** 用户输入密码,尝试30秒内去查询支付结果 */
private static Integer remainingTimeMs = 10000;
private WXPayConfig config;
public WXPayClient(WXPayConfig config, WXPayConstants.SignType signType, boolean useSandbox) {
super(config, signType, useSandbox);
this.config = config;
}
/**
*
* 刷卡支付
*
* 对WXPay#microPay(Map)增加了当支付结果为USERPAYING时去轮询查询支付结果的逻辑处理
*
* 注意:该方法没有处理return_code=FAIL的情况,暂时不考虑网络问题,这种情况直接返回错误
*
* @param reqData
* @return
* @throws Exception
*/
public Map microPayWithPOS(Map reqData) throws Exception {
// 开始时间(毫秒)
long startTimestampMs = System.currentTimeMillis();
Map responseMapForPay = super.microPay(reqData);
// // 先判断 协议字段返回(return_code),再判断 业务返回,最后判断 交易状态(trade_state)
// 通信标识,非交易标识
String returnCode = responseMapForPay.get("return_code");
if (WXPayConstants.SUCCESS.equals(returnCode)) {
String errCode = responseMapForPay.get("err_code");
// 余额不足,信用卡失效
if (ERR_CODE_USERPAYING.equals(errCode) || "SYSTEMERROR".equals(errCode) || "BANKERROR".equals(errCode)) {
Map orderQueryMap = null;
Map requestData = new HashMap<>();
requestData.put("out_trade_no", reqData.get("out_trade_no"));
// 用户支付中,需要输入密码或系统错误则去重新查询订单API err_code, result_code, err_code_des
// 每次循环时的当前系统时间 - 开始时记录的时间 > 设定的30秒时间就退出
while (System.currentTimeMillis() - startTimestampMs < remainingTimeMs) {
// 商户收银台得到USERPAYING状态后,经过商户后台系统调用【查询订单API】查询实际支付结果。
orderQueryMap = super.orderQuery(requestData);
String returnCodeForQuery = orderQueryMap.get("return_code");
if (WXPayConstants.SUCCESS.equals(returnCodeForQuery)) {
// 通讯成功
String tradeState = orderQueryMap.get("trade_state");
if (WXPayConstants.SUCCESS.equals(tradeState)) {
// 如果成功了直接将查询结果返回
return orderQueryMap;
}
// 如果支付结果仍为USERPAYING,则每隔5秒循环调用【查询订单API】判断实际支付结果
Thread.sleep(1000);
}
}
// 如果用户取消支付或累计30秒用户都未支付,商户收银台退出查询流程后继续调用【撤销订单API】撤销支付交易。
String tradeState = orderQueryMap.get("trade_state");
if (TRADE_STATE_NOTPAY.equals(tradeState) || ERR_CODE_USERPAYING.equals(tradeState) || ERR_CODE_AUTHCODEEXPIRE.equals(tradeState)) {
Map reverseMap = this.reverse(requestData);
String returnCodeForReverse = reverseMap.get("return_code");
String resultCode = reverseMap.get("result_code");
if (WXPayConstants.SUCCESS.equals(returnCodeForReverse) && WXPayConstants.SUCCESS.equals(resultCode)) {
// 如果撤销成功,需要告诉客户端已经撤销订单了
responseMapForPay.put("err_code_des", "用户取消支付或尚未支付,后台已经撤销该订单,请重新支付!");
}
}
}
}
return responseMapForPay;
}
/**
* 从request的inputStream中获取参数
* @param request
* @return
* @throws Exception
*/
public Map getNotifyParameter(HttpServletRequest request) throws Exception {
InputStream inputStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length = 0;
while ((length = inputStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, length);
}
outSteam.close();
inputStream.close();
// 获取微信调用我们notify_url的返回信息
String resultXml = new String(outSteam.toByteArray(), "utf-8");
Map notifyMap = WXPayUtil.xmlToMap(resultXml);
return notifyMap;
}
/**
* 解密退款通知
*
* 账户设置-->API安全-->密钥设置 )
Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
SecretKeySpec key = new SecretKeySpec(WXPayUtil.MD5(config.getKey()).toLowerCase().getBytes(), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key);
//(3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)
// java.security.InvalidKeyException: Illegal key size or default parameters
// https://www.cnblogs.com/yaks/p/5608358.html
String responseXml = new String(cipher.doFinal(bytes),"UTF-8");
Map responseMap = WXPayUtil.xmlToMap(responseXml);
return responseMap;
}
/**
* 获取沙箱环境验签秘钥API
* 获取验签秘钥API文档
* @return
* @throws Exception
*/
public Map getSignKey() throws Exception {
Map reqData = new HashMap<>();
reqData.put("mch_id", config.getMchID());
reqData.put("nonce_str", WXPayUtil.generateNonceStr());
String sign = WXPayUtil.generateSignature(reqData, config.getKey(), WXPayConstants.SignType.MD5);
reqData.put("sign", sign);
String responseXml = this.requestWithoutCert("https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey", reqData,
config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs());
Map responseMap = WXPayUtil.xmlToMap(responseXml);
return responseMap;
}
}
四、建立controller
@RestController
@RequestMapping("/web/wxpay")
public class WXPayPrecreateController {
@Autowired
private WXPay wxPay;
@Autowired
private WXPayClient wxPayClient;
@Autowired
private MyWXPayConfig myWXPayConfig;
@Autowired
private OrderService orderService;
@Autowired
private PayWebSocket payWebSocket;
/**
* 扫码支付 - 统一下单
*
*
* 扫码支付API
*/
// @PostMapping("/order")
@ApiOperation(value = "createCode", notes = "返回支付二维码")
@GetMapping("/common/createCode")
public void precreate(Order order, HttpServletResponse response)
throws Exception {
order = orderService.selectListSelective(order).get(0);
Map reqData = new HashMap<>();
reqData.put("appid", myWXPayConfig.getAppID());
reqData.put("mch_id", myWXPayConfig.getMchID());
// 订单号
reqData.put("out_trade_no", order.getOrderCode());
reqData.put("trade_type", "NATIVE");
reqData.put("product_id", order.getFilmId() + "");
reqData.put("body", "商户下单");
// 交易结束时间
// SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
// reqData.put("time_expire", dateFormat.format(new
// Date().getTime()+20*60*1000L));
// 订单总金额,单位为分
reqData.put("total_fee", order.getOrderPrice().longValue() + "");
// APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
reqData.put("spbill_create_ip", "129.226.53.55");
// 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
reqData.put("notify_url", myWXPayConfig.getNotifyUrl());
// 自定义参数, 可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB"
reqData.put("device_info", "WEB");
// 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
reqData.put("attach", "");
// sign签名
reqData.put("sign", myWXPayConfig.getKey());
// 随机字符串
reqData.put("nonce_str", RandomStringUtils.randomAlphanumeric(10));
/**
* { code_url=weixin://wxpay/bizpayurl?pr=vvz4xwC, trade_type=NATIVE,
* return_msg=OK, result_code=SUCCESS, return_code=SUCCESS,
* prepay_id=wx18111952823301d9fa53ab8e1414642725 }
*/
Map responseMap = wxPay.unifiedOrder(reqData);
System.out.println(responseMap);
String returnCode = responseMap.get("return_code");
String resultCode = responseMap.get("result_code");
if (WXPayConstants.SUCCESS.equals(returnCode)
&& WXPayConstants.SUCCESS.equals(resultCode)) {
String prepayId = responseMap.get("prepay_id");
String codeUrl = responseMap.get("code_url");
BufferedImage image = PayUtil.getQRCodeImge(codeUrl);
response.setContentType("image/jpeg");
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
// response.setIntHeader("Expires",-1);
ImageIO.write(image, "JPEG", response.getOutputStream());
}
}
/**
*
* @param request
* @return
* @throws Exception
*/
@RequestMapping("/common/notify")
public void precreateNotify(HttpServletRequest request,
HttpServletResponse response) throws Exception {
Map reqData = wxPayClient.getNotifyParameter(request);
/**
* { transaction_id=4200000138201806180751222945,
* nonce_str=aaaf3fe4d3aa44d8b245bc6c97bda7a8, bank_type=CFT,
* openid=xxx, sign=821A5F42F5E180ED9EF3743499FBCF13, fee_type=CNY,
* mch_id=xxx, cash_fee=1, out_trade_no=186873223426017, appid=xxx,
* total_fee=1, trade_type=NATIVE, result_code=SUCCESS,
* time_end=20180618131247, is_subscribe=N, return_code=SUCCESS }
*/
// 特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。
boolean signatureValid = wxPay.isPayResultNotifySignatureValid(reqData);
if (signatureValid) {
/**
* 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
* 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,
* 判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。
* 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
*/
String orderCode = reqData.get("out_trade_no");
Order order = new Order();
order.setOrderCode(orderCode);
order = orderService.selectListSelective(order).get(0);
if(order.getOrderState()==1){
if ("SUCCESS".equals(reqData.get("result_code"))) {
order.setOrderState(3);
} else {
order.setOrderState(2);
}
orderService.updateByPrimaryKeySelective(order);
// 通知浏览器结果
payWebSocket.sendMessageTo(JSON.toJSONString(order), orderCode);
}
}
}
}
其中precreate方法就是创建支付二维码的方法,precreateNotify方法为支付过后的回调地址