微信扫码支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景
请根据营业执照类型选择以下主体注册:个体工商户| 企业/公司| 政府| 媒体| 其他类型。
公众号认证后才可申请微信支付,认证费:300元/年。
登录公众平台,点击左侧菜单【微信支付】,开始填写资料等待审核,审核时间为1-5个工作日内。
资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填写财付通备付金打的小额资金数额,完成账户验证。
本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效。
微信支付接口调用的整体思路:
按API要求组装参数,以XML方式发送(POST)给微信支付接口(URL),微信支付接口也是以XML方式给予响应。程序根据返回的结果(其中包括支付URL)生成二维码或判断订单状态。
在线微信支付开发文档:https://pay.weixin.qq.com/wiki/doc/api/index.html
(1) appid:微信公众账号或开放平台APP的唯一标识
(2)mch_id:商户号 (配置文件中的partner)
(3)partnerkey:商户密钥
(4)sign:数字签名, 根据微信官方提供的密钥和一套算法生成的一个加密信息, 就是为了保证交易的安全性
主要是会用到微信支付SDK的以下功能,不然我们还要自己处理挺麻烦的:
(1)获取随机字符串
WXPayUtil.generateNonceStr()
(2)MAP转换为XML字符串(自动添加签名)
WXPayUtil.generateSignedXml(param, partnerkey)
(3)XML字符串转换为MAP
WXPayUtil.xmlToMap(result)
线下:家乐福超市、7-11便利店、上品折扣线下店等
线上:大众点评网站、携程网站、唯品会、美丽说网站等
模式一:商户在后台给你生成二维码,用户打开扫一扫
模式二:商户后台系统调用微信支付统一下单API生成预付交易,将接口返回的链接生成二维码,用户扫码后输入密码完成支付交易。注意:该模式的预付单有效期为2小时,过期后无法支付。
微信支付:生成xml发送请求
(1)商户后台系统根据用户选购的商品生成订单。
(2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;
(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
(4)商户后台系统根据返回的code_url生成二维码。
(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
(8)微信支付系统根据用户授权完成支付交易。
(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
(11)未收到支付通知的情况,商户后台系统调用【查询订单API】(查单实现可参考:支付回调和查单实现指引)。
(12)商户确认订单已支付后给用户发货。
对微信支付开发者文档中给出的API进行了封装。
com.github.wxpay.sdk.WXPay类下提供了对应的方法:
方法名 说明
microPay 刷卡支付
unifiedOrder 统一下单
orderQuery 查询订单
reverse 撤销订单
closeOrder 关闭订单
refund 申请退款
refundQuery 查询退款
downloadBill 下载对账单
report 交易保障
shortUrl 转换短链接
authCodeToOpenid 授权码查询openid
示例
配置类MyConfig:
import com.github.wxpay.sdk.WXPayConfig; import java.io.*; public class MyConfig implements WXPayConfig{ private byte[] certData; public MyConfig() throws Exception { String certPath = "/path/to/apiclient_cert.p12"; File file = new File(certPath); InputStream certStream = new FileInputStream(file); this.certData = new byte[(int) file.length()]; certStream.read(this.certData); certStream.close(); } public String getAppID() { return "wx8888888888888888"; } public String getMchID() { return "12888888"; } public String getKey() { return "88888888888888888888888888888888"; } public InputStream getCertStream() { ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData); return certBis; } public int getHttpConnectTimeoutMs() { return 8000; } public int getHttpReadTimeoutMs() { return 10000; } }
统一下单:
import com.github.wxpay.sdk.WXPay; import java.util.HashMap; import java.util.Map; public class WXPayExample { public static void main(String[] args) throws Exception { MyConfig config = new MyConfig(); WXPay wxpay = new WXPay(config); Map<String, String> data = new HashMap<String, String>(); data.put("body", "腾讯充值中心-QQ会员充值"); data.put("out_trade_no", "2016090910595900000012"); data.put("device_info", ""); data.put("fee_type", "CNY"); data.put("total_fee", "1"); data.put("spbill_create_ip", "123.12.12.123"); data.put("notify_url", "http://www.example.com/wxpay/notify"); data.put("trade_type", "NATIVE"); // 此处指定为扫码支付 data.put("product_id", "12"); try { Map<String, String> resp = wxpay.unifiedOrder(data); System.out.println(resp); } catch (Exception e) { e.printStackTrace(); } } }
订单查询:
import com.github.wxpay.sdk.WXPay; import java.util.HashMap; import java.util.Map; public class WXPayExample { public static void main(String[] args) throws Exception { MyConfig config = new MyConfig(); WXPay wxpay = new WXPay(config); Map<String, String> data = new HashMap<String, String>(); data.put("out_trade_no", "2016090910595900000012"); try { Map<String, String> resp = wxpay.orderQuery(data); System.out.println(resp); } catch (Exception e) { e.printStackTrace(); } } }
退款查询:
import com.github.wxpay.sdk.WXPay; import java.util.HashMap; import java.util.Map; public class WXPayExample { public static void main(String[] args) throws Exception { MyConfig config = new MyConfig(); WXPay wxpay = new WXPay(config); Map<String, String> data = new HashMap<String, String>(); data.put("out_trade_no", "2016090910595900000012"); try { Map<String, String> resp = wxpay.refundQuery(data); System.out.println(resp); } catch (Exception e) { e.printStackTrace(); } } }
下载对账单:
import com.github.wxpay.sdk.WXPay; import java.util.HashMap; import java.util.Map; public class WXPayExample { public static void main(String[] args) throws Exception { MyConfig config = new MyConfig(); WXPay wxpay = new WXPay(config); Map<String, String> data = new HashMap<String, String>(); data.put("bill_date", "20140603"); data.put("bill_type", "ALL"); try { Map<String, String> resp = wxpay.downloadBill(data); System.out.println(resp); } catch (Exception e) { e.printStackTrace(); } } }
其他API的使用和上面类似。
暂时不支持下载压缩格式的对账单,但可以使用该SDK生成请求用的XML数据:
import com.github.wxpay.sdk.WXPay; import com.github.wxpay.sdk.WXPayUtil; import java.util.HashMap; import java.util.Map; public class WXPayExample { public static void main(String[] args) throws Exception { MyConfig config = new MyConfig(); WXPay wxpay = new WXPay(config); Map<String, String> data = new HashMap<String, String>(); data.put("bill_date", "20140603"); data.put("bill_type", "ALL"); data.put("tar_type", "GZIP"); try { data = wxpay.fillRequestData(data); System.out.println(WXPayUtil.mapToXml(data)); } catch (Exception e) { e.printStackTrace(); } } }
收到支付结果通知时,需要验证签名,可以这样做:
import com.github.wxpay.sdk.WXPay; import com.github.wxpay.sdk.WXPayUtil; import java.util.Map; public class WXPayExample { public static void main(String[] args) throws Exception { String notifyData = "...."; // 支付结果通知的xml格式数据 MyConfig config = new MyConfig(); WXPay wxpay = new WXPay(config); Map<String, String> notifyMap = WXPayUtil.xmlToMap(notifyData); // 转换成map if (wxpay.isPayResultNotifySignatureValid(notifyMap)) { // 签名正确 // 进行处理。 // 注意特殊情况:订单已经退款,但收到了支付结果成功的通知,不应把商户侧订单状态从退款改成支付成功 } else { // 签名错误,如果数据里没有sign字段,也认为是签名错误 } } }
HTTPS请求可选HMAC-SHA256算法和MD5算法签名:
import com.github.wxpay.sdk.WXPay; import com.github.wxpay.sdk.WXPayConstants; public class WXPayExample { public static void main(String[] args) throws Exception { MyConfig config = new MyConfig(); WXPay wxpay = new WXPay(config, WXPayConstants.SignType.HMACSHA256); // ...... } }
若需要使用sandbox环境:
import com.github.wxpay.sdk.WXPay; import com.github.wxpay.sdk.WXPayConstants; public class WXPayExample { public static void main(String[] args) throws Exception { MyConfig config = new MyConfig(); WXPay wxpay = new WXPay(config, WXPayConstants.SignType.MD5, true); // ...... } }
<dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency>
# Redis配置信息 spring: redis: database: 0 host: 192.168.137.72 lettuce: pool: max-active: 20 max-idle: 5 max-wait: -1 min-idle: 0 port: 6379 timeout: 1800000 # 微信支付相关配置 weixin: pay: appid: #关联的公众号appid partner: #商户号 partnerkey: #商户key
package com.qbb.yygh.order.controller.user; import com.qbb.yygh.order.service.WeiXinService; import com.qbb.yygh.result.R; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; /** * @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit) * @version 1.0 * @date 2022-04-29 21:56 * @Description: */ @RestController @RequestMapping("/weixin/order") public class WeiXinController { @Autowired private WeiXinService weiXinService; /** * 生成微信支付二维码 * * @param orderId * @return */ @GetMapping("/createNative/{orderId}") public R createNative(@PathVariable("orderId") Long orderId) { Map<String, Object> map = weiXinService.createNative(orderId); return R.ok().data(map); } }
package com.qbb.yygh.order.service.impl; import com.github.wxpay.sdk.WXPayUtil; import com.qbb.yygh.enums.PaymentTypeEnum; import com.qbb.yygh.exception.YyghException; import com.qbb.yygh.model.order.OrderInfo; import com.qbb.yygh.order.service.OrderInfoService; import com.qbb.yygh.order.service.PaymentInfoService; import com.qbb.yygh.order.service.WeiXinService; import com.qbb.yygh.order.utils.ConstantPropertiesUtils; import com.qbb.yygh.order.utils.HttpClient; import com.qbb.yygh.result.ResponseEnum; import lombok.extern.slf4j.Slf4j; import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; /** * @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit) * @version 1.0 * @date 2022-04-29 21:56 * @Description: */ @Service @Slf4j public class WeiXinServiceImpl implements WeiXinService { @Autowired private OrderInfoService orderInfoService; @Autowired private PaymentInfoService paymentInfoService; @Autowired private RedisTemplate redisTemplate; /** * 生成微信支付二维码 * * @param orderId * @return */ @Override public Map<String, Object> createNative(Long orderId) { try { String key = "syt:order:pay:" + orderId; // 先从redis中查询一下支付的二维码相关信息 Map payMap = (Map) redisTemplate.opsForValue().get(key); if (payMap != null) { return payMap; } // 查询订单信息 OrderInfo orderInfo = orderInfoService.getById(orderId); if (orderInfo == null) { throw new YyghException(ResponseEnum.DATA_ERROR); } // 保存支付记录 paymentInfoService.savePaymentInfo(orderInfo, PaymentTypeEnum.WEIXIN.getStatus()); // 设置调用微信生成支付二维码的参数 Map<String, String> params = new HashMap<>(); params.put("appid", ConstantPropertiesUtils.APPID); params.put("mch_id", ConstantPropertiesUtils.PARTNER); params.put("nonce_str", WXPayUtil.generateNonceStr()); Date reserveDate = orderInfo.getReserveDate(); String reserveDateString = new DateTime(reserveDate).toString("yyyy/MM/dd"); String body = reserveDateString + "就诊" + orderInfo.getDepname(); params.put("body", body); params.put("out_trade_no", orderInfo.getOutTradeNo()); //params.put("total_fee", orderInfo.getAmount().multiply(new BigDecimal("100")).longValue()+""); params.put("total_fee", "1");//单位是分 ---> 代表着1分钱 params.put("spbill_create_ip", "127.0.0.1"); // 通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http params.put("notify_url", "http://guli.shop/api/order/weixinPay/weixinNotify"); params.put("trade_type", "NATIVE"); // 调用工具类向微信支付接口发请求,https://api.mch.weixin.qq.com/pay/unifiedorder HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder"); // 设置参数 httpClient.setXmlParam(WXPayUtil.generateSignedXml(params, ConstantPropertiesUtils.PARTNERKEY)); // 设置httpclient支持Https请求 httpClient.setHttps(true); httpClient.post(); // 获取返回的参数(返回的是一个xml形式的文件格式,参考官方文档) String content = httpClient.getContent(); Map<String, String> toMap = WXPayUtil.xmlToMap(content); log.info("返回的结果集:{}", toMap); Map<String, Object> resultMap = new HashMap<>(); resultMap.put("orderId", orderInfo.getId()); resultMap.put("totalFee", orderInfo.getAmount()); resultMap.put("resultCode", resultMap.get("result_code")); // 生成二维码的地址 resultMap.put("codeUrl", resultMap.get("code_url")); // 将结果集存入redis,并设置120分钟有效时间 redisTemplate.opsForValue().set(key, resultMap, 120, TimeUnit.MINUTES); return resultMap; } catch (Exception e) { e.printStackTrace(); return null; } } }
# 在页面使用npm安装生成微信支付二维码插件`npm install vue-qriously` <!-- 微信支付弹出框 --> <el-dialog :visible.sync="dialogPayVisible" style="text-align: left" :append-to-body="true" width="500px" @close="closeDialog"> <div class="container"> <div class="operate-view" style="height: 350px;"> <div class="wrapper wechat"> <div> <img src="images/weixin.jpg" alt=""> <qriously :value="payObj.codeUrl" :size="220"/> <div style="text-align: center;line-height: 25px;margin-bottom: 40px;"> 请使用微信扫一扫<br/> 扫描二维码支付 </div> </div> </div> </div> </div> </el-dialog> # 在/plugins/myPlugin.js文件添加引入 `import VueQriously from 'vue-qriously'` `Vue.use(VueQriously)`
https://api.mch.weixin.qq.com/pay/orderquery
接口