美文网首页
h5与react-native(android端) 接入支付宝

h5与react-native(android端) 接入支付宝

作者: sunner168 | 来源:发表于2017-08-08 14:13 被阅读546次

    半年前跟老师做项目的时候遇到需要接入支付宝进行支付。这一年以来接触前端觉得涉猎的东西太多了,现在好像什么都不会。。。 ~还是把🌰炒一下,实际接入场景可能根据业务都有所区别,这里通过以前做的项目作为示例,业务的内容不需要纠结,只需要从用法上理解即可。
    示例
    (前端是有PC端的web以及手机RN(android端),后端是koa2+leancloud)

    接入支付宝支付的先置条件

    个人无法申请,目前只有企业可以进行申请

    1.在支付宝注册企业账号
    2.需要在蚂蚁金服开放平台申请应用

    15020699174012.jpg

    完善资料——包括进行授权回调的地址和应用的网关以及接口加签方式,生成好的证书需要好好保管,这不仅是进行后续开发需要使用的,而且更关系到该应用的数据安全。
    后进行签约并提交审核

    15020699713243.jpg

    PS: 签约的时候对不同途径有不同的要求,包括手机app支付需要上传关于app的文档(内含必须要有app界面截图!不然会被打回)

    接入支付宝

    业务思路

    无论是支付宝又或者是第三方支付都是以这个为核心

    111 -3-.png

    接入方式

    可以通过第三方进行快捷接入,也可以使用原生接口/SDK进行接入
    在网页H5中使用的是beecloud 依然是只有企业用户可以使用,不对个人进行开放.第三方支付的好处就是统一入口,支持大量的支付途径如

    15020707996912.jpg

    但是依然是每个途径需要自己去开通相关的应用。

    beecloud h5 秒支付

    beecloud 秒支付的运作方式是将数据渲染在他提供的模板页面上,模板页面校验数据后跳转到支付宝支付页面,最后通过回调通知后台。官方文档

    通过使用beecloud的秒支付button可以直接适配pc端和手机端的h5支付。
    (在先置条件中必须进行PC端支付的签约才能继续使用)
    在开通快捷支付后会得到以下内容

    const appid='';
    const secret='';
    const masterSecret = '';
    

    再在beecloud中填入回调地址后,支付成功会通知该地址,即有请求附带数据发送到该url中。

    从用户访问页面开始看代码,由于数据都是储存到leancloud的云数据库中,所以会看到AV(😵不是你们想的那个!)类的相关内容,只需要知道该类是作为数据储存获取所用即可,以下是访问充值页面的路由。

    var fun_payBalance = async(ctx, next) => {
      var user = await AV.User.current();
      var requestData = ctx.request.query;
      var amount = requestData.amount;
      if (user == null) {
        ctx.response.redirect('/personal/recharge');
      } else {
        if (!amount) {
          ctx.response.redirect('/personal/recharge');
        }
    
      }
      var result = Pay.rechargeBalance(amount, '余额充值');
      //这个是进行生成订单包括储存到数据库以及生成signStr进行校验
      if (result) {
        var signStr = result.signStr;
        var outTradeNo = result.outTradeNo;
        //将数据传到模板页面
        ctx.render('paytest.html', {
          title: '余额充值',
          OutTradeNo: outTradeNo,
          Sign: signStr,
          amount: amount
        });
      } else {
        console.log('error');
        ctx.response.redirect('/personal/recharge');
      }
    
    }
    

    Pay.rechargeBalance的内容

    rechargeBalance:(amount,title)=>{
            var user = AV.User.current();
            if(!user){
              return ;
            }
            var pay = new pays();
            var outTradeNo=uuid.v4();
            outTradeNo = outTradeNo.replace(/-/g, '');
            amount = parseFloat(amount);
            var data = appid + title + amount + outTradeNo + secret;
            pay.set('orderNumber',outTradeNo);
            pay.set('lines',amount);
            pay.set('info',title);
            pay.set('user',user);
            //将订单存入数据库
            pay.save();
            return  {
            signStr:sign.createHash('md5').update(data, 'utf8').digest("hex"),
            'outTradeNo':outTradeNo,
        };
       },
    

    支付页面模板内容,核心的是Bc.click method

    <!DOCTYPE html>
    <html>
      <head>
        <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
        <title>{{ title }}</title>
        <link rel='stylesheet' href='stylesheets/style.css' />
      </head>
      <body>
        <div class="description">
            支付单号:{{ OutTradeNo}}
            支付内容:{{title}}
            支付金额:{{amount}}
            {% if card %}
            卡类型:{{card.typeInfo.get('cardName')}}
            卡编号:{{card.get('cardId')}}
            {%endif%}
          <p><center><button id="test" class="button">点击支付</button></center></p>
        </div>
    
       <script id='spay-script' src='https://jspay.beecloud.cn/1/pay/jsbutton/returnscripts?appId=af1df3a3-ab4b-48d8-b71a-8dbd60a92451'></script>
      <script>
        document.getElementById("test").onclick = function() {
            asyncPay();
        };
        function bcPay() {
            var  out_trade_no = "{{ OutTradeNo}}";
            var sign = "{{ Sign }}";
            console.log(sign);
            console.log(out_trade_no);
            /**
             * click调用错误返回:默认行为console.log(err)
             */
            BC.err = function(data) {
                //注册错误信息接受
                alert(data["ERROR"]);
            }
            /**
             * 3. 调用BC.click 接口传递参数
             */
            BC.click({
                "title": "{{ title }}",
                "amount": "{{amount}}",
                "out_trade_no": out_trade_no, //唯一订单号
                "sign" : sign,
                /**
                 * optional 为自定义参数对象,目前只支持基本类型的key =》 value, 不支持嵌套对象;
                 * 回调时如果有optional则会传递给webhook地址,webhook的使用请查阅文档
                 */
                "optional": {"test": "willreturn"}
            });
        }
        function asyncPay() {
            if (typeof BC == "undefined"){
                if( document.addEventListener ){
                    document.addEventListener('beecloud:onready', bcPay, false);
                }else if (document.attachEvent){
                    document.attachEvent('beecloud:onready', bcPay);
                }
            }else{
                bcPay();
            }
        }
      </script>
      </body>
    </html>
    

    支付成功的回调,回调路由

    'use strict';
    const AV = require('leanengine');
    const Pay = require('../pay-service')
    var beecloudWebhook = async(ctx,next)=>{
      var data  = ctx.request.body;
      Pay.webhook(data);
    }
    module.exports={
        'POST /webhook/back':beecloudWebhook,
    }
    
    

    回调的业务函数

     webhook:async(data)=>{
          var signKey = data.signature;
          var signData = appid+data.transaction_id+data.transaction_type + data.channel_type + data.transaction_fee + masterSecret;
          var signStr = sign.createHash('md5').update(signData, 'utf8').digest("hex")
          //校验signStr 如果不符合则说明回调来源不合法
          if(signStr!=signKey){console.log("key failure");}
          //查询订单,在请求支付的时候会产生一个订单号
           var query  = new AV.Query('pays');
           query.equalTo('orderNumber',data.transaction_id);
           //查询第一条符合记录
           var pay = await query.first();
          //如果该订单已经完成了,说明回调重复了 退出
           if(pay&&pay.get('status')===1){return {
            "result":-1,
            "message":"had been finished"
           };}
           else{
            //如果订单不存在 退出
              if(typeof pay ==="undefined"){
                return{
                "result":-1,
                "message":"pay do not exit"};
              }
           //check the transaction_fee(这里可以进一步根据数据库中的订单信息检查金额是否正确) 这个历史版本,懒了就没做了orz
            
          // about the pay result
          if(data.transaction_type == "PAY"){
            var user = await AV.User.current(); 
            //如果支付成功了
            var status = data.trade_success;
              if(status){
                 //update the pay status,更新订单信息
                 pay.set('status',1);
                 var payId = pay.id;
                 await pay.save();
                 var pay_record =null;
                 let card_Id = pay.get('cardId');
                 //后面是业务内容了 不需要再看了 
                 if(card_Id){
                  pay_record = new rechargeCard();
                  pay_record.set('cardId',pay.cardId);
                   let card = new AV.Query('Cards');
                    var fee = data.transaction_fee;
                    //测试模式订单金额扩大1000
                   if(envMode=="test"){
                     fee*=1000;
                   }
                   card.get(card_Id).then(function(findedCard){
                       findedCard.set(findedcard.get('cardBalance')+fee);
                       findedCard.save();
                       console.log(findedCard);
                   })
                 }else{
                  pay_record = new rechargeBalance();
                  var fee = data.transaction_fee;
                  // if the test mode the fee * 1000
                  if(envMode=="test"){
                    fee*=100;
                  }
                  console.log("before"+user.get('balance'));
                   user.set('balance',user.get('balance')+fee);
                   user.save();
                   console.log("after"+user.get('balance'));
                 }
                  if(pay_record){
                    pay_record.set('user',user);
                    pay_record.set('payLines',data.transaction_fee);
                    pay_record.set('payId',payId);
                    pay_record.save();
                  }
                  return {
            "result":1,
            "message":"success"
           };
              }else{
                console.log("status failure");
              }
          }
    
           }
       },
    

    至此第三方支付的🌰已经结束,可能夹杂了一些业务逻辑在里面,但是总体思路还是跟上面的图是一致的。

    15021724010967.jpg 15021724250679.jpg 15021724623938.jpg 15021725041931.jpg

    react-native(android) 接入原生支付

    这部分接入是接入是参照一个小姐姐写的教程
    这部分的核心内容是,集成原生的android模块
    以及使用Ali Sdk nodejs第三方包

    这部分以上说说的比较详细了
    其中遇到的问题只有signStr 不正确的时候可能会出现
    ALI 40247 在server端的 signStr 有问题
    暂时无法获取订单信息 —— 没有传递金额

    123.png

    程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给支付宝的字符不是success这7个字符,支付宝服务器会不断重发通知,直到超过24小时22分钟。一般情况下,25小时以内完成8次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h);
    程序执行完成后,该页面不能执行页面跳转。如果执行页面跳转,支付宝会收不到success字符,会被支付宝服务器判定为该页面程序运行出现异常,而重发处理结果通知;

    需要注意在处理alipay webhook的时候需要注意,返回信息的问题,在校验信息时候需要返回success,不然会一直发送信息。
    koa2 中 使用上面提到的Ali Sdk nodejs第三方包,可以将这个ali对象存储在ctx.state中。

    暂时写到这里,实际应用中涉及支付业务的部分必须要进行每天对账才能保证安全。

    相关文章

      网友评论

          本文标题: h5与react-native(android端) 接入支付宝

          本文链接:https://www.haomeiwen.com/subject/psutrxtx.html