🦄 2024 独立开发者训练营,一起创业!查看介绍 / 立即报名 →

微信支付:公众号支付实施细节

用户在微信应用内部的浏览器打开我们应用的支付页面,页面会去请求一个登录凭证(code),通过这个登录凭证又会得到用户的 Openid,这个 Openid 就是加密之后的用户的微信号。

这个时候用户就可以按一下支付按钮请求支付,我们的应用会组织好好需要调用统一下单带的数据,去获取到一个微信支付的预支付。然后我们的应用会组织好公众号支付需要的一些参数, 再把它们返回给应用的前端,再由前端去调用微信的 JSAPI 来请求支付。请求支付会调起用户的微信支付功能,用户可以确认并且完成支付 。

支付成功以后,会把用户带回到我们指定的页面上,在页面上可以引导用户查询交易状态,如果查询的结果是 SUCCESS,可以把用户带到一个成功提示的页面。这个就是我们要实现的一个微信支付的公众号支付功能。

文章内容有一个配套的演示视频,宁皓网会员可以学习对应的视频课程

配置

在公众平台的功能设置里,可以设置一下网页授权域名。

在微信支付的开发设置里,设置一下公众号支付里的支付授权目录。

 

微信用户的 OpenID

实现公众号微信支付的关键一步就是获取到用户的 OpenID。这个 OpenID 的值会包含在用户的相关信息里面,在公众号里你可以向用户申请授权得到用户的信息。用户同意以后(如果请求得到用户相关信息会提示授权,如果只是请求 OpenID 的话不会出现授权提示),微信会给我们返回微信用户的相关信息,在这个相关信息里面,就会有这个 OpenID。

OpenID 其实就是加密之后的用户的微信号,在同一个应用之下,用户的 OpenID 是唯一的。不过要注意你可能会在同一主体下创建多个应用,比如网站应用,或者一些小程序什么的。微信用户在同一主体下的不同的应用里面,他们的 OpenID 是不一样的。

如果你想识别唯一的用户,那你需要开通并认证微信的开放平台,然后在开放平台去绑定同一主体下的不同的应用。这样你就有能力得到用户的 UnionID,你可以使用这个 UnionID 的值来确定某个特定的用户,因为在你绑定在开放平台里的不同的应用下面,用户的 UnionID 的值是一样的。

登录凭证(code)

想要获取用户的信息需要使用 access_token 去换,得到这个 access_token 得先有个 code,这个 code 就是我们说的登录凭证。获取到这个 code 我们得先去准备一些数据。先组织好获取登录凭证需要的一些数据,带着这些数据把用户重定向到微信授权地址上。如果没问题,会被重定向回我们自己指定的页面上,重定向回来的时候,地址上就会带着我们需要用的登录凭证。

配置

在项目里可以先去创建一个新的配置文件,文件的名字是 wexin.js,在里面可以存储一些跟微信相关的配置数据。这些数据我们会在项目的其它地方用到。下面是文件里面的内容,暂时添加了一个 open.auth,它的值是微信登录授权用的一个地址。在请求获取登录凭证的时候会用到这个地址。

config/weixin.js:

'use strict'

module.exports = {
  open: {
    auth: 'https://open.weixin.qq.com/connect/oauth2/authorize'
  }
}

获取登录凭证

获取登录凭证需要的代码,我们可以放在 CheckoutController 里面的 render 这个方法里。它个方法目前渲染的就是结账页面(/checkout),用户在访问这个页面的时候,可以试着去获取登录凭证。

app/Controllers/Http/CheckoutController.js

/**
  * 结账页面。
  * @param  {Object}  view
  * @return 渲染结账页面视图。
  */
 async render ({ view, request, response, session }) {
   /**
    * 获取申请 access_token 需要的 code。
    */
   const code = request.input('code')
   logger.debug('code: ', code)

   const appid = Config.get('wxpay.appid') 

   if (!code) {
     const redirect_uri = `https://${ request.hostname() }${ request.url() }`
     const response_type = 'code'
     const scope = 'snsapi_base'

     const openAuthUrlParams = {
       appid,
       redirect_uri,
       response_type,
       scope
     }
     const openAuthUrlString = queryString.stringify(openAuthUrlParams)

     const openAuthApi = Config.get('weixin.open.auth')
     const openAuthUrl = `${ openAuthApi }?${ openAuthUrlString }`

     return response.redirect(openAuthUrl)
   }

   return view.render('commerce.checkout')
 }

在上面的 render 方法里, 我们先把 viewrequestresponse,还有 session 解构出来。方法的一开始,添加了一个 code,表示登录凭证,使用 request.input 方法可以得到请求里面包含的数据。成功得到登录凭证返回到这个页面的时候,地址里面会包含 code 这个数据的值,它就是我们需要的登录凭证。

const code = request.input('code')
logger.debug('code: ', code)

然后又去判断了一下,如果访问 checkout 页面的时候,没有得到 code 的值,就去组织好请求登录凭证需要的数据,然后重定向到微信授权地址。重定向的时候,用到了 response.redirect

