网页实现微信支付

简介: 网页实现微信支付

1 微信扫码支付开通

1.1 方式一

第一步:注册公众号

https://mp.weixin.qq.com/ 类型须为:服务号

请根据营业执照类型选择以下主体注册:个体工商户| 企业/公司| 政府| 媒体| 其他类型

第二步:认证公众号

公众号认证后才可申请微信支付,认证费:300元/次。

第三步:注册商户号

https://pay.weixin.qq.com PC网站接入支付

第四步:账户验证

step1:汇款验证

step2:等待审核,1-2个工作日

第五步:签署协议

第六步:关联APPID

将微信公众号appid关联至商户平台:

产品中心 ==> APPID授权管理 ==> 申请账号关联

第七步:设置API秘钥

账户中心 ==>API安全 ==> API秘钥 ==> 设置API秘钥

1.2方式二

第一步:注册公众号

https://mp.weixin.qq.com/ 类型须为:服务号

请根据营业执照类型选择以下主体注册:个体工商户| 企业/公司| 政府| 媒体| 其他类型

第二步:认证公众号

公众号认证后才可申请微信支付,认证费:300元/次。

第三步:提交资料申请微信支付

登录公众平台,点击左侧菜单【微信支付】,开始填写资料等待审核,审核时间为1-5个工作日内。

第四步:开户成功,登录商户平台进行账户验证

资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填写财付通备付金打的小额资金数额,完成账户验证。

第五步:签署协议

本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效。

第六步:设置API秘钥账户中心 ==>API安全 ==> API秘钥 ==> 设置API秘钥

2 开发文档

2.1 场景介绍

参考官方文档:https://pay.weixin.qq.com/wiki/doc/api/index.html Native支付

用户扫描商户展示在各种场景的二维码进行支付。

步骤1:商户根据微信支付的规则,为不同商品生成不同的二维码,展示在各种场景,用于用户扫描购买。

步骤2:用户使用微信“扫一扫”扫描二维码后,获取商品支付信息,引导用户完成支付。

步骤3:用户确认支付,输入支付密码。

步骤4:支付完成后会提示用户支付成功,商户后台得到支付成功的通知。

2.2 开发步骤

推荐使用模式二

1、模式一

参考流程:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4


商户后台系统根据微信支付规则链接生成二维码,链接中带固定参数productid(可定义为产品标识或订单号)。用户扫码后,微信支付系统将productid和用户唯一标识(openid)回调商户后台系统(需要设置支付回调URL),商户后台系统根据productid生成支付交易,最后微信支付系统发起用户支付流程。


2、模式二


参考流程:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5(时序图中红色部分是需要我们开发的内容)


模式二与模式一相比,流程更为简单,不依赖设置的回调支付URL。商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。注意:code_url有效期为2小时,过期后扫码不能再发起支付。

2.3 微信支付接口规则

微信支付接口调用的整体思路:按API要求组装参数,以XML方式发送(POST)给微信支付接口(URL),微信支付接口也是以XML方式给予响应。


了解参考官方文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_1


协议规则

参数规定

安全规范

使用sdk可以简化接口的调用

3 生成支付二维码并支付

3.1 准备

1、添加微信支付SDK方式一:service_trade中添加依赖:

<dependencies>
    <!--微信支付-->
    <dependency>
        <groupId>com.github.wxpay</groupId>
        <artifactId>wxpay-sdk</artifactId>
        <version>0.0.3</version>
    </dependency>
</dependencies>

方式二:将下载的sdk源码放入service_trade源码目录中

2、配置yml参数

支付账户相关参数:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=3_1

weixin:
  pay:
    #关联的公众号appid
    appId: wxf913bfa3a225c7eeeb
    #商户号
    partner: 154333258551
    #商户key
    partnerKey: atguigu3b0kn9g5v25426MKfHQH7X8rKwb
    #回调地址
    notifyUrl: http://imhelen.free.idcfengye.com/api/trade/weixin-pay/callback/notify 

