h5與react-native(android端) 接入支付寶進(jìn)行支付的??

半年前跟老師做項(xiàng)目的時(shí)候遇到需要接入支付寶進(jìn)行支付。這一年以來(lái)接觸前端覺(jué)得涉獵的東西太多了,現(xiàn)在好像什么都不會(huì)。。。 ~還是把??炒一下,實(shí)際接入場(chǎng)景可能根據(jù)業(yè)務(wù)都有所區(qū)別,這里通過(guò)以前做的項(xiàng)目作為示例,業(yè)務(wù)的內(nèi)容不需要糾結(jié),只需要從用法上理解即可。
示例
(前端是有PC端的web以及手機(jī)RN(android端),后端是koa2+leancloud)

接入支付寶支付的先置條件

個(gè)人無(wú)法申請(qǐng),目前只有企業(yè)可以進(jìn)行申請(qǐng)

1.在支付寶注冊(cè)企業(yè)賬號(hào)
2.需要在螞蟻金服開(kāi)放平臺(tái)申請(qǐng)應(yīng)用

15020699174012.jpg

完善資料——包括進(jìn)行授權(quán)回調(diào)的地址和應(yīng)用的網(wǎng)關(guān)以及接口加簽方式,生成好的證書(shū)需要好好保管,這不僅是進(jìn)行后續(xù)開(kāi)發(fā)需要使用的,而且更關(guān)系到該應(yīng)用的數(shù)據(jù)安全。
后進(jìn)行簽約并提交審核

15020699713243.jpg

PS: 簽約的時(shí)候?qū)Σ煌緩接胁煌囊?,包括手機(jī)app支付需要上傳關(guān)于app的文檔(內(nèi)含必須要有app界面截圖!不然會(huì)被打回)

接入支付寶

業(yè)務(wù)思路

無(wú)論是支付寶又或者是第三方支付都是以這個(gè)為核心

111 -3-.png

接入方式

可以通過(guò)第三方進(jìn)行快捷接入,也可以使用原生接口/SDK進(jìn)行接入
在網(wǎng)頁(yè)H5中使用的是beecloud 依然是只有企業(yè)用戶可以使用,不對(duì)個(gè)人進(jìn)行開(kāi)放.第三方支付的好處就是統(tǒng)一入口,支持大量的支付途徑如

15020707996912.jpg

但是依然是每個(gè)途徑需要自己去開(kāi)通相關(guān)的應(yīng)用。

beecloud h5 秒支付

beecloud 秒支付的運(yùn)作方式是將數(shù)據(jù)渲染在他提供的模板頁(yè)面上,模板頁(yè)面校驗(yàn)數(shù)據(jù)后跳轉(zhuǎn)到支付寶支付頁(yè)面,最后通過(guò)回調(diào)通知后臺(tái)。官方文檔

通過(guò)使用beecloud的秒支付button可以直接適配pc端和手機(jī)端的h5支付。
(在先置條件中必須進(jìn)行PC端支付的簽約才能繼續(xù)使用)
在開(kāi)通快捷支付后會(huì)得到以下內(nèi)容

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

再在beecloud中填入回調(diào)地址后,支付成功會(huì)通知該地址,即有請(qǐng)求附帶數(shù)據(jù)發(fā)送到該url中。

從用戶訪問(wèn)頁(yè)面開(kāi)始看代碼,由于數(shù)據(jù)都是儲(chǔ)存到leancloud的云數(shù)據(jù)庫(kù)中,所以會(huì)看到AV(??不是你們想的那個(gè)!)類的相關(guān)內(nèi)容,只需要知道該類是作為數(shù)據(jù)儲(chǔ)存獲取所用即可,以下是訪問(wèn)充值頁(yè)面的路由。

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, '余額充值');
  //這個(gè)是進(jìn)行生成訂單包括儲(chǔ)存到數(shù)據(jù)庫(kù)以及生成signStr進(jìn)行校驗(yàn)
  if (result) {
    var signStr = result.signStr;
    var outTradeNo = result.outTradeNo;
    //將數(shù)據(jù)傳到模板頁(yè)面
    ctx.render('paytest.html', {
      title: '余額充值',
      OutTradeNo: outTradeNo,
      Sign: signStr,
      amount: amount
    });
  } else {
    console.log('error');
    ctx.response.redirect('/personal/recharge');
  }

}

Pay.rechargeBalance的內(nèi)容

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);
        //將訂單存入數(shù)據(jù)庫(kù)
        pay.save();
        return  {
        signStr:sign.createHash('md5').update(data, 'utf8').digest("hex"),
        'outTradeNo':outTradeNo,
    };
   },