return response.redirect(openAuthUrl)

获取登录凭证需要的数据

请求登录凭证需要几个数据,这些数据要使用地址查询符(Query String)的形式发送到微信授权地址。 下面是几个必要的数据:

  • appid:公众号应用 ID。
  • redirect_uri:重定向到的地址,得到登录凭证以后会被重定向到这里设置的地址上。
  • response_type:这里要设置成 code,因为我们需要的是登录凭证。
  • scope:可以是 snsapi_base 或者 snsapi_userinfo,因为我们只想得到微信用户的 OpenID,所以可以设置成 snsapi_base,如果想获取用户的其它的信息,可以设置成 snsapi_userinfo

我们把上面这些数据放在了一个叫 openAuthUrlParams 的对象里,然后又把这个对象转换成了地址查询符的形式。把转换之后的数据跟微信授权地址拼接在一起,组织成一个最终要重定向到的地址(openAuthUrl)。

测试

把应用的 /checkout 页面地址发送给微信用户,用户在微信内部浏览器打开这个页面以后,我们再回到项目里面检查 app.log 这个日志文件。你会发现,第一次访问 /checkout 页面的时候,要求输出的 code(登录凭证)的值是 undefined,这样就会把用户重定向到微信授权地址,成功以后,再次返回 /checkout 页面,这时候请求里面就会带着 code 的值了。所以第二条记录里面,code 是有值的。

app.log

10:08:25 DEBUG - code:  undefined
10:08:39 DEBUG - code:  011O1Na51dTrvM1Z7Ja51XNOa51O1Nai

获取 access_token

有了登录凭证(code)以后,下一步就可以去请求得到 access_token,这里面会包含 access_token 本身,还有我们需要的微信用户的 OpenID。

配置

先添加两个配置,一个是 appSecret,它是公众账号的应用密钥,你在公众平台的管理后台可以得到这个密钥的值。我们可以把这个密钥的值放在项目的 .env 文件里面,这里我给它起了一个名字叫 WXMP_APP_SECRET,也就是在 .env 文件里,添加一个叫 WXMP_APP_SECRET 的东西,对应的值就是公众平台里面的应用的密钥。

然后再修改配置文件,添加一个 appSecret,对应的值可以使用 Env.get 去得到 .env 文件里面添加的环境变量的值。在 weixin.js 这个配置文件里,我又添加了一个 api.accessToken,它的值是请求 access_token 的时候需要用的接口的地址。

config/weixin.js:

'use strict'

const Env = use('Env')

module.exports = {
  appSecret: Env.get('WXMP_APP_SECRET'),
  open: {
    auth: 'https://open.weixin.qq.com/connect/oauth2/authorize'
  },
  api: {
    accessToken: 'https://api.weixin.qq.com/sns/oauth2/access_token'
  }
}

控制器

请求 access_token 需要的代码我们也可以把它们放在 CheckoutController 里的 render 方法里面。要做的就是先去组织好请求 access_token 需要的数据,把数据转换成地址查询符的形式,然后把转换之后的数据跟 access_token 接口地址拼接在一起,再用一个 HTTP 客户端(比如 axios)去请求这个地址。得到的响应里面,就会包含 access_token 相关的数据。

改造 render 方法,添加下面这些代码:

/**
 * 获取 access_token
 */
const secret = Config.get('weixin.appSecret')
const grant_type = 'authorization_code'

const accessTokenUrlParams = {
  appid,
  secret,
  grant_type,
  code
}

const accessTokenUrlString = queryString.stringify(accessTokenUrlParams)
const accessTokenApi = Config.get('weixin.api.accessToken')
const accessTokenUrl = `${ accessTokenApi }?${ accessTokenUrlString }`

const wxResponse = await axios.get(accessTokenUrl)
logger.debug('accessToken: ', wxResponse.data)
session.put('accessToken', wxResponse.data)

请求 access_token 需要的数据

  • appid:公众号应用 ID。
  • secret:公众号应用密钥。
  • grant_type:授权类型,这里要设置成 authorization_code。
  • code:登录凭证。

我把上面这些数据放在了一个叫 accessTokenUrlParams 的对象里,又把这个对象转换成了地址查询符形式的字符串,然后把它跟 access_token 拼接在了一起,接着用 axiosget 方法去请求最终组织好的地址。得到的响应给它起了个名字叫 wxResponse,它里面的 data 属性的数据就是我们需要的东西。

我们把需要的数据输出到了应用的日志文件里了,然后又把这个数据放在了 session 里面存储起来了。这样在支付的时候,可以读取用户 session 里的数据,获取到我们需要使用的 openid 的值。

app.log

15:09:08 DEBUG - accessToken: 
{ 
 access_token: '7_1_zu2EpTHYS6gNn88xZyY6zfIypl-69QVXh3Ya7pcFtVV9P6IIE3Qj182S0f27lowQeOOpAj8U4r4nEQpAmiFeDu_JKVK7I6g0EEJElyl6o',
 expires_in: 7200,
 refresh_token: '7_J-g1AtVc0K89_9v8XME_kFhhC4Nes5Jt3YNR51BJ-XPKykQ77z6QsG-f-pf9vfgcaMxfUm43o9ymx-kvlkocH_6KhGUf3OPHt3XssU4jNNc',
 openid: 'osbKIjtJPwZzfMea5X7Q_q2tH_EU',
 scope: 'snsapi_base',
 unionid: 'oUJ7H0caaUFoviKZrU9-DFHvtOdo' 
}

