Back

微信支付的过程, 以及如何在 原生 + 微信浏览器中使用微信支付.

发布时间: 2016-08-18 01:13:00

基本知识:

微信的原生支付, 跟 公众号支付是不一样的.
app 支付入口: open.weixin.qq.com (微信开放平台)
公众号支付入口: mp.weixin.qq.com (微信公众平台)

所以,他们的 appid, key, mchid 都是不一样的(我了个大去)

使用场景:   小王(普通用户)  希望在途铃商城(平台后端) 上使用微信支付,购买1元的商品.  

参与的角色有三个:  1.  普通用户.   2. 平台的服务器端 .   3. 微信服务器. 

支付的大体过程: 

1. 普通用户小王, 点击一个商品, 点击购买按钮,  这时,  小王的手机会向 平台服务器端 发起POST请求,  

2. 平台服务器端 收到请求后, 会生成一个 订单号 (这个就是第一次发起请求的意义) 

3. 平台服务器再向 微信服务器发起请求,  (带上 各种参数, 商户id等) ,

4. 微信服务器返回结果, 包含了若干机密的参数. 给到平台服务器.

5. 平台服务器把 拿到的所有参数, 给到 普通用户小王的手机.

6. 普通用户小王的手机, 收到 各种参数后,  (使用微信支付SDK) 调用一个方法, 于是, 支付的界面就会在小王手机上弹出来了.  小王付款, 点击密码等这些操作我们无法干涉.  我们假设小王支付成功, 钱打到了 微信端. 

7. 微信收到钱后, 发送一个 请求给 平台服务器, 告诉它钱收到了.  这时, 平台服务器应该做一些操作, 例如改变订单状态等. 

目前微信的版本号 都是 3.0 .  所以可以无视 微信文档中的  "老接口", "老版本" 这样的字样. 

微信支付的过程: 

1. 后端使用rubygem.   https://github.com/jasl/wx_pay

1.1  Gemfile: 

gem 'wx_pay'

1.2  新建一个 文件: 

# config/initializers/wx_pay.rb
# 有下面三个参数就足够了. 
WxPay.appid = 'wxc77dd????????89b'
WxPay.key = '6L8D4QRDV3C????????C9RO9TCABGZW7'
WxPay.mch_id =  '134?????01'     # 这个是商户的id . 

1.3 创建一个 接口文件:

