微信支付提供了多种产品,即 微信支付多种支付的形式。如:
微信小程序支付时序图:
微信小程序支付 主要内容为以下三个部分:
- “商家端JSAPI下单” 接口 / 微信下单接口 / 预下单接口
- “微信小程序端调起支付” 接口 / 调起微信支付
- 推送支付结果 / notify_url回调请求
商户系统 调用 (微信小程序支付中的) JSAPI接口 /JSAPI预下单接口 在 微信支付服务后台 生成 预支付交易单(即: "prepay_id ) ,同时将该预支付交易单,响应给商户系统。
“商家端JSAPI下单” 接口-详解
商家端通过 访问 https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi 接口来生成预支付交易单。
商家端JSAPI下单 / 生成预支付交易单 要访问的接口
请求示例
{ "mchid": "1900006XXX", "out_trade_no": "1217752501201407033233368318", "appid": "wxdace645e0bc2cXXX", "description": "Image形象店-深圳腾大-QQ公仔", "notify_url": "https://www.weixin.qq.com/wxpay/pay.php", "amount": { "total": 1, "currency": "CNY" }, "payer": { "openid": "o4GgauInH_RCEdvrrNGrntXDuXXX" } }
- 返回示例 (正常示例)
{ "prepay_id": "wx26112221580621e9b071c00d9e093b0000" }
适用对象: 直连商户
请求URL:https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi
请求方式:POST
请求参数
请求参数-详解
参数名 变量 类型[长度限制] 必填 描述 应用ID appid string[1,32] 是 body 由微信生成的应用ID,全局唯一。请求基础下单接口时请注意APPID的应用属性,例如公众号场景下,需使用应用属性为公众号的服务号APPID 示例值:wxd678efh567hg6787 直连商户号 mchid string[1,32] 是 body 直连商户的商户号,由微信支付生成并下发。 示例值:1230000109 商品描述 description string[1,127] 是 body 商品描述 示例值:Image形象店-深圳腾大-QQ公仔 通知地址 notify_url string[1,256] 是 body异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http 示例值:https://www.weixin.qq.com/wxpay/pay.php 商户订单号 out_trade_no string[6,32] 是 body 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一 示例值:1217752501201407033233368018
订单金额 amount object 是 body 订单金额信息 参数名 变量 类型[长度限制] 必填 描述 总金额 total int 是 订单总金额,单位为分。 示例值:100 货币类型 currency string[1,16] 否 CNY:人民币,境内商户号仅支持人民币。 示例值:CNY
支付者 payer object 是 body 支付者信息 参数名 变量 类型[长度限制] 必填 描述 用户标识 openid string[1,128] 是 用户在直连商户appid下的唯一标识。 下单前需获取到用户的Openid,Openid获取详见 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o 返回参数
参数名 变量 类型[长度限制] 必填 描述 预支付交易会话标识 prepay_id string[1,64] 是 预支付交易会话标识。用于后续接口调用中使用,该值有效期为2小时 示例值:wx201410272009395522657a690389285100
通过JSAPI下单接口获取到发起支付的必要参数:prepay_id,然后调用微信支付平台提供的 “微信小程序端调起支付” 接口 / wx.requestPayment(OBJECT) 方法完成微信小程序支付。
“微信小程序端调起支付” 接口-详解
微信小程序通过调用wx.requestPayment(OBJECT) 发起微信支付。
请求示例
wx.requestPayment ( { "timeStamp": "1414561699", "nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS", "package": "prepay_id=wx201410272009395522657a690389285100", "signType": "RSA", "paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==", "success":function(res){}, "fail":function(res){}, "complete":function(res){} } )适用对象: 直连商户
接口定义
此API无后台接口交互,需要将列表中的数据签名。
参数名 变量 类型[长度限制] 必填 描述 小程序ID appId string[1,32] 是 商户申请的小程序对应的appid,由微信支付生成,可在小程序后台查看 示例值:wx8888888888888888 时间戳 timeStamp string[1,32] 是 时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数。注意:部分系统取到的值为毫秒级,需要转换成秒(10位数字)。 示例值:1414561699 随机字符串 nonceStr string[1,32] 是 随机字符串,不长于32位。推荐随机数生成算法。 示例值:5K8264ILTKCH16CQ2502SI8ZNMTM67VS 订单详情扩展字符串 package string[1,128] 是 小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** 示例值:prepay_id=
wx201410272009395522657a690389285100签名方式 signType string[1,32] 是 签名类型,默认为RSA,仅支持RSA。 示例值:RSA 签名 paySign string[1,512] 是 签名,使用字段appId、timeStamp、nonceStr、
package计算得出的签名值
oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRA
Z/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZ
vI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4
WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17
D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYK
UR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLm
R9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDm
XxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcun
Xt8cqNjKNqZLhLw4jq/xDg==调用wx.requestPayment(OBJECT)发起微信支付
接口名称: wx.requestPayment
Object 参数说明:
( 这些需要用到的参数全都是后端计算好,返回给微信小程序,然后小程序直接使用这些参数来调用方法来就会弹出“微信支付”的窗口,来完成微信支付。 )
参数名 变量 类型[长度限制] 必填 描述 时间戳 timeStamp string[1,32] 是 当前的时间,其他详见时间戳规则。 示例值:1414561699 随机字符串 nonceStr string[1,32] 是 随机字符串,不长于32位。 示例值:5K8264ILTKCH16CQ2502SI8ZNMTM67VS 订单详情扩展字符串 package string[1,128] 是 小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** 示例值:prepay_id=wx201410272009395522657a690389285100 签名方式 signType string[1,32] 是 签名类型,默认为RSA,仅支持RSA。 示例值:RSA 签名 paySign string[1,512] 是 签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值 示例值:oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq/xDg==
- 回调结果
回调类型 errMsg 说明 success requestPayment:ok 调用支付成功 fail requestPayment:fail cancel 用户取消支付 fail requestPayment:fail (detail message) 调用支付失败,其中 detail message 为后台返回的详细失败原因
微信小程序支付”流程-详细讲解 :
- 小程序支付页面,点击 “确认支付” 按钮,发送一个 “支付请求” 给 “商户系统”。
- 商户系统根据 “已有 或 “从配置文件中获得的参数” 来调用 “商家端JSAPI预下单”接口。
核心代码操作集中于 OrderServiceImpl.java ,在该.java文件 中会调用 “微信支付工具类”(中的方法) 来根据 “准备好的参数” 来访问JSAPI接口,生成 预支付交易单 ( prepay_id )。 同时在微信支付工具类中 获得小程序端 调用“微信小程序端调起支付”接口 所需要的 一系列参数 ,将这一系列参数封装为JSON对象作为( 微信支付工具类中方法的 )返回值,在微信工具类之外将JSON对象转换为VO对象响应给微信小程序端。
- 微信小程序端 根据 后端响应来的参数 + wx.requestPayment( )来调用“微信小程序端调起支付”接口,以此来完成“微信支付”。
- 当“小程序端支付”成功时,微信支付后台会根据 notifyUrl (支付成功回调 / 支付成功通知) 设置的 对应的路径,发送一个请求,在该请求的方法体中,一般调用商户系统的方法来修改“订单表”中的数据,同时还可给微信做出响应。
- 获得微信支付平台证书、商户私钥文件 :这两个文件是从微信的商户平台下载下来的,程序开发过程中会使用到这两个文件。
ps :要获得这两个文件必须注册成商户。
- 让当前 电脑能获取一个公网的IP地址,让微信后台能调用到当前外卖系统的后端服务,这样我们就需要来 获取临时域名。
(这个临时域名对应的就是一个公网IP)
- 下载链接:https://dashboard.cpolar.com/login
cpolar软件下载
- 安装包 (百度网盘下载链接):https://pan.baidu.com/s/10O1mK06ts-l37exniuTquw?pwd=ir23 提取码:ir23
百度网盘获取cpolar安装包
①
登录cpolar官网 : https://dashboard.cpolar.com/login 获得配置cpolar的cmd命令。
②
在cpolar.exe的目录中敲cmd进入 cmd页面。
在命令行页面中输入 :cpolar.exe authtoken 获得的Authtoken
该命令生成了一个 .yml文件 : 该文件是 当前“内网穿透工具”的配置文件。
输入命令 :cpolar.exe http 8080
OrderController.java 中的代码 ( 订单Controller) :
@RestController("userOrderController") //起别名 @Slf4j @RequestMapping("/user/order") @Api(tags = "用户端订单相关接口") public class OrderController { @Autowired private OrderService orderService; /** * 订单支付 * * 小程序点击去“支付”,请求到该接口,此接口(在商户系统中)先调用JSAPI预下单接口,获得 *prepay_id ,同时设置好微信小程序端调用 ”微信小程序端调起支付“接口所需的各种参数,且将 * 参数封装到 OrderPaymentVO 中响应给微信用户端,微信用户端用这些已经准备好的参数来完 * 成“微信小程序支付” * * @param ordersPaymentDTO * @return */ @PutMapping("/payment") @ApiOperation("订单支付") public Resultpayment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception { log.info("订单支付:{}", ordersPaymentDTO); //该方法的返回值为: 小程序端中使用 ”微信小程序端调起支付“ 接口所需的各种参数 OrderPaymentVO orderPaymentVO = orderService.payment(ordersPaymentDTO); // orderPaymentVO : 包含了小程序端中使用 ”微信小程序端调起支付“ 接口所需的各种参数 log.info("生成预支付交易单:{}", orderPaymentVO); //微信小程序端,拿到这个(wx.requestPayment()方法所需的)参数即会进行“微信支付” return Result.success(orderPaymentVO); } } OrderPaymentVO.Java :
@Data @Builder @NoArgsConstructor @AllArgsConstructor //这个VO里的属性为: “微信小程序端掉漆支付”接口 所需要的参数 public class OrderPaymentVO implements Serializable { private String nonceStr; //随机字符串 private String paySign; //签名 private String timeStamp; //时间戳 private String signType; //签名算法 private String packageStr; //统一下单接口返回的 prepay_id 参数值 }OrdersPaymentDTO.java :
@Data //微信小程序中传来: 订单号、付款方式 public class OrdersPaymentDTO implements Serializable { //订单号 private String orderNumber; //付款方式 private Integer payMethod; }
OrderService.java :
public interface OrderService { /** * 订单支付 * @param ordersPaymentDTO * @return */ OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception; /** * 支付成功,修改订单状态 * @param outTradeNo */ void paySuccess(String outTradeNo); }
OrderServiceImpl.java :
@Service //将该类加入到容器中,成为bean public class OrderServiceImpl implements OrderService { @Autowired private OrderMapper orderMapper; @Autowired private OrderdetailMapper orderdetailMapper; @Autowired private AddressBookMapper addressBookMapper; @Autowired private ShoppingcartMapper shoppingcartMapper; @Autowired private WeChatPayUtil weChatPayUtil; @Autowired private UserMapper userMapper; /** * 订单支付 * * @param ordersPaymentDTO * @return */ public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception { // 当前登录用户id Long userId = BaseContext.getCurrentId(); User user = userMapper.getById(userId); //调用微信支付接口(“商户系统”调用的“JSAPT预下单接口”),生成“预支付交易单” /* 调用微信支付工具类的方法,来进行调用 JSAPI预支付接口 , 同时构造成 微信小程序端调起支付 接口 所以的 JSON参数,将该参数作为pay()方法的返回值 */ //jsonObject : 为一个JSON对象,其中包含了 “微信小程序端调起支付”接口所需的JSON参数 JSONObject jsonObject = weChatPayUtil.pay( ordersPaymentDTO.getOrderNumber(), //商户订单号 new BigDecimal(0.01), //支付金额,单位 元 "苍穹外卖订单", //商品描述 user.getOpenid() //微信用户的openid ); if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) { throw new OrderBusinessException("该订单已支付"); } //将JSON参数对象 转换为 Java对象 OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class); vo.setPackageStr(jsonObject.getString("package")); //将该OrderPaymentVO 对象作为返回值 ,该对象中包含了 : “微信小程序端调起支付”接口所需的JSON参数 return vo; } /** * 支付成功,修改订单状态 * * 该方法是在 “支付回调”路径下的方法中 被调用的,用于修改数据库中的“订单表”的数据 * (小程序支付成功时,会按照 notifyUrl 设置的回调路径 进行路径访问,在访问到的方法中 会 * 调用此处的 paySuccess()方法修改数据库 * 中“订单表的数据”) * * @param outTradeNo */ public void paySuccess(String outTradeNo) { // 根据订单号查询订单 Orders ordersDB = orderMapper.getByNumber(outTradeNo); // 根据订单id更新订单的状态、支付方式、支付状态、结账时间 //修改“订单表”的数据 Orders orders = Orders.builder() .id(ordersDB.getId()) .status(Orders.TO_BE_CONFIRMED) .payStatus(Orders.PAID) .checkoutTime(LocalDateTime.now()) .build(); orderMapper.update(orders); //修改数据库中“订单表”的信息 } }BaseContext.class :
public class BaseContext { //该类对ThreadLocal对象本身其其下的三个方法进行了封装,方便且更好的调用 //创建 ThreadLocal 对象,可在其中设置“线程局部变量”,存储数据该独享的线程中,后“该存入的数据”会被取出来 public static ThreadLocalthreadLocal = new ThreadLocal<>(); /** * 存入请求用户的id * (设置“线程局部变量”) * @param id */ public static void setCurrentId(Long id) { //调用ThreadLocal对象的.set(T value) 设置线程局部变量 / 存储数据进该“请求”独享的“线程”中 threadLocal.set(id); } /** * 获得请求用户的id * (获得“线程局部变量”) * @return */ public static Long getCurrentId() { return threadLocal.get(); } /** * 移除请求用户的id * (移除“线程局部变量”) */ public static void removeCurrentId() { threadLocal.remove(); } } 微信支付工具类 / WeChatPayUtil.java :
/** * 微信支付工具类 */ @Component public class WeChatPayUtil { //微信支付下单接口地址 (JSAPI"商户系统"预下单接口) public static final String JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; //申请退款接口地址 public static final String REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"; @Autowired private WeChatProperties weChatProperties; /** * 获取调用微信接口的客户端工具对象 * * @return */ private CloseableHttpClient getClient() { PrivateKey merchantPrivateKey = null; try { //merchantPrivateKey商户API私钥,如何加载商户API私钥请看常见问题 merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath()))); //加载平台证书文件 X509Certificate x509Certificate = PemUtil.loadCertificate(new FileInputStream(new File(weChatProperties.getWeChatPayCertFilePath()))); //wechatPayCertificates微信支付平台证书列表。你也可以使用后面章节提到的“定时更新平台证书功能”,而不需要关心平台证书的来龙去脉 ListwechatPayCertificates = Arrays.asList(x509Certificate); WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create() .withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey) .withWechatPay(wechatPayCertificates); // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签 CloseableHttpClient httpClient = builder.build(); return httpClient; } catch (FileNotFoundException e) { e.printStackTrace(); return null; } } /** * 发送post方式请求 * * @param url 请求地址 * @param body 请求参数: JSON字符串 * @return */ private String post(String url, String body) throws Exception { CloseableHttpClient httpClient = getClient(); HttpPost httpPost = new HttpPost(url); httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString()); httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo()); httpPost.setEntity(new StringEntity(body, "UTF-8")); //该post请求时,返回一个response对象 CloseableHttpResponse response = httpClient.execute(httpPost); try { //获得一个 bodyAsString 字符串 String bodyAsString = EntityUtils.toString(response.getEntity()); return bodyAsString; //返回 } finally { httpClient.close(); response.close(); } } /** * 发送get方式请求 * * @param url * @return */ private String get(String url) throws Exception { CloseableHttpClient httpClient = getClient(); HttpGet httpGet = new HttpGet(url); httpGet.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString()); httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo()); CloseableHttpResponse response = httpClient.execute(httpGet); try { String bodyAsString = EntityUtils.toString(response.getEntity()); return bodyAsString; } finally { httpClient.close(); response.close(); } } /** * jsapi下单 (商户系统调用 JSAPI预下单接口 ,该操作为小程序微信支付的第一步) * * @param orderNum 商户订单号 * @param total 总金额 * @param description 商品描述 * @param openid 微信用户的openid * @return */ private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception { JSONObject jsonObject = new JSONObject(); //JSON对象 jsonObject.put("mchid", weChatProperties.getMchid()); //商户号 jsonObject.put("out_trade_no", orderNum); //商户订单号 //小程序的appid (自己建的小程序有专属的appid) jsonObject.put("appid", weChatProperties.getAppid()); jsonObject.put("description", description); //商品描述 //这个及其重要: 小程序用户支付成功则调用指定url (访问指定路径的请求) /* 例子如: notifyUrl: https://58869fb.r2.cpolar.top/notify/paySuccess :支付成功则 访问 .../8080/notify/paySuccess路径 (https://58869fb.r2.cpolar.top/notify : 为内网穿透内容) */ //支付成功的回调地址 jsonObject.put("notify_url", weChatProperties.getNotifyUrl()); JSONObject amount = new JSONObject(); //JSON对象 ---订单金额信息 amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue()); amount.put("currency", "CNY"); jsonObject.put("amount", amount); //订单金额信息 JSONObject payer = new JSONObject(); //JSON对象 ---支付者信息 payer.put("openid", openid); //微信用户的唯一id jsonObject.put("payer", payer); //支付者信息 String body = jsonObject.toJSONString(); //转换为“JSON字符串” //JSAPI 为url常量 return post(JSAPI, body); // 将post()方法的返回值 作为jsapi()方法的返回值 } /** * 小程序支付 * * @param orderNum 商户订单号(订单号) * @param total 金额,单位 元 * @param description 商品描述 * @param openid 微信用户的 openid * @return */ public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception { //统一下单,生成预支付交易单 String bodyAsString = jsapi(orderNum, total, description, openid); /* 获得bodyAsString表明,商户系统成功调用JSAPI预下单接口了,此时或获得 “prepay_id” ---将prepay_id传给“小程序端”用于微信支付 */ //解析返回结果 JSONObject jsonObject = JSON.parseObject(bodyAsString); System.out.println(jsonObject); //获得 prepayId : 预支付交易会话标识 String prepayId = jsonObject.getString("prepay_id"); // 如果 prepayId : 预支付交易会话标识 不为空,才能让用户小程序端进行"微信支付" if (prepayId != null) { //时间戳 String timeStamp = String.valueOf(System.currentTimeMillis() / 1000); //随机字符串 String nonceStr = RandomStringUtils.randomNumeric(32); ArrayList OrderBusinessException.java :
public class OrderBusinessException extends BaseException { public OrderBusinessException(String msg) { super(msg); } }
OrderMapper.java :
@Mapper //将该接口的实现类加入到容器中 public interface OrderMapper { /** * 插入订单数据 * @param orders */ void insert(Orders orders); /** * 根据订单号查询订单 * @param orderNumber */ @Select("select * from orders where number = #{orderNumber}") Orders getByNumber(String orderNumber); /** * 修改订单信息 * @param orders */ void update(Orders orders); }Orders.java :
/** * 订单 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class Orders implements Serializable { /** * 订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消 */ public static final Integer PENDING_PAYMENT = 1; public static final Integer TO_BE_CONFIRMED = 2; public static final Integer CONFIRMED = 3; public static final Integer DELIVERY_IN_PROGRESS = 4; public static final Integer COMPLETED = 5; public static final Integer CANCELLED = 6; /** * 支付状态 0未支付 1已支付 2退款 */ public static final Integer UN_PAID = 0; public static final Integer PAID = 1; public static final Integer REFUND = 2; private static final long serialVersionUID = 1L; private Long id; //订单号 private String number; //订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消 7退款 private Integer status; //下单用户id private Long userId; //地址id private Long addressBookId; //下单时间 private LocalDateTime orderTime; //结账时间 private LocalDateTime checkoutTime; //支付方式 1微信,2支付宝 private Integer payMethod; //支付状态 0未支付 1已支付 2退款 private Integer payStatus; //实收金额 private BigDecimal amount; //备注 private String remark; //用户名 private String userName; //手机号 private String phone; //地址 private String address; //收货人 private String consignee; //订单取消原因 private String cancelReason; //订单拒绝原因 private String rejectionReason; //订单取消时间 private LocalDateTime cancelTime; //预计送达时间 private LocalDateTime estimatedDeliveryTime; //配送状态 1立即送出 0选择具体时间 private Integer deliveryStatus; //送达时间 private LocalDateTime deliveryTime; //打包费 private int packAmount; //餐具数量 private int tablewareNumber; //餐具数量状态 1按餐量提供 0选择具体数量 private Integer tablewareStatus; }
OrderMapper.xml :
update orders where id = #{id} cancel_reason=#{cancelReason}, rejection_reason=#{rejectionReason}, cancel_time=#{cancelTime}, pay_status=#{payStatus}, pay_method=#{payMethod}, checkout_time=#{checkoutTime}, status = #{status}, delivery_time = #{deliveryTime}
UserMapper.java :
@Mapper public interface UserMapper { /** * 根据id查询数据 */ @Select("select * from user where id = #{id}") User getById(Long userId); }
PayNotifyController.java / 支付回调相关接口:
/** * 支付回调相关接口 */ @RestController @RequestMapping("/notify") @Slf4j public class PayNotifyController { @Autowired private OrderService orderService; @Autowired private WeChatProperties weChatProperties; /** * 支付成功回调 : * 小程序用户支付成功时,放根据 notify_url 设置的路径来访问到此处的 paySuccess路径 *下的 paySuccessNotify()方法。 * 同时在该方法中将调用 “商户系统”中的方法来修改数据库中的“订单表”的数据 * @param request */ @RequestMapping("/paySuccess") public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception { //读取数据 String body = readData(request); log.info("支付成功回调:{}", body); //数据解密 String plainText = decryptData(body); log.info("解密后的文本:{}", plainText); JSONObject jsonObject = JSON.parseObject(plainText); String outTradeNo = jsonObject.getString("out_trade_no"); //商户平台订单号 String transactionId = jsonObject.getString("transaction_id"); //微信支付交易号 log.info("商户平台订单号:{}", outTradeNo); log.info("微信支付交易号:{}", transactionId); //业务处理,修改订单状态、来单提醒 -- 用于修改"订单表"中的数据 /** * “商户系统”中的方法来修改数据库中的“订单表”的数据 */ orderService.paySuccess(outTradeNo); //给微信响应 responseToWeixin(response); } /** * 读取数据 * * @param request * @return * @throws Exception */ private String readData(HttpServletRequest request) throws Exception { BufferedReader reader = request.getReader(); StringBuilder result = new StringBuilder(); String line = null; while ((line = reader.readLine()) != null) { if (result.length() > 0) { result.append("\n"); } result.append(line); } return result.toString(); } /** * 数据解密 * * @param body * @return * @throws Exception */ private String decryptData(String body) throws Exception { JSONObject resultObject = JSON.parseObject(body); JSONObject resource = resultObject.getJSONObject("resource"); String ciphertext = resource.getString("ciphertext"); String nonce = resource.getString("nonce"); String associatedData = resource.getString("associated_data"); AesUtil aesUtil = new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8)); //密文解密 String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext); return plainText; } /** * 给微信响应 * @param response */ private void responseToWeixin(HttpServletResponse response) throws Exception{ response.setStatus(200); HashMap
application.yml
server: port: 8080 spring: profiles: active: dev main: allow-circular-references: true datasource: druid: driver-class-name: ${sky.datasource.driver-class-name} url: jdbc:mysql://${sky.datasource.host}:${sky.datasource.port}/${sky.datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true username: ${sky.datasource.username} password: ${sky.datasource.password} redis: host: ${sky.redis.host} port: ${sky.redis.port} password: ${sky.redis.password} database: ${sky.redis.database} mybatis: #mapper配置文件 mapper-locations: classpath:mapper/*.xml type-aliases-package: com.sky.entity configuration: #开启驼峰命名 map-underscore-to-camel-case: true logging: level: com: sky: mapper: debug service: info controller: info sky: jwt: # 设置jwt签名加密时使用的秘钥 admin-secret-key: itcast # 设置jwt过期时间 admin-ttl: 7200000 # 设置前端传递过来的令牌名称 admin-token-name: token user-secret-key: itheima user-ttl: 7200000 user-token-name: authentication alioss: endpoint: ${sky.alioss.endpoint} access-key-id: ${sky.alioss.access-key-id} access-key-secret: ${sky.alioss.access-key-secret} bucket-name: ${sky.alioss.bucket-name} wechat: appid: ${sky.wechat.appid} secret: ${sky.wechat.secret} mchid : ${sky.wechat.mchid} mchSerialNo: ${sky.wechat.mchSerialNo} privateKeyFilePath: ${sky.wechat.privateKeyFilePath} apiV3Key: ${sky.wechat.apiV3Key} weChatPayCertFilePath: ${sky.wechat.weChatPayCertFilePath} notifyUrl: ${sky.wechat.notifyUrl} refundNotifyUrl: ${sky.wechat.refundNotifyUrl} shop: address: 北京市海淀区上地十街10号 baidu: ak: your-ak
application-dev.xml
sky: datasource: driver-class-name: com.mysql.cj.jdbc.Driver host: localhost port: 3306 database: sky_take_out username: root password: root alioss: endpoint: oss-cn-beijing.aliyuncs.com access-key-id: your-access-key-id access-key-secret: your-access-key-secret bucket-name: your-bucket-name redis: host: localhost port: 6379 password: 123456 database: 10 wechat: appid: wxffb3637a228223b8 #小程序的appid secret: 84311df9199ecacdf4f12d27b6b9522d #小程序的秘钥 mchid : 1561414331 #商户号 mchSerialNo: 4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606 #商户API证书的证书序列号 privateKeyFilePath: D:\pay\apiclient_key.pem #商户私钥文件(路径) apiV3Key: CZBK51236435wxpay435434323FFDuv3 #证书解密的密钥 weChatPayCertFilePath: D:\pay\wechatpay_166D96F876F45C7D07CE98952A96EC980368ACFC.pem #微信支付平台证书(路径) #(支付成功)通知url : 当“小程序端微信支付成功”时,调用该请求,请求到后端 #这个及其重要: 小程序用户支付成功则调用该url (访问该路径的请求), 如这里: 支付成功则访问 #.../8080/notify/paySuccess路径 notifyUrl: https://58869fb.r2.cpolar.top/notify/paySuccess #支付成功的回调地址 #(退款成功)通知Url refundNotifyUrl: https://58869fb.r2.cpolar.top/notify/refundSuccess
微信支付工具类 :
/** * 微信支付工具类 */ @Component public class WeChatPayUtil { //微信支付下单接口地址 (JSAPI"商户系统"预下单接口) public static final String JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; //申请退款接口地址 public static final String REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"; @Autowired private WeChatProperties weChatProperties; /** * 获取调用微信接口的客户端工具对象 * * @return */ private CloseableHttpClient getClient() { PrivateKey merchantPrivateKey = null; try { //merchantPrivateKey商户API私钥,如何加载商户API私钥请看常见问题 merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath()))); //加载平台证书文件 X509Certificate x509Certificate = PemUtil.loadCertificate(new FileInputStream(new File(weChatProperties.getWeChatPayCertFilePath()))); //wechatPayCertificates微信支付平台证书列表。你也可以使用后面章节提到的“定时更新平台证书功能”,而不需要关心平台证书的来龙去脉 ListwechatPayCertificates = Arrays.asList(x509Certificate); WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create() .withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey) .withWechatPay(wechatPayCertificates); // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签 CloseableHttpClient httpClient = builder.build(); return httpClient; } catch (FileNotFoundException e) { e.printStackTrace(); return null; } } /** * 发送post方式请求 * * @param url 请求地址 * @param body 请求参数: JSON字符串 * @return */ private String post(String url, String body) throws Exception { CloseableHttpClient httpClient = getClient(); HttpPost httpPost = new HttpPost(url); httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString()); httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo()); httpPost.setEntity(new StringEntity(body, "UTF-8")); //该post请求时,返回一个response对象 CloseableHttpResponse response = httpClient.execute(httpPost); try { //获得一个 bodyAsString 字符串 String bodyAsString = EntityUtils.toString(response.getEntity()); return bodyAsString; //返回 } finally { httpClient.close(); response.close(); } } /** * 发送get方式请求 * * @param url * @return */ private String get(String url) throws Exception { CloseableHttpClient httpClient = getClient(); HttpGet httpGet = new HttpGet(url); httpGet.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString()); httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo()); CloseableHttpResponse response = httpClient.execute(httpGet); try { String bodyAsString = EntityUtils.toString(response.getEntity()); return bodyAsString; } finally { httpClient.close(); response.close(); } } /** * jsapi下单 (商户系统调用 JSAPI预下单接口 ,该操作为小程序微信支付的第一步) * * @param orderNum 商户订单号 * @param total 总金额 * @param description 商品描述 * @param openid 微信用户的openid * @return */ private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception { JSONObject jsonObject = new JSONObject(); //JSON对象 jsonObject.put("mchid", weChatProperties.getMchid()); //商户号 jsonObject.put("out_trade_no", orderNum); //商户订单号 //小程序的appid (自己建的小程序有专属的appid) jsonObject.put("appid", weChatProperties.getAppid()); jsonObject.put("description", description); //商品描述 //这个及其重要: 小程序用户支付成功则调用指定url (访问指定路径的请求) /* 例子如: notifyUrl: https://58869fb.r2.cpolar.top/notify/paySuccess :支付成功则 访问 .../8080/notify/paySuccess路径 (https://58869fb.r2.cpolar.top/notify : 为内网穿透内容) */ //支付成功的回调地址 jsonObject.put("notify_url", weChatProperties.getNotifyUrl()); JSONObject amount = new JSONObject(); //JSON对象 ---订单金额信息 amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue()); amount.put("currency", "CNY"); jsonObject.put("amount", amount); //订单金额信息 JSONObject payer = new JSONObject(); //JSON对象 ---支付者信息 payer.put("openid", openid); //微信用户的唯一id jsonObject.put("payer", payer); //支付者信息 String body = jsonObject.toJSONString(); //转换为“JSON字符串” //JSAPI 为url常量 return post(JSAPI, body); // 将post()方法的返回值 作为jsapi()方法的返回值 } /** * 小程序支付 * * @param orderNum 商户订单号(订单号) * @param total 金额,单位 元 * @param description 商品描述 * @param openid 微信用户的 openid * @return */ public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception { //统一下单,生成预支付交易单 String bodyAsString = jsapi(orderNum, total, description, openid); /* 获得bodyAsString表明,商户系统成功调用JSAPI预下单接口了,此时或获得 “prepay_id” ---将prepay_id传给“小程序端”用于微信支付 */ //解析返回结果 JSONObject jsonObject = JSON.parseObject(bodyAsString); System.out.println(jsonObject); //获得 prepayId : 预支付交易会话标识 String prepayId = jsonObject.getString("prepay_id"); // 如果 prepayId : 预支付交易会话标识 不为空,才能让用户小程序端进行"微信支付" if (prepayId != null) { //时间戳 String timeStamp = String.valueOf(System.currentTimeMillis() / 1000); //随机字符串 String nonceStr = RandomStringUtils.randomNumeric(32); ArrayList
1