配置后启动ngrok内网穿透工具

3、参数读取工具类

package com.oldlu.service.trade.util;
@Data
@Component
@ConfigurationProperties(prefix="weixin.pay")
public class WeixinPayProperties {
    private String appId;
    private String partner;
    private String partnerKey;
    private String notifyUrl;
}

4、辅助业务方法

OrderService:getOrderByOrderNo 根据订单号查询订单

接口:

Order getOrderByOrderNo(String orderNo);

实现:

public Order getOrderByOrderNo(String orderNo) {
    QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("order_no", orderNo);
    return baseMapper.selectOne(queryWrapper);
}

3.2 生成支付二维码

1、统一下单接口文档

https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1

调用统一下单接口,根据返回值中的code_url在前端使用javascript工具生成支付二维码

2、业务层

接口:创建 WeixinPayService:createNative
package com.atguigu.guli.service.order.service;
public interface WeixinPayService {
    Map<String, Object> createNative(String orderNo, String remoteAddr);
}

实现:

package com.oldlu.service.trade.service.impl;
@Service
@Slf4j
public class WeixinPayServiceImpl implements WeixinPayService {
    @Autowired
    private OrderService orderService;
    @Autowired
    private WeixinPayProperties weixinPayProperties;
    @Override
    public Map<String, Object> createNative(String orderNo, String remoteAddr) {
        try{
            //根据课程订单号获取订单
            Order order = orderService.getOrderByOrderNo(orderNo);
            //调用微信api接口:统一下单(支付订单)
            HttpClientUtils client = new HttpClientUtils("https://api.mch.weixin.qq.com/pay/unifiedorder");
            //组装接口参数
            Map<String, String> params = new HashMap<>();
            params.put("appid", weixinPayProperties.getAppId());//关联的公众号的appid
            params.put("mch_id", weixinPayProperties.getPartner());//商户号
            params.put("nonce_str", WXPayUtil.generateNonceStr());//生成随机字符串
            params.put("body", order.getCourseTitle());
            params.put("out_trade_no", orderNo);
            //注意,这里必须使用字符串类型的参数(总金额:分)
            String totalFee = order.getTotalFee().intValue() + "";
            params.put("total_fee", totalFee);
            params.put("spbill_create_ip", remoteAddr);
            params.put("notify_url", weixinPayProperties.getNotifyUrl());
            params.put("trade_type", "NATIVE");
            //将参数转换成xml字符串格式:生成带有签名的xml格式字符串
            String xmlParams = WXPayUtil.generateSignedXml(params, weixinPayProperties.getPartnerKey());
            log.info("\n xmlParams:\n" + xmlParams);
            client.setXmlParam(xmlParams);//将参数放入请求对象的方法体
            client.setHttps(true);//使用https形式发送
            client.post();//发送请求
            String resultXml = client.getContent();//得到响应结果
            log.info("\n resultXml:\n" + resultXml);
            //将xml响应结果转成map对象
            Map<String, String> resultMap = WXPayUtil.xmlToMap(resultXml);
            //错误处理
            if("FAIL".equals(resultMap.get("return_code")) || "FAIL".equals(resultMap.get("result_code"))){
                log.error("微信支付统一下单错误 - "
                        + "return_code: " + resultMap.get("return_code")
                        + "return_msg: " + resultMap.get("return_msg")
                        + "result_code: " + resultMap.get("result_code")
                        + "err_code: " + resultMap.get("err_code")
                        + "err_code_des: " + resultMap.get("err_code_des"));
                throw new GuliException(ResultCodeEnum.PAY_UNIFIEDORDER_ERROR);
            }
            //组装需要的内容
            Map<String, Object> map = new HashMap<>();
            map.put("result_code", resultMap.get("result_code"));//响应码
            map.put("code_url", resultMap.get("code_url"));//生成二维码的url
            map.put("course_id", order.getCourseId());//课程id
            map.put("total_fee", order.getTotalFee());//订单总金额
            map.put("out_trade_no", orderNo);//订单号
            return map;
        } catch (Exception e) {
            log.error(ExceptionUtils.getMessage(e));
            throw new GuliException(ResultCodeEnum.PAY_UNIFIEDORDER_ERROR);
        }
    }
}

