Gem 微信支付 Gem

jasl · 2014年11月11日 · 最后由 suhongliang 回复于 2019年08月15日 · 18642 次阅读

大会办完以后就去补工作了...总结都没来及做 - -

这两天在做微信支付的集成,基于最新的 V3 协议,于是做了这个 gem https://github.com/jasl/wx_pay ,目前只实现了统一支付接口。

使用方式看 ReadMe 吧

PS1:微信支付的文档和演示代码里也有不少错误,浪费了非常多的时间在这上边。等不忙了再写个演示项目出来。

PS2:其实已经有很多同类的 Gem 了,比如 https://github.com/RaymondChou/Rwepayhttps://github.com/jasl/jasl_tenpay ,这些都是基于老版协议的,也仍然可用

支持,刚好 最近也要用 微信和支付宝支付

我也写了个 https://github.com/HungYuHei/wechat_pay 说起来微信支付的文档真是看得蛋疼

#2 楼 @HungYuHei 关键文档坑,就光说签名算法,他给了个运算步骤,好了,我用它的样本做测试用例吧,结果那个文档里计算结果是错的...

另外一方面 JSAPI 在微信浏览器下几乎无法调试,解决浏览器端各种交互的时候有一种回到北大青鸟的感觉 - -

#5 楼 @jasl 这个 gem 可以在产品环境用吗?

#7 楼 @flowerwrong KnewOne 已经在生产环境使用了两个多月了,我仅仅实现了 API 中的支付功能因为目前我们只需要这一个 只有 KO 需要其他 API 的时候,我才会去实现,并应用到我们的生产环境中。

#8 楼 @jasl 我遇到了一个问题,不知道你有没有遇到过。我测试支付的时候显示 商户签名失败 , 网上的大多是没有拿到 prepay_id ,但是我这边是拿到了的。问题出在 js 那里:

function onBridgeReady(){
   WeixinJSBridge.invoke(
       'getBrandWCPayRequest', {
           "appId" : "wx2421b1c4370ec43b",     //公众号名称,由商户传入     
           "timeStamp":" 1395712654",         //时间戳,自1970年以来的秒数     
           "nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串     
           "package" : "prepay_id=u802345jgfjsdfgsdg888",     
           "signType" : "MD5",         //微信签名方式:     
           "paySign" : "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 
       },
       function(res){     
           if(res.err_msg == "get_brand_wcpay_request:ok" ) {}     // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回    ok,但并不保证它绝对可靠。 
       }
   ); 
}
if (typeof WeixinJSBridge == "undefined"){
   if( document.addEventListener ){
       document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
   }else if (document.attachEvent){
       document.attachEvent('WeixinJSBridgeReady', onBridgeReady); 
       document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
   }
}else{
   onBridgeReady();
} 

paySign 我不知道应该是填写预下单的时候发送的 sign,还是返回的数据中的 sign,两个 sign 不一样。我还试过自己生成一个新的。但都是报 商户签名失败

@flowerwrong 看文档还需要拼接 package 的内容

#9 楼 @flowerwrong paySign 填写传给 js 里的参数的签名,就是

{
           "appId" : "wx2421b1c4370ec43b",     //公众号名称,由商户传入     
           "timeStamp":" 1395712654",         //时间戳,自1970年以来的秒数     
           "nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串     
           "package" : "prepay_id=u802345jgfjsdfgsdg888",     
           "signType" : "MD5"         //微信签名方式:   
}

这些的签名

@jasl paySign 就是 invoke_unifiedorder 成功之后,微信返回的参数里面的 sign 吧? 然后微信的 JSAPI 里面对 js 的 package 有这些说明:

//然后进行最后一步,这里按照key=value除了sign外进行字典序排序后组成下列的字符串,最后再串接sign=value
var completeString = "bank_type="+banktype+"&body="+body+"&fee_type="+fee_type+"&input_charset="+input_charset+"&notify_url="+notify_url+"&out_trade_no="+out_trade_no+"&partner="+partner+"&spbill_create_ip="+spbill_create_ip+"&total_fee="+total_fee;
                completeString = completeString + "&sign="+md5SignValue;

需要重新拼接 package 字符串,然后放到 js button 里面吧?否则确实一直出现签名错误

