目录
以上的代码还包含与实际业务有关的其他功能 比如rabbitmq的死信队列 分布式feign调用等,可以注释掉不使用
前言:
点击购买按钮,创建二维码,让用户可以进行扫码支付。具体流程如下图
我们主要负责商户后台系统的编写。
完整代码:
前端代码:
<!-- 微信支付二维码-->
<el-dialog :visible.sync="dialogFormVisible" :before-close="cancelOrder" :modal="true" :close-on-click-modal="false" style="width:800px;margin:0px auto;" >
<h1 style="font-size:30px;color:#00B38A" >微信扫一扫支付</h1>
<img id="qrcode" :src="wxpayUrl">
<h2 id="statusText"></h2>
<p id="closeText"></p>
</el-dialog>
<!-- 底部购买 -->
<div
class="public-class-footer"
slot="bottom"
style="border:1px solid #eee; height:60px; text-align:left;"
>
<span class="product-descript" style="font-size:.347rem"
>成就自己</span
>
<span class="current-price" style="font-size:28px">
<span class="current-price-unite" style="font-size:.347rem">
¥</span
>{{course.discounts}}
</span>
<span class="current-price price">
<span class="current-price-unite">¥</span>
{{course.price}}
</span>
<button
@click="buy(course.id)"
type="button"
class="weui-btn purchase-button weui-btn_mini weui-btn_primary"
style="width:155px;height:45px;font-size:17px;"
>
立即购买
<!-- ::after -->
</button>
// 购买课程
buy() {
if( this.user != null ){
this.dialogFormVisible = true; //显示提示框
//微信下单
console.log("user.id:"+this.user.id)
console.log("course.id:"+this.course.id)
console.log("activityId:"+this.course.activity.id)
let args = {"userId":this.user.id,"courseId":this.course.id,"activityId":this.course.activity.id,"price":1};
this.axios.post("/wxpay/makeOrder",this.qs.stringify(args))
.then(res => {
console.log(res.data);
//利用返回的code_url,显示支付二维码
this.wxpayUrl = "http://localhost:9000/wxpay/code?url="+res.data.code_url;
console.log(this.wxpayUrl);
//检查后台返回的订单号支付状态
this.checkOrder(res.data.trade_no);
});
}else{
this.$message.error("购买失败,请先登录!");
}
},
//检查订单号支付状态 websocket
checkOrder(tradeNo){
console.log("订单的tradeno:"+tradeNo)
let count = 0;
//定时1分钟轮询检查
let timer = setInterval(() =>{
this.axios.get("/wxpay/checkOrder?tradeNo="+tradeNo)
.then(res => {
console.log(res.data +"," + count);
if(res.data == "SUCCESS"){
clearInterval(timer);
this.dialogFormVisible = false;
}
});
count++;
if(count == 10){
clearInterval(timer);
this.dialogFormVisible = false;
this.$message.error("支付超时");
}
},6000);
},
this.axios.post("/wxpay/makeOrder",this.qs.stringify(args))
.then(res => { console.log(res.data); //利用返回的code_url,显示支付二维码 this.wxpayUrl = "http://localhost:9000/wxpay/code?url="+res.data.code_url; console.log(this.wxpayUrl); //检查后台返回的订单号支付状态 this.checkOrder(res.data.trade_no); });
注:估计现在很多人一脸懵逼,二维码怎么莫名其妙生成了,记得往后面看,前端只不过是调接口,后端才是满满干货。
后端代码:
所需依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.dmdd</groupId>
<artifactId>educate_paopao</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>edu-pay-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>edu-pay-service</name>
<description>edu-pay-service</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.zxing/core -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.zxing/javase -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.dmdd</groupId>
<artifactId>common-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--rabitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
我用的功能比较多,所以依赖有点杂,我把只跟微信支付相关的依赖列出来,并讲解
<dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency>调用微信的api就需要这依赖
<!-- https://mvnrepository.com/artifact/com.google.zxing/core --> <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.3.3</version> </dependency> <!-- https://mvnrepository.com/artifact/com.google.zxing/javase --> <dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId> <version>3.3.3</version> </dependency>这两个依赖是用于生成二维码的,二维码只不过是一个链接,我们扫码,就等于访问一个链接
代码部分:
config:
package com.dmdd.edupayservice.config;
import com.github.wxpay.sdk.WXPayConfig;
import java.io.InputStream;
public class MyWXPayConfig implements WXPayConfig {
@Override
public String getAppID() {
return "wx30************e";
}
@Override
public String getMchID() {
return "15**********81";
}
@Override
public String getKey() {
return "HJd*******************g";
}
@Override
public InputStream getCertStream() {
return null;
}
@Override
public int getHttpConnectTimeoutMs() {
return 0;
}
@Override
public int getHttpReadTimeoutMs() {
return 0;
}
}
需要向微信申请才可获得
service:
package com.dmdd.edupayservice.service.impl;
import com.dmdd.common.entity.UserCourseOrder;
import com.dmdd.edupayservice.config.MyWXPayConfig;
import com.dmdd.edupayservice.config.RabbitMQConfig;
import com.dmdd.edupayservice.feign.CourseServiceFeignClient;
import com.dmdd.edupayservice.feign.OrderServiceFeignClient;
import com.dmdd.edupayservice.service.IWxPayService;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayUtil;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.util.Streams;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Console;
import java.io.IOException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* 微信支付Service
*/
@Slf4j
@Service
public class WxPayServiceImpl implements IWxPayService {
@Autowired
private CourseServiceFeignClient courseServiceFeignClient;
@Autowired
private OrderServiceFeignClient orderServiceFeignClient;
@Autowired
private RabbitTemplate rabbitTemplate;
public static final String DEVICE_INFO = "WEB";
public static final String BODY = "dmdd";
public static final String FEE_TYPE = "CNY";
//回调接口的URL
public static final String NOTIFY_URL = "http://ygp8pz4342342ee.cc/wxpay/callback";
public static final String TRADE_TYPE = "NATIVE";
public static final String SIGN = "5E00F9F72173C9449F802411E362012312312317078";
/**
* 下单
*/
public Map<String, String> makeWxOrder(long userId, long courseId, long activityId, long price) throws Exception {
//创建支付对象
MyWXPayConfig config = new MyWXPayConfig();
WXPay wxPay = new WXPay(config);
//配置微信支付
Map<String, String> map = new HashMap<>();
//应用id
map.put("appid", config.getAppID());
//商户id
map.put("mch_id", config.getMchID());
//device_info web
map.put("device_info", DEVICE_INFO);
map.put("nonce_str", WXPayUtil.generateNonceStr());
map.put("body", BODY);
//创建随机订单号
String tradeNo = UUID.randomUUID().toString().replace("-", "");
map.put("out_trade_no", tradeNo);
//FEE_TYPE=CNY 代表人民币
map.put("fee_type", FEE_TYPE);
//价格
map.put("total_fee", String.valueOf(price));
//微信对商户后台的回调接口,更新订单状态
map.put("notify_url", NOTIFY_URL);
map.put("trade_type", TRADE_TYPE);
map.put("product_id", String.valueOf(activityId));
//执行统一下单
Map<String, String> result = wxPay.unifiedOrder(map);
log.info("微信下单:{}", result);
if (result != null) {
//保存订单号
result.put("trade_no", tradeNo);
//创建课程订单
Timestamp now = Timestamp.valueOf(LocalDateTime.now());
UserCourseOrder order = new UserCourseOrder(System.currentTimeMillis(), userId, courseId, activityId, 1L, 0, tradeNo, price, now, now, 0);
orderServiceFeignClient.makeOrder(order);
//减少课程库存
courseServiceFeignClient.reduceStock(activityId);
//发送订单号,超时会进入死信队列
rabbitTemplate.convertAndSend(RabbitMQConfig.ORDER_EXCHANGE, RabbitMQConfig.ORDER_QUEUE_KEY, tradeNo,
//消息的后置处理
message -> {
message.getMessageProperties().setMessageId(UUID.randomUUID().toString());
//设置消息超时
message.getMessageProperties().setExpiration(String.valueOf(30 * 1000));
return message;
});
log.info("完成下单,订单:{}", result);
}
return result;
}
@Override
public void makeQRCode(String url, HttpServletResponse response) {
//通过支付链接生成二维码
HashMap<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
hints.put(EncodeHintType.MARGIN, 2);
try {
//把code_url包装到二维码图片
BitMatrix bitMatrix = new MultiFormatWriter().encode(url, BarcodeFormat.QR_CODE, 200, 200, hints);
MatrixToImageWriter.writeToStream(bitMatrix, "PNG", response.getOutputStream());
log.info("创建二维码完成");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
/**
* 检查订单状态
*/
public String checkWxOrder(String tradeNo) throws Exception {
MyWXPayConfig config = new MyWXPayConfig();
WXPay pay = new WXPay(config);
Map<String, String> map = new HashMap<>();
map.put("appid", config.getAppID());
map.put("mch_id", config.getMchID());
map.put("nonce_str", WXPayUtil.generateNonceStr());
map.put("out_trade_no", tradeNo);
map.put("sign", SIGN);
//查询订单
Map<String, String> res = pay.orderQuery(map);
String state = res.get("trade_state");
log.info("订单" + tradeNo + ",状态" + state);
return state;
}
@Override
public void wxpayCallback(HttpServletRequest request, HttpServletResponse response) throws Exception {
//获得微信传来的xml字符串
String str = Streams.asString(request.getInputStream());
//将字符串xml转换为Map
Map<String, String> map = WXPayUtil.xmlToMap(str);
//给微信发送消息
response.getWriter().println("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
//读取订单号
String tradeNo = map.get("out_trade_no");
//查询课程id
ResponseEntity<UserCourseOrder> entity = orderServiceFeignClient.getOrderByTradeNo(tradeNo);
UserCourseOrder order = entity.getBody();
//增加课程销量
log.info("课程id为" + order.getCourseId());
log.info("order:" + order);
courseServiceFeignClient.addCourseSales(order.getCourseId());
// //修改订单状态为已支付
order.setStatus(1);
orderServiceFeignClient.changeOrderStatus(order);
log.info("支付成功:{}", order);
}
}
创建订单功能详解:
public static final String BODY = "dmdd";
app的名字,随便取。
public static final String FEE_TYPE = "CNY";
表示支付的货币类型是人民币。
//回调接口的URL public static final String NOTIFY_URL = "http://ygp8pz.natappfree.cc/wxpay/callback";
微信服务会跟我们的后端发送一个一个请求,告诉我们商户支付结果,因为企业内部都是把服务部署到内网里面,所以想要微信访问就得用内网穿透这种技术,顾名思义就是外网可以访问内网。
//微信对商户后台的回调接口,更新订单状态 map.put("notify_url", NOTIFY_URL);
创建订单的时候把回调url的信息也存入订单中
那么如何内网穿透?
到natapp官网申请隧道,下载软件
"http://ygp8pz.natappfree.cc/wxpay/callback"
表示从外网访问 内网7777服务器内的wxpay/callback的请求。
嫌麻烦这一块也可以不搞,不影响微信支付的功能。
订单创建返回结果如下,红色框框勾出来的url就是调用微信支付功能的url,我们需要把这个url转换成二维码
创建二维码核心功能:
//把code_url包装到二维码图片 BitMatrix bitMatrix = new MultiFormatWriter().encode(url, BarcodeFormat.QR_CODE, 200, 200, hints); MatrixToImageWriter.writeToStream(bitMatrix, "PNG", response.getOutputStream()); log.info("创建二维码完成");创建二维码图片的核心功能。
返回订单状态核心功能:
public static final String SIGN = "5E00F9************************8";认证签名,只有正确的签名wx才会接受你的返回
WXPay pay = new WXPay(config);
//查询订单 Map<String, String> res = pay.orderQuery(map);
得到wx返回给我们的支付状态,不懂?看下面
支付有没有成功,看上面的打印就知道了。玩过连连看的,应该都看得懂。
注:大伙应该都点过外卖,在支付超时的时间之内,每隔一段时间就会通知我们支付结果。
成功结果返回核心功能:
根据自己的实际需求来。
controller层:
package com.dmdd.edupayservice.controller;
import com.dmdd.edupayservice.service.IWxPayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
@RestController
@Slf4j
@RequestMapping("wxpay")
public class WXOrderController {
@Autowired
private IWxPayService payService;
/**
* 微信下单
*/
@PostMapping("makeOrder")
public Map<String,String> makeOrder(Long userId, Long courseId, Long activityId, Long price) throws Exception {
//微信下单
return payService.makeWxOrder(userId,courseId, activityId, price);
}
/**
* 生成支付二维码
* @param url
* @param response
*/
@RequestMapping("code")
public void createWxPayCode(String url, HttpServletResponse response){
//创建支付二维码
payService.makeQRCode(url,response);
log.info("生成微信支付二维码:{}",url);
}
/**
* 检查订单状态
*/
@RequestMapping("checkOrder")
public String checkOrder(String tradeNo) throws Exception {
if (StringUtils.isEmpty(tradeNo)) {
return null;
}
String status = payService.checkWxOrder(tradeNo);
log.info("检查订单{} 状态:{}",tradeNo,status);
return status;
}
/**
* 微信支付平台支付成功的回调
* @param request
* @param response
* @throws Exception
*/
@RequestMapping("callback")
public void paySuccessCallback(HttpServletRequest request, HttpServletResponse response) throws Exception {
payService.wxpayCallback(request,response);
}
}
以上的代码还包含与实际业务有关的其他功能 比如rabbitmq的死信队列 分布式feign调用等,可以注释掉不使用