4、web层

创建 ApiWeixinPayController:createNative
package com.oldlu.service.trade.controller.api;
@RestController
@RequestMapping("/api/trade/weixin-pay")
@Api(description = "网站微信支付")
@CrossOrigin //跨域
@Slf4j
public class ApiWeixinPayController {
    @Autowired
    private WeixinPayService weixinPayService;
    @GetMapping("create-native/{orderNo}")
    public R createNative(@PathVariable String orderNo, HttpServletRequest request) {
        String remoteAddr = request.getRemoteAddr();
        Map map = weixinPayService.createNative(orderNo, remoteAddr);
        return R.ok().data(map);
    }
}

3.3 支付前端

1、安装二维码生成器

npm install vue-qriously@1.1.1

2、配置插件

创建 plugins/vue-qriously-plugin.js

import Vue from 'vue'
import VueQriously from 'vue-qriously'
Vue.use(VueQriously)

nuxt.config.js中配置

plugins: [
    { src: '~/plugins/vue-qriously-plugin.js', ssr: true }
],

3、api

创建 api/pay.js

import request from '~/utils/request'
export default {
  createNative(orderNo) {
    return request({
      baseURL: 'http://localhost:8170',
      url: `/api/trade/weixin-pay/create-native/${orderNo}`,
      method: 'get'
    })
  }
}

4、订单页面

html:

<el-button :disabled="!agree" type="danger" @click="toPay()">去支付</el-button>

脚本:

methods: {
    toPay() {
        if (this.agree) {
            this.$router.push({ path: '/pay/' + this.order.orderNo })
        }
    }
}

5、支付页面

创建pages/pay/_id.vue

<template>
  <div class="cart py-container">
    <!--主内容-->
    <div class="checkout py-container  pay">
      <div class="checkout-tit" style="width: 1050px; margin: 0 auto; padding: 10px 0;">
        <h4 class="fl tit-txt"><span class="success-info">支付申请提交成功,请您及时付款!订单号:{{ payObj.out_trade_no }}</span>
        </h4>
        <span class="fr"><em class="sui-lead">应付金额:</em><em class="orange money">¥{{ payObj.total_fee/100 }}</em></span>
        <div class="clearfix"/>
      </div>
      <div class="checkout-steps">
        <div class="fl weixin">微信支付</div>
        <div class="fl sao">
          <div class="fl code">
            <!-- <img id="qrious" src="~/assets/img/erweima.png" alt=""> -->
            <qriously :value="payObj.code_url" :size="338"/>
          </div>
          <div style="color: red; text-align:center;">请使用微信扫一扫</div>  
        </div>
        <div class="clearfix"/>
        <!-- <p><a href="pay.html" target="_blank"> 其他支付方式</a></p> -->
      </div>
    </div>
  </div>
</template>
<script>
import payApi from '~/api/pay'
export default {
  async asyncData(page) {
    const response = await payApi.createNative(page.route.params.id)
    return {
      payObj: response.data
    }
  }
  // 在created中获取数据,报告Invalid prop: type check failed for prop "value".
  // created() {
  //   payApi.createNative(this.$route.params.id).then(response => {
  //     this.payObj = response.data
  //   })
  // }
}
</script>

4 支付回调

4.1 准备

1、配置ngrok内网穿透

将ngrok映射到本地8170端口,并启动

2、添加工具类

在common_util中添加工具类

StreamUtils.java

4.2 支付回调

1、回调方法

该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。


参考文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8


ApiWeixinPayController

@Autowired
private WeixinPayProperties weixinPayProperties;
@Autowired
private OrderService orderService;
/**
     * 支付回调:注意这里是【post】方式
     */
