braintree 支付
<?php /** +------------------------------------------------------------------------------ * 支付宝类 */ namespace Common\Lib\Pay; use Common\Lib\AliyunEail; use Common\Lib\PayFactory; use Common\Lib\Payment; use Common\Model\OrderModel; use Common\Model\OrderPaymentModel; use Think\Exception; use Think\Log; use Think\Think; class BraintreeMacaroonApp implements Payment{ private $_debug = false; private $_pay_method = 'braintree'; private $_config = null; private $_gateway = null; private $_merchantAccountId = [ 'usd' => 'macaroonUSD', //美元 'hkd' => 'macaroonHKD', //港币 'cny' => 'macaroonCNY', //人民币 'jpy' => 'macaroonJPY', //日元 'eur' => 'macaroonEUR', //欧元 'gbp' => 'macaroonGBP', //英镑 'krw' => 'macaroonKRW', //韩元 ]; //货币对应的merchantAccountId; 目前仅可以使用usd private $_customerPre = ''; //用户名前缀, 当前仅 环球使用,写死。后期可以做多账号配置,需要改表。 private $_customerId = null; public $_err = ''; const LAU = 1; //商品语言类型必须是1 英文。 uro_retail_product,language_version const IV_SIZE = 16; public function __construct() { if( APP_DEBUG ) $this->_debug = true; if( $this->_debug ) { $this->_config['environment'] = 'sandbox'; $this->_config['merchantId'] = '8cwhx5kvnkt9cnq5'; $this->_config['publicKey'] = 'bh9k88388mxk3g99'; $this->_config['privateKey'] = '58d9f669ee35d2502ab5447bfb968dc2'; $this->_merchantAccountId['usd'] = ''; $this->_merchantAccountId['hkd'] = 'had'; $this->_merchantAccountId['jpy'] = 'riyuan'; $this->_customerPre = 'macaroon_test123'; }else { $this->_config['environment'] = ''; $this->_config['merchantId'] = ''; $this->_config['publicKey'] = ''; $this->_config['privateKey'] = ''; $this->_customerPre = ''; } 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, ] ); } public function getAllowCurrency() { return array_keys( $this->_merchantAccountId ); } /** * 初始化订单信息,返回 clientoken,orderinfo 信息 * author liuxiaodong * date 2018/7/27 17:56 * @param array $params * @return array */ public function send( $order, $needClienToken = true ) { $this->_customerId = $this->getCreateCustom( $order['braintree']['uid'],$order['currency_short_hand'] ); $this->_warnNotice( 'send -- getClientToken', 'get request', 'debug' ); $res = ['clientoken' => '', 'transaction' => []]; if( $needClienToken ) { try{ $param = []; if( $this->_customerId ) { $param = [ 'customerId' => $this->_customerId ]; } $res['clientoken'] = $this->_gateway->clientToken()->generate( $param ); }catch ( \Exception $e ) { $this->_err = $e->getMessage(); $this->_warnNotice( 'send -- getClientToken', '错误信息:格式化exception ==' . json_encode( $order ) . ' , errmsg = ' . $e->getMessage() ); return []; } } $res['transaction'] = $this->_encrypt( $order['braintree'] ); $this->_warnNotice( 'send -- getClientToken', 'send request' . json_encode( $res ), 'debug' ); return $res; } /** 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, $currency = '' ) { $model = M('braintreeUid'); $bid = $model->where( ['uid' => $uid] )->getField( 'bid' ); if( $bid ) return $bid; $bid = ''; $_id = $this->_customerPre . $uid; try{ $find = $this->_gateway->customer()->find( $_id ); if( $find->merchantId ) { $bid = $_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( $find ) ); return $bid; }catch ( \Exception $e ) { try{ $res = $this->_gateway->customer()->create( [ 'id' => $_id ] ); 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 $bid; } } } public function receive($params) { } //此接口做支付 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; } //读取订单信息 $currency = $transaction['currency']; if( empty( $transaction['skipCheck'] ) ) { $model = D('Common/Order'); $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; } //2019-3-11获取商品币种 $currency = M('Country')->where(['id' => $order['coin_id']])->getField('currency_short_hand'); if(empty($currency)) return false; } $currency = strtolower($currency); $amount = $transaction['amount']; //商品信息 $lineItems = []; // if( !empty( $transaction['goods'] ) ) { // 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[$currency], //merchantAccountId 'paymentMethodNonce' => $params['nonce'], 'orderId' => $transaction['oid'], 'customerId' => $this->_customerId, 'options' => [ 'submitForSettlement' => true, //申请结算 ] ]; if( !empty( $lineItems ) ) $trans['lineItems'] = $lineItems; $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; // $this->write_log( json_encode( $res ), 2, $transaction['trade_number'] ); if( empty( $transaction['skipCheck'] ) ) { $payMentData = [ 'result' => 1, 'order_number' => $transaction['trade_number'], 'trade_number' => $transaction['trade_number'], 'serial_number' => $trance->id ]; $completeres = PayFactory::PayComplete( $payMentData, OrderModel::ORDER_PAY_TYPE_BRAINTREE ,$res); $this->_warnNotice( 'notify', 'PayComplete == ' . json_encode( $payMentData ) . 'from ' . $params['client'] . ' == res ==' . json_encode( $completeres ), 'debug' ); if( !$completeres ){ $this->_err = 'server error '; $this->_warnNotice( 'notify', 'PayComplete失败 , 参数 == ' . json_encode( $payMentData ) . ' from ' . $params['client'] ); } return ['id' => $transaction['trade_number']]; }else { return ['id' => $trance->id, 'order_id' => $trance->orderId]; } }else { $__msg = $res->message; if( $res->transaction->processorResponseCode == 2046 ) { $__msg = "{$res->message} . The customer's bank is unwilling to accept the transaction. For credit/debit card transactions, the customer will need to contact their bank for more details regarding this generic decline; if this is a PayPal transaction, the customer will need to contact PayPal."; } $this->_err = $__msg . '('.$res->transaction->processorResponseCode.')'; $this->_warnNotice( 'notify', '请求 sale 发送交易抛出异常, 简讯 '.$this->_err.' 详情 == ' . json_encode( $res ), 'error' ); return false; } } public function write_log($content, $status, $trade_number){ $data = array( 'order_number' => $trade_number, 'pay_method' => $this->_pay_method, 'status' => $status, 'content' => $content, 'created_time' => date('Y-m-d H:i:s') ); D('Common/OrderPaymentLog')->add($data); } /** * 获取客户端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' ); Log::write( $action . ' -- ' . $msg, $level, '', durableLog( 'braintreeErr' ) ); if( !$this->_debug ) { $email = new AliyunEail(); $email->sendEmail( 'email?????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", 'macaroon', 0, $iv); // Encode the data with IV as prefix return 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", 'macaroon', 0, $iv); return json_decode( $output, true ); } public function getError() { $msg = $this->_err ? $this->_err : 'server fail'; return $msg; } //退款 public function refund($params) { $transaction_id = $params['trade_no']; $amount = abs($params['totalFee'] * 1.00); $transaction_info = $this->_gateway->transaction()->find($transaction_id); if($transaction_info->status == 'submitted_for_settlement'){ //如果交易尚未结算 暂不让退款 return ['code' => 3, 'info' => 'braintree尚未结算,暂不能退款']; } $response = $this->_gateway->transaction()->refund($transaction_id, $amount); $ext_str = json_encode($response); if($response->success) { printLog("退款成功,订单号:{$params['orderNo']},信息:{$ext_str}",'braintree_refund_suc'); return ['code' => 1, 'info' => $ext_str]; }else{ printLog("退款失败,订单号:{$params['orderNo']},信息:{$ext_str}",'braintree_refund_fail'); return ['code' => 0, 'info' => $ext_str]; } } //检测币种 public function checkCurrency($currency){ //获取商家支持的货币账户 $merchantAccountIterator = $this->_gateway->merchantAccount()->all(); $support_currencys = []; foreach($merchantAccountIterator as $merchantAccount) { $support_currencys[] = $merchantAccount->currencyIsoCode; } //如果商品币种商户已创建 if(in_array($currency,$support_currencys)){ return true; }else{ return false; } } }