国外的支付集成接入。
只使用基础的卡支付,跟PayPal支付。
braintree 有沙盒环境可以申请测试,有php sdk包直接下载调用,非常简单。
1,声明配置信息
private $_debug = false;private $_pay_method = 'braintree';private $_config = null;private $_gateway = null;private $_merchantAccountId = [ 'usd' => 'xxxUSD' ]; //货币对应的merchantAccountId,建议自定义该值,private $_customerPre = ''; //用户名前缀,用户生成顾客id,private $_customerId = null;public $_err = '';
public function __construct() {if( $this->_debug ) {$this->_config['environment'] = 'sandbox';$this->_config['merchantId'] = '6xqytmznhcczwwph';$this->_config['publicKey'] = 'ks2j7rg234tq7fm7';$this->_config['privateKey'] = '13012ef64ae8b95443f08226ac25c890';$this->_merchantAccountId['usd'] = ''; //测试使用默认,也可以自定义,$this->_customerPre = 'test1'; }else {$this->_config['environment'] = 'production';$this->_config['merchantId'] = '';$this->_config['publicKey'] = '';$this->_config['privateKey'] = '';$this->_customerPre = 'Pro';}
//实例化gate类,为基础类import("Pay.braintree_macaroon_app.lib.Braintree", VENDOR_PATH, '.php' );$this->_gateway = new \Braintree_Gateway( ['merchantId' => $this->_config['merchantId'],'publicKey' => $this->_config['publicKey'],'privateKey' => $this->_config['privateKey'],'environment' => $this->_config['environment'],'timeout' => 2,] );}
2,生成并获取顾客id,顾客id可以保留顾客的支付方式,方便其下次直接已购买,而不用重复输入卡或者paypal信息
/**create table uro_braintree_uid(id int unsigned not null auto_increment,uid int unsigned not null default 0 comment '本站id',bid varchar(256) not null default '' comment 'braintree customerid',primary key (`id`),unique key (`uid`)) engine innodb charset utf8 comment 'braintree 用户对应关系表,creditcard,paypal等信息以后可加字段';* 获取、创建braintree 用户,* author liuxiaodong* date 2018/7/31 15:38* @param $uid* @return string*/private function getCreateCustom( $uid ){$model = M('braintreeUid');$bid = $model->where( ['uid' => $uid] )->getField( 'bid' );if( $bid )return $bid;try{$res = $this->_gateway->customer()->create( ['id' => $this->_customerPre . $uid] );if( $res->success ) {$bid = $res->customer->id;if( !$model->add( ['uid' => $uid, 'bid' => $bid] ) )$this->_warnNotice( 'send -- into db error', '数据入库失败,入库数据为 = '. json_encode( ['uid' => $uid, 'bid' => $bid] ) . ',err =' . $model->getLastSql() .'|'. $model->getDbError() );}else$this->_warnNotice( 'send -- res error', '请求braintree服务器,生成用户信息失败 = ' . json_encode( $res ) );return $bid;}catch ( \Exception $e ) {$this->_warnNotice( 'send -- createCustom', '请求braintree服务器,抛出异常' . json_encode( $e ) );return '';}}
3,获取客户端token,同时可以追加自己的信息,比如这边追加返回了订单的信息,让客户端回传,
/*** 初始化订单信息,返回 clientoken,orderinfo 信息* author liuxiaodong* date 2018/7/27 17:56* @param array $params* @return array*/public function send( $order ){$this->_customerId = $this->getCreateCustom( $order['braintree']['uid'] );$this->_warnNotice( 'send -- getClientToken', 'get request', 'debug' );$res = ['clientoken' => '', 'transaction' => []];try{$res['clientoken'] = $this->_gateway->clientToken()->generate( ['customerId' => $this->_customerId] );}catch ( \Exception $e ) {$this->_warnNotice( 'send -- getClientToken', '错误信息:格式化exception ==' . json_encode( $e ) . ' , errmsg = ' . $e->getMessage() );return [];}$res['transaction'] = $this->_encrypt( $order['braintree'] ); $this->_warnNotice( 'send -- getClientToken', 'send request' . json_encode( $res ), 'debug' );return $res;}
4,支付, 是客户端请求后,请求braintree直接获取支付结果,做逻辑操作。
//此接口做支付public function notify( $params ){$this->_warnNotice( 'notify', '参数' . json_encode( $params ), 'debug' );if( empty( $params['nonce'] )|| empty( $params['transaction'] )) {$this->_warnNotice( 'notify', '请求参数异常 无nonce、transaction, 参数为 == ' . json_encode( $params ) );$this->_err = 'invalid params';return false;}$transaction = $this->_decrypt( $params['transaction'] );if( empty( $transaction ) || !is_array( $transaction ) ) {$this->_warnNotice( 'notify', '参数异常, 无transaction == ' . json_encode( $params ) );$this->_err = 'invalid params';return false;}//读取订单信息$model = D('Common/OrderRetail');$order = $model->find( $transaction['oid'] );if( !$order ) {$this->_err = 'invalid order info';$this->_warnNotice( 'notify', '读取订单信息异常 == ' . json_encode( $transaction ) . ' from ' . $params['client'] . ' order == ' . json_encode( $order ) . ' sql ==' . $model->getLastSql() );return false;}if( $order['amount'] != $transaction['amount'] ) {$this->_err = 'check amount error';$this->_warnNotice( 'notify', '核对订单金额失败 == ' . json_encode( $transaction ) . ' from ' . $params['client'] . ' order amount = ' .$order['amount'] );return false;}$amount = $transaction['amount'];if( $this->_debug )$amount = 0.01; //debug下请保证前端使用的金额也是0.01 //商品信息foreach ( $transaction['goods'] as $v ) {$lineItems[] = ['description' => $transaction['oid'], // Maximum 127 characters'kind' => 'debit','name' => mb_substr( $v['product_info']['name'], 0, 30, 'utf8' ) . '...','productCode' => $v['product_id'], //gid'quantity' => $v['num'],'totalAmount' => $v['product_info']['price'] * $v['product_info']['num'],'unitAmount' => $v['product_info']['price'],'url' => ''];}$this->_customerId = $this->getCreateCustom( $transaction['uid'] );$trans = ['amount' => $amount, //总金额'merchantAccountId' => $this->_merchantAccountId['usd'], //merchantAccountId'paymentMethodNonce' => $params['nonce'],'lineItems' => $lineItems,'orderId' => $transaction['oid'], //自定义的值'customerId' => $this->_customerId,'options' => ['submitForSettlement' => true, //申请结算, ]];$this->_warnNotice( 'notify', 'sale 发送请求' . json_encode( $trans ) . 'from ' . $params['client'], 'debug' );$res = $this->_gateway->transaction()->sale( $trans );$this->_warnNotice( 'notify', 'sale 结果' . json_encode( $res->success ) . 'from ' . $params['client'], 'debug' );if( $res->success ) {$trance = $res->transaction;
//个人逻辑
return true;}else {$this->_err = $res->message . '('.$res->transaction->processorResponseCode.')';$this->_warnNotice( 'notify', '请求 sale 发送交易抛出异常, 简讯 '.$this->_err.' 详情 == ' . json_encode( $res ), 'error' );return false;}}
5, 其他附加方法,报警;加解密
/*** 获取客户端token* author liuxiaodong* date 2018/7/26 15:57* @return array*/public function getClientToken(){try{$token = $this->_gateway->clientToken()->generate();return [true, $token];}catch ( \Exception $e ) {$this->_warnNotice( 'getClientToken', '获取clienttoken 失败。。' . json_encode( $e ), 'error' );return [false, $e->getMessage()];}}private function _warnNotice( $action, $msg, $level = 'error' ){if( $level == 'debug' && !$this->_debug )return;$msg .= PHP_EOL;Log::write( $action . ' -- ' . $msg, $level, '', C('LOG_PATH') . 'braintreeErr/' . date( 'Y-m-d' ) . '.log' );if( !$this->_debug ) {$email = new AliyunEail();$email->sendEmail( 'email...', 'braintree pay error', date( 'Y-m-d H:i:s' ) . '<br />' . $msg );}}/*** Encrypts the input text using the cipher key** @param $input* @return string*/private function _encrypt( Array $input){$input = json_encode( $input );// Create a random IV. Not using mcrypt to generate one, as to not have a dependency on it.$iv = substr(uniqid("", true), 0, self::IV_SIZE);// Encrypt the data$encrypted = openssl_encrypt($input, "AES-256-CBC", 'key', 0, $iv);// Encode the data with IV as prefixreturn base64_encode($iv . $encrypted);}/*** Decrypts the input text from the cipher key** @param $input* @return string*/private function _decrypt($input){// Decode the IV + data$input = base64_decode($input);// Remove the IV$iv = substr($input, 0, self::IV_SIZE);// Return Decrypted Data$output = openssl_decrypt(substr($input, self::IV_SIZE), "AES-256-CBC", 'key', 0, $iv);return json_decode( $output, true );}public function getError(){$msg = 'err: ';$msg .= $this->_err ? $this->_err : 'server fail';return $msg;}
截图:
报警: (该错误是前段生成的金额跟后端的不一致导致,多是debug的时候)