@PostMapping("callback/notify")
public String wxNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
    System.out.println("callback/notify 被调用");
    // 获得通知结果
    ServletInputStream inputStream = request.getInputStream();
    String notifyXml = StreamUtils.inputStream2String(inputStream, "utf-8");
    System.out.println("xmlString = " + notifyXml);
    // 定义响应对象
    HashMap<String, String> returnMap = new HashMap<>();
    // 签名验证:防止伪造回调
    if (WXPayUtil.isSignatureValid(notifyXml, weixinPayProperties.getPartnerKey())) {
        // 解析返回结果
        Map<String, String> notifyMap = WXPayUtil.xmlToMap(notifyXml);
        //判断支付是否成功
        if("SUCCESS".equals(notifyMap.get("result_code"))){
            // 校验订单金额是否一致
            String totalFee = notifyMap.get("total_fee");
            String outTradeNo = notifyMap.get("out_trade_no");
            Order order = orderService.getOrderByOrderNo(outTradeNo);
            if(order != null && order.getTotalFee().intValue() == Integer.parseInt(totalFee)){
                // 判断订单状态:保证接口调用的幂等性,如果订单状态已更新直接返回成功响应
                // 幂等性:无论调用多少次结果都是一样的
                if(order.getStatus() == 1){
                    returnMap.put("return_code", "SUCCESS");
                    returnMap.put("return_msg", "OK");
                    String returnXml = WXPayUtil.mapToXml(returnMap);
                    response.setContentType("text/xml");
                    log.warn("通知已处理");
                    return returnXml;
                }else{
                    // 更新订单支付状态,并返回成功响应
                    orderService.updateOrderStatus(notifyMap);
                    returnMap.put("return_code", "SUCCESS");
                    returnMap.put("return_msg", "OK");
                    String returnXml = WXPayUtil.mapToXml(returnMap);
                    response.setContentType("text/xml");
                    log.info("支付成功,通知已处理");
                    return returnXml;
                }
            }
        }
    }
    // 校验失败,返回失败应答
    returnMap.put("return_code", "FAIL");
    returnMap.put("return_msg", "");
    String returnXml = WXPayUtil.mapToXml(returnMap);
    response.setContentType("text/xml");
    log.warn("校验失败");
    return returnXml;
}

2、更新订单状态

更新订单支付状态并记录支付日志,将微信返回的支付结果全部记录进数据库的json字段中

接口:OrderService
void updateOrderStatus(Map<String, String> map);

实现:OrderServiceImpl

@Autowired
private PayLogMapper payLogMapper;
@Transactional(rollbackFor = Exception.class)
@Override
public void updateOrderStatus(Map<String, String> map) {
    //更新订单状态
    String orderNo = map.get("out_trade_no");
    Order order = this.getOrderByOrderNo(orderNo);
    order.setStatus(1);//支付成功
    baseMapper.updateById(order);
    //记录支付日志
    PayLog payLog = new PayLog();
    payLog.setOrderNo(orderNo);
    payLog.setPayTime(new Date());
    payLog.setPayType(1);//支付类型
    payLog.setTotalFee(Long.parseLong(map.get("total_fee")));//总金额(分)
    payLog.setTradeState(map.get("result_code"));//支付状态
    payLog.setTransactionId(map.get("transaction_id"));
    payLog.setAttr(new Gson().toJson(map));
    payLogMapper.insert(payLog);
}

5 查询支付状态

5.1 查询支付状态

1、业务层

接口:OrderService
boolean queryPayStatus(String orderNo);
实现:OrderServiceImpl
@Override
public boolean queryPayStatus(String orderNo) {
    QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("order_no", orderNo);
    Order order = baseMapper.selectOne(queryWrapper);
    return order.getStatus() == 1;
}

2、web层

