<?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;
}
}
}