支付頁(yè)面模板內(nèi)容,核心的是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">
        支付單號(hào):{{ OutTradeNo}}
        支付內(nèi)容:{{title}}
        支付金額:{{amount}}
        {% if card %}
        卡類型:{{card.typeInfo.get('cardName')}}
        卡編號(hào):{{card.get('cardId')}}
        {%endif%}
      <p><center><button id="test" class="button">點(diǎn)擊支付</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調(diào)用錯(cuò)誤返回:默認(rèn)行為console.log(err)
         */
        BC.err = function(data) {
            //注冊(cè)錯(cuò)誤信息接受
            alert(data["ERROR"]);
        }
        /**
         * 3. 調(diào)用BC.click 接口傳遞參數(shù)
         */
        BC.click({
            "title": "{{ title }}",
            "amount": "{{amount}}",
            "out_trade_no": out_trade_no, //唯一訂單號(hào)
            "sign" : sign,
            /**
             * optional 為自定義參數(shù)對(duì)象,目前只支持基本類型的key =》 value, 不支持嵌套對(duì)象;
             * 回調(diào)時(shí)如果有optional則會(huì)傳遞給webhook地址,webhook的使用請(qǐng)查閱文檔
             */
            "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>

支付成功的回調(diào),回調(diào)路由

'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,
}

回調(diào)的業(yè)務(wù)函數(shù)

 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")
      //校驗(yàn)signStr 如果不符合則說(shuō)明回調(diào)來(lái)源不合法
      if(signStr!=signKey){console.log("key failure");}
      //查詢訂單,在請(qǐng)求支付的時(shí)候會(huì)產(chǎn)生一個(gè)訂單號(hào)
       var query  = new AV.Query('pays');
       query.equalTo('orderNumber',data.transaction_id);
       //查詢第一條符合記錄
       var pay = await query.first();
      //如果該訂單已經(jīng)完成了,說(shuō)明回調(diào)重復(fù)了 退出
       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(這里可以進(jìn)一步根據(jù)數(shù)據(jù)庫(kù)中的訂單信息檢查金額是否正確) 這個(gè)歷史版本,懶了就沒(méi)做了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');
             //后面是業(yè)務(wù)內(nèi)容了 不需要再看了 
             if(card_Id){
              pay_record = new rechargeCard();
              pay_record.set('cardId',pay.cardId);
               let card = new AV.Query('Cards');
                var fee = data.transaction_fee;
                //測(cè)試模式訂單金額擴(kuò)大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");
          }
      }

       }
   },

至此第三方支付的??已經(jīng)結(jié)束,可能夾雜了一些業(yè)務(wù)邏輯在里面,但是總體思路還是跟上面的圖是一致的。

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

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

這部分接入是接入是參照一個(gè)小姐姐寫的教程
這部分的核心內(nèi)容是,集成原生的android模塊
以及使用Ali Sdk nodejs第三方包
?
這部分以上說(shuō)說(shuō)的比較詳細(xì)了
其中遇到的問(wèn)題只有signStr 不正確的時(shí)候可能會(huì)出現(xiàn)
ALI 40247 在server端的 signStr 有問(wèn)題
暫時(shí)無(wú)法獲取訂單信息 —— 沒(méi)有傳遞金額

123.png

程序執(zhí)行完后必須打印輸出“success”(不包含引號(hào))。如果商戶反饋給支付寶的字符不是success這7個(gè)字符,支付寶服務(wù)器會(huì)不斷重發(fā)通知,直到超過(guò)24小時(shí)22分鐘。一般情況下,25小時(shí)以內(nèi)完成8次通知(通知的間隔頻率一般是:4m,10m,10m,1h,2h,6h,15h);
程序執(zhí)行完成后,該頁(yè)面不能執(zhí)行頁(yè)面跳轉(zhuǎn)。如果執(zhí)行頁(yè)面跳轉(zhuǎn),支付寶會(huì)收不到success字符,會(huì)被支付寶服務(wù)器判定為該頁(yè)面程序運(yùn)行出現(xiàn)異常,而重發(fā)處理結(jié)果通知;

需要注意在處理alipay webhook的時(shí)候需要注意,返回信息的問(wèn)題,在校驗(yàn)信息時(shí)候需要返回success,不然會(huì)一直發(fā)送信息。
koa2 中 使用上面提到的Ali Sdk nodejs第三方包,可以將這個(gè)ali對(duì)象存儲(chǔ)在ctx.state中。

暫時(shí)寫到這里,實(shí)際應(yīng)用中涉及支付業(yè)務(wù)的部分必須要進(jìn)行每天對(duì)賬才能保證安全。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容