基本知识:
微信的原生支付, 跟 公众号支付是不一样的.
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" 这样的值.