#10 楼 @robot_zhang package 拼了,是签名时大小写不对。比如 appId , 小写就不行。

#11 楼 @jasl :plus1: 谢谢,大小写都必须一样

#11 楼 @jasl 谢谢你的 wx_pay gem,让我省了不少时间。代码也很漂亮。

#12 楼 @robot_zhang 不是的,是需要利用 11 楼的 hash 再加上 key,然后 sort~>md5

@js_noncestr = SecureRandom.uuid.tr('-', '')
@js_timestamp = Time.now.getutc.to_i.to_s
@app_id = app_id
@package = "prepay_id=#{@ra[:r]['prepay_id']}"

params_pre_pay_js = {
    appId: @app_id,
    nonceStr: @js_noncestr,
    package: @package,
    timeStamp: @js_timestamp,
    signType: 'MD5'
}
@js_pay_sign =     WxPay::Sign.generate(params_pre_pay_js)

#12 楼 @robot_zhang 楼上的额做法是对滴~

@flowerwrong 对的,package 只需要 prepay 就行了 感谢 @jasl

#15 楼 @flowerwrong 我也是这么生成签名的,但还是报 fail_invalid signature

#18 楼 @nancy 用我库里带的算法吧,或者按照这个来实现,KO 生产环境验证有半年了,最近看了下微信支付的最新文档,3.3.7 这块 API 没有变动过 如果有问题 90% 可能是配置有问题

#19 楼 @jasl 用的你的 gem, 很好用,谢谢啦,我再看看配置

微信回调时,出现 Can't verify CSRF token authenticity 这个问题,大家怎么处理的啊?直接去掉么?

不知 是否可以使用 SHA 加密呢?

你好,请问一下,WxPay 里的 trade_type 难道不支持 APP,看你的介绍只支持 JSAPI、NATIVE。但是官方文档里面都支持的啊

#25 楼 @qicaisheng 理论是支持的,不过因为我目前没有环境去测试 APP 下的情况,所以避免误导没有写在文档里,如果你可以帮忙测试,通过后请告知我,更新文档、

#24 楼 @pathbox 貌似微信支付不支持 SHA 签名吧?

#27 楼 @jasl 刚做了 app 的微信支付 统一下单、查询订单。生成 prepay_id 签名方法一样。但是 APP 环境下,有 2 点要注意。 1.要将 appid, noncestr(第一次签名用的随机字符串),package(设置为'Sign=WXPay'),partnerid(商户 ID),timestamp,prepayid(第一次签名生成的)再按之前的签名规则生成签名 sign,然后随同 prepay_id,一块返回给 APP 客户端。 2.二次签名传参数时候,appid、noncestr、package、partnerid、timestamp、prepayid 这些参数名 全是小写,不是 appID、nonceStr 这样的大小写

#27 楼 @jasl 还请教一下,我按照你这样填下 notify_url,然后在 route.rb 里配置 post "notify" => "orders#notify",并在对于 controller 对于 action 里执行订单处理信息。 但是发现没有执行到对应的 action,这可能是什么原因啊?

#29 楼 @qicaisheng 关于 APP 方面配置的情况,麻烦帮忙更新一下 wxpay 的文档吧~ 感谢!

这个问题要看下日志了,可能是你路由的配置问题,文档里我的只是为了示意 实际上我们的系统里是这样配置的路由

resources :orders do
  member do
    post 'wxpay_notify'
  end
end
# will generate route like /orders/:id/wxpay_notify

还是 CSRF 的问题,加上 skip_before_filter :verify_authenticity_token 就好了。 我研究 ruby 和 rails 才 2 个多月,还没写过 gem ,也没用你 wx_pay 这个 gem ,只是参照了里面的方法。 我晚上再研究一下,再更新啊

#31 楼 @qicaisheng 辛苦~ CSRF 这个是 Rails 的防重放的机制,但是对于 API 是没有必要的,看来还是写到文档上比较好~

#32 楼 @jasl 提交新分支了

#33 楼 @qicaisheng 看到了,昨天忙,我稍微重构下合并,感谢!

#20 楼 @nancy 请教一个,你的问题解决了吗。请问是怎么解决的?谢谢!