ApiOrderController:queryPayStatus
@GetMapping("/query-pay-status/{orderNo}")
public R queryPayStatus(@PathVariable String orderNo) {
    boolean result = orderService.queryPayStatus(orderNo);
    if (result) {//支付成功
        return R.ok().message("支付成功");
    }
    return R.setResult(ResultCodeEnum.PAY_RUN);//支付中
}

5.2 前端整合

1、api

api/order.js

queryPayStatus(orderNo) {
    return request({
        baseURL: 'http://localhost:8170',
        url: `/api/trade/order/query-pay-status/${orderNo}`,
        method: 'get'
    })
}

2、axios响应拦截

utils/request.js中的response响应拦截器

else if (res.code === 25000) { // 支付中
  return response.data // 不显示错误信息
}

3、支付页面

pages/pay/_id.vue

import orderApi from '~/api/order'
data() {
    return {
      timer: null // 定时器
    }
},
// created的时候就查询支付状态,没有必要,因为二维码页面尚未渲染,不可能支付成功
mounted() {
    // 启动定时器
    this.timer = setInterval(() => {
      this.queryPayStatus()
    }, 3000)
},
methods: {
    // 查询订单状态
    queryPayStatus() {
      orderApi.queryPayStatus(this.payObj.out_trade_no).then(response => {
        console.log('查询订单状态:' + response.code)
        // 支付成功后的页面跳转
        if (response.success) {
          this.$message.success(response.message)
          console.log('清除定时器')
          clearInterval(this.timer)
          // 三秒后跳转到课程详情页面观看视频
          setTimeout(() => {
            this.$router.push({ path: '/course/' + this.payObj.course_id })
          }, 3000)
        }
      })
    }
}


目录
相关文章
|
2月前
|
小程序 API
微信小程序-微信支付
微信小程序-微信支付
20 0
|
2月前
|
移动开发 小程序 安全
使用阿里云短信+微信短链接跳转微信小程序
此内容是关于使用阿里云短信带传递参数的微信短链接跳转到微信小程序。首先,需要准备微信开发者工具和一个已认证的小程序。接着,开通云开发并配置云开发权限。然后,配置H5静态网页,包括设置云开发权限和处理不同设备的跳转方式。最后,上传云函数并修改其权限,获取微信短信链接,配置短链接参数,并开通阿里云短信服务以进行测试验证。整个过程涉及到了微信开发者工具、云开发、H5页面配置、云函数的创建和部署以及阿里云短信服务的开通和使用等步骤。
149 0
|
10月前
|
JSON 小程序 安全
微信小程序支付
当下,微信小程序十分火爆,现在无论是购物还是生活服务,都是推荐你使用微信小程序,主要是它无需下载安装就可以使用,让手机变得非常清爽,给用户也带来很大的方便之处。
193 2
微信小程序支付
|
10月前
|
前端开发 API 数据库
网页实现微信登录
网页实现微信登录
132 0
|
12月前
|
小程序 前端开发 Java
微信小程序系列之-微信小程序授权-获取手机号码
微信小程序在用户授权之后去获取用户的手机号码
|
小程序 前端开发 API
微信小程序-支付
微信小程序-支付
171 0
|
小程序 前端开发 JavaScript
微信小程序前端解密手机号码
小程序需要用到用户的手机号码,看了下API 以及相关的demo,基本都是服务端进行解密的,问题是需要的参数并没有用到secret,只需要 session_key / iv /encryptedData 即可,那完全可以在客户端进行处理啊。
微信小程序前端解密手机号码
|
移动开发 前端开发 Android开发
前端微信支付开发H5支付(微信外)
前端微信支付开发H5支付(微信外)
397 0
前端微信支付开发H5支付(微信外)
|
数据采集 Web App开发 Python
为什么你会被限制登录网页版微信?
为什么你会被限制登录网页版微信?
91 0
|
移动开发 安全 JavaScript
h5网页调起扫一扫最简单的方法
h5网页调起扫一扫最简单的方法
724 0