流程图
1、JS版二维码
- QRCode.js是一个用于生产二维码的JavaScript 库。
- 主要是通过获取DOM的标签,再通过 HTML5 Canvas 绘制而成,不依赖任何库。
- 通过document获得标签,此js库不能使用在nuxt.js服务端渲染中。
- 后端:根据订单号生成微信支付的支付连接
- 前端:根据支付连接生成对应的二维码
2、后端实现
- pom
<!--微信支付-->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
- yml
server:
port: 8100
spring:
application:
name: cgorderservice
datasource:
url: jdbc:mysql://127.0.0.1:3306/changgou_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 1234
driver-class-name: com.mysql.jdbc.Driver
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 1000
test-on-borrow: true
redis:
host: 127.0.0.1
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
instance-id: ${eureka.instance.ip-address}.${server.port}
lease-renewal-interval-in-seconds: 3
lease-expiration-duration-in-seconds: 10
sc:
worker:
workerId: 1
datacenterId: 1
jwt:
secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
pubKeyPath: D:/rsa/rsa.pub # 公钥地址
priKeyPath: D:/rsa/rsa.pri # 私钥地址
expire: 360 # 过期时间,单位分钟
pay:
appID: wx8397f8696b538317
mchID: 1473426802
key: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
httpConnectTimeoutMs: 5000
httpReadTimeoutMs: 10000
- 配置类
package com.czxy.config;
import com.github.wxpay.sdk.WXPayConfig;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.io.InputStream;
/**
* @author 庭前云落
* @Date 2020/5/8 15:12
* @description
*/
@Data
@Component
@ConfigurationProperties(prefix = "sc.pay")
public class PayProperties implements WXPayConfig {
private String appID; // 公众账号ID
private String mchID; // 商户号
private String key; // 生成签名的密钥
private int httpConnectTimeoutMs; // 连接超时时间
private int httpReadTimeoutMs; // 读取超时时间
@Override
public InputStream getCertStream() {
//加载证书,需要通过账号中心生成
return null;
}
private String notifyUrl; //回调程序地址
}
-
工具类:
- PayState :枚举类,用于列举支付查询返回结果
- PayHelper:工具类
-
PayState :枚举类,用于列举支付查询返回结果
package com.czxy.utils; import lombok.Getter; /** * @author 庭前云落 * @Date 2020/5/8 17:40 * @description */ @Getter public enum PayState { NOT_PAY(0, "未支付"), SUCCESS(1, "支付成功"), CLOSED(2, "已关闭"), PAY_ERROR(3, "支付失败"); PayState(int code, String desc) { this.code = code; this.desc = desc; } private int code; //自定义编码 private String desc; //描述信息 }
-
PayHelper:工具类
package com.czxy.utils;
import com.czxy.config.PayProperties;
import com.github.wxpay.sdk.WXPay;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* @author 庭前云落
* @Date 2020/5/8 15:29
* @description
*/
@Component
@EnableConfigurationProperties(PayProperties.class)
public class PayHelper {
private WXPay wxPay;
private PayProperties payProperties;
@Bean
public WXPay wxPay(PayProperties payProperties) {
if (wxPay == null) {
wxPay = new WXPay(payProperties);
this.payProperties=payProperties;
}
return wxPay;
}
private static final Logger logger = LoggerFactory.getLogger(PayHelper.class);
public PayHelper() {
}
public PayHelper(PayProperties payProperties) {
// 真实开发时
wxPay = new WXPay(payProperties);
// 测试时
// wxPay = new WXPay(payProperties, WXPayConstants.SignType.MD5, true);
}
public String createPayUrl(Long sn) {
String key = "pay.url." + sn;
try {
Map<String, String> data = new HashMap<>();
// 商品描述
data.put("body", "商城测试");
// 订单号
data.put("out_trade_no", sn.toString());
//货币
data.put("fee_type", "CNY");
//金额,单位是分
data.put("total_fee", "1");
//调用微信支付的终端IP(商城的IP)
data.put("spbill_create_ip", "127.0.0.1");
//回调地址
data.put("notify_url", "http://test.jingxi.com/wxpay/notify");
// 交易类型为扫码支付
data.put("trade_type", "NATIVE");
//商品id,使用假数据
data.put("product_id", "1234567");
Map<String, String> result = this.wxPay.unifiedOrder(data);
if ("SUCCESS".equals(result.get("return_code"))) {
if ("SUCCESS".equals(result.get("result_code"))) {
String url = result.get("code_url");
return url;
} else {
logger.error("创建预交易订单失败,错误信息:{}", result.get("err_code_des"));
return null;
}
} else {
logger.error("创建预交易订单失败,错误信息:{}", result.get("return_msg"));
return null;
}
} catch (Exception e) {
logger.error("创建预交易订单异常", e);
return null;
}
}
/**
* 查询订单状态
* 交易状态参考:(trade_state)
SUCCESS—支付成功
REFUND—转入退款
NOTPAY—未支付
CLOSED—已关闭
REVOKED—已撤销(付款码支付)
USERPAYING--用户支付中(付款码支付)
PAYERROR--支付失败(其他原因,如银行返回失败)
* @param sn
* @return
*/
public PayState queryOrder(Long sn) {
Map<String, String> data = new HashMap<>();
// 订单号
data.put("out_trade_no", sn.toString());
try {
Map<String, String> result = this.wxPay.orderQuery(data);
if("SUCCESS".equals(result.get("return_code"))){
if("SUCCESS".equals(result.get("result_code"))) {
String tradeState = result.get("trade_state");
if ("SUCCESS".equals(tradeState)) {
return PayState.SUCCESS;
}
if ("NOTPAY".equals(tradeState)) {
return PayState.NOT_PAY;
}
if ("CLOSED".equals(tradeState)) {
return PayState.CLOSED;
}
}
}
return PayState.PAY_ERROR;
} catch (Exception e) {
logger.error("查询订单状态异常", e);
return PayState.PAY_ERROR;
}
}
}
- controller
package com.czxy.controller;
import com.czxy.utils.PayHelper;
import com.czxy.vo.BaseResult;
import com.czxy.vo.PayRequest;
import com.github.wxpay.sdk.WXPayUtil;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* @author 庭前云落
* @Date 2020/5/8 17:43
* @description
*/
@RestController
@RequestMapping("/pay")
public class PayController {
@Resource
private PayHelper payHelper;
@PostMapping
public BaseResult pay(@RequestBody PayRequest payRequest){
String payUrl = payHelper.createPayUrl(payRequest.getSn());
return BaseResult.ok("二维码生产成功").append("wxurl",payUrl);
}
@PostMapping("/callback")
public void callback(HttpServletRequest request , HttpServletResponse response){
try {
//1 处理请求
// 1.1 以流的方式,获得请求体(请求参数)
ServletInputStream is = request.getInputStream();
// 1.2 将流转换成字符串 "<xml></xml>"
String xmlStr = IOUtils.toString(is, "UTF-8");
// 1.3 将字符串转换Map
Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
//2 获得订单号,修改状态
if("SUCCESS".equals(map.get("return_code")) &&
"SUCCESS".equals(map.get("result_code"))){ //返回状态码 && 业务结果
String sn = map.get("out_trade_no");
System.out.println(sn);
//3 响应规定格式的内容
// 3.1 响应类型
response.setContentType("text/xml");
// 3.2 响应内容
response.getWriter().print("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
} else {
System.out.println("错误了");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3、前端实现
-
获得微信支付的路径
//获得微信支付路径 pay: (params) => { return axios.post("/cgorderservice/pay", params) }
-
创建flow3组件,导入第三方资源(QRCode.js)
- 页面加载成功后,获得支付路径,在指定位置处,渲染二维码
<template> <div> <!-- 顶部导航 start --> <TopNav></TopNav> <!-- 顶部导航 end --> <div style="clear:both;"></div> <!-- 页面头部 start --> <div class="header w990 bc mt15"> <div class="logo w990"> <h2 class="fl"> <a href="index.html"> <img src="images/logo.png" alt="畅购商城" /> </a> </h2> <div class="flow fr flow3"> <ul> <li>1.我的购物车</li> <li>2.填写核对订单信息</li> <li class="cur">3.成功提交订单</li> </ul> </div> </div> </div> <!-- 页面头部 end --> <div style="clear:both;"></div> <!-- 主体部分 start --> <div class="success w990 bc mt15"> <div class="success_hd"> <h2>订单提交成功</h2> </div> <div class="success_bd"> <p> <span></span>订单提交成功,我们将及时为您处理 </p> <p> <div id="qrcode" style="width:256px;margin:0 auto;"></div> </p> <p class="message"> 完成支付后,你可以 <a href>查看订单状态</a> <a href>继续购物</a> <a href>问题反馈</a> </p> </div> </div> <!-- 主体部分 end --> <div style="clear:both;"></div> <!-- 底部版权 start --> <Footer></Footer> <!-- 底部版权 end --> </div> </template> <script> import TopNav from "../components/TopNav"; import Footer from "../components/Footer"; export default { head: { title: "支付页面", link: [{ rel: "stylesheet", href: "/style/success.css" }], script: [{ type: "text/javascript", src: "/js/qrcode.min.js" },] }, components: { TopNav, Footer }, data() { return { sn: this.$route.query.sn }; }, async mounted() { let { data } = await this.$request.pay({ "sn": this.sn }); new QRCode(document.getElementById("qrcode"), data.other.wxurl); } }; </script> <style> #qrcode img { background-color: #fff; padding: 6px; } </style>