统一下单

跟其它的微信支付方式一样,支付的时候我们得去请求微信支付的统一下单接口。这里需要改动几个地方,请求统一下单的代码是在 CheckoutController 里的 pay 这个方法里面。

app/Controllers/Http/CheckoutController.js:

async pay ({ request, session }) {
  ...
  /** 支付类型 */
  const trade_type = 'JSAPI'
  ...
  /** 微信用户 openid */
  const accessToken = session.get('accessToken')
  const openid = accessToken.openid
  ...
  /**
   * 准备支付数据。
   */
  let order = {
   ...
   openid
  }
  ...
}

使用公众号支付方式的时候,调用统一下单接口,trade_type 的值应该设置成 JSAPI。还有就是需要带着微信用户的 openid,这个值我们之前已经得到了,并且把它存储在了用户的 session 里了,这里我们用 session.get 得到了存储在 session 里的 accessToken,它里面的 openid 属性的值就是我们需要的 openid

公众号支付

同样是在这个 pay 这个方法里,继续去添加一些代码。去准备 JSAPI 需要的一些参数数据,组织好这些数据以后,把它响应给应用的前端,在应用的前端可以调用 JSAPI 去请求支付。

    /**
     * JSAPI 参数
     */
    const timeStamp = moment().local().unix()
    const prepay_id = data.prepay_id

    let wxJSApiParams = {
      appId: appid,
      timeStamp: `${ timeStamp }`,
      nonceStr: nonce_str,
      package: `prepay_id=${ prepay_id }`,
      signType: 'MD5'
    }

    const paySign = this.wxPaySign(wxJSApiParams, key)

    wxJSApiParams = {
      ...wxJSApiParams,
      paySign
    }

    /**
     * 为前端返回 JSAPI 参数,
     * 根据这些参数,调用微信支付功能。
     */
    return wxJSApiParams

JSAPI 参数数据

  • appId:公众号应用 ID。
  • timeStamp:时间戳。
  • nonceStr:随机数。
  • package:它的值应该使用这样的形式 `prepay_id=${ prepay_id }`prepay_id 的值就是请求统一下单接口返回来的数据里面的预支付的 id 号。
  • signType:签名类型,可以是 MD5
  • sign:签名。根据微信支付提供的规则算出签名的值。这个签名的值要根据一些特定的数据生成,这里这些数据就是 JSAPI 需要的一些参数的值(除 sign 本身)。

最后得到的参数数据的名字叫 wxJSApiParams,我们把这个数据响应给了前端。在前端,点击确认支付以后,会用 Ajax 的形式请求应用的支付接口,这个接口会使用 CheckoutController 里的这个 pay 方法来处理,所以这个方法里返回的东西,就是给前端提供的响应数据。

请求支付

在结账页面上的自定义脚本里面,添加一个新的方法(public/main.js):

  /**
   * JSAPI 微信支付。
   *
   * @param  {Object} wxJSApiParams 支付需要的参数数据。
   */
  const wxPay = (wxJSApiParams) => {
    WeixinJSBridge.invoke(
      'getBrandWCPayRequest',
      wxJSApiParams,
      (response) => {
        console.log(response)
      }
    )
  }

改造前端确认支付按钮的事件处理(public/main.js):

  /**
   * 请求支付。
   */
  $('#pay').click(() => {
    $.ajax({
      url: '/checkout/pay',
      method: 'POST',
      data: {
        _csrf
      },
      success: (response) => {
        console.log(response)
        if (response) {
          modalQuery.modal()
          localStorage.setItem('#modal-query', 'show')

          wxPay(response)
        }
      },
      error: (error) => {
        console.log(error)
      }
    })
  })

上面这块请求支付的代码,就是用户点击了结账页面上的确认支付按钮以后的事件处理。这里我们用 ajax 请求 /checkout/pay 页面,页面用的处理方法是 CheckoutController 里的 pay 方法,这个方法会给我们准备好 JSAPI 需要的参数数据。得到的这些数据就是 response

然后我们把请求支付的功能放在了一个自定义的方法里面,名字是 wxPay,在这个方法里使用了微信内部浏览器特有的 WeixinJSBridge.invoke 请求支付。

相关资源

  1. 微信支付:公众号支付》视频。
  2. 公众号支付开发文档
微信支付
微信好友

用微信扫描二维码,
加我好友。

微信公众号

用微信扫描二维码,
订阅宁皓网公众号。

240746680

用 QQ 扫描二维码,
加入宁皓网 QQ 群。

统计

14696
分钟
0
你学会了
0%
完成

社会化网络

关于

微信订阅号

扫描微信二维码关注宁皓网,每天进步一点