@jasl 调用统一订单接口,一直提示签名失败,还没找到原因,帮忙给个定位思路?可能是 xml 编码问题吗? *错误结果 Key: return_code Value: FAIL Key: return_msg Value: 签名错误

*代码

@params = {
      body: 'pay one cent',
      out_trade_no: 'test003', #"test_order_#{Time.now.to_i.to_s}",
      total_fee: 1,
      spbill_create_ip: '127.0.0.1',
      notify_url: 'http://origin.xxx.com.cn/blogs/show/2',
      trade_type: 'JSAPI', # could be "JSAPI", "NATIVE" or "APP",
      openid: @blog.title # required when trade_type is `JSAPI`
    }

    @params = WxPay::Service.invoke_unifiedorder @params

gem 算出的签名值和微信签名调试工具计算出来的值是一样的。http://mch.weixin.qq.com/wiki/tools/signverify/

#36 楼 @caiqinghua 找到原因了,key 配错了,配置为 appsecret 了。@jasl 到处都是坑啊 此处要填商户 key,而不是 appsecret

看到成功的 response,哈皮

Key: return_code Value: SUCCESS

Key: return_msg Value: OK

#38 楼 @jasl 最后调用 javascript 时,需要把 jsApiParameters 传到 javascript,请问如何优雅的实现? https://ruby-china.org/topics/9844 这里的方法不好。

//调用微信JS api 支付
        function jsApiCall()
        {
            WeixinJSBridge.invoke(
                'getBrandWCPayRequest',
                <?php echo $jsApiParameters; ?>,  #这行在rails中如何实现?
                function(res){
                    WeixinJSBridge.log(res.err_msg);
                    //alert(res.err_code+res.err_desc+res.err_msg);
                }
            );
        }

@caiqinghua 可以用 <%= xx %> 来实现,也可以用 ajax 来取。xx 可以用对应的 controller 中取。

#39 楼 @caiqinghua 你发的代码 erb 本身就是可以这么用地~ 不过我是在控制器直接渲染好 json 然后这样写

WeixinJSBridge.invoke(
        'getBrandWCPayRequest',
        <%= raw @params %>,
        function (res) {
          if (res.err_msg == "get_brand_wcpay_request:ok") {
            window.location.href = '<%= wxpay_callback_order_path(@order) %>';
          } else if (res.err_msg == "get_brand_wcpay_request:cancel") {
            window.location.href = '<%= order_path(@order) %>';
          } else if (res.err_msg == "get_brand_wcpay_request:fail") {
            alert('系统错误');
          } else {
            alert('未知错误');
          }
        }
);

#41 楼 @jasl 控制器端是怎么写的? 这有问题吗?

def wechat_pay_js_params
    @params = {
        appid: 'wx7d3xxxxxxxxc',
        prepay_id: 'wx201506150843452f821f7b070705485969'
      }
    _prepay_id = @params[:prepay_id]
    @params = {
        appId: 'WxPay.appid',
        timeStamp: Time.now.to_i.to_s,
        nonceStr: SecureRandom.uuid.tr('-', ''),
        package: "prepay_id=#{_prepay_id}",
        signType: "MD5"
      }
    render json: @params
  end

我用 ajax 死活不成功,关键没有定位思路

#42 楼 @caiqinghua @jasl ajax 获取变量也成功了,最后调用 getBrandWCPayRequest,返回 invalid appid 错误,猜测是支付授权目录没有设置正确。rails 这支付授权目录要怎么设置?php 非 MVC 的比较简单。

#43 楼 @caiqinghua 也不难吧 比如你设置支付授权目录 https://knewone.com/orders/

那发起支付使用 https://knewone.com/orders/wxpay 是 ok 的 那么 route 定义

resources :orders do
  collection do
    get 'wxpay'
  end
end

然后实现 wxpay action 就可以了,只要在这个 url 里调用微信 js 支付即可 回调因为是在参数里指定,所以用 restful 风格就是 rails 的标准实践来搞没有任何问题

AJAX 不成功恐怕要靠你自己分析了,这个自己的代码别人不好帮