# -*- encoding : utf-8 -*-
class Interface::PaymentsController < Interface::ApplicationController

  def information
    fee = params[:fee]
    order_sn = params[:order_sn]

    # 订单号, 需要每次都要变化.
    @order = ShoppingOrder.find(params[:order_id])
    trade_type = params[:trade_type]

    payment_params = {
      body: "商品名称: #{@order.shopping_product.name}, 总价: #{fee}元",
      out_trade_no: order_sn,
      # 单位是 分, 所以要 乘以 100
      total_fee: (fee.to_f * 100).to_i,

      # 我们服务器的 IP
      spbill_create_ip: '123.56.76.212',
      # 微信服务器 在支付成功后, 调用我们服务器的接口, 来告诉我们.
      notify_url: 'http://api.touring.com.cn/interface/payments/notify',      # JSAPI: 微信支付.  APP:  app支付. NATIVE: 二维码扫码支付.
      trade_type: trade_type, #" JSAPI", "NATIVE" or "APP",
      # 用户的open id
      openid: params[:open_id]  # 当支付方式是 公众号内支付的时候, 用这个.
    }
    Rails.logger.info "== payment_params: #{payment_params.inspect}"

    # 第一次访问微信服务器, 主要目的是获取 prepay_id  在这个r 中,  r['prepay_id'] 就是微信返回的值
    r = WxPay::Service.invoke_unifiedorder payment_params

    Rails.logger.info "== information-: #{r.inspect}"
    # 准备为第二次 访问 微信服务器做准备.
    if r.success? # => true
      @order.update_attribute('collect', fee)

      # 这段代码没有太大作用.
      temp_return_code =  r["return_code"]
      temp_return_msg =  r["return_msg"]

      # 大师增加:
      params_for_app = {
        prepayid: r['prepay_id'],  # 这个就是我们第一步弄到手的值. 特别重要. 
        noncestr: SecureRandom.uuid.tr('-', '')
      }

      # 第二次访问 微信服务器, 获取 app 支付所需要的参数:
      r = WxPay::Service::generate_app_pay_req params_for_app

      Rails.logger.info "==== generate_app_pay_req : #{r.inspect}"

      # 这里的参数的 key 可以随意更改. 关键要跟 app 端的同学协调好.
      result = {
        appId: r[:appid],
        partnerid: r[:partnerid],
        prepay_id: r[:prepayid],
        package: r[:package],
        timeStamp: r[:timestamp],
        nonceStr: r[:noncestr],
        return_code: temp_return_code,  # 这个参数没太大用. 我们项目使用而已.
        return_msg: temp_return_msg,# 这个参数没太大用. 我们项目使用而已.
        sign: r[:sign]
      }
    else
      result = { return_code: r["return_code"], return_msg: r["return_msg"], result_code: r["result_code"], err_code: r["err_code"], err
    end
    Rails.logger.info "== 最终返回给app的结果 final_result :--#{result.inspect}-------"

    render json: result

  end

  # 该方法是 接收 微信服务器的 通知. 目前来看还是比较准确的. 
  def notify
    logger.info "== notify from weixin server: "
    logger.info params.inspect
    logger.info "== notify from weixin server( done ) : "

    # 请求是由 微信服务器 发送过来.
    result = Hash.from_xml(request.body.read)["xml"]

    Rails.logger.info "----notify-----result------#{result}-------"
    if WxPay::Sign.verify?(result)
      order_number = result["out_trade_no"].to_s
      logger.info "==  sign verified"
      Rails.logger.info "---------order_no------#{result["out_trade_no"]}-------"
      @order = ShoppingOrder.find_by_order_number(order_number)
      logger.info "==  #{@order.inspect} order !!!!!----"
      unless @order.blank?
        logger.info "==  order is not blank !!!!!----"
        time = Time.now.to_datetime
        @order.update_attributes(:payment_status => 1, :order_status => 1, :collect => result["total_fee"], :payed_at => time)
      end
      render :xml => { return_code: "SUCCESS" }.to_xml(root: 'xml', dasherize: false)
    else
      logger.error "==  sign NOT verified"
      render :xml => { return_code: "FAIL", return_msg: "" }.to_xml(root: 'xml', dasherize: false)
    end
  end


1.4 上面的 接口, 访问的方式的例子是: 

最终返回给app的结果如下:
{
    appId: "wxc77dd9897931589b",
    partnerid: "1342877701",
    prepay_id: "wx2016082618494048a8f66f3d0300457835",
    package: "Sign=WXPay",
    timeStamp: "1472208580",
    nonceStr: "59833558813f45bbab067eb1eef0b944",
    return_code: "SUCCESS",
    return_msg: "OK",
    sign: "7E92EB08E8EB6FEC8692A4403BB9F67F"
}

对于IOS端的配置, 参考: http://www.jianshu.com/p/8a182d764884. 注意, 里面的代码稍加修改, (例如上面的key, 大小写修改一下,就可以了)

IOS 端收到上面接口的信息时, 就可以依次添加到对应的代码中: (IOS代码中, app_id 是要放在 appdelegate.m 中 , 其他 上面的信息,放到官方给的DEMO中)

1.5 操作成功后, notify 方法打印出来的日志是: 

./tuling_web_2016-08-20.log:11:28:01 INFO: == notify from weixin server:  
./tuling_web_2016-08-20.log-11:28:01 INFO: {"action"=>"notify", "controller"=>"interface/payments"} 
./tuling_web_2016-08-20.log:11:28:01 INFO: == notify from weixin server( done ) :  
./tuling_web_2016-08-20.log-11:28:01 INFO: ----notify-----result------{"appid"=>"wxc77dd9897931589b", "bank_type"=>"CFT", "cash_fee"=>"4", "fee_type"=>"CNY", "is_subscribe"=>"N", "mch_id"=>"1342877701", "nonce_str"=>"34c0fb4e849841bd96cdc8decee1384f", "openid"=>"ocRL8wp9sHUpDAuM-JPA-6nUCcXA", "out_trade_no"=>"20160820-001000-48-", "result_code"=>"SUCCESS", "return_code"=>"SUCCESS", "sign"=>"B47C5B7B21F5BD0F16E7DC854ECDAF01", "time_end"=>"20160820112801", "total_fee"=>"4", "trade_type"=>"APP", "transaction_id"=>"4005852001201608201779844813"}------- 
./tuling_web_2016-08-20.log-11:28:01 INFO: ==  sign verified 
./tuling_web_2016-08-20.log-11:28:01 INFO: ---------order_no------20160820-001000-48-------- 

2. rubygem返回的接口,必须有下面的信息(
( https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_12&index=2 )

请求参数
字段名 变量名 类型 必填 示例值 描述
应用ID appid String(32) 是 wx8888888888888888 微信开放平台审核通过的应用APPID
商户号 partnerid String(32) 是 1900000109 微信支付分配的商户号
预支付交易会话ID prepayid String(32) 是 WX1217752501201407033233368018 微信返回的支付交易会话ID
扩展字段 package String(128) 是 Sign=WXPay 暂填写固定值Sign=WXPay
随机字符串 noncestr String(32) 是 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,不长于32位。推荐随机数生成算法
时间戳 timestamp String(10) 是 1412000000 时间戳,请见接口规则-参数规定
签名 sign String(32) 是 C380BEC2BFD727A4B6845133519F3AD6 签名,详见签名生成算法
举例请见:APP端开发说明

我发现 rubygem所返回的字段, 缺少了 商家端( 官方文档叫: partnerid, gem 配置中原名叫: mch_id), 也没有返回: package 'Sign=WXPay'.

另外,确保 签名是正确的.

52 result = {
53 appId: r['appid'],
54 partnerid: '1342877701',
55 prepay_id: r["prepay_id"],
56 package: 'Sign=WXPay',
57 timeStamp: timeStamp,
58 nonceStr: nonceStr,
59 paySign: paySign,
60 return_code: r["return_code"],
61 return_msg: r["return_msg"],
62 signature: signature
63 }

3. IOS 端收到上面接口的信息时, 就可以依次添加到对应的代码中:

(IOS代码中, app_id 是要放在 appdelegate.m 中 , 其他 上面的信息,放到官方给的DEMO中)

对于 公众号 中的 微信支付:

这里是一个大坑, 坑到不行.

一号坑:  它有两个文档:

1. 公众平台的 JS SDK : https://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E5.8F.91.E8.B5.B7.E4.B8.80.E4.B8.AA.E5.BE.AE.E4.BF.A1.E6.94.AF.E4.BB.98.E8.AF.B7.E6.B1.82
2. 商户平台的 支付方式:

https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6

二号坑: 

Android 和 IOS 对于微信支付的处理方式不同.  所以,如果使用 单页应用 (Single Page App )的话, 就要在微信后台好好的设置一下了. 

这里已经搞定了。  在后台设置就可以了。  

(2016年8月19的记录: 记得把支付目录,从 a.com/#!/login  改成: a.com/?#!/login)

2017.8.5的记录: 

如果我们的支付路径是   a.com/#/shops/start_to_pay?id=777   

那么,我们就需要在 "商户平台 -> 产品中心 -> 开发配置" 中,"支付授权目录" 中,添加: 'http://a.com/#/shops/ ' 

注意,上面的路径是 /#/shops 而不是 /#/shops/start_to_pay   

android下亲测有效.  

iphone待定.

三号坑: 

优先在IOS端的微信中调试.  这里的话对于出错的提示消息比较友好.  不要使用Android机.  安卓机的话不会给出出错提示. 

四号坑:  

Error message: config: ok    不一定代表的是 配置正确.  

服务器端的过程同上,  略作一点修改:

+  JSAPI_OPTIONS = {
+      appid: Settings.wx_pay.jsapi.appid,
+      mch_id: Settings.wx_pay.jsapi.mch_id,
+      key: Settings.wx_pay.jsapi.key
+  }

# 下面这里, 针对不同的类型, 决定是否使用JSAPI的 app_id, mch_id等
+    if trade_type == 'JSAPI'
+      Rails.logger.info "== as JSAPI"
+      r = WxPay::Service.invoke_unifiedorder payment_params, JSAPI_OPTIONS
+    else
+      Rails.logger.info "== as Android/IOS"   #这里在之前已经有了默认的 initializers. 所以不必单独使用.
+      r = WxPay::Service.invoke_unifiedorder payment_params
+    end

# 下面这个代码片段也是.
+      if trade_type == 'JSAPI'
+        Rails.logger.info "== as JSAPI, second request"
+        r = WxPay::Service::generate_app_pay_req params_for_app, JSAPI_OPTIONS
+      else
+        Rails.logger.info "== as Android/IOS, second request"
+        r = WxPay::Service::generate_app_pay_req params_for_app
+      end



在客户端, 需要注意的,就是: appId 要设置正确, 而且 package 要是 "prepay_id=xxx"  这样的值.

Back