以前事情比較繁忙,壓根都沒有時間去整理最近的工作。
最近稍微輕松點,就把自己在公司處理的支付業務拿出來,留個紀念,順道回顧下以前自己支付的知識。
俗話說實踐是檢驗整理的唯一標准,東西做的是否能用,只能去實際檢驗。
公司業務量可能是小點,去年9月初上線的東西,到現在充值也就是,我現查吧。。。。。。。。。。。。
這種級別的充值量,可能確實小了點,不過最起碼說明,代碼處理的業務邏輯,基本上沒有什么大的問題。
行了,廢話不說了,直接看以下介紹吧。
支付的邏輯需要三步:
(1)去合作的第三方拿到 商家號和秘鑰 。
(2)自己項目中集成第三方的SDK,完成支付。
(3)第三方的異步通知,自己接收通知,修改訂單狀態。
知道RSA加密的可以忽略:
秘鑰:RSA加密,需要生成,公鑰publicKey 和私鑰originKey
規則: 私鑰加密,公鑰解密。 知道雙方的公鑰和私鑰,就互相驗證了。
代碼邏輯 【加密方式自己查看SDK,我們用的是RSA加密,所以就以它為例子】:
(1)頁面寫好提交的方法,然后准備提交。
//js獲取頁面數據,然后submit 建議:最好表單去提交
function sub(){
var pay_money = $("#pay_money").val(); var bank_code = $("input[name='bank']:checked").val(); $.ajax({ url:'/recharge/pay_data', type:'post', data:{'pay_money':pay_money,'bank_code':bank_code}, dataType:'json', async: false, success:function(e){ document.getElementById('order_no_number').value= e.order_no; if(e.sign !='' && e.sign_type !=''){
//js動態創建表單,然后提交 var form = $("<form method='post' id='myform'></form>"); form.attr({"action":"跳轉地址"}); form.attr({"target":"_blank"}); for (arg in e) { var input = $("<input type='hidden'>"); input.attr({"name":arg}); input.val(e[arg]); form.append(input); } $("#form_hidden").append(form); $("#myform").submit(); } } })
}
(2)服務端,得按照SDK所需參數,配置好各種必須的參數,提供頁面數據提交的參數。
public function pay_data(){ //銀行代碼和充值金額 $bank_code = $_POST['bank_code']; $pay_money = $_POST['pay_money']; //充值數據 $uid = $this->session->userdata('uid'); if(!empty($uid) && !empty($bank_code) && !empty($pay_money)){ $hkd = $pay_money; //港幣(測試完成,放開) $hk_rate = $this->comm_func->getHkRate();//匯率,港幣兌人民幣 $order_amount = $pay_money * $hk_rate; //人民幣 = 港幣 * 匯率 $data['merchant_code'] = $this->config->item('merchant_code'); //配置中獲取-商戶號 $data['service_type'] = 'direct_pay'; $data['interface_version'] = 'V3.0'; $data['input_charset'] = 'UTF-8'; $data['notify_url'] = $this->config->item('web_notify_url'); //配置中獲取-異步通知地址 $data['return_url'] = $this->config->item('web_return_url'); //配置中獲取-支付完成后的跳轉地址 $data['client_ip'] = $_SERVER["REMOTE_ADDR"]; $data['order_no'] = self::get_orderno($uid); $data['order_time'] = date('Y-m-d H:i:s'); $data['order_amount'] = sprintf('%01.2f',$order_amount); $data['product_name'] = '港幣充值testpay'; $data['bank_code'] = $bank_code; $sign_type ="RSA-S"; $sign_str = $this->doSign($data,$this->config->item('origPriKey'));//配置中獲取-RSA加密的私鑰 $pay_type = 1; //插入訂單記錄 $sql = "INSERT INTO tb_pay (`uid`,`order_no`,`order_time`,`order_amount`,`hkd`,`rate`,`pay_type`,`bank_code`) VALUES('{$uid}','{$data['order_no']}','{$data['order_time']}','{$data['order_amount']}','{$hkd}','{$hk_rate}','{$pay_type}','{$data['bank_code']}')"; try { $this->db->query($sql); if ($this->db->affected_rows() <=0) { self::write_log('sql未起效:'.$sql,'web_pay_err'); } } catch (Exception $e) { self::write_log('sql報錯:'.$e,'web_pay_err'); } //支付請求日志 self::write_log('返回數據:uid='.$uid.' data='.json_encode($data)."sign_str=".$sign_str,'web_get_paydata'); $data['sign'] = $sign_str; $data['sign_type'] = $sign_type; echo json_encode($data); } }
(3)服務端,處理異步通知--這個是重點,做好各種處理措施。
/* * 服務器通知notify_url */ public function notify_url(){ $notifyStr = json_encode($_POST); self::write_log($notifyStr,'web_notify_pay');//記錄日志 $signArr = $_POST; unset($signArr['sign'],$signArr['sign_type']); $dinpaySign = base64_decode($_POST["sign"]); $sign = $this->CheckSign($signArr,$dinpaySign,$this->config->item('PublicPriKey')); //獲取驗證的公鑰 if(isset($_POST['order_no']) && isset($_POST['sign']) && $sign) { $DB_LOCK = $this->load->database('lock_tb', TRUE); $order_no = isset($_POST['order_no'])?$_POST['order_no']:''; $trade_no = isset($_POST['trade_no'])?$_POST['trade_no']:''; $trade_time = isset($_POST['trade_time'])?$_POST['trade_time']:''; $trade_status = isset($_POST['trade_status'])?$_POST['trade_status']:''; //$bank_code = isset($_POST['bank_code'])?$_POST['bank_code']:''; $bank_seq_no = isset($_POST['bank_seq_no'])?$_POST['bank_seq_no']:''; $trade_amount = isset($_POST['order_amount'])?$_POST['order_amount']:'';
//-----開啟事物處理 $DB_LOCK->trans_start();
//mysql行級鎖,處理完成,釋放鎖 $query = $DB_LOCK->query("SELECT * FROM `tb_pay` WHERE order_no = '{$_POST['order_no']}' FOR UPDATE"); $row = $query->row_array(); if (isset($row) && !empty($row)) { //有且還未返回狀態 更新 if (isset($row['trade_status']) && $row['trade_status']==0) { $trade_status_field = ($trade_status == 'SUCCESS')?1:2; $upSql = "UPDATE tb_pay SET `trade_time` = '{$trade_time}',`trade_no` = '{$trade_no}',`trade_amount` = '{$trade_amount}',`trade_status`='{$trade_status_field}',`bank_seq_no`='{$bank_seq_no}',`back_time`='".date('Y-m-d H:i:s')."' WHERE `order_no` = '{$order_no}'"; try { $DB_LOCK->query($upSql); if ($DB_LOCK->affected_rows() <=0) { self::write_log('sql未起效:'.$upSql,'web_pay_err'); }else{ $this->update_money($row['uid'],$row['hkd']); } } catch (Exception $e) { self::write_log('sql報錯:'.$e,'web_pay_err'); } } //已有返回狀態 if (isset($row['trade_status']) && $row['trade_status']!=0) { $trade_status_field = ($trade_status == 'SUCCESS')?1:2; if ($trade_status_field == $row['trade_status'] && $trade_amount == $row['trade_amount']) { //重復發送 self::write_log('重復異步通知:'.$notifyStr,'web_pay_err'); }else { //兩次數據不一致 self::write_log('重要bug:與已有數據沖突:'.$notifyStr,'web_pay_err'); } } }else { //無此記錄 self::write_log('訂單庫中無此記錄:'.$notifyStr,'web_pay_err'); } $DB_LOCK->trans_complete();
//----事物結束,處理失敗,回滾業務
echo 'SUCCESS'; //沒有問題,打印success,處理成功 }else { //驗簽失敗 self::write_log('驗簽失敗:'.$notifyStr.'sign:'.$sign,'web_pay_err'); } }
代碼所用的一些方法:
/*生成訂單號 * @uid */ public static function get_orderno($id) { $rand = date('ymd').substr(time(),-5).substr(microtime(),2,7); $fix = substr(md5($id),0,7); return $rand.$fix; } /** * sign * */ public function doSign($inA,$origPriKey) { if(empty($inA)) return false; $signStr = $this->doSort($inA); $priKey = openssl_get_privatekey($origPriKey); openssl_sign($signStr,$sign_info,$priKey,OPENSSL_ALGO_MD5); $sign = base64_encode($sign_info); return $sign; } /* * sort */ private function doSort($param) { $signStr = ''; ksort($param); foreach($param as $k => $v){ $signStr .= $k."=".$v."&"; } $signStr = rtrim($signStr,'&'); self::write_log("web_send_signStr::".$signStr,'web_send_pay_signStr'); return $signStr; } /* * 驗證notify中的簽名 */ public function CheckSign($inA,$dinpaySign,$pubKey){ if(empty($inA)) return false; $param = $inA; $signStr = ''; ksort($param); foreach($param as $k => $v){ $signStr .= $k."=".$v."&"; } $signStr = rtrim($signStr,'&'); $priKey = openssl_get_publickey($pubKey); $sign = openssl_verify($signStr,$dinpaySign,$priKey,OPENSSL_ALGO_MD5); if($sign){ return true; }else{ return false; } }