微信支付的过程, 以及如何在 原生 + 微信浏览器中使用微信支付.
访问量: 6189
基本知识:
微信的原生支付, 跟 公众号支付是不一样的.
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" 这样的值.