另外根据我的经验,微信给的错误信息不能全信,比如你支付授权目录设置的有问题,给你的报错不靠蒙这辈子也想不通是支付授权目录设置的问题,解决方法就是,一个尽可能的多 alert 信息(微信支付只能在微信浏览器里调试,console.log 没有),一个要多注意检查配置。

#44 楼 @jasl 昨天晚上已经调通了,是授权目录设置问题,之前的猜测是正确的,还要注意授权目录设置后 10 分生效,请耐心等待

#22 楼 @jasl skip_before_action :verify_authenticity_token 还是提示 WARN -- : Can't verify CSRF token authenticity

#46 楼 @caiqinghua 这样估计是你的代码问题啦,CSRF 和 Gem 无关了

get_brand_wcpay_request:fail 怎么解

 function onBridgeReady(){
        WeixinJSBridge.invoke(
                'getBrandWCPayRequest',{
                   "appId": "<%= @r['appid']%>",
                     "timeStamp": "<%= Time.now.to_i.to_s %>",
                     "nonceStr": "<%= @r['nonce_str']%>",
                     "package": "prepay_id=<%= @r['prepay_id']%>",
                     "signType": "MD5",
                     "paySign": "<%= @r['sign'] %>"
                 },
                 function(res){
alert(res.err_msg);
                    if(res.err_msg == "get_brand_wcpay_request:ok"){
                            alert('ok');
                        }else if(res.err_msg == "get_brand_wcpay_request:cancel") {
                        }else if(res.err_msg == "get_brand_wcpay_request:fail"){
                            alert('fail');
                        }else{
                            } 
                 }
            );   
     }
     if (typeof WeixinJSBride == "undefined"){
         if (document.addEventListener){
             document.addEventListener('WeixinJSBridgeReady',onBridgeReady,false); 
            } else if (document.attachEvent){
             document.attachEvent('WeixinJSBridgeReady',onBridgeReady);
             document.attachEvent('onWeixinJSBridegReady',onBridgeReady);
            }
        }else{
          onBridgeReady();  
        }

请问 fail 是什么原因?

#49 楼 @shindouhiro 微信支付文档里有...手头文档删了,这个查下就了解了

51 楼 已删除

Wxpay 可以支持扫描支付吗?统一下单支付能支持 PC 端的使用吗?我算出 paySign 了发现不知道怎么继续下去?请指导

#52 楼 @liwen_zhang 支持的,你只要按照微信支付文档的要求调整统一支付接口的参数即可

#2 楼 @hungyuhei 我准备调用你写的 gem 结果浪费了我半天的时间,最后果断放弃了。

#54 楼 @yuan 我那个 gem 是基于微信支付 v2.x API 的,如果你是 v3.X 就应该用楼主那个。如果你是 v2.x 在使用过程中有什么问题,欢迎在 github 上提 issue,确认后我会尽快修复

56 楼 已删除

用了你的 Gem,很好用,授权和支付都 ok 了,现在做企业付款到零钱的业务遇到问题,一直返回

{"xml"=>{"return_code"=>"SUCCESS", "return_msg"=>"参数错误:输入的商户订单号有误", "result_code"=>"FAIL", "err_code"=>"PARAM_ERROR", "err_code_des"=>"参数错误:输入的商户订单号有误"}}

订单号是根据时间戳生成的一个,应该不会有问题,不知道有没有企业付款到零钱的示例代码可以参考一下

Stone 回复

API 的封装得看大伙努力了,我现在不做微信支付的开发了,这个你看下微信支付的文档看下是不是调用的参数有问题吧,看样子是个小问题,是某个参数不对。

另外可以提一个 issue 在 Github 上,看看有没人能帮忙

@jasl 已经解决了,问题出在调用 to_xml 方法把参数 hash 转化成 xml 的时候,它把下划线转化成短横线了,加上 to_xml(dasherize: false) 参数就好了

response = RestClient::Request.execute(
          method: :post,
          url: url,
          payload: payload.to_xml(dasherize: false),
          ssl_client_cert: WxPay.apiclient_cert,
          ssl_client_key: WxPay.apiclient_key
      )

再次感谢你的 gem,节省不少时间,么么哒

Stone 回复

好 如果有 Bug 麻烦提交 PR 哈

需要 登录 后方可回复, 如果你还没有账号请 注册新账号