h5与react-native(android端) 接入支付宝进行支